Arquitectura del sistema¶
BeneficiosCenter es un único backend Spring Boot con consola embebida, no un enjambre de servicios. Esta página explica el modelo mental —dónde vive la verdad, qué garantiza la velocidad y por qué la auditoría nunca frena un cobro—, no los endpoints (eso vive en la Referencia de API). Si te llevás una sola idea de acá, que sea esta: la respuesta al gateway jamás espera a la base de datos (INV-O01), y de esa decisión cuelga casi toda la arquitectura.
El problema que resuelve¶
El GatewayQR consulta a BeneficiosCenter en medio de un cobro y después debe reenviar la intención al autorizador. El objetivo es responder en menos de 20 ms (p99). La configuración de beneficios cambia pocas veces al día; las consultas llegan miles de veces al día. Cada consulta debe quedar auditada igual, aplique o no un beneficio.
Resolver esto contra la base en cada consulta ata la latencia y la disponibilidad del gateway a SQL Server. Auditar de forma sincrónica (sin persistir la traza no hay respuesta) daría trazas garantizadas, pero una base lenta frenaría cobros. Ninguna de las dos alternativas es aceptable (ver ADR-002).
Ruta caliente + snapshot en memoria + auditoría asincrónica¶
La solución tiene tres piezas que trabajan juntas:
flowchart LR
GW[GatewayQR] -->|"POST /resolver"| RC[Ruta caliente<br/>BenefitResolver]
RC -->|lee| SNAP[("ConfiguracionActiva<br/>snapshot inmutable en memoria")]
RC -->|responde benefits[]| GW
RC -.->|"offer() no bloqueante"| COLA[("Cola acotada<br/>de auditoría")]
COLA --> CONSUMER["Consumidor<br/>(virtual threads, en lotes)"]
CONSUMER --> DB[(SQL Server<br/>tabla consulta)]
CONSOLA[Consola web] -->|guarda beneficio/sucursal/tipo| EVENTO["Evento ConfiguracionCambiada"]
EVENTO --> SNAP
REFRESH["Refresco periódico<br/>(red de seguridad, cada 60s)"] --> SNAP
style RC fill:#E0F848,color:#111
style SNAP fill:#F3FBB9,color:#111
- Snapshot inmutable en memoria (
ConfiguracionActiva). Cada request resuelve contra la referencia que tomó al entrar; nunca ve un estado intermedio. Guardar un beneficio, tipo de cliente o sucursal desde la consola publica un evento que reconstruye el snapshot y lo intercambia atómicamente (INV-O03: rige en la próxima consulta, sin reinicio). Un refresco periódico (por defecto cada 60 s,bc.resolver.refresh-seconds) actúa como red de seguridad ante cualquier fallo del evento. BenefitResolver— función pura, sin Spring, sin I/O. Toma un request y laConfiguracionActiva, y devuelve el resultado. Es la pieza que se prueba al 100 % (ver Matriz de aceptación) porque no depende de nada externo.- Auditoría asincrónica. Cada consulta se intenta encolar en una cola acotada (por defecto
10 000 elementos,
bc.auditoria.queue-size).offer()nunca bloquea: si la cola está llena, el excedente se cuenta enauditoria.perdidasen vez de frenar la respuesta al gateway. Un consumidor en virtual threads escribe en lotes contra SQL Server.
Invariante que no se negocia
INV-O01 — la respuesta al gateway jamás espera a la base de datos. Con la base
detenida, POST /resolver sigue respondiendo dentro del presupuesto (AC-18) y
auditoria.perdidas incrementa en vez de ocultar la pérdida (INV-O02). Se acepta perder
traza en un incidente prolongado porque el gateway conserva su propio registro de la
transacción, y un cobro frenado es peor que una traza faltante (RISKS.md, A-01).
Componentes del sistema¶
flowchart TB
subgraph Externos["Fuera de este sistema"]
POS[POS de DINO]
GW[GatewayQR — Tipre]
AUT[Autorizador DINI/TECSO]
end
subgraph BC["BeneficiosCenter — un único jar"]
API["API de resolución<br/>POST /api/v1/beneficios/resolver<br/>auth: API key"]
ADMIN["API de administración<br/>ABM + tablero + export<br/>auth: Basic (ADMIN/OPERADOR)"]
CONSOLA["Consola web (React)<br/>servida en /gestor/"]
RESOLVER[BenefitResolver]
AUDIT[ColaAuditoria + consumidor]
DB[(SQL Server<br/>Flyway)]
end
POS --> GW
GW -->|X-Api-Key| API
API --> RESOLVER
RESOLVER --> AUDIT
AUDIT --> DB
CONSOLA -->|Basic auth| ADMIN
ADMIN --> DB
ADMIN -->|ConfiguracionCambiada| RESOLVER
GW --> AUT
style API fill:#E0F848,color:#111
| Pieza | Rol |
|---|---|
| API de resolución | Único punto de contacto con el GatewayQR. Autenticada por X-Api-Key (INV-S01), en su propia cadena de seguridad. |
| API de administración + consola | ABM de beneficios, tipos de cliente, sucursales y usuarios; tablero; auditoría con filtros. Autenticada por HTTP Basic stateless (ver ADR-003), roles ADMIN/OPERADOR verificados en el servidor (INV-S04). |
| SQL Server + Flyway | Persiste configuración y auditoría. Fuera de la ruta caliente. Migraciones aditivas (ver Base de datos). |
El jar único¶
Backend y consola tienen builds independientes, pero se despliegan como un solo jar de
Spring Boot (ver ADR-001): la consola compilada
se empaqueta bajo static/gestor/ y el mismo proceso sirve la API de resolución, la API de
administración y la consola.
La razón es el contexto de despliegue: un servidor del cliente, sin nube, con backup diario. Un artefacto único es lo más simple de instalar, versionar y respaldar — un jar corriendo como servicio de Windows detrás de un reverse proxy que termina HTTPS (ver Despliegue).
La configuración vive FUERA del jar
El artefacto no embebe ningún archivo de configuración. Cadena de conexión, API key del
gateway, admin inicial, retención de auditoría, branding: todo vive en config/ al lado
del jar. Si esa carpeta falta, el sistema no arranca en silencio con defaults invisibles:
falla rápido con un mensaje que dice qué falta (INV-O04). Los secretos nunca van en un
archivo versionado.
Seguridad de las dos superficies¶
BeneficiosCenter expone dos superficies con modelos de autenticación distintos, en cadenas de seguridad separadas:
- La API de resolución (el gateway) usa
X-Api-Key. Sin clave válida,401sin evaluar nada (INV-S01). - La consola y su API de administración (personas) usan HTTP Basic stateless sobre HTTPS,
con usuarios en tabla y contraseñas hasheadas con BCrypt (INV-S02). El rol
OPERADORno puede ejecutar ninguna escritura, y esa verificación vive en el servidor, no solo en la consola (INV-S04, ver ADR-003). - TLS y exposición pública son responsabilidad del reverse proxy; el jar nunca escucha directamente en Internet (INV-S05).
Dónde sigue¶
- Para el vocabulario exacto —qué es un Beneficio, una Consulta, un Tipo de cliente—, pasá por el Glosario.
- Para las reglas que ningún cambio puede violar, Invariantes.
- Para el detalle de entidades y columnas, el Modelo de dominio y Base de datos.
- Para el contrato exacto de la API, API de resolución.