Initial commit: Vuelato - buscador de vuelos
Some checks failed
ci / ci (22, ubuntu-latest) (push) Has been cancelled

Nuxt 4 + Supabase + Flightics API. Incluye búsqueda de vuelos,
inspiraciones, watchlist, tracking de precios y mapa interactivo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alejandro Martinez
2026-04-10 23:37:06 +02:00
commit b8906efc80
122 changed files with 37809 additions and 0 deletions

View File

@@ -0,0 +1,80 @@
<script setup lang="ts">
import type { Leg } from '~/server/utils/flightics'
const props = defineProps<{
leg: Leg
index: number
originTzOffset?: number
showOriginTime?: boolean
}>()
const { resolve } = useAirlineNames()
function formatTime(dateStr: string) {
return new Date(dateStr).toLocaleTimeString('es-ES', { hour: '2-digit', minute: '2-digit' })
}
function formatDate(dateStr: string) {
return new Date(dateStr).toLocaleDateString('es-ES', { day: 'numeric', month: 'short' })
}
function segDuration(dep: number, arr: number) {
const mins = Math.round((arr - dep) / 60)
const h = Math.floor(mins / 60)
const m = mins % 60
return h > 0 ? `${h}h ${m}m` : `${m}m`
}
// Convert a UTC timestamp to a time string in the origin airport's timezone
function toOriginTime(utcTimestamp: number): string {
const localSeconds = utcTimestamp + (props.originTzOffset ?? 0)
const d = new Date(localSeconds * 1000)
return d.toLocaleTimeString('es-ES', { hour: '2-digit', minute: '2-digit', timeZone: 'UTC' })
}
// Check if a segment's local timezone differs from the origin
function isDifferentTz(localTimestamp: number, utcTimestamp: number): boolean {
return (localTimestamp - utcTimestamp) !== (props.originTzOffset ?? 0)
}
</script>
<template>
<div class="flex items-center gap-4 py-2">
<UBadge :label="index === 0 ? 'Ida' : 'Vuelta'" :color="index === 0 ? 'primary' : 'info'" variant="subtle" size="sm" />
<div v-for="(seg, i) in leg.segments" :key="i" class="flex items-center gap-3 flex-1">
<div class="text-center">
<p class="text-lg font-semibold">{{ formatTime(seg.departureDate) }}</p>
<p v-if="showOriginTime && isDifferentTz(seg.departureTimestamp, seg.departureUtcTimestamp)" class="text-[10px] text-primary-500">({{ toOriginTime(seg.departureUtcTimestamp) }})</p>
<p class="text-xs text-neutral-500">{{ seg.departureCode }}</p>
<p class="text-xs text-neutral-400">{{ formatDate(seg.departureDate) }}</p>
</div>
<div class="flex-1 flex flex-col items-center">
<p class="text-xs text-neutral-500">
<span v-if="resolve(seg.company.code, seg.company.name)" class="font-medium">
{{ resolve(seg.company.code, seg.company.name) }} ·
</span>
<span>{{ seg.company.code }} {{ seg.number }}</span>
</p>
<div class="w-full relative flex items-center">
<div class="flex-1 border-t border-dashed border-neutral-300 dark:border-neutral-600" />
<div class="flex items-center gap-1 px-1.5">
<UIcon name="i-lucide-plane" class="text-neutral-400 text-xs" />
<span class="text-[10px] text-neutral-400 whitespace-nowrap">{{ segDuration(seg.departureUtcTimestamp, seg.arrivalUtcTimestamp) }}</span>
</div>
<div class="flex-1 border-t border-dashed border-neutral-300 dark:border-neutral-600" />
</div>
</div>
<div class="text-center">
<p class="text-lg font-semibold">{{ formatTime(seg.arrivalDate) }}</p>
<p v-if="showOriginTime && isDifferentTz(seg.arrivalTimestamp, seg.arrivalUtcTimestamp)" class="text-[10px] text-primary-500">({{ toOriginTime(seg.arrivalUtcTimestamp) }})</p>
<p class="text-xs text-neutral-500">{{ seg.arrivalCode }}</p>
<p class="text-xs text-neutral-400">{{ formatDate(seg.arrivalDate) }}</p>
</div>
<UIcon v-if="i < leg.segments.length - 1" name="i-lucide-arrow-right" class="text-neutral-300" />
</div>
</div>
</template>