Initial commit: Vuelato - buscador de vuelos
Some checks failed
ci / ci (22, ubuntu-latest) (push) Has been cancelled
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:
107
app/composables/useBookingUrl.ts
Normal file
107
app/composables/useBookingUrl.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
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 }
|
||||
}
|
||||
Reference in New Issue
Block a user