E2E con GatewayQR¶
Corridas de extremo a extremo, sin binario de POS, contra los dos procesos reales
(BeneficiosCenter y GatewayQR) levantados localmente contra SQL Server real. Esta página
documenta dos casos concretos, en orden cronológico: el fix de signo de importe_recdesc
(entrega 2) y el pipeline de resultado de pago (entrega 3, Q2).
Corrida 1 — fix de signo de importe_recdesc (entrega 2)¶
Fuente: reports/e2e-servidor-20260911-162402.md del repositorio del sistema.
Topología de la corrida¶
| Proceso | Puerto | Detalle |
|---|---|---|
| BeneficiosCenter | 8081 | beneficioscenter-backend-0.0.1-SNAPSHOT.jar, DB beneficioscenter, JDK 25 (requiere class-file 61). |
| GatewayQR | 8082 | gatewayqr-20250919.war, rama feature/beneficios-integration, commit fddef57, DB GatewayQR, perfil dev, corrido con JDK 8 (Spring Boot 1.5.14). |
app.beneficioscenter.api-key se pasó por línea de comandos, sin commitear ni imprimir en el
reporte original.
El fix que valida esta corrida¶
Commit fddef57 en GatewayQR: fix(webhook): store importe_recdesc as negative discount, not
raw positive. Antes del fix, importe_recdesc se guardaba en positivo; el POS rechazaba la
coherencia de importes.
Paso 0 — confirmar el caso jueves¶
Mismo escenario resuelto con fecha sintética de jueves y con el reloj real (viernes) — el signo es el punto del fix, no cuál beneficio gana:
| Reloj | Beneficio ganador | discountAmount |
|---|---|---|
Jueves sintético (2026-09-10T14:00:00-03:00) |
Benefit 1, "10% los jueves para DINI Negocios" | 10000.00 |
| Viernes real (reloj del sistema) | Benefit 3, "15% por aniversario" | 15000.00 |
Loop HTTP completo (POS → GatewayQR → BeneficiosCenter → emulador → webhook)¶
POST /api/trxid (comercio "Dino", sucursal 99, pos 89) devolvió el id 10140.
POST /api/trxpago con {"id":10140,"importe":100000,"tipoautorizador":"TECSO"} disparó el
loop completo, bloqueando hasta la respuesta.
(a) Intención de pago saliente, con benefits_methods_data real¶
{
"original_amount": 100000,
"benefits_methods_data": [
{
"benefits_card": {"code": "991", "description": "15% por aniversario en SUC-001/SUC-014/SUC-022, con tope de descuento"},
"establishment_id": "29880765",
"discount": {"amount": 15000, "percentage": 15, "maximum_discount_amount": 25000, "type": "DISCOUNT"}
},
{
"benefits_card": {"code": "990", "description": "15% por aniversario en SUC-001/SUC-014/SUC-022, con tope de descuento"},
"establishment_id": "29880765",
"discount": {"amount": 15000, "percentage": 15, "maximum_discount_amount": 25000, "type": "DISCOUNT"}
}
],
"branch": "SUC-014"
}
benefits_methods_data viene de la llamada real de BeneficiosCenterClient a
/api/v1/beneficios/resolver — idéntica a la del Paso 0, viernes.
(b) Respuesta del emulador (webhook entrante)¶
El emulador sigue mandando discounted_amount en positivo (es el contrato del autorizador
real, no cambió). El fix está en cómo el gateway lo guarda.
(c) TrxRtaPos final entregado al POS¶
{
"id": 10140,
"estado": "AUTORIZACION_DE_PAGO_APROBADA",
"importe": 100000.00,
"payment_method_id": 990,
"importe_final": 85000.00,
"importe_recdesc": -15000.00
}
Coherencia verificada en el POS: importe (100000) + importe_recdesc (-15000) == importe_final
(85000) ✔. dD = -1.0 * importe_recdesc = 15000 >= 0 → el POS acepta el pago (antes del
fix, dD daba -15000 < 0 y rechazaba con "recdesc positivo = recargo: dato inválido").
Antes / después¶
importe_recdesc |
Resultado en el POS | |
|---|---|---|
| Antes del fix | +15000.00 (positivo) |
Rechazado por coherencia/signo |
| Ahora (esta corrida) | -15000.00 (negativo) |
Aceptado, coherencia verificada end-to-end |
Qué es en vivo vs qué no¶
Todo lo de esta corrida es HTTP en vivo contra los dos procesos reales, sin componentes mockeados, stubs ni resultados inventados. La única fuente no-HTTP consultada fue una lectura por SQL (sin escrituras) para confirmar que los datos de entorno (URLs del autorizador, sucursal) seguían intactos de la corrida anterior.
Corrida 2 — Pipeline de resultado de pago (entrega 3, Q2)¶
Misma topología (BeneficiosCenter real en 8081, GatewayQR real en 8082) contra SQL Server real,
esta vez para verificar el pipeline completo de
resultado de pago: que el gateway llame a /resultado
después de que el autorizador aprueba, que quede persistido en resultado_pago, y que el
panel de Resultados lo refleje.
Money-path del GatewayQR corregido antes de esta corrida¶
Dos fixes de las llamadas al autorizador, ambos necesarios para que este loop fuera confiable antes de medir nada sobre él:
- SEC-01 — webhook fail-closed. Antes, si
server.apikey.tecsono estaba configurada en el ambiente, la validación del webhook de aprobación se salteaba entera (ifque solo comparaba cuando la clave sí estaba seteada) — cualquiera podía simular una aprobación. Ahora, sin la clave configurada, el webhook responde401(fail-closed) y la comparación usaMessageDigest.isEqualen tiempo constante. - CORR-01 — crash en pagos en cuotas.
BigDecimal.divide(cuot)sin escala explícita lanzabaArithmeticExceptionen cualquier división no exacta (por ejemplo 3 cuotas). Se cambió adivide(cuot, 2, RoundingMode.HALF_EVEN).
Estos dos fixes son del money-path del gateway (cómo cobra), no del pipeline de resultado en sí — se documentan acá porque esta corrida los ejercita en vivo.
Loop verificado¶
Un pago de 100.000 con la cuenta 990 (Dini Negocios), contra el emulador del gateway:
POST /resolverresuelve el beneficio "Aniversario DINO 15%" (benefitId: 3) para la cuenta 990:discountAmount: 15000.00.- El emulador aprueba con
payment_method_id: 990,discounted_amount: 15000. - El GatewayQR arma la respuesta al POS:
importe_final: 85000.00. - El GatewayQR llama, fire-and-forget, a
POST /api/v1/beneficios/resultado:
{
"requestId": "GW-...",
"estado": "APROBADO",
"paymentMethodId": 990,
"benefitId": 3,
"descuentoAplicado": 15000.00,
"importeOriginal": 100000.00,
"importeFinal": 85000.00
}
benefitId: 3 viaja porque el gateway lo recuperó de su propia intención persistida
(jsonreqintencion), matcheando por payment_method_id — ver
benefitId: de dónde sale.
5. BeneficiosCenter responde 202 y persiste, poco después, la fila en resultado_pago.
6. GET /api/admin/tablero/resultados para el día de la corrida devuelve exactamente lo que
pasó:
{
"totalConResultado": 1,
"aprobados": 1,
"rechazados": 0,
"tasaAprobacionPct": 100.00,
"descuentoEfectivoTotal": 15000.00,
"porMedioDePago": [
{ "paymentMethodId": 990, "cantidad": 1, "porcentaje": 100.00 }
]
}
Estos son los mismos números que muestra el panel poblado en Consola web — Resultados de pago.
Qué prueba esta corrida y qué no
Es una corrida real de punta a punta contra el emulador del gateway (no contra el
autorizador DINI/TECSO real) — confirma que el pipeline completo funciona (resolución →
aprobación → /resultado → persistencia → panel), no reemplaza la confirmación formal de
DINI sobre el contrato con el autorizador real (ver
Contrato con el autorizador, RISKS.md R-02).
Dónde sigue¶
- El contrato exacto de
benefits_methods_dataybenefits_data, en Contrato con el autorizador. - El contrato completo del pipeline de resultado, en Pipeline de resultado de pago.
- Probar el mismo contrato sin levantar el GatewayQR real, en Simulador del gateway.