Saltar a contenido

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

python gateway_sim.py --base-url http://localhost:8080 --api-key <clave-del-gateway>

La clave nunca se hardcodea en el script. Se puede pasar por --api-key o por la variable de entorno BC_API_KEY:

export BC_API_KEY=<clave-del-gateway>
python gateway_sim.py

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)

  1. Request de ejemplo — el request de muestra del contrato de resolución, con los parámetros anteriores.
  2. Monto menor al mínimo — mismo request con amount = 0.01 (dispara MONTO_MINIMO si hay beneficios configurados con mínimo).
  3. Sucursal desconocida — mismo request con branchId = SUC-DESCONOCIDA-999 (dispara SUCURSAL_DESCONOCIDA; solo aplican beneficios sin restricción de sucursal).
  4. 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 }
    ]
  }
}
  • request es el body exacto que espera POST /api/v1/beneficios/resolver.
  • expected.benefits trae, para cada paymentMethodId esperado, percentage, maxDiscountAmount (null cuando el beneficio no tiene tope) y discountAmount (amount × percentage / 100, redondeado HALF_UP a dos decimales y recortado a maxDiscountAmount cuando existe). No hace falta incluir benefitId, description, customerTypeCode ni type: el simulador los ignora al comparar.
  • El orden de expected.benefits no 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:

  1. 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).
  2. 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%.
  3. monto_bajo_minimo — mismo jueves, amount = 0.01: no llega al montoMinimo de $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

cd ../../backend
mvn -DskipTests package
java -jar target/beneficioscenter-backend-*.jar

Dónde sigue