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,145 @@
<script setup lang="ts">
const props = defineProps<{
flightCode: string
}>()
const info = ref<any>(null)
const fr24Url = ref<string | null>(null)
const loading = ref(false)
const loaded = ref(false)
async function loadInfo() {
if (loaded.value) return
loading.value = true
try {
const data = await $fetch<any>('/api/flight-info', {
query: { flightno: props.flightCode }
})
fr24Url.value = data.fr24Url || `https://www.flightradar24.com/data/flights/${props.flightCode.toLowerCase()}`
if (data.found) info.value = data.flight
} catch {
fr24Url.value = `https://www.flightradar24.com/data/flights/${props.flightCode.toLowerCase()}`
} finally {
loading.value = false
loaded.value = true
}
}
function formatAltitude(ft: number) {
return `${Math.round(ft * 0.3048)}m (FL${Math.round(ft / 100)})`
}
function formatSpeed(knots: number) {
return `${Math.round(knots * 1.852)} km/h`
}
function formatDelay(min: number | null) {
if (min == null || min === 0) return null
if (min > 0) return `+${min} min`
return `${min} min`
}
</script>
<template>
<div>
<!-- Toggle button -->
<UButton
:label="loading ? 'Cargando...' : (info ? 'Info del vuelo' : 'Ver info en vivo')"
:icon="info ? 'i-lucide-radar' : 'i-lucide-radio'"
color="neutral"
variant="ghost"
size="xs"
:loading="loading"
@click="loadInfo"
/>
<!-- Flight info panel -->
<Transition
enter-active-class="transition-all duration-200 ease-out"
enter-from-class="opacity-0 max-h-0"
enter-to-class="opacity-100 max-h-96"
leave-active-class="transition-all duration-150 ease-in"
leave-from-class="opacity-100 max-h-96"
leave-to-class="opacity-0 max-h-0"
>
<div v-if="info" class="mt-2 overflow-hidden">
<div class="rounded-lg bg-neutral-50 dark:bg-neutral-800/50 p-3 space-y-2 text-sm">
<!-- Status -->
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<span
class="w-2 h-2 rounded-full"
:class="info.onGround ? 'bg-amber-500' : info.altitude > 0 ? 'bg-green-500 animate-pulse' : 'bg-neutral-400'"
/>
<span class="font-medium">{{ info.status }}</span>
</div>
<a
:href="info.fr24Url"
target="_blank"
class="text-xs text-primary-500 hover:underline"
>
Flightradar24
<UIcon name="i-lucide-external-link" class="inline text-[10px]" />
</a>
</div>
<!-- Aircraft & airline -->
<div class="grid grid-cols-2 gap-2 text-xs">
<div v-if="info.aircraft">
<span class="text-muted">Avion</span>
<p class="font-medium">{{ info.aircraftAge || info.aircraft }}</p>
</div>
<div v-if="info.registration">
<span class="text-muted">Matricula</span>
<p class="font-medium">{{ info.registration }}</p>
</div>
</div>
<!-- Live data (if in flight) -->
<div v-if="info.altitude > 0 && !info.onGround" class="grid grid-cols-3 gap-2 text-xs">
<div>
<span class="text-muted">Altitud</span>
<p class="font-medium">{{ formatAltitude(info.altitude) }}</p>
</div>
<div>
<span class="text-muted">Velocidad</span>
<p class="font-medium">{{ formatSpeed(info.speed) }}</p>
</div>
<div>
<span class="text-muted">Rumbo</span>
<p class="font-medium">{{ info.heading }}°</p>
</div>
</div>
<!-- Delays -->
<div v-if="formatDelay(info.departureDelay) || formatDelay(info.arrivalDelay)" class="flex gap-4 text-xs">
<div v-if="formatDelay(info.departureDelay)">
<span class="text-muted">Retraso salida</span>
<p class="font-medium" :class="info.departureDelay > 0 ? 'text-red-500' : 'text-green-500'">
{{ formatDelay(info.departureDelay) }}
</p>
</div>
<div v-if="formatDelay(info.arrivalDelay)">
<span class="text-muted">Retraso llegada</span>
<p class="font-medium" :class="info.arrivalDelay > 0 ? 'text-red-500' : 'text-green-500'">
{{ formatDelay(info.arrivalDelay) }}
</p>
</div>
</div>
</div>
</div>
</Transition>
<!-- Not found link to FR24 anyway -->
<div v-if="loaded && !info && !loading" class="mt-1">
<a
:href="fr24Url"
target="_blank"
class="text-xs text-muted hover:text-primary-500 transition-colors"
>
No hay datos en vivo · Ver historial en Flightradar24
<UIcon name="i-lucide-external-link" class="inline text-[10px]" />
</a>
</div>
</div>
</template>