Vouchers nominados (becas) — Consumo desde Lambda contra el motor de Humming

1. Qué es un voucher nominado

Un voucher nominado es un instrumento de descuento personal, emitido a un documento (no a una cuenta, no a un passengerId). Es la base del programa de becas y de los pasajes abiertos personales.

Se distingue del voucher de cuenta (prepago corporativo) en que:

Voucher de cuenta Voucher nominado
Atado a acc_id (empresa) Documento (IDNumber + IDCountry)
Lo usa Cualquiera de la cuenta Solo la persona cuyo documento coincide
Valor Prepago (punit) % de descuento (discount)
Se aplica sobre La tarifa Tarifa + equipaje del pasajero

La regla de oro: un voucher nominado solo aplica si uno de los pasajeros del booking tiene el documento del voucher. Ese match es la validación central y el mecanismo de seguridad.


2. Autenticación

Todos los endpoints se consumen por POST al motor de Humming:

POST  {BASE_URL}/intranet/sys/mods/flights/motor.php

{BASE_URL} depende del entorno (PRD / DEV / LOC).

Header obligatorio:

Authorization: Bearer {HUMMING_API_TOKEN}
Content-Type: application/x-www-form-urlencoded   (o JSON; el motor acepta ambos)
No hay gate de rol. Cualquier cliente autenticado por Bearer (incluido un Lambda) puede consumir estos endpoints. La seguridad no es el rol, es el match de documento: no se puede aplicar la beca de otra persona a un booking donde esa persona no viaja.

3. El flujo completo

1. OFFER      getBookingSummary       ->  qué pasajeros son elegibles (nominal_vouchers)
2. VERIFY     verifyPassengerVoucher  ->  ¿el código aplica? (dry-run, NO consume, devuelve preview)
3. REDEEM     redeemPassengerVoucher  ->  aplica + consume el descuento
4. REFRESH    getBookingSummary       ->  el total ya refleja el descuento

El paso VERIFY es opcional (para previsualizar antes de aplicar). REDEEM revalida todo internamente, así que se puede ir directo de OFFER a REDEEM.


4. Endpoint 1 — getBookingSummary (el OFFER)

Devuelve el resumen del booking incluyendo nominal_vouchers: qué reservas/pasajeros son sujetos de aplicar un voucher nominado.

Request

POST /intranet/sys/mods/flights/motor.php
Authorization: Bearer {HUMMING_API_TOKEN}

{
  "execute": "getBookingSummary",
  "id": "{md5(bookingId)}"
}
⚠️ Ojo con el identificador: getBookingSummary recibe id = md5 del bookingId (no el entero). Los otros dos endpoints reciben bookingId (entero). Es una asimetría del contrato actual — tenerla presente.

Response (fragmento relevante)

{
  "nominal_vouchers": {
    "148264": {
      "reservationId": 148264,
      "passengerId": 2954,
      "passengerName": "Perez, Juan",
      "IDNumber": "17923900",
      "IDCountry": "AR",
      "vouchers": [
        {
          "voucherId": 130,
          "voucherCode": "XW9I7PSGZ4",
          "discount": 50.0,
          "untilDate": "2027-02-28",
          "remaining": 35,
          "applied": false
        }
      ]
    }
  }
}

Estructura de nominal_vouchers

Objeto keyed por reservationId. Cada entrada:

Campo Tipo Significado
reservationId int La reserva (pasajero × tramo) elegible
passengerId int Pasajero de esa reserva
passengerName string "Apellido, Nombre"
IDNumber / IDCountry string Documento normalizado del pasajero
vouchers[] array Vouchers nominados disponibles para ese documento
vouchers[].voucherId int ID del voucher
vouchers[].voucherCode string Código del voucher
vouchers[].discount float % de descuento
vouchers[].untilDate date Vigencia
vouchers[].remaining int / null Usos restantes (tripCount − usados); null = sin límite
vouchers[].applied bool Si ya está aplicado a esta reserva

