Saltar a contenido

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.

Entrega 3 — Tablero de promociones (Q3)

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