Simulador del gateway¶
tools/gateway-sim/gateway_sim.py es un script Python 3 (solo librería estándar, sin
dependencias) que simula al GatewayQR: envía POST /api/v1/beneficios/resolver contra un
BeneficiosCenter en ejecución y muestra, para cada escenario, la respuesta cruda, la latencia
de ida y vuelta y la traducción a benefits_methods_data descrita en
Contrato con el autorizador.
discount.amount y discount.type son extensión Tipre, no estándar Prisma
Los cuatro campos de ADR-004
(benefits_card.code/.description, discount.percentage/.maximum_discount_amount) sí
son del estándar Prisma v27. discount.amount y discount.type no lo son: viajan solo
porque BeneficiosCenter ya calculó el monto y evita que el autorizador recompute y redondee
distinto. Lo pendiente de confirmar con DINI/TECSO no es su forma sino si el autorizador los
acepta (AC-45).
Uso¶
La clave nunca se hardcodea en el script. Se puede pasar por --api-key o por la variable de
entorno BC_API_KEY:
Parámetros¶
| Flag | Default | Descripción |
|---|---|---|
--base-url |
http://localhost:8080 |
URL base de BeneficiosCenter |
--api-key |
$BC_API_KEY |
Clave X-Api-Key. Obligatoria (por flag o env var) |
--branch |
SUC-014 |
merchant.branchId del escenario base |
--amount |
100000.00 |
transaction.amount del escenario base |
--datetime |
2026-09-03T18:42:10-03:00 |
transaction.datetime del escenario base (ISO-8601 con offset) |
--establishment-id |
12345678 |
establishment_id que se estampa en benefits_methods_data (viene de la configuración del gateway, no de BeneficiosCenter) |
Escenarios que corre (sin --corpus)¶
- Request de ejemplo — el request de muestra del contrato de resolución, con los parámetros anteriores.
- Monto menor al mínimo — mismo request con
amount = 0.01(disparaMONTO_MINIMOsi hay beneficios configurados con mínimo). - Sucursal desconocida — mismo request con
branchId = SUC-DESCONOCIDA-999(disparaSUCURSAL_DESCONOCIDA; solo aplican beneficios sin restricción de sucursal). - API key inválida — mismo request con una clave incorrecta (espera
401).
Para cada escenario se imprime el HTTP <status> con la latencia en milisegundos, el cuerpo de
la respuesta tal como llega, y — solo cuando la respuesta es 200 — el bloque
benefits_methods_data traducido.
Código de salida¶
0 si el script pudo completar todos los escenarios (incluye los que responden 400/401 a
propósito, como el de API key inválida). 1 si no pudo conectarse a --base-url o si falta la
API key.
Corpus real (AC-41)¶
python gateway_sim.py --base-url http://localhost:8080 --api-key <clave-del-gateway> \
--corpus corpus/sample.jsonl
Con --corpus el script ignora los cuatro escenarios de arriba: corre cada caso del archivo
JSONL, compara los benefits de la respuesta contra los expected.benefits del caso
(ignorando benefitId, description y customerTypeCode), imprime una tabla PASS/FAIL por
caso y un resumen passed/total. Sale con código 1 si algún caso no coincide.
Formato del corpus¶
Un objeto JSON por línea (JSONL), sin array envolvente:
{
"name": "jueves_dini_negocios_10pct",
"request": {
"requestId": "GW-CORPUS-0001",
"transaction": { "datetime": "2026-09-10T18:42:10-03:00", "amount": 100000.00, "currency": "ARS" },
"merchant": { "branchId": "SUC-014", "posId": 7, "salesPointId": 3 },
"invoice": { "type": "B", "taxIdKind": "CUIL", "taxId": "00000000000", "extraCustomerTypeId": null }
},
"expected": {
"benefits": [
{ "paymentMethodId": "990", "percentage": 10.0, "maxDiscountAmount": null, "discountAmount": 10000.00 }
]
}
}
requestes el body exacto que esperaPOST /api/v1/beneficios/resolver.expected.benefitstrae, para cadapaymentMethodIdesperado,percentage,maxDiscountAmount(nullcuando el beneficio no tiene tope) ydiscountAmount(amount × percentage / 100, redondeado HALF_UP a dos decimales y recortado amaxDiscountAmountcuando existe). No hace falta incluirbenefitId,description,customerTypeCodenitype: el simulador los ignora al comparar.- El orden de
expected.benefitsno importa; el simulador normaliza y ordena antes de comparar.
corpus/sample.jsonl¶
Tres casos sintéticos, alineados con los datos del seed y verificados contra una corrida real del jar:
jueves_dini_negocios_10pct— jueves 2026-09-10, dentro de la vigencia de "Jueves Dini Negocios 10%" y de "Aniversario DINO 15%": para DINI Negocios gana el Jueves (prioridad 10 < 20 de Aniversario); para Gift Card se aplica Aniversario (15 %, tope $25.000).viernes_sin_jueves— mismo request pero viernes: el beneficio de jueves no aplica ese día, así que ambos tipos de cliente caen en Aniversario DINO 15%.monto_bajo_minimo— mismo jueves,amount = 0.01: no llega almontoMinimode $20.000 del beneficio de jueves, así que Negocios también cae en Aniversario DINO 15%.
Corpus real de DINO¶
El corpus real (requests reales del gateway con sus fechas, sucursales y promociones vigentes,
AC-41) va en tools/gateway-sim/corpus/, en un archivo .jsonl propio. Nunca subir datos
personales: enmascarar siempre invoice.taxId (por ejemplo con ceros o un valor sintético)
antes de commitear el archivo. El resto de los campos (branchId, montos, fechas,
paymentMethodId) no son datos personales y pueden viajar tal cual.
Levantar el jar antes de correrlo¶
Dónde sigue¶
- El contrato exacto que este script ejercita, en API de resolución.
- Cómo se traduce la respuesta hacia el autorizador, en Contrato con el autorizador.