✈ full vuelos Vuela bien, vuela barato

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) y seats_available.
  • Al confirmar la reserva, seats_available se 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_update en 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).