5. Endpoint 2 — verifyPassengerVoucher (validar, sin consumir)

Valida un código contra el booking en modo dry-run: corre todas las validaciones y devuelve el preview del descuento, sin insertar nada ni tocar la facturación. Ideal para el momento en que el usuario ingresa el código.

Request

POST /intranet/sys/mods/flights/motor.php
Authorization: Bearer {HUMMING_API_TOKEN}

{
  "execute": "verifyPassengerVoucher",
  "voucherCode": "XW9I7PSGZ4",
  "bookingId": 73269
}

Se puede pasar voucherCode o voucherId. bookingId es el entero.

Response

{
  "status": 1000,
  "msg": "OK",
  "reservations": [148264],
  "discountAmount": 107.50,
  "discount": 50.0
}
Campo Significado
status 1000 = válido / aplicable · 1026 = rechazado · 2000 = error técnico
reservations[] Las reservas del booking a las que aplicaría (las del becado)
discountAmount Monto total del descuento en USD (sobre tarifa + equipaje)
discount % del voucher
err[] Presente si hubo rechazo/error: motivo(s) legibles

6. Endpoint 3 — redeemPassengerVoucher (aplicar + consumir)

Valida (igual que verify) y, si pasa, aplica el descuento (registra la línea per-reserva) y refresca la facturación. Es idempotente: reaplicar el mismo voucher al mismo booking recalcula, no duplica.

Request

POST /intranet/sys/mods/flights/motor.php
Authorization: Bearer {HUMMING_API_TOKEN}

{
  "execute": "redeemPassengerVoucher",
  "voucherCode": "XW9I7PSGZ4",
  "bookingId": 73269
}

Response: idéntica a verifyPassengerVoucher (status, reservations, discountAmount, discount, err).

Tras un redeem status=1000, volvé a llamar getBookingSummary para que el total refleje el descuento.


7. Reglas de validación (lo que el motor verifica)

Al verificar/redimir, el motor aplica en orden:

  1. Es nominado: discount > 0 y tiene IDNumber. Si no → error (no es una beca).
  2. Vigencia: untilDate >= hoy. Si venció → rechazo (1026).
  3. Consumo: cantidad de bookings distintos que ya redimieron el voucher < tripCount. Si se agotó → rechazo (1026).
  4. Documento (el central): algún pasajero del booking debe tener el mismo documento normalizado que el voucher (upper(regexp_replace(IDNumber,'[^A-Za-z0-9]','')) + upper(trim(IDCountry))). Si ninguno → rechazo (1026, "el código es del documento X, que no está entre los pasajeros").
  5. Cálculo per-pasajero: el descuento aplica solo a la(s) reserva(s) del becado, y sobre (tarifa base + equipaje de esa reserva) × discount%. Los demás pasajeros del booking pagan full.

8. Códigos de estado

status Constante Significado
1000 stxOk Válido / aplicado correctamente
1026 stxRejected Rechazo de negocio: documento no coincide, vencido, o agotado. Mostrar msg/err al usuario.
2000 stxError Error técnico: falta bookingId/voucherId, voucher inexistente, etc.

Regla para el cliente Lambda: 1026 es esperable y accionable (mostrar el motivo). 2000 es un problema de integración (loguear).


9. Secuencia recomendada para el Lambda

1) getBookingSummary(id = md5(bookingId))
     -> leer nominal_vouchers
     -> si vacío: no hay becas, seguir el checkout normal

2) Por cada reservationId elegible:
     mostrar el pasajero y pedir/confirmar el código

3) verifyPassengerVoucher(voucherCode, bookingId)
     -> status 1000: mostrar "aplica {discount}% — ahorro USD {discountAmount}"
     -> status 1026: mostrar err (documento/vencido/agotado)

4) Al confirmar o cerrar:
     redeemPassengerVoucher(voucherCode, bookingId)
     -> status 1000: aplicado

5) getBookingSummary(...) de nuevo
     -> el total ya viene con el descuento

10. Notas y advertencias