[CONSEJO — Doc 07] Contrarian: El path crítico del backend de BookMind no es el motor de tesorería — es la emisión de FE. Un bug en el motor de tesorería produce un forecast inexacto. Un bug en el emisor de FE produce una multa real al cliente. Diseñar la arquitectura FE con el mismo nivel de resiliencia que un sistema de pagos: retry automático, cola persistente, idempotencia estricta. Executor: La cola de FE debe sobrevivir un reinicio completo del servidor. Si la FE está en cola y el servidor cae, no puede perderse. Redis Streams con ACK explícito + respaldo en PostgreSQL es el patrón correcto, no fire-and-forget. Auditor Seg.: Las credenciales de acceso a Hacienda CR, SAT MX y DIAN CO son las llaves del reino fiscal del cliente. Si un atacante obtiene esas credenciales, puede emitir facturas a nombre del cliente o anularlas. Deben estar encriptadas en reposo con pgcrypto y nunca en variables de entorno en texto plano.
1. Arquitectura general
┌─────────────────────────────────────────────────────────────────┐
│ FRONTEND (Next.js) │
└────────────────────────────┬────────────────────────────────────┘
│ HTTPS + JWT httpOnly cookie
┌────────────────────────────▼────────────────────────────────────┐
│ FastAPI (API Gateway) │
│ /api/v1/bookmind/* │
└──┬──────────────┬──────────────┬──────────────┬─────────────────┘
│ │ │ │
▼ ▼ ▼ ▼
FEService GLService TreasuryService BankService
(emisión FE) (asientos) (forecast) (Belvo/Prometeo)
│ │ │ │
└──────────────┴──────────────┴──────────────┘
│
┌────────▼────────┐
│ PostgreSQL 16 │
│ (bk_* tables) │
└─────────────────┘
│
┌────────▼────────┐
│ Celery Workers │
│ Redis Streams │
└─────────────────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
Hacienda CR SAT MX DIAN CO
(FE v4.4) (CFDI 4.0) (FE DIAN)
2. API endpoints principales
2.1 Facturación electrónica
POST /api/v1/bookmind/fe/emitir
GET /api/v1/bookmind/fe/{fe_id}
GET /api/v1/bookmind/fe/lista?pais=CR&mes=2026-08&estado=emitida
POST /api/v1/bookmind/fe/{fe_id}/anular # requiere motivo
GET /api/v1/bookmind/fe/contingencia # FEs en cola con error
POST /api/v1/bookmind/fe/{fe_id}/reenviar # reintento manual
# Webhooks entrantes (Hacienda CR notifica confirmación o rechazo)
POST /api/v1/bookmind/webhooks/hacienda-cr
POST /api/v1/bookmind/webhooks/sat-mx
POST /api/v1/bookmind/webhooks/dian-co
POST /api/v1/bookmind/fe/emitir — request:
{
"tenant_id": "uuid",
"pais": "CR",
"tipo_documento": "01",
"receptor": {
"cedula": "3-101-XXXXXX",
"nombre": "Distribuidora Ramírez S.A.",
"email": "facturacion@ramirez.com"
},
"lineas": [
{
"detalle": "Servicio de consultoría",
"cantidad": 1,
"precio_unitario": 8500.00,
"iva_porcentaje": 13
}
],
"tipo_pago": "01",
"moneda": "CRC",
"actividad_economica": "741001"
}
POST /api/v1/bookmind/fe/emitir — response (201):
{
"fe_id": "bk-fe-uuid",
"clave": "50601202608310101110001200100001010000000114842280",
"numero_consecutivo": "00100001010000000001",
"estado": "en_cola",
"mensaje": "FE en cola de envío a Hacienda. Confirmación esperada en <30 segundos.",
"webhook_url": "/api/v1/bookmind/webhooks/hacienda-cr"
}
2.2 General Ledger
GET /api/v1/bookmind/gl/plan-cuentas
POST /api/v1/bookmind/gl/asientos # asiento manual
GET /api/v1/bookmind/gl/balance-comprobacion?fecha=2026-08-31
GET /api/v1/bookmind/gl/estado-resultados?desde=2026-01-01&hasta=2026-08-31
GET /api/v1/bookmind/gl/balance-general?fecha=2026-08-31
GET /api/v1/bookmind/gl/asientos?cuenta=1101&desde=2026-08-01
2.3 Cuentas por cobrar y pagar
GET /api/v1/bookmind/ar/aging # aging report
GET /api/v1/bookmind/ar/scoring # scoring cobranza por cliente
POST /api/v1/bookmind/ar/facturas/{id}/pago-parcial
GET /api/v1/bookmind/ap/calendario-vencimientos
POST /api/v1/bookmind/ap/facturas # registrar factura proveedor
2.4 Conciliación bancaria
GET /api/v1/bookmind/bancos/cuentas
POST /api/v1/bookmind/bancos/sync # trigger sync Belvo/Prometeo
GET /api/v1/bookmind/bancos/{cuenta_id}/movimientos?mes=2026-08
POST /api/v1/bookmind/bancos/conciliacion/emparejar
POST /api/v1/bookmind/bancos/conciliacion/cerrar # cierre de conciliación
2.5 Tesorería predictiva
GET /api/v1/bookmind/tesoreria/forecast # P10/P50/P90 90 días
GET /api/v1/bookmind/tesoreria/alertas # alertas activas
POST /api/v1/bookmind/tesoreria/escenario # escenario manual ad-hoc
GET /api/v1/bookmind/tesoreria/supuestos # qué facturas/pagos alimentan el forecast
GET /api/v1/bookmind/tesoreria/forecast — response:
{
"generado_en": "2026-08-12T08:00:00Z",
"horizonte_dias": 90,
"saldo_actual": 21650.00,
"moneda_funcional": "USD",
"series": [
{
"fecha": "2026-08-13",
"p10": 18200.00,
"p50": 20100.00,
"p90": 23400.00,
"confianza": "alta"
}
],
"alertas": [
{
"tipo": "gap_liquidez",
"dia_estimado": 18,
"severidad": "warning",
"saldo_p50": 1200.00,
"descripcion": "Saldo proyectado bajo escenario base cruza umbral de $2,000",
"accion_sugerida": "Priorizar cobro Factura A-0234 ($8,500)"
}
],
"confianza_modelo": "alta",
"dias_historial_usado": 365
}
3. Cola de FE — arquitectura de resiliencia
3.1 Flujo de emisión FE (CR)
1. Cliente llama POST /fe/emitir
2. FEService valida el payload (Pydantic) → rechaza si campos requeridos faltan
3. FEService genera XML firmado (v4.4 CR / CFDI 4.0 MX / FE DIAN CO)
4. FEService inserta en bk_fe_documents con estado='en_cola'
5. FEService publica mensaje en Redis Stream 'bk:fe:queue'
6. FEService retorna 201 inmediatamente al cliente (no espera Hacienda)
7. fe_worker consume el mensaje del Stream
8. fe_worker envía XML a Hacienda CR vía HTTPS con retry exponencial:
- Intento 1: inmediato
- Intento 2: +5 segundos
- Intento 3: +30 segundos
- Intento 4+: +5 minutos (hasta 24 horas max)
9. Hacienda CR responde con confirmación → fe_worker actualiza estado='emitida'
O: Hacienda rechaza → estado='rechazada' + motivo_rechazo
O: Timeout × 3 → estado='contingencia' + alerta al cliente
10. Webhook entrante (si Hacienda usa push) → /webhooks/hacienda-cr → actualizar estado
3.2 Idempotencia estricta
Cada emisión FE tiene idempotency_key = sha256(tenant_id + numero_consecutivo + clave_hacienda). Si el mismo XML se intenta emitir dos veces (por retry en el cliente), el segundo intento devuelve el estado del primero sin re-enviar a Hacienda.
async def emitir_fe(payload: FEPayload, tenant_id: UUID) -> FEDocument:
idempotency_key = sha256(f"{tenant_id}:{payload.numero_consecutivo}".encode()).hexdigest()
existing = await db.get(BkFEDocument, idempotency_key=idempotency_key)
if existing:
return existing # idempotente: no re-envía
# ... crear nuevo documento
3.3 Persistencia de la cola
Redis Streams con XACK obligatorio. Si el worker muere sin hacer XACK, el mensaje queda en PEL (Pending Entry List) y otro worker lo retoma automáticamente en el siguiente arranque.
4. Integración bancaria (Belvo / Prometeo)
class BankSyncService:
async def sync_account(self, tenant_id: UUID, cuenta_id: UUID):
# Prometeo para CR (Banco Nacional, BAC, Scotiabank CR)
# Belvo para CO/MX (más cobertura)
provider = self._get_provider(cuenta_id)
movimientos = await provider.get_transactions(
account_id=cuenta_id,
from_date=last_sync_date,
to_date=date.today()
)
await self._upsert_movimientos(tenant_id, movimientos)
await self._trigger_auto_match(tenant_id, movimientos)
Auto-match heurístico (para conciliación automática): 1. Monto exacto + fecha ±3 días → match score 95% 2. Monto ±0.5% + descripción coincide → match score 80% 3. Monto ±2% → match score 40% (sugerir pero no auto-confirmar) 4. Sin match → presentar al contador para confirmación manual
5. Workers Celery
| Worker | Tarea | Schedule |
|---|---|---|
fe_worker |
Procesar cola de emisión FE | Redis Stream consumer (continuo) |
bank_sync_worker |
Sincronizar movimientos bancarios | Cada 6 horas |
treasury_worker |
Recalcular forecast P10/P50/P90 | Diario 6:00 AM (antes del horario laboral) |
scoring_worker |
Recalcular scoring cobranza clientes | Diario 5:00 AM |
gl_auto_post_worker |
Contabilizar automáticamente FEs emitidas/recibidas | Trigger post-confirmación FE |
report_worker |
Generar declaraciones IVA mensuales | Primer día del mes |
6. Contabilización automática de FE
Cada FE emitida confirmada genera asientos GL automáticos:
# FE de venta emitida → asientos automáticos
async def gl_auto_post_fe_venta(fe: BkFEDocument):
asientos = [
# Débito: Cuentas por Cobrar
Asiento(cuenta='1101', monto=fe.total_con_iva, tipo='D', referencia=fe.clave),
# Crédito: Ingresos por ventas (por línea de actividad)
Asiento(cuenta='4101', monto=fe.subtotal, tipo='C', referencia=fe.clave),
# Crédito: IVA por pagar
Asiento(cuenta='2301', monto=fe.iva_total, tipo='C', referencia=fe.clave),
]
await db.bulk_insert(BkAsiento, asientos, audit_origin='auto_fe')
7. Credenciales FE — almacenamiento seguro
Las credenciales de acceso a los portales tributarios se almacenan encriptadas:
-- En bk_integraciones
credenciales_enc BYTEA -- pgcrypto: pgp_sym_encrypt(json_credenciales, APP_SECRET)
-- Nunca en texto plano
-- Nunca en variables de entorno sin encriptar
-- Acceso solo desde FEService interno, nunca expuesto en API
8. Integración con StockMind y SalesMind
BookMind recibe datos automáticos de los otros módulos para el forecast de tesorería:
StockMind → BookMind:
- Órdenes de compra aprobadas → AP futuro (pagos a proveedores esperados)
- Recepciones de mercancía → devenga el pago del proveedor
SalesMind → BookMind:
- Ventas del POS → AR inmediato (efectivo o por cobrar)
- Pipeline CRM cerrado-ganado → AR futuro esperado (alimenta P50/P90)
BookMind → SalesMind:
- Scoring de cobranza de clientes → enriquece perfil de cliente en CRM
- Facturas vencidas → alerta de cobranza en SalesMind
Comunicación vía eventos internos en Redis Streams (no llamadas HTTP directas entre módulos).
9. Multi-tenancy y RLS
Idéntico al patrón MindStack Suite:
- tenant_id UUID NOT NULL en todas las tablas bk_*
- RLS en PostgreSQL habilitado por defecto
- JWT con claim tid = tenant_id
- FastAPI dependency get_current_tenant() extrae tid del JWT
10. Health checks y monitoreo
GET /health → {"status": "ok", "version": "1.0.0"}
GET /health/fe → {"cr": "ok", "mx": "ok", "co": "degraded"}
GET /health/bancos → {"belvo": "ok", "prometeo": "ok"}
GET /health/workers → {"fe_worker": "ok", "treasury_worker": "ok"}
Alerta crítica automatizada (PagerDuty/Uptime Kuma → email + WhatsApp a Douglas):
- Estado FE CR/MX/CO = degraded por más de 10 minutos → P0
- fe_worker queue length > 100 mensajes sin procesar → P1
- bank_sync_worker sin ejecutar en >8 horas → P2
11. Testing
| Capa | Cobertura objetivo |
|---|---|
| Emisión FE (generación XML + firma) | ≥99% — incluye fixtures para cada versión (v4.4 CR, CFDI 4.0 MX, FE DIAN) |
| GL engine (asientos, balance) | ≥95% — invariante: débitos = créditos en todos los asientos |
| Motor tesorería | ≥90% — tests con historial sintético de 365 días |
| Conciliación bancaria (auto-match) | ≥85% |
| API endpoints | ≥80% |
Tests de integración obligatorios con fixture de Hacienda CR (sandbox oficial) antes de cada deploy.
Ver también: Doc 06 (SRD Frontend — pantallas y UX) · Doc 08 (Modelo de Datos — esquema bk_) · Doc 09 (Motor Core — algoritmo tesorería predictiva) · Doc 10 (Seguridad — credenciales FE y pgcrypto)*