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,115 @@
export interface TrackedSearch {
id: string
name: string
search_params: Record<string, unknown>
route_summary: string
interval_hours: number
is_active: boolean
next_run_at: string | null
last_run_at: string | null
run_count: number
last_error: string | null
expires_at: string | null
created_at: string
latest_snapshot: PriceSnapshot | null
}
export interface PriceSnapshot {
cheapest_price: number
avg_price: number | null
median_price: number | null
total_results: number
recorded_at: string
}
export interface TopTrip {
price: number
currency: string
bookingToken: string
legs: Array<{ from: string; to: string; departure: string; airlines: string[] }>
}
export interface SearchRun {
id: string
tracked_search_id: string
status: string
cheapest_price: number | null
total_trips_found: number
top_trips: TopTrip[] | null
from_cache: boolean
error_message: string | null
started_at: string | null
completed_at: string | null
created_at: string
}
export function useTrackedSearches() {
const user = useSupabaseUser()
const trackedSearches = ref<TrackedSearch[]>([])
const loading = ref(false)
async function fetchAll() {
if (!user.value) return
loading.value = true
try {
const data = await $fetch<TrackedSearch[]>('/api/tracking')
trackedSearches.value = data || []
} catch {
trackedSearches.value = []
} finally {
loading.value = false
}
}
async function create(params: {
name: string
searchParams: Record<string, unknown>
routeSummary: string
intervalHours?: number
expiresAt?: string
}) {
const data = await $fetch<TrackedSearch>('/api/tracking', {
method: 'POST',
body: params
})
await fetchAll()
return data
}
async function update(id: string, patch: Partial<Pick<TrackedSearch, 'name' | 'interval_hours' | 'is_active' | 'expires_at' | 'search_params' | 'route_summary'>>) {
const data = await $fetch<TrackedSearch>(`/api/tracking/${id}`, {
method: 'PATCH',
body: patch
})
const idx = trackedSearches.value.findIndex(s => s.id === id)
if (idx >= 0) {
trackedSearches.value[idx] = { ...trackedSearches.value[idx], ...data }
}
return data
}
async function remove(id: string) {
await $fetch(`/api/tracking/${id}`, { method: 'DELETE' })
trackedSearches.value = trackedSearches.value.filter(s => s.id !== id)
}
async function getHistory(id: string, days = 30): Promise<PriceSnapshot[]> {
return $fetch<PriceSnapshot[]>(`/api/tracking/${id}/history`, {
query: { days }
})
}
async function getRuns(id: string, limit = 20): Promise<SearchRun[]> {
return $fetch<SearchRun[]>(`/api/tracking/${id}/runs`, {
query: { limit }
})
}
watch(user, (u) => {
if (u) fetchAll()
else trackedSearches.value = []
}, { immediate: true })
return { trackedSearches, loading, fetchAll, create, update, remove, getHistory, getRuns }
}