Matriz de aceptación
Fuente de verdad con checkboxes: ACCEPTANCE.md en el repositorio del sistema. Cada criterio
cierra solo con un test verde que lleva su id en el nombre (ac01_..., AC-01: ...).
Motor de resolución (puro, sin Spring)
| Id |
Descripción |
Estado |
| AC-01 |
Beneficio activo para Negocios y Gift Card con el mismo porcentaje: la respuesta trae dos entradas con ese porcentaje. |
Cumplido |
| AC-02 |
Beneficio por tipo con porcentajes distintos: cada entrada trae el suyo. |
Cumplido |
| AC-03 |
Beneficio solo para código 990: una sola entrada, no aparece 991. |
Cumplido |
| AC-04 |
Ticket menor al monto mínimo: lista vacía, motivo MONTO_MINIMO. |
Cumplido |
| AC-05 |
Ticket igual al monto mínimo: el beneficio aplica (mayor o igual). |
Cumplido |
| AC-06 |
Consulta fuera de vigencia, día de semana o franja horaria: lista vacía con el motivo correspondiente; decide transaction.datetime en zona Córdoba. |
Cumplido |
| AC-07 |
Beneficio restringido a sucursales: aplica solo si branchId está en la lista. |
Cumplido |
| AC-08 |
Beneficio que excluye factura A: la descarta con FACTURA_NO_COINCIDE; los demás siguen evaluándose. |
Cumplido |
| AC-09 |
Dos beneficios vigentes para el mismo tipo: gana prioridad menor, luego porcentaje mayor, luego id menor; nunca se suman. |
Cumplido |
| AC-10 |
Beneficio con tope: la respuesta incluye maxDiscountAmount; sin tope, null. |
Cumplido |
| AC-11 |
branchId desconocido: aplican solo los beneficios sin restricción de sucursal; marca SUCURSAL_DESCONOCIDA. |
Cumplido |
| AC-12 |
Beneficio con codigosExtra: aplica solo con código de la lista; sin código, CODIGO_EXTRA_NO_COINCIDE. |
Cumplido |
| AC-13 |
Dos consultas idénticas contra el mismo snapshot devuelven exactamente la misma respuesta. |
Cumplido |
API de resolución
| Id |
Descripción |
Estado |
| AC-14 |
Sin X-Api-Key o con clave inválida: 401 sin evaluar ni auditar evaluaciones. |
Cumplido |
| AC-15 |
Request con campo inválido: 400 nombrando el campo. |
Cumplido |
| AC-16 |
Request válido: 200 con el esquema del contrato y elapsedMs. |
Cumplido |
| AC-17 |
requestId repetido: 200 normal y segunda auditoría marcada REPETIDA. |
Cumplido |
| AC-18 |
Con la base detenida: 200 dentro de 20 ms en el test y auditoria.perdidas incrementa. |
Cumplido |
| AC-19 |
Guardar un beneficio por la API de administración cambia la respuesta de la siguiente consulta sin reinicio. |
Cumplido |
| AC-20 |
Cada consulta auditada persiste request, response, latencia y evaluaciones con motivo. |
Cumplido |
Consola y administración
| Id |
Descripción |
Estado |
| AC-21 |
OPERADOR recibe 403 en cualquier POST, PUT o DELETE de administración. |
Cumplido |
| AC-22 |
Usuario inactivo recibe 401; contraseñas persisten solo como hash BCrypt. |
Cumplido |
| AC-23 |
Primer arranque siembra el admin desde config externa; segundo arranque no lo duplica ni resetea. |
Cumplido |
| AC-24 |
No se puede crear un tipo de cliente con paymentMethodId vacío o duplicado. |
Cumplido |
| AC-25 |
No se puede guardar un beneficio sin tipo de cliente, con porcentaje fuera de (0, 100], o con vigencia invertida. |
Cumplido |
| AC-26 |
La consola muestra los cuatro ABM y la auditoría con filtros por fecha, sucursal y requestId; las acciones de escritura no se muestran al OPERADOR. |
Cumplido |
| AC-27 |
Arranque sin configuración externa falla con mensaje claro. |
Cumplido |
Contrato con el gateway
| Id |
Descripción |
Estado |
| AC-28 |
El simulador del gateway traduce cada benefits[] a un elemento benefits_methods_data con benefits_card.code, .description, discount.percentage y .maximum_discount_amount. |
Cumplido |
Métricas de éxito (entrega 1)
- Cobertura de líneas backend ≥ 85 %; motor de resolución 100 %.
- p99 de
resolver.latencia < 20 ms en la prueba de humo.
- Cero hallazgos HIGH en SpotBugs/FindSecBugs y análisis de dependencias.
Entrega 2 — Tablero
| Id |
Descripción |
Estado |
| AC-29 |
GET /api/admin/tablero (rango por defecto hoy, máximo 90 días) devuelve consultas totales, porcentaje con beneficio, p99 de latencia, cinco beneficios más aplicados y sucursales desconocidas con conteo. |
Cumplido |
| AC-30 |
Un rango mayor a 90 días responde 400 nombrando el campo. |
Cumplido |
| AC-31 |
La consola muestra el tablero como pantalla inicial con hoy por defecto y un selector de rango. |
Cumplido |
Entrega 2 — Retención y exportación de auditoría
| Id |
Descripción |
Estado |
| AC-32 |
Job programado borra las consultas anteriores a bc.auditoria.retencion-dias en lotes; nunca borra dentro del período. |
Cumplido |
| AC-33 |
GET /api/admin/consultas/export devuelve CSV en streaming, UTF-8 con BOM, separador ;, limitado a 90 días. |
Cumplido |
| AC-34 |
Existe un índice compuesto (recibida_en, branch_id); la búsqueda construye el predicado solo con los filtros presentes. |
Cumplido |
| AC-67 |
El job de purga (AC-32) retiene ahora 365 días y corre semanalmente en vez de a diario (bc.auditoria.retencion-dias/bc.auditoria.purga-cron, antes 90 días/nocturno); la misma pasada purga también resultado_pago (sin cascada, mismo batcheo, nunca dentro del período), acumulando el total en auditoria.purgadas. Agrega los índices V7 ix_resultado_pago_estado_benefit_id e ix_resultado_pago_estado_payment_method_id, que respaldan las agregaciones del tablero de captura y del panel de resultados. |
Cumplido |
Entrega 2 — Marca blanca y consola
| Id |
Descripción |
Estado |
| AC-35 |
GET /api/branding (permitAll) devuelve logo_url, color_primario y cliente_nombre; la consola los aplica en runtime sin rebuild. |
Cumplido |
| AC-36 |
La consola es instalable como PWA: manifest e iconos como archivos, service worker con fallback de navegación. |
Cumplido |
| AC-37 |
Los tipos de factura del formulario se eligen con casillas A/B/C; los montos rechazan negativos en consola y servidor. |
Cumplido |
| AC-44 |
Marca blanca completa por configuración: bc.branding.assets-dir sirve /branding/** con fallback a los embebidos; manifest generado en runtime; la consola usa "BeneficiosCenter" como texto de respaldo, nunca "DINI". |
Cumplido |
Entrega 2 — Contrato con el gateway y operación
| Id |
Descripción |
Estado |
| AC-38 |
/v3/api-docs describe ambas APIs con los mismos esquemas del contrato; un test compara el JSON generado con la respuesta real. |
Cumplido |
| AC-39 |
Prueba de carga: 200 req/s sostenidos 5 minutos con la base activa y luego detenida; p99 < 20 ms y auditoria.perdidas > 0 solo en la segunda corrida. |
Cumplido |
| AC-40 |
/gestor/assets/** con Cache-Control: public, max-age=31536000, immutable; index.html con no-cache. |
Cumplido |
| AC-45 |
Cada entrada de benefits incluye type ("DISCOUNT") y discountAmount redondeado HALF_UP a 2 decimales, recortado al tope. |
Cumplido |
Entrega 3 — Pipeline de resultado de pago (Q2)
| Id |
Descripción |
Estado |
| AC-46 |
POST /api/v1/beneficios/resultado con X-Api-Key válida y cuerpo válido responde 202 Accepted de inmediato y encola el resultado para persistencia asíncrona correlacionada por requestId; ResultadoConsumer lo persiste eventualmente (mismo patrón cola + consumidor que auditoría, ADR-002) — nunca espera a la base en el hilo del llamador. |
Cumplido |
| AC-47 |
Sin X-Api-Key (o inválida): 401 y no persiste ningún resultado (INV-S01). |
Cumplido |
| AC-48 |
Reenviar el mismo resultado (mismo requestId) es idempotente: ResultadoConsumer actualiza una sola fila, nunca duplica. |
Cumplido |
| AC-49 |
Cuerpo inválido (requestId vacío, estado nulo, importe negativo): 400 nombrando el campo. |
Cumplido |
| AC-50 |
Con la cola llena, el endpoint responde 202 igual y no falla; el resultado se descarta y resultados.perdidos incrementa (mirror AC-18/INV-O02). |
Cumplido |
Entrega 3 — Panel de resultados
| Id |
Descripción |
Estado |
| AC-51 |
Dado un rango de fechas válido (por defecto hoy, máximo 90 días), GET /api/admin/tablero/resultados devuelve totales, tasa de aprobación, desglose por medio de pago (con fila "sin dato" para paymentMethodId nulo) y descuento efectivo total, sobre resultado_pago con agregaciones SQL agrupadas; ambos roles leen (INV-S04). |
Cumplido |
| AC-52 |
Un rango mayor a 90 días responde 400 nombrando el campo. |
Cumplido |
| AC-53 |
Sin resultados en el rango: 200 con tasa de aprobación 0.00, descuento efectivo total 0.00 y porMedioDePago vacío, sin dividir por cero. |
Cumplido |
| AC-54 |
La consola muestra la pantalla "Resultados de pago" junto al Tablero, con hoy por defecto y los KPIs devueltos por la API. |
Cumplido |
Gap documentado, no un olvido
El KPI "ofrecido vs. aplicado por beneficio" queda deliberadamente fuera de esta entrega:
benefitId en resultado_pago dependía de un round-trip del lado del GatewayQR que no
existía cuando se construyó el panel. Ese round-trip ya se implementó del lado del gateway
(ver Pipeline de resultado de pago), pero
el panel todavía no agrupa por beneficio — se retoma como extensión aditiva del mismo
endpoint.
| Id |
Descripción |
Estado |
| AC-55 |
Dado un rango de fechas válido (por defecto hoy, máximo 90 días), GET /api/admin/tablero/captura devuelve el embudo ofrecido→aplicado (consultas, conBeneficioOfrecido, pagosAprobados, beneficiosAplicados) y un ranking por beneficio (ofrecido, aplicado, efectividadPct, descuentoEntregado), sobre consulta_beneficio (ofrecido) y resultado_pago (aplicado); ambos roles leen (INV-S04). |
Cumplido |
| AC-56 |
Un rango mayor a 90 días responde 400 nombrando el campo. |
Cumplido |
| AC-57 |
Sin datos en el rango, responde 200 con el embudo en cero y porBeneficio vacío; un beneficio nunca ofrecido no divide por cero (efectividadPct = 0.00). |
Cumplido |
| AC-58 |
La consola redibuja el Tablero como pantalla de promociones: primero el ranking por beneficio (ofrecido/aplicado/efectividad/descuento entregado, ordenable) con los valores de GET /api/admin/tablero/captura. |
Cumplido |
| AC-59 |
La sala de control (misma pantalla) colorea la tasa de aprobación y la tasa de rechazo según umbral (verde/ámbar/rojo, siempre con ícono + etiqueta, nunca solo color), reutilizando GET /api/admin/tablero y GET /api/admin/tablero/resultados, y lista las sucursales desconocidas como alerta con link a Sucursales. |
Cumplido |
| AC-60 |
GET /api/admin/tablero/por-hora devuelve 24 buckets (hora 0..23, ascendentes) del día de hoy (zona Córdoba), siempre completos aunque no haya datos; el límite de zona horaria se verifica explícitamente (01:00 Córdoba = 04:00 UTC cae en el bucket hora=1, no hora=4) — bucketing hecho en Java sobre Instant, no agrupado en SQL; ambos roles leen (INV-S04). |
Cumplido |
AC-61 a AC-65: sin entrada formal en ACCEPTANCE.md
A diferencia de AC-55..60, estos cinco criterios no tienen (todavía) una entrada con
checkbox en ACCEPTANCE.md del repositorio del sistema — la descripción de abajo se
reconstruyó de los comentarios y nombres de test (describe(...)) del código
(gestor/src/pages/tablero/ActividadPorHora.tsx, gestor/tests/pages/*.test.tsx), no de un
enunciado formal. Tratarlos como comportamiento verificado por test, pendientes de
formalizar en ACCEPTANCE.md.
| Id |
Descripción |
Estado |
| AC-61 |
El heatmap de actividad por hora (GET /api/admin/tablero/por-hora, 24 celdas) reemplaza al embudo de captura como historia principal de apertura del Tablero. |
Cumplido (test de componentes) |
| AC-62 |
En los cuatro ABM (Beneficios, Sucursales, Tipos de cliente, Usuarios), "Crear" abre un diálogo modal con el formulario vacío. |
Cumplido (test de componentes) |
| AC-63 |
"Editar" abre el mismo diálogo modal, precargado con los datos de la fila elegida. |
Cumplido (test de componentes) |
| AC-64 |
El listado de cada ABM pagina de a 5 filas (paginación en el cliente). |
Cumplido (test de componentes) |
| AC-65 |
En el formulario de Beneficios, el campo Sucursales usa un combobox buscable con chips (MultiSelect): filtra por texto, permite elegir varias y preserva "vacío = todas" si no se toca nada. |
Cumplido (test de componentes) |
Entrega 3 — Observabilidad del tráfico de GatewayQR
| Id |
Descripción |
Estado |
| AC-66 |
Cada llamada a /api/v1/beneficios/** (resolver/resultado) queda registrada en logs/traffic.log (logger trafico.beneficios, additivity=false) con método, URI, status, latencia, requestId y los cuerpos completos de request y response, sin alterar la respuesta real al cliente; nunca loguea la X-Api-Key (INV-S03); el appender es asíncrono con neverBlock=true para no bloquear el hot path (INV-O01). |
Cumplido |
Verdad de campo (depende de DINO)
| Id |
Descripción |
Estado |
| AC-41 |
El corpus real de DINO, corrido contra la configuración real, coincide en el 100 % de los casos con la tabla esperada. |
Pendiente — depende del corpus real de DINO |
Deuda de código
| Id |
Descripción |
Estado |
| AC-42 |
Rename masivo de identificadores del dominio a inglés. |
Cancelado (2026-09-04, decisión del operador: el vocabulario en castellano es la convención del proyecto) |
| AC-43 |
Sucursales, Tipos de cliente y Usuarios comparten un hook useCrudResource y un cliente crudApi<T>; BeneficiosPage en contenedor + presentación. |
Cumplido |
Dónde sigue