1Resumen ejecutivo
Regla de lectura. Cada cifra lleva su etiqueta: medido se observó en un entorno real; objetivo es una meta escrita en un documento de diseño; estimado es un cálculo de este documento. Ninguna cifra de diseño se presenta como medida.
Dónde estamos. El diseño documentado de la base del core apunta a ≈1 M tx/día, 12 TPS promedio y 100 TPS pico objetivo. El switch declara más de 1.000 TPS y P99 menor de 100 ms objetivo sin prueba de carga registrada; la única medición disponible es una transacción de 99 ms en la demo local del 2026-09-12 medido, sin p95.
Qué ya existe y funciona (verificado en código): Kafka real con outbox en el core consumido por Invest y Trust; Quartz clusterizado en el core; circuit breakers en switch, core, edge y legacy-bridge; idempotencia por índice único en el Merchant (UQ_DAT_INTENT_IDEMPOTENCY); claves API por comercio (DatMerchantApiKey); Payment Hub ISO 20022 con pacs.008 y pacs.002 operativos; Pulse leyendo una réplica.
Qué satura primero: un único primario PostgreSQL, un único servidor Merchant (InMemoryDispatchChannel), dedupe del switch por consulta a DatCardLog, Redis de 256 MB sin clúster, agregaciones de liquidación en memoria y archivos de clearing en el disco del contenedor.
Las cinco decisiones
- Aceptación sin estado. Todo componente que multiplica (Merchant, switch) no guarda nada entre peticiones; el estado compartido —despacho, dedupe, idempotencia, límites— vive en Redis.
- Switch activo-activo. Dos instancias Aurora con dedupe y SAF compartidos, particionables por BIN o nodo emisor, con HSM real detrás del mismo
HsmPort. - Datos por escalón. Patroni, particionado mensual, resúmenes por evento y réplicas de lectura en E1; CQRS y ledger append-only en E2; sharding con Citus en E3.
- Un solo backbone de eventos. Kafka, con
trace_idde la caja a la cabina; elredpandadel compose, que ningún código usa, se retira. - Medir antes de mover. Ningún escalón se declara cerrado sin una prueba de carga reproducible que lo valide.
2Dimensionamiento
Factor hora pico de 8 a 10 veces el promedio: quincenas, diciembre, Black Friday. Es el mismo factor que ya usan el análisis de 20 M usuarios (12 → 100 TPS) y el diseño del Merchant a escala (6 M/día → 300–500 TPS).
TPS pico por escalón
Capacidad objetivo de cada escalón; el de hoy no tiene medición de carga que lo respalde.
Escala lineal a propósito: la distancia entre E0 y E3 es de 50 veces y se muestra tal cual. La barra rayada marca la única cifra sin prueba de carga. Detalle numérico en la tabla siguiente.
| Escalón | tx/día | TPS promedio | TPS pico (×8–10) | Filas/año | Datos/año | Qué satura primero (evidencia) |
|---|---|---|---|---|---|---|
| E0 hoy | ≈1 M obj. | 12 | 100 | 365 M | 0,6–1,2 TB est. | Nada hasta 100 TPS si el switch cumple su objetivo; no hay medición |
| E1 | 5 M | 58 | 500 | 1,8 G | 3–6 TB | InMemoryDispatchChannel (un servidor), sesión Jmix, dedupe por count en DatCardLog, Redis 256 MB |
| E2 | 15 M | 174 | 1.500 | 5,5 G | 9–18 TB | Un solo primario con 6 índices en la tabla caliente; agregaciones en memoria de PosSettlementService; sweeper netgw de una instancia |
| E3 | 50 M | 579 | 5.000 | 18 G | 30–60 TB | Techo de escritura de un primario (~8–10 K TPS), particiones Kafka, capacidad del HSM |
El tamaño por fila (1–2 KB en dat_acquiring_transaction, ×1,6 con índices) es estimado a partir de la entidad; se mide con pg_total_relation_size tras la primera prueba de carga. dat_payment_event añade ≈5 eventos por pago de ≈300 B y por eso nace append-only y particionada. Retención en línea 13 meses; legal 7–10 años en frío.
3Arquitectura objetivo por capa
Cada tarjeta contrasta lo verificado en el código hoy con el componente real de FINORA que lo sustituye. Nada cambia de contrato con la caja, el terminal ni el banco.
Captura y aceptación · Merchant finora-pos
Por qué: es el componente que multiplica (cada terminal, cada caja, cada tienda). Si no es sin estado, ningún balanceador ayuda.
Hoy (verificado)
- Una instancia;
InMemoryDispatchChannelcon una cola por terminal («sirve para una instancia del servidor, que es el despliegue de hoy») - Idempotencia por índice único
UQ_DAT_INTENT_IDEMPOTENCY - Claves API por comercio (
DatMerchantApiKey);permitir-globalescon default true - Sin rate limiting por comercio; RC 96 y
EN_DUDAya bien resueltos hacia el switch
Objetivo
- N réplicas sin estado detrás de HAProxy ×2 con VIP; UI con sesión pegajosa, API libre
DispatchChannelsobre Redis pub/sub (el javadoc ya lo prevé: «se sustituye este bean y nada más cambia»)- Redis
SETNX48 h delante del índice; token bucket por comercio y terminal POS_API_KEYS_PERMITIR_GLOBALES=falseen producción y rotación depos-dev-key-2026
Switch Aurora
Por qué: la instancia única es el punto único de fallo del riel de tarjetas, y el dedupe consulta una tabla que crece 365 M filas al año en cada transacción.
Hoy (verificado)
- Una instancia;
AuroraNettyServercon un par boss/worker, puerto 9502 + 9503/9504 - Dedupe:
TransactionService.isDuplicatedhace uncountJPQL sobreDatCardLogpor seis campos - Pipeline
@AuroraStepcon circuit breaker;NodeRouterpor prefijo BIN; SAF persistido SimulatorHsmAdapterdetrás deHsmPort
Objetivo
- A/B activo-activo detrás de un port manager externo; salud por
/actuator/health - Dedupe en Redis compartido; índice compuesto en
DatCardLogsolo para conciliación - Pool por nodo con latido 0800/0810; SAF compartido para que cualquier nodo drene reversos
- HSM real (presupuesto: P99 criptográfico ≤ 50 ms); desde E2, instancias por BIN/nodo
Interconexión bancaria · Payment Hub finora-netgw, SWIFT, nostro/vostro
Por qué: el hub ya está bien diseñado (hexagonal, idempotente, estado por confirmación). Lo que falta es operativo: multi-réplica del sweeper y cierre del ciclo con el core.
Hoy (verificado)
- pacs.008 salida + BAH, pacs.002 entrada; camt.053/054 en hoja de ruta
- Outbox JPA con
UNIQUE(idempotency_key, operation); el código deja elTODO multi-réplica: FOR UPDATE SKIP LOCKED - Rieles SWIFT/RTGS/ACH/INSTANT/INTERNAL por país; circuit breaker + reintento, sin DLQ
- El core no consume
finora.payments.status; MT y MX desdeDatSwiftMessage; monitor nostro/vostro operativo
Objetivo
- camt.053/054 entrada con conciliación de huérfanas y posición nostro/vostro alimentada por extracto
- Sweeper con
SELECT … FOR UPDATE SKIP LOCKEDpara N réplicas - Un adaptador por riel real con circuit breaker, reintento con jitter y cola de muertos
- Tópico
finora.payments.statusconsumido por el core para cerrar el ciclo contable
Datos
Por qué: a 1–2 KB por fila el problema no es el espacio sino el mantenimiento de índices y el VACUUM sobre miles de millones de filas. Particionar primero, resumir por evento, y solo al tocar el techo del primario, shardear.
Hoy (verificado)
- Un primario PostgreSQL 16; pgbouncer en el compose
dat_acquiring_transactionsin particionar, seis índices; cabeceras conSUMsobre la tabla grande- Pulse ya lee una réplica (
PulseReadReplica) - Saldos y estados se actualizan en sitio; todo el histórico en línea
Objetivo
- E1: Patroni + etcd, pgBackRest con PITR fuera de sitio, pgbouncer en transaction pooling; particionado mensual de transacción, clearing, intento y
dat_payment_event; BRIN e índices parciales; resúmenessum_outlet_daily,sum_terminal_daily,sum_merchant_hourlypor evento; réplica de reportes - E2: CQRS + ledger append-only para posiciones y liquidación, proyecciones reconstruibles
- E3: Citus por
merchant_id/pan_hash, EOD por shard; particiones mayores de 13 meses a columnar/Parquet
Eventos
Por qué: un solo backbone con clave de partición por comercio y por nodo da orden y reproducibilidad; dos brokers en el compose sin consumidores solo dan confusión.
Hoy (verificado)
- Kafka real:
OutboxKafkaConfig+OutboxRelayJoben el core, consumidos por Invest y Trust redpandalevantado en el compose sin ningún consumidor en código
Objetivo
- Kafka de 3 brokers (KRaft) como único backbone; tópicos por dominio (
finora.acc.entries.posted,finora.payments.status,finora.pos.transaction.events,finora.switch.transaction.events) trace_idnace en la caja y viaja en cabecera Kafka, ISO 8583 ydat_payment_event- Retirar
redpandadel compose
Clearing y liquidación
Por qué: los jobs y las agregaciones fueron escritos para una instancia; a escala hay que clusterizarlos, paralelizarlos por marca y sacar los archivos del disco del contenedor.
Hoy (verificado)
- Archivos en
clearing-out/ysettlement-out/del contenedor; formato lógico textual, no el físico GCMS PosClearingJob→PosSettlementJob→ReconciliationJob; finora-pos no declaraisClustered(el core sí)computePositionsycomputeMerchantSettlementsagrupan en memoria- Posteo al core tras flags (default false); doble partida asíncrona cada 15 min; settlement Mastercard real pendiente
Objetivo
- Object storage (MinIO/S3) con versionado y checksum; formato físico IPM/GCMS con la certificación
- Quartz clusterizado en finora-pos; jobs paralelos por marca e institución
- Agregación incremental en SQL por partición; tarifa por registro cuando BIN/MCC difieren
- Encender el posteo solo con la conciliación de tres vías cubierta
Observabilidad
Por qué: hoy no existe un tablero transaccional en tiempo real del Merchant; MerchantDashboardService alimenta un panel embebido en la ficha del comercio, no una cabina.
Hoy (verificado)
- Prometheus + Grafana en el compose;
netgw_outbound_total{operation,rail,outcome} - Pulse con 8 pantallas por STOMP; monitor de salud de 12 servicios
Objetivo
- Métricas de negocio con
trace_id: TPS por riel y comercio, RC por nodo emisor,EN_DUDAabiertas y antigüedad, P50/P95/P99 por pipeline, lotes sin cerrar, cola de despacho por terminal, lag de Kafka y de réplica, profundidad del outbox - Pulse como cabina de mando del Merchant; alertas por escalón (P95 > 200 ms, RC 96 > 0,5 %,
EN_DUDA> 50, lag > 30 s)
Seguridad y PCI
Por qué: el PAN nunca sale del terminal ni del switch; la caja no lo ve y el Merchant guarda solo el enmascarado. Tokenización de red y HSM real dependen del banco patrocinante y de la certificación de marca.
Hoy (verificado)
- Hallazgo cerrado:
apply-conceptdel core sí exige@PreAuthorize("hasAuthority('SCOPE_accounting:write')")(commit595b64a, 2026-07-02); el inventario del 12-09 lo listaba como abierto y aquí queda corregido finora.pos.api-keys.permitir-globalescon default true;pos-dev-key-2026en compose y docs
Objetivo
POS_API_KEYS_PERMITIR_GLOBALES=falseen producción; rotación de la clave de desarrollo- mTLS con nodos de banco; secretos fuera de imágenes; trigger que bloquee UPDATE/DELETE en
dat_payment_event - Tokenización MDES/VTS y HSM físico cuando exista convenio de red
4Hoja de ruta por escalones
Esfuerzo estimado en semanas de un equipo de dos ingenieros backend y uno de plataforma. La prueba de carga es el criterio de salida: sin ella el escalón no se declara cerrado.
- Kafka, outbox, circuit breakers y Quartz clusterizado ya existen
- Sin prueba de carga registrada
- Redis pub/sub, dedupe e idempotencia compartidos
- Patroni + pgBackRest + pgbouncer
- Particionado mensual,
dat_payment_event, resúmenes por evento - Sweeper netgw multi-réplica; Quartz clusterizado en POS; archivos a MinIO
- Cabina Pulse con métricas de negocio
- Switch A/B activo-activo con port manager y SAF compartido
- Kafka de 3 brokers; retiro de redpanda; estados al core
- CQRS + ledger append-only; agregaciones SQL por partición
- HSM real detrás de
HsmPort; réplica dedicada a conciliación
- Citus por
merchant_id/pan_hash; EOD por shard - Switch particionado por BIN/nodo
- Kubernetes con autoescalado y segmentación CDE
- Frío columnar para más de 13 meses
Antes de E3 hay que confirmar que el negocio necesita 50 M tx/día: es un salto de infraestructura que el plan maestro cifra en CAPEX de seis cifras.
5Riesgos y dependencias externas
| Riesgo / dependencia | Impacto | Mitigación |
|---|---|---|
| Certificación de marca (Visa / Mastercard) | Sin ella no hay clearing físico ni settlement real; bloquea E2 en tarjetas | Avanzar E1 con el formato lógico; simulador y banco simulado; paquete de certificación en paralelo |
| Banco patrocinante | Su API es hoy punto único de fallo; sin orquestación en producción | Nodo autorizador replicado, circuito abierto por nodo, EN_DUDA con barrido; segundo banco por convenio |
| Firma de la aplicación Wiseasy (T2 / RX / P5L) | Sin firma no hay EMV real en terminal | Certificación con el fabricante; mientras tanto ECR + PIN pad certificado |
| HSM real | Procurement de 8–12 semanas y CAPEX; bloquea E2 | Iniciar al cerrar E1; HSM en nube como puente si el P99 cabe en 50 ms |
| Regulación VE (adquirencia reservada a bancos) | Define el modelo de negocio | Modelo de administradora bajo banco patrocinante |
| Cifras de diseño tomadas por medidas | Decisiones de capacidad erróneas | Nada se declara cerrado sin prueba de carga registrada |
6Diagramas
Diagramas interactivos (pan, zoom, búsqueda, vistas guiadas, exportación). Se abren en la misma pestaña.
7Referencias
finora-docs/architecture/ARQUITECTURA_ORQUESTACION_PAGOS_ESCALA_2026-09-12.md— el documento completo del que sale esta página.finora-docs/architecture/ANALISIS_ESCALABILIDAD_20M_USUARIOS.md— escalones F1–F3 y técnicas de lectura histórica.finora-docs/architecture/ARQUITECTURA_TRANSACCIONES_ALTA_ESCALABILIDAD.mdyARCHITECTURE_HIGH_PERFORMANCE.md— diseño del core para +1 M tx/día (cifras objetivo).finora-docs/architecture/ADR_EVENT_BACKBONE.md— outbox transaccional y elección de broker.finora-docs/architecture/GAP_ANALYSIS_PAYMENTS_2026-07.md— brechas del Payment Hub y del lifecycle adquirente.finora-docs/architecture/PLAN_MAESTRO_INFRAESTRUCTURA.md— Kubernetes on-premise, HSM, segmentación PCI, costos.finora-docs/merchant/ARQUITECTURA_MERCHANT_ESCALA_2026-09-04.md— jerarquía, resúmenes por evento y alta disponibilidad del Merchant.finora-docs/merchant/DISENO_COBRO_EMPUJADO_2026-09-11.md— canal de despacho (decisión D6).