Reservas e inventario
La reserva (PNR)
Una reserva es un registro con código PNR de 6 caracteres del alfabeto sin ambigüedades ABCDEFGHJKLMNPQRSTUVWXYZ23456789 (sin 0/O ni 1/I), generado al azar hasta ser único. Un viaje de ida y vuelta es una sola reserva con dos tramos (vuelos_booking_legs, posición 0 = ida, 1 = regreso).
Validaciones al reservar
Todas se verifican dos veces: al cotizar (paso de pago) y dentro de la transacción (paso de confirmación). Cualquier violación responde 400 con el mensaje correspondiente:
| Regla | Mensaje |
|---|---|
| 1–9 personas por reservación | El grupo máximo permitido son 9 personas por reservación. |
| Al menos un adulto | Debe viajar al menos un adulto. |
| Máximo 1 bebé por adulto | Cada bebé debe viajar en el regazo de un adulto: máximo 1 bebé por adulto. |
Tipo de pasajero válido (adulto / menor / bebe) |
El tipo del pasajero N no es válido (adulto, menor o bebe). |
| Nombre, apellido y documento obligatorios por pasajero | Completa nombre, apellido y documento del pasajero N. |
| Documento único dentro de la reserva | El documento X está repetido en la reserva. |
| El vuelo de regreso debe ser la ruta inversa al de ida | El vuelo de regreso debe ser la ruta inversa al de ida. |
Tarifa válida (basica / plus / full) |
Tarifa desconocida: X |
| El vuelo debe estar programado | El vuelo X ya no está disponible. |
| El vuelo no debe haber partido | El vuelo X ya partió. |
| Asientos suficientes para los pasajeros con silla | El vuelo X no tiene suficientes asientos disponibles. |
Inventario de asientos
- Cada vuelo tiene
seats_total(180) yseats_available. - Al confirmar la reserva,
seats_availablese descuenta en el conteo con asiento (adultos + menores; los bebés en regazo no descuentan) por tramo, en la misma transacción que crea reserva + tramos + pasajeros: o todo o nada. - Los vuelos se bloquean con
with_for_updateen orden de id (mismo idioma anti-deadlock que las transferencias del Banco Americano): dos compras simultáneas del último asiento no pueden ganar ambas. - La búsqueda solo muestra vuelos con
seats_available >= pasajeros_con_asiento, así que el agotamiento solo se manifiesta entre la búsqueda y el pago (condición de carrera clásica — ejercicio de testing intencional).
Búsqueda
validate_search impone: origen dentro del mapa direccional de venta (ROUTE_DESTINATIONS: BAQ, CCS, CLO, GUA, HAV), destino servido por ese origen, ida no en el pasado, regreso ≥ ida (si aplica), al menos 1 adulto, ≤ 1 bebé por adulto y ≤ 9 personas en total. Los resultados se ordenan por hora de salida y solo incluyen vuelos del día consultado con estado scheduled.
Los selectores de ciudad se ordenan alfabéticamente por nombre de ciudad (ignorando acentos), no por código IATA.
Tira de fechas (resultados)
date_strip calcula, para la ruta y el día consultados, la disponibilidad de los 3 días anteriores y 3 posteriores (nunca en el pasado): cada día sabe si hay vuelos con asientos suficientes y el precio base más barato. La UI pinta de gris y deshabilita los días sin vuelos (title="No hay vuelos disponibles") y enlaza los disponibles a /buscar conservando ruta, tipo de viaje y composición de pasajeros. Cuando no hay selección posible, el botón Continuar se deshabilita y GET /reservar con selección inválida redirige a la página principal con ?aviso=seleccion.
Mis reservas (gestión)
filter_bookings aplica búsqueda por código (substring, sin mayúsculas) y filtro de estado (todas / confirmada / checkin / cancelada); split_bookings separa el resultado en próximas (confirmadas con primer vuelo futuro, más cercanas primero) e historial (voladas o canceladas, más recientes primero, plegadas en la UI).