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>
108 lines
4.6 KiB
TypeScript
108 lines
4.6 KiB
TypeScript
interface BookingParams {
|
|
airlineCode: string
|
|
origin: string
|
|
destination: string
|
|
date: string // ISO date string
|
|
passengers?: number
|
|
}
|
|
|
|
// Direct booking URL templates for major airlines
|
|
// date format helpers applied per-airline
|
|
const BOOKING_TEMPLATES: Record<string, (p: BookingParams & { d: string; ymd: string }) => string> = {
|
|
FR: p => `https://www.ryanair.com/es/es/trip/flights/select?adults=${p.passengers}&teens=0&children=0&infants=0&dateOut=${p.ymd}&originIata=${p.origin}&destinationIata=${p.destination}&isReturn=false`,
|
|
U2: p => `https://www.easyjet.com/es/search?origin=${p.origin}&destination=${p.destination}&outboundDate=${p.ymd}&adults=${p.passengers}`,
|
|
VY: () => `https://www.vueling.com/es/reserva-tu-vuelo/busca-tu-vuelo`,
|
|
LH: p => `https://www.lufthansa.com/es/es/offer/search?origin=${p.origin}&destination=${p.destination}&departureDate=${p.ymd}&paxAdult=${p.passengers}&cabinClass=ECONOMY&tripType=O`,
|
|
IB: p => `https://www.iberia.com/es/?language=es&market=ES&origin=${p.origin}&destination=${p.destination}&outbound=${p.ymd}&adults=${p.passengers}&cabin=ECONOMY`,
|
|
W6: p => `https://wizzair.com/es-es#/booking/select-flight/${p.origin}/${p.destination}/${p.ymd}/null/${p.passengers}/0/0/null`,
|
|
NK: p => `https://www.spirit.com/book/flights?origStation=${p.origin}&destStation=${p.destination}&date=${p.ymd}&adt=${p.passengers}&chd=0&inf=0&promoCode=&tripType=OW`,
|
|
KL: p => `https://www.klm.es/search?pax=${p.passengers}:0:0:0:0:0:0:0&cabinClass=ECONOMY&connections=${p.origin}:C%3E${p.destination}:C&bookingFlow=LEISURE`,
|
|
AF: p => `https://www.airfrance.es/search?pax=${p.passengers}:0:0:0:0:0:0:0&cabinClass=ECONOMY&connections=${p.origin}:C%3E${p.destination}:C&bookingFlow=LEISURE`,
|
|
TK: p => `https://www.turkishairlines.com/es-es/flights/?origin=${p.origin}&destination=${p.destination}&departureDate=${p.ymd}&adult=${p.passengers}&child=0&infant=0&tripType=O`,
|
|
AA: p => `https://www.aa.com/booking/find-flights?origin=${p.origin}&destination=${p.destination}&departureDate=${p.ymd}&pax=${p.passengers}&tripType=OneWay&locale=es_ES`,
|
|
}
|
|
|
|
interface AirlineData {
|
|
website: string | null
|
|
bookingUrl: string | null
|
|
bookingUrlTemplate: string | null
|
|
}
|
|
|
|
// Airline data cache (loaded once from API)
|
|
const airlineData = ref<Map<string, AirlineData> | null>(null)
|
|
let loadingPromise: Promise<void> | null = null
|
|
|
|
async function loadAirlineData() {
|
|
if (airlineData.value) return
|
|
if (loadingPromise) return loadingPromise
|
|
|
|
loadingPromise = $fetch<{ airlines: { iata: string; website: string | null; booking_url: string | null; booking_url_template: string | null }[] }>('/api/airlines')
|
|
.then(res => {
|
|
const map = new Map<string, AirlineData>()
|
|
for (const a of res.airlines) {
|
|
map.set(a.iata, {
|
|
website: a.website,
|
|
bookingUrl: a.booking_url,
|
|
bookingUrlTemplate: a.booking_url_template,
|
|
})
|
|
}
|
|
airlineData.value = map
|
|
})
|
|
.catch(() => {
|
|
airlineData.value = new Map()
|
|
})
|
|
|
|
return loadingPromise
|
|
}
|
|
|
|
function applyTemplate(template: string, params: BookingParams, pax: number, ymd: string): string {
|
|
return template
|
|
.replace(/\{origin\}/gi, params.origin)
|
|
.replace(/\{destination\}/gi, params.destination)
|
|
.replace(/\{date\}/gi, ymd)
|
|
.replace(/\{passengers\}/gi, String(pax))
|
|
}
|
|
|
|
function buildGoogleFlightsUrl(p: BookingParams): string {
|
|
const ymd = p.date.slice(0, 10)
|
|
return `https://www.google.com/travel/flights?hl=es&curr=EUR&q=flights+from+${p.origin}+to+${p.destination}+on+${ymd}+one+way+${p.passengers}+passenger`
|
|
}
|
|
|
|
export function useBookingUrl() {
|
|
// Start loading on first use
|
|
loadAirlineData()
|
|
|
|
function getBookingUrl(params: BookingParams): string {
|
|
const pax = params.passengers || 1
|
|
const ymd = params.date.slice(0, 10)
|
|
const d = ymd.replace(/-/g, '')
|
|
|
|
// 1. Hardcoded templates (most reliable, manually verified)
|
|
const hardcoded = BOOKING_TEMPLATES[params.airlineCode]
|
|
if (hardcoded) {
|
|
return hardcoded({ ...params, passengers: pax, d, ymd })
|
|
}
|
|
|
|
const data = airlineData.value?.get(params.airlineCode)
|
|
|
|
// 2. Auto-discovered template with placeholders
|
|
if (data?.bookingUrlTemplate) {
|
|
return applyTemplate(data.bookingUrlTemplate, params, pax, ymd)
|
|
}
|
|
|
|
// 3. Discovered booking page URL (no params, but lands on the right page)
|
|
if (data?.bookingUrl) {
|
|
return data.bookingUrl
|
|
}
|
|
|
|
// 4. Google Flights as universal fallback
|
|
return buildGoogleFlightsUrl({ ...params, passengers: pax })
|
|
}
|
|
|
|
function getAirlineWebsite(code: string): string | null {
|
|
return airlineData.value?.get(code)?.website || null
|
|
}
|
|
|
|
return { getBookingUrl, getAirlineWebsite }
|
|
}
|