Saltar al contenido
MultiFactu · Blume POC
Español
Esc
navigateopen⌘Jpreview
En esta página

Hoja de ruta — Integración TrueLayer (Conciliación bancaria) en MultiFactu

Hoja de ruta — Integración TrueLayer (Conciliación bancaria) en MultiFactu

Estado: runtime implementado en la rama TrueLayer (P0–P7 en verde: typecheck + ultracite + tests + arch:check + gate del módulo). Pendientes operativos: aplicar migraciones en cada entorno, alta/contrato comercial con TrueLayer (§17) y ampliar journeys de carga (L10) y E2E. Rama: TrueLayer · Objetivo: integración modular, escalable y profesional con calificación 10/10. Fuente oficial (local): docs-official/TrueLayer/ (192 documentos). Índice agéntico: https://docs.truelayer.com/llms.txt. Producto principal: Data API V1 (Account Information / AIS). Módulo destino: src/modules/bank-reconciliation · Schema Prisma: bank-reconciliation. Alcance MVP (decidido): conciliar ingresos y gastos — entradas contra facturas de venta (ProductInvoice, ya existe) y salidas contra gastos/facturas de proveedor (ProductExpense, dominio nuevo a crear). Revisión integral de documentación: 2026-06-24.


0. Estado de implementación (rama TrueLayer)

Fase Alcance Estado
P0 Cimientos: módulo en capas, ReconciliationConfigService, value objects, constantes, puertos, catálogo truelayer-api-endpoints.yml, .env.example, wiring en AppModule. ✅ Hecho
P1 Auth y consentimiento: OAuth2 (auth dialog + /connect/token), refresh, cifrado de tokens en reposo (AES-256-GCM, clave vía scrypt), máquina de estados de la conexión. ✅ Hecho
P2 Transporte HTTP resiliente (timeout/budget, retry+jitter, circuit breaker, rate limit, X-PSU-IP) + parsing/normalización (cuentas, saldos, transacciones booked+pending). ✅ Hecho
P3 Persistencia (Prisma bank-reconciliation, RLS real vía withTenantContext) + sincronización: backfill atendido + incremental async (202+polling) + webhook + scheduler + reauth proactiva (hook+métrica). ✅ Hecho
P4 Documentos conciliables: puerto BILLING_DOCUMENTS_PORT a product (facturas de venta) + entidad de gastos ProductExpense + asignaciones N:M + estado de cobro/pago derivado. ✅ Hecho
P4b Verificación de titularidad (KYB, opcional): Verification API (ES), detrás de flag. 🟡 Adaptador presente; flujo opcional
P5 Motor de conciliación: CREDIT↔factura y DEBIT↔gasto; determinista + reglas + scoring; parcial/agrupado (N:M); idempotencia por normalised_provider_transaction_id (+ fingerprint); invariantes con bloqueo pesimista. ✅ Hecho
P6 Superficie REST + OpenAPI (11 endpoints; alimenta dashboard y Eve), auth por scope reconciliation:*; split-openapi + contract spec + openapi:diff en el gate. ✅ Hecho
P7 Endurecimiento (mutación, property-based incl. invariante agregada, seguridad/GDPR, aislamiento multi-tenant) + observabilidad (Prometheus) + precision/recall. Carga (L10) pendiente. 🟡 Mayoría hecho; L10 pendiente

Bloqueante de go-live (matizado): con el auth dialog, un cliente no regulado puede operar (TrueLayer actúa de AISP). Solo el direct bank auth (/v1/authuri) exige ser AISP propio. El MVP usa auth dialog → no necesita licencia AISP, solo alta/contrato comercial. Ver §17.


1. Resumen ejecutivo

TrueLayer expone una Data API (AIS) PSD2 que permite a un usuario vincular sus cuentas bancarias (con SCA) y leer cuentas, identidad, saldos y transacciones. Sobre esos datos MultiFactu construye la conciliación bancaria: casar automáticamente los movimientos con las facturas de venta (cobros) y los gastos (pagos) de la organización.

1.1 De dónde salen los documentos a conciliar (fuente interna)

Aclaración clave (verificada en el repo). Las facturas no salen de las integraciones fiscales. Verifactu, TicketBAI y AEAT-modelos son tuberías de reporte que leen la factura y la declaran a Hacienda; no son su repositorio.

  • Facturas de venta (ingresos): viven en ProductInvoice (módulo src/modules/product) — organizationId, customerId, importes en céntimos (totalGrossCents…), issueDate/dueDate, paymentMethod. Existe. ⚠️ Pero no tiene estado de cobro (solo estado fiscal: enviada/aceptada) → hay que derivarlo (ver §1.2 y §11).
  • Gastos / facturas de proveedor (gastos): no existe ningún dominio hoy. Para el alcance MVP (ingresos+gastos) se crea una nueva entidad ProductExpense en el módulo product, paralela a ProductInvoice (proveedor, importe, fecha, vencimiento). Ver §10.1.
  • La conciliación NO importa esas tablas directamente. Las consume a través de un puerto BILLING_DOCUMENTS_PORT implementado por un adaptador que consulta product → mantiene el módulo de conciliación desacoplado del dominio de facturación.

1.2 Estado de cobro/pago: proyección derivada, sin tocar la factura fiscal

El estado “cobrada/pagada/parcial” no se escribe sobre ProductInvoice (no contaminamos el documento fiscal). Se deriva en el módulo de conciliación a partir de las asignaciones (allocations): el estado de un documento = f(Σ asignaciones confirmadas vs total). Se expone vía API/evento para que la app/Eve y, en su caso, el módulo product lo muestren. Modular y reversible.

1.3 Alcance real: subsistemas

Subsistema Rol
Consentimiento y tokens OAuth2 + SCA, refresh (30/90 días), cifrado en reposo, máquina de estados.
Sincronización Backfill atendido + incremental async + webhooks; idempotente, reanudable, respeta límites.
Documentos conciliables Puerto a product: facturas de venta + gastos (ProductExpense, nuevo).
Motor de conciliación Valor diferencial: CREDIT↔venta, DEBIT↔gasto; reglas + scoring; asignaciones N:M.
Estado de cobro/pago Proyección derivada de las asignaciones.
Superficie pública REST + OpenAPI → dashboard y Eve (misma API).
Cumplimiento PSD2 + GDPR; webhooks firmados; RLS.

1.4 Productos TrueLayer: qué usamos, qué diferimos, qué descartamos

Producto Decisión Motivo
Data API (AIS) ✅ Núcleo del MVP Es la conciliación.
Verification API ✅ Incluido — opcional, flag (P4b) KYB: titular del IBAN = organización (ES, beta).
Payments API v3 🟡 Adyacente futura Cobros (pay-by-bank, SEPA Instant ES). Añade firma JWS + merchant account.
VRP / Bank on file ❌ Fuera de alcance Solo UK.
Signup+ ❌ Fuera de alcance Solo UK/FI.

Detalle en §18.

1.5 Superficie: dashboard y Eve (decisión de peso diferida)

El núcleo (motor de matching) es determinista y auditable — maneja dinero, no puede depender de aciertos de una IA. La superficie REST + OpenAPI (Opción A, como BOE) alimenta ambas sin rehacer nada:

  • Dashboard tradicional — ver/confirmar/editar/deshacer asignaciones (lo que una asesoría exige).
  • Eve (modo agéntico) sobre la misma API — explicar por qué casa una sugerencia, resolver ambigüedades, “¿qué me queda por cobrar?”, auto-confirmar los matches de alta confianza.

Recomendación: dashboard como base fiable + Eve como capa de potencia encima. No hace falta decidir el peso ahora: el diseño (lógica en application/domain, superficies finas) deja ambas abiertas; se decide al tener la funcionalidad delante. GraphQL/MCP propios siguen diferidos. El MCP de TrueLayer (truelayer_mcp) es no oficial/solo Payments → no se usa.

1.6 Reversibilidad de proveedor

Proveedor aislado tras ACCOUNT_INFORMATION_PORT; el motor opera sobre el dominio normalizado. Permite multi-proveedor (GoCardless BAD, Tink) sin tocar el dominio. TrueLayer es la elección del MVP (familiaridad + normalised_provider_transaction_id estable). Plan B documentado.


2. Rúbrica 10/10

# Dimensión Peso Criterio de “10”
1 Arquitectura en capas 10% api → application → domain ← infrastructure; arch:check limpio; proveedor y product tras puertos.
2 Consentimiento y tokens 10% auth dialog + token exchange + refresh (30/90 días) + máquina de estados + tokens cifrados; reauth proactiva.
3 Cobertura funcional Data API 6% Cuentas, identidad, saldos, transacciones booked+pending; opcional direct_debits/standing_orders.
4 Resiliencia saliente 8% Timeout + retry(jitter) + breaker + budget + rate limit + X-PSU-IP; mapeo invalid_grant/access_denied/sca_exceeded.
5 Sincronización (backfill + incremental) 10% Backfill atendido + incremental async + webhooks firmados + polling fallback; dedup; reanudable; sin duplicados.
6 Motor de conciliación (ingresos+gastos, N:M) 15% CREDIT↔venta y DEBIT↔gasto; determinista + reglas + scoring; parcial/agrupado; auditable; precision/recall.
7 Documentos y estado de cobro/pago 8% Puerto a product; entidad de gastos; estado derivado de asignaciones N:M; sin mutar el documento fiscal.
8 Persistencia y modelo de datos 6% Schema bank-reconciliation con RLS, índices, Decimal/céntimos, migración reproducible.
9 Contrato (REST + OpenAPI) 6% Documentado, scopes, baseline sin drift; sirve dashboard y Eve.
10 Seguridad y cumplimiento 11% Cifrado de tokens, RLS, RFC 7807, GDPR, webhooks firmados, secretos fuera del repo.
11 Testing multicapa (L01–L14) 10% Incl. L13 aislamiento multi-tenant y L05 con MSW; mock users de sandbox.

El “10/10” se alcanza con todas las dimensiones en verde y reconciliation:quality:gate (§16) pasando en CI.


3. Alcance — endpoints upstream (Data API V1)

Catálogo canónico (entregable P0): src/modules/bank-reconciliation/truelayer-api-endpoints.yml. Base: https://api.truelayer.com/data/v1 (sandbox …-sandbox.com); auth https://auth.truelayer.com.

Familia Endpoint Scopes Notas
Auth (dialog) GET auth.truelayer.com/?response_type=code&client_id&redirect_uri&scope&state SCA; provider_id/providers/PKCE opcional
Auth (token) POST auth.truelayer.com/connect/token authorization_code → access+refresh; refresh_token
Identidad GET /info info titular (full_name)
Cuentas GET /accounts · /accounts/{id} accounts IBAN, divisa, tipo
Saldos GET /accounts/{id}/balance accounts+balance current/available
Transacciones GET /accounts/{id}/transactions (from/to) accounts+transactions booked; normalised_provider_transaction_id
Transacciones GET /accounts/{id}/pending_transactions accounts+transactions pending
Domiciliaciones GET /accounts/{id}/direct_debits accounts+direct_debits recibos (gastos)
Órdenes GET /accounts/{id}/standing_orders accounts+standing_orders pagos periódicos
Verificación POST /v1/verify (titular) · /v3/account-holder-verifications verification KYB; /v1 sin firma; /v3 con firma → payouts
Proveedores GET auth.truelayer.com/api/providers (público) cobertura (country=es)
Async ?async=true&webhook_uri=… 202 + results_uri + webhook
Extend POST /connections/extend solo UK (ES: refresh o re-auth)

Cobertura ES verificada (producción, 2026-06-24): 7 proveedores — Santander, BBVA, CaixaBank, Sabadell, ING (xs2a-redsys-*) + Revolut, Wise. Faltan Bankinter, Kutxabank, Abanca, Unicaja, Ibercaja, Openbank y cajas → refuerza el plan B multi-proveedor.


4. Principios de arquitectura

CLAUDE.md raíz + src/modules/CLAUDE.md; se replica boe (resiliencia, config Zod, caché, scheduler, OpenAPI) y patrones transaccionales de aeat-modelos:

  • Módulo en capas, stateful: api → application → domain ← infrastructure.
  • Dos puertos hacia fuera del dominio: ACCOUNT_INFORMATION_PORT (proveedor, TrueLayer) y BILLING_DOCUMENTS_PORT (documentos: product). El motor no conoce ni TrueLayer ni las tablas de product.
  • Controllers/resolvers/tools finos; lógica en application/domain.
  • Config Zod (ReconciliationConfigService); errores RFC 7807 (AllExceptionsFilter).
  • Secretos fuera del repo (skill secrets-management).
  • Data API sin firma (solo Bearer); firma JWS (ES512) solo Payments → futura fase de cobros (§18).

5. Estructura de carpetas propuesta

src/modules/bank-reconciliation/
├── truelayer-api-endpoints.yml
├── bank-reconciliation.module.ts
├── bank-reconciliation.config.ts            # Zod + .spec.ts

├── api/
│   ├── controllers/                         # connections, accounts, transactions, reconciliation
│   ├── dto/                                 # class-validator + @ApiProperty
│   └── mappers/

├── application/
│   ├── use-cases/
│   │   ├── create-connection.use-case.ts
│   │   ├── complete-connection.use-case.ts  # callback: code -> tokens (cifrados)
│   │   ├── refresh-connection.use-case.ts
│   │   ├── backfill-account.use-case.ts     # primer import atendido (tras SCA)
│   │   ├── sync-transactions.use-case.ts    # incremental idempotente (async)
│   │   ├── suggest-allocations.use-case.ts  # motor: candidatos movimiento<->documento
│   │   ├── confirm-allocation.use-case.ts   # asignación confirmada (parcial/total)
│   │   ├── revoke-connection.use-case.ts    # revocación + borrado (GDPR)
│   │   └── verify-account-holder.use-case.ts# KYB opcional (flag)
│   └── services/
│       ├── reconciliation-engine.service.ts # reglas + scoring (ingresos y gastos)
│       ├── settlement-projection.service.ts # estado cobro/pago = f(asignaciones)
│       ├── token-cipher.service.ts          # AES-256-GCM
│       ├── reconciliation-cache.service.ts
│       ├── circuit-breaker.service.ts
│       └── reconciliation-metrics.service.ts

├── domain/
│   ├── ports/
│   │   ├── account-information.port.ts      # ACCOUNT_INFORMATION_PORT (TrueLayer)
│   │   ├── billing-documents.port.ts        # BILLING_DOCUMENTS_PORT (product: ventas + gastos)
│   │   ├── connection.repository.port.ts
│   │   ├── transaction.repository.port.ts
│   │   ├── allocation.repository.port.ts
│   │   ├── token-vault.port.ts
│   │   └── account-verification.port.ts
│   ├── value-objects/                       # iban.vo, money.vo, transaction-fingerprint.vo
│   ├── state/connection-state.ts
│   └── types/                               # Connection, Account, Transaction, BillingDocument, Allocation, MatchCandidate

└── infrastructure/
    ├── adapters/
    │   ├── truelayer-data.adapter.ts        # ACCOUNT_INFORMATION_PORT (Bearer, sin firma)
    │   ├── truelayer-auth.adapter.ts        # OAuth2
    │   ├── truelayer-verification.adapter.ts# KYB (opcional)
    │   └── product-billing-documents.adapter.ts # BILLING_DOCUMENTS_PORT -> módulo product
    ├── parsers/                             # JSON TL -> dominio (number->Decimal seguro)
    ├── cache/ · crypto/token-vault.adapter.ts
    ├── db/                                  # connection / transaction / allocation repos (RLS)
    ├── webhooks/truelayer-webhook.controller.ts  # verificación Tl-Signature (JWS) + encola sync
    └── schedulers/transaction-sync.scheduler.ts  # @nestjs/schedule + reauth proactiva

6. Modelo de dominio

  • Iban, Money (céntimos/Decimal; la Data API da amount como numbernumber→string→Decimal), TransactionFingerprint (preferir normalised_provider_transaction_id; fallback a hash porque no es obligatorio).
  • BillingDocument (normalizado desde product): kind (sale_invoice/expense), id, organizationId, total, currency, issueDate, dueDate, counterparty, references (nº factura/NIF), outstanding (pendiente).
  • Allocation (asignación, N:M): liga un bank_transaction con un BillingDocument por un importe (permite parcial). Un movimiento → varias asignaciones; un documento → varias asignaciones. Estado: suggested/confirmed/rejected.
  • MatchCandidate: documento + score + motivo (entrada del motor).
  • Estado de cobro/pago del documento = derivado: pending / partial / settled según Σ asignaciones confirmadas vs total.

Máquina de estados de la conexión:

PENDING ─(SCA ok)→ ACTIVE ─(90d / invalid_grant / sca_exceeded)→ EXPIRED ─(reauth)→ ACTIVE
   │                  │                                              │
   └─(SCA fallo)→ ERROR └─(revoke / GDPR)→ REVOKED ←──────────────────┘

Invariantes (testeables): (a) re-sincronizar el mismo rango no duplica (fingerprint); (b) la suma de asignaciones confirmadas de un documento nunca supera su total.


7. Autenticación, consentimiento y tokens (P1)

  • auth dialog (no regulados): create-connection genera el link a auth.truelayer.com con info accounts balance transactions offline_access (+ direct_debits/standing_orders si se usan) y state. No requiere ser AISP. No iframe (CSP) → redirect/ventana.
  • Token exchange: POST /connect/token (authorization_code, code 5 min) → access_token (1 h) + refresh_token (si offline_access).
  • Cifrado en reposo: TokenVaultAdapter (AES-256-GCM); clave en secretos; tabla separada.
  • Refresh: usar en los primeros 30 días, reutilizable hasta 90. 400 invalid_grant/403 access_deniedEXPIRED.
  • 90 días (PSD2): extend solo UK; en ES, refresh y, al caducar, repetir auth dialog. Reauth proactiva (reauthDays).
  • Revocación: revoke-connection borra tokens/datos (GDPR, §9).

8. Sincronización: backfill + incremental (P3)

8.1 Backfill (primer import) — decisión: atendido

  • Se dispara justo tras el SCA, con el usuario presente → enviamos su X-PSU-IPno cuenta contra el límite de 4/día. Trae el histórico disponible (ventana máxima del banco) en modo async (?async=true&webhook_uri=…202+results_uri) por ventanas de fecha.
  • Idempotente y reanudable (fingerprint + upsert). Estado por cuenta: backfill_status (pending/running/done) + backfilled_from.

8.2 Incremental — desatendido

  • Scheduler diario (patrón boe-snapshot-refresh.scheduler.ts) + webhook async + on-demand. Cursor por último bookingDate con solape; las pending se reemplazan al pasar a booked.
  • Guardar la X-PSU-IP de la última sesión y enviarla; respetar provider_too_many_requests/provider_request_limit_exceeded (429). Polling de results_uri como fallback (TrueLayer reintenta el webhook ~6 veces/24 h).
  • Caché: respetar Cache-Control/Last-Modified/update_timestamp.

9. Seguridad y cumplimiento (P7)

  • Data API sin firma (Bearer). Firma JWS ES512 solo Payments (§18).
  • Webhooks firmados: verificar Tl-Signature (JWS) + X-TL-Webhook-Timestamp; idempotencia por event_id.
  • Cifrado de tokens (AES-GCM) + TLS; secretos fuera de repo/BD.
  • GDPR: minimización de scopes, retención + purga, revocación/supresión, sin PII/secretos en logs.
  • Multitenancy: RLS en bank-reconciliation; el adaptador a product filtra por organizationId.
  • KYB (Verification API, ES) — opcional (P4b): /v1/verify sobre el consentimiento AIS (sin firma), detrás del flag reconciliationVerifyAccountHolder. No bloquea la conciliación.

10. Persistencia (Prisma)

10.1 Cambios en el módulo product (dominio de facturación)

  • ProductExpense (NUEVO): gasto/factura de proveedor — id, organizationId, supplier, issueDate, dueDate, currency, totalGrossCents, references, metadata. Paralelo a ProductInvoice. (En el futuro alimentará reporte fiscal de gastos — fuera de alcance ahora.)
  • ProductInvoice no se modifica. El estado de cobro se deriva en conciliación (§1.2).

10.2 Schema bank-reconciliation (RLS, @@schema, Timestamptz, importes en céntimos Int/Decimal)

  • bank_connection (+ bank_connection_token separada con *_enc, iv, last_psu_ip).
  • bank_account (backfill_status, backfilled_from).
  • account_balance.
  • bank_transaction: fingerprint @unique, amount, currency, booking_date, value_date, description, merchant_name?, category, direction(CREDIT/DEBIT), status(booked/pending).
  • reconciliation_allocation (N:M): id, organization_id, transaction_id, document_kind(sale_invoice/expense), document_id, amount_cents, score, state(suggested/confirmed/rejected), confirmed_by?, confirmed_at?; índices (transaction_id), (document_kind, document_id), (organization_id, state).

11. Motor de conciliación (P5 — corazón del producto)

reconciliation-engine.service.ts produce MatchCandidate[] por movimiento, según la dirección:

  • CREDIT (entrada) → facturas de venta pendientes (ProductInvoice con outstanding > 0).
  • DEBIT (salida) → gastos pendientes (ProductExpense con outstanding > 0).

Capas: (1) determinista (importe + divisa + ventana de fechas + referencia exacta nº factura/NIF en description); (2) reglas (tolerancia de fechas, parcial y agrupado vía asignaciones, normalización de texto, reglas por organización); (3) scoring (score ∈ [0,1]; sobre umbral = sugerencia; el usuario confirma/rechaza → realimenta).

  • N:M real: una sugerencia puede proponer varias asignaciones (un movimiento que paga 3 facturas; o una factura cubierta por 2 transferencias). El settlement-projection.service recalcula pending/partial/settled.
  • ⚠️ Sin enriquecimiento en ES: merchant_name/classification solo UK/IE/FR → el motor se apoya en description (crudo) + amount + transaction_category + fecha. Invertir en parsing del concepto (NIF, nº factura, nombre de cliente/proveedor). Tink superior aquí (plan B).
  • Auditable/reversible: cada asignación guarda motivo, score e importe; confirm/reject/undo. Métrica: precision/recall sobre fixtures etiquetados (gate, §15).

12. Superficie pública (REST + OpenAPI) — dashboard y Eve

Única superficie activa REST + OpenAPI; sirve dashboard y Eve (§1.5). GraphQL/MCP diferidos.

Contrato REST Hace Scope
POST /reconciliation/connections inicia vínculo → auth link reconciliation:write
GET /reconciliation/connections conexiones + estado/expiración reconciliation:read
DELETE /reconciliation/connections/{id} revoca + borra (GDPR) reconciliation:write
GET /reconciliation/accounts cuentas + saldos + backfill_status reconciliation:read
GET /reconciliation/transactions movimientos (filtros, paginado propio) reconciliation:read
GET /reconciliation/suggestions sugerencias de asignación (ingresos y gastos) reconciliation:read
POST /reconciliation/allocations · DELETE …/{id} confirma / deshace asignación (parcial/total) reconciliation:write
GET /reconciliation/documents/{kind}/{id}/settlement estado de cobro/pago derivado reconciliation:read
POST /reconciliation/sync sincroniza ahora reconciliation:write
POST /reconciliation/webhooks/truelayer webhooks (firma verificada) — (público, firmado)

Controllers finos + DTOs; respuesta JSON normalizada; RestAuthGuard + @RequireScopes. OpenAPI con @nestjs/swagger; bank-reconciliation en scripts/split-openapi.ts + baseline + openapi:diff. Handoff a Eve en docs-official/TrueLayer/README.md (inputSchema zod desde el OpenAPI).


13. Configuración (env vars)

ReconciliationConfigService (Zod), prefijos RECONCILIATION_/TRUELAYER_:

Variable Default Uso
reconciliationEnabled false habilita el módulo
trueLayerAuthBaseUrl / trueLayerDataBaseUrl prod URLs OAuth2 / Data API
trueLayerClientId / trueLayerClientSecret — (secreto) OAuth2 client
trueLayerRedirectUri callback (registrado en Console)
trueLayerEnvironment sandbox sandbox/live
reconciliationTokenEncKey — (secreto) clave AES-256-GCM
reconciliationWebhookPublicKey verificación Tl-Signature
reconciliationConsentReauthDays 7 reauth proactiva
reconciliationVerifyAccountHolder false KYB opcional (Verification API)
reconciliationTimeoutMs / reconciliationRetries / reconciliationRatePerSecond 20000/2/5 resiliencia
reconciliationCacheBackend auto memory/redis/auto
(futuro Payments) trueLayerSigningKid / trueLayerPrivateKeyPem — (secreto) firma JWS — solo si se activa Payments

Validación de arranque si reconciliationEnabled=true. .env.example sin secretos (entregable P0).


14. Observabilidad (P7 / L14)

Prometheus (prom-client): sync (backfill/incremental), matching (precision/recall), estado de conexiones, webhooks. Logs nestjs-pino con correlationId; propagar X-Client-Correlation-Id, registrar X-TL-Correlation-Id. OTel. Client tracking/Debug ID (conversión del consentimiento, retención 60 días). SLO: p95 < 500 ms con caché caliente; error upstream < 1%; éxito de sync > 99%.


15. Estrategia de testing por capa (núcleo del 10/10)

Stack estándar L01–L14, local-first. Cobertura ≥ 85/80; 95/90 en motor, normalizadores y value-objects.

Capa Herramienta(s) Aplicación
L01 Estático TypeScript + Ultracite (Biome) 0 errores en src/modules/bank-reconciliation.
L02 Unit Jest Value objects, normalizadores, motor (ingresos+gastos, N:M), token-cipher, máquina de estados, settlement-projection, use-cases con puertos mockeados.
L03 Mutación Stryker Motor, value-objects, normalizadores, token-cipher, proyección de estado.
L04 Integración Testcontainers + Jest Postgres (repos, asignaciones, RLS); Redis (caché/rate-limit).
L05 Contrato jest-openapi + oasdiff · MSW (TrueLayer) OpenAPI sin breaking; MSW mockea la Data API (incl. 429/sca_exceeded/202+webhook).
L06 Bootstrap DI @nestjs/testing El módulo resuelve puertos (ACCOUNT_INFORMATION_PORT, BILLING_DOCUMENTS_PORT, TOKEN_VAULT_PORT…).
L07 Arquitectura dependency-cruiser TrueLayer y product solo tras puertos; el motor no los importa.
L08 Property-based fast-check Idempotencia de sync; Money sin pérdida; invariante “asignaciones ≤ total”; Iban/fingerprint.
L09 E2E supertest (+ Testcontainers) connect→backfill→sync→suggest→confirm (parcial/agrupado); webhook firmado; fetch estilo Eve.
L10 Carga K6 SLOs de lectura con caché caliente.
L11 Microbench tinybench Hot path del matching N×M.
L12 Seguridad — scanners GitLab Ultimate Secret Detection (tokens); DAST/API Fuzzing. Mirror de GitLab.
L13 Authz/aislamiento Jest (*.security.spec) CRÍTICO: una organización no ve datos de otra (RLS + matriz authz); el adaptador a product no fuga cross-tenant.
L14 Observabilidad Prometheus + Grafana/Alertmanager Métricas/alertas (consentimiento por caducar, fallos de sync, 429).

Fixtures/sandbox: test/fixtures/truelayer/ (accounts/balance/transactions booked+pending/info/providers) + mock users (john/doe, john/eternal, error.*). Set etiquetado movimiento↔documento para precision/recall.

Local-first: pre-commit (L01 + unit afectados) y pre-push (unit+cobertura + L04 + L06 + L07 + L05), coste 0; L03/L09/L10/L11 local/nightly; L12 en GitLab; CI-GitHub re-ejecuta como required checks. SLOs K6 (L10) y lista de journeys E2E (L09): a cerrar en P7.


16. Quality gate del módulo

"reconciliation:test:unit":  "jest --testPathPatterns=src/modules/bank-reconciliation",
"reconciliation:test:security": "jest --testPathPatterns=src/modules/bank-reconciliation/.*security",
"reconciliation:test:e2e":   "jest --config ./test/jest-e2e.json --testPathPatterns=reconciliation",
"reconciliation:test:int":   "jest --testPathPatterns=src/modules/bank-reconciliation/.*integration",
"reconciliation:openapi:contract": "jest --runInBand --testPathPatterns=src/modules/bank-reconciliation/.*openapi-contract",
"reconciliation:quality:gate": "pnpm typecheck && ultracite check src/modules/bank-reconciliation && pnpm arch:check && pnpm reconciliation:test:unit && pnpm reconciliation:test:security && pnpm reconciliation:test:e2e && pnpm openapi:diff"

Cubre L01/L02/L07/L13/L09/L05. L04 en pre-push/CI con infra; L03/L10/L11 local/nightly; L12 en GitLab. Sin gates de MCP/GraphQL (diferidos).


17. Regulación y go-live

  1. auth dialog (MVP): no requiere ser AISP (TrueLayer es el AISP). Requisitos: alta/contrato con TrueLayer, due diligence, pantallas aprobadas. No bloquea sandbox (P0–P7 dev).
  2. direct bank auth (futuro): exige ser AISP propio. Solo si se quiere control total de UX.

Acción: confirmar con TrueLayer las condiciones del auth dialog para producción ES en paralelo a P0–P6.


18. Add-ons y productos adyacentes

  • Payments API v3 — cobros (adyacente futura). Pay-by-bank, payment links, merchant accounts; SEPA Instant ES. Complementa: la Data API responde “¿qué movimientos tiene el cliente?”; Payments, “¿me han pagado?”. Reutiliza infra/webhooks pero añade firma JWS ES512 + merchant account (~2-3 sprints). Diferenciador vs Holded. Diseñar para no cerrar la puerta (puerto separado, webhooks compartidos).
  • Verification API — KYB (✅ INCLUIDO, P4b; ES beta). /v1/verify (sin firma) detrás de flag. Confirmar activación del scope verification y host/path en consola.
  • VRP / Bank on file y Signup+ — ❌ fuera de alcance (UK / UK-FI).

19. Plan de fases

Commits sobre TrueLayer, gate verde por fase. P0–P6 entregan conciliación productiva en sandbox; P7 eleva a 10/10 y habilita producción.

P0 — Cimientos

Módulo en capas, config Zod, truelayer-api-endpoints.yml, dominio (tipos, value objects, puertos, máquina de estados), .env.example, registro en app.module.ts. DoD: typecheck && arch:check && reconciliation:test:unit verde.

P1 — Auth, consentimiento y tokens cifrados

truelayer-auth.adapter, token-vault.adapter, use-cases connect/complete/refresh/revoke, reglas 30/90 días. DoD: OAuth2+SCA en sandbox; tokens cifrados; expiración detectada. (skills: oauth, secrets-management)

P2 — Transporte Data API + normalización

truelayer-data.adapter (resiliencia + X-PSU-IP, sin firma); parsers (+number→Decimal). DoD: datos normalizados a dominio único.

P3 — Persistencia + sincronización (backfill + incremental)

Schema bank-reconciliation (RLS), repos, backfill-account (atendido, async, reanudable) + sync-transactions (incremental) + scheduler + webhook firmado. DoD: backfill reanudable; dos syncs ⇒ sin duplicados; webhook verificado dispara sync. (skills: prisma-table-design, postgresql-table-design, gdpr-data-handling)

P4 — Documentos conciliables (ventas + gastos)

BILLING_DOCUMENTS_PORT + product-billing-documents.adapter; nueva entidad ProductExpense en product + migración; modelo reconciliation_allocation (N:M) + settlement-projection. DoD: el motor puede leer ventas y gastos pendientes; estado de cobro/pago se deriva correctamente; invariante “asignaciones ≤ total”. (skills: prisma-table-design, api-design-principles)

P4b — Verificación de titularidad (KYB, opcional)

truelayer-verification.adapter + verify-account-holder.use-case, detrás del flag, vía /v1/verify. DoD: con flag activo, titular no coincidente se marca; con flag inactivo, no afecta. (skill: auth-implementation-patterns)

P5 — Motor de conciliación

reconciliation-engine (CREDIT↔venta, DEBIT↔gasto; determinista+reglas+scoring; parcial/agrupado), use-cases suggest/confirm/reject. DoD: precision/recall medidos; asignaciones auditables/reversibles. (skill incl. parsing de concepto ES)

P6 — Superficie REST + OpenAPI (dashboard + Eve)

Controllers/DTOs/mappers, auth/scopes, Swagger; split + baseline. DoD: reconciliation:test:e2e y openapi:diff verdes; consumible por dashboard y por Eve con fetch. (skills: nestjs-best-practices, openapi-spec-generation)

P7 — Endurecimiento, observabilidad, rendimiento y producción

Stryker + property-based ampliado + GDPR + L13; Prometheus/OTel/Client tracking; K6 + microbench; runbook. Cierre del alta/contrato con TrueLayer. DoD: reconciliation:quality:gate verde en CI; SLO medido; vía libre comercial.


20. Riesgos y mitigaciones

Riesgo Impacto Mitigación
Dominio de gastos no existe (hay que crearlo) Amplía alcance ProductExpense mínima en product; captura de gastos puede crecer en fases posteriores.
Consentimiento caduca a 90 días (sin extend ES) Pérdida de acceso Reauth proactiva; refresh 30/90; máquina de estados.
4 llamadas/día desatendidas sin X-PSU-IP Sync bloqueada Backfill atendido; guardar IP; respetar 429.
Cobertura ES (7 bancos) Clientes sin su banco Plan B multi-proveedor; avisar en el alta.
Sin enriquecimiento en ES Peor matching Parsing del description (NIF/nº factura); Tink plan B.
Pagos parciales/agrupados Conciliación incompleta Modelo N:M de asignaciones + invariante “≤ total”.
normalised_* ausente Duplicados TransactionFingerprint con fallback; @unique.
Filtración de tokens Crítico AES-GCM, secretos fuera de repo, RLS, sin logs.
Webhooks falsificados Datos corruptos Verificar Tl-Signature + timestamp; idempotencia event_id.
amount como float Céntimos number→Decimal seguro; property-based.
Acoplamiento a product Coste de cambio BILLING_DOCUMENTS_PORT; el motor no importa product.
GDPR (datos sensibles) Legal Minimización, retención, supresión, cifrado.

21. Definition of Done (checklist 10/10)

  • Módulo en capas; arch:check verde; TrueLayer y product tras puertos.
  • auth dialog + token exchange + refresh (30/90) + reauth proactiva; tokens cifrados.
  • Data API cubierta (cuentas, identidad, saldos, transacciones booked+pending) normalizada.
  • Resiliencia (timeout/retry/jitter/breaker/budget/rate-limit/X-PSU-IP) + mapeo de errores.
  • Backfill atendido + sync incremental idempotente (async + webhooks firmados + polling); sin duplicados.
  • Documentos: puerto a product; ProductExpense creada; estado de cobro/pago derivado de asignaciones N:M.
  • Motor ingresos+gastos (CREDIT↔venta, DEBIT↔gasto; parcial/agrupado), auditable/reversible; precision/recall medidos.
  • Schema bank-reconciliation con RLS, índices, céntimos/Decimal, migración reproducible.
  • Contrato REST + OpenAPI (dashboard y Eve); baseline sin drift.
  • Seguridad/GDPR: cifrado, RLS, retención, supresión, webhooks verificados, secretos fuera del repo.
  • KYB opcional (Verification API) tras flag; desactivado no afecta.
  • Observabilidad: Prometheus, logs con correlationId, OTel, Client tracking.
  • Tests L01–L14 (incl. L13 aislamiento multi-tenant y L05 con MSW) con mock users de sandbox.
  • Cobertura ≥ 85/80 global y ≥ 95/90 en motor/normalizadores/value-objects.
  • reconciliation:quality:gate verde en CI.
  • Producción: alta/contrato con TrueLayer (modelo auth dialog) resuelto.
  • Documentación: este roadmap, truelayer-api-endpoints.yml, docs-official/TrueLayer/, runbook.

22. Referencias

Documentación oficial (local): docs-official/TrueLayer/ — Data API (Documentation/Data API/**, ApiReference/Data API V1/**), Auth/seguridad (ApiReference/Authentication server/**, Documentation/Get Started/Security best practices.md, firma en Documentation/Payments/Request authentication/**), adyacentes (Payments API V3, Add-on verification + Verification API, Bank on file UK, Signup+ UK/FI).

Repo:

  • Fuente de documentos: src/modules/product/** (ProductInvoice; ProductExpense a crear), packages/prisma/schema.prisma (modelo Organization, ProductInvoice).
  • Patrones: src/modules/boe/** + docs/boe-integration-roadmap.md; transaccional src/modules/aeat-modelos/**.
  • Core: src/core/config/*, src/core/prisma/* (RLS), src/core/auth/*, src/core/error/all-exception-filter.ts, src/core/metrics, src/core/otel.
  • OpenAPI: scripts/split-openapi.ts, scripts/openapi/check-breaking.ts, test/contract/openapi.baseline.json. Testing: test/support/testcontainers/*, test/verifactu.e2e-spec.ts.
  • Consumidores: dashboard (app) y Eve (github.com/ADP-DIGITEK/eve.multifactu.com, agent/tools/*.ts con defineTool+zod).
  • Skills: oauth, secrets-management, gdpr-data-handling, prisma-table-design, postgresql-table-design, nestjs-best-practices, openapi-spec-generation, api-design-principles, auth-implementation-patterns.
  • Diferidos — GraphQL: src/graphql/integrations/verifactu/**; MCP: src/mcp/tools/verifactu.ts.

23. Cuestiones abiertas — por cerrar

Decisiones aún no tomadas. No bloquean arrancar P0, pero deben cerrarse antes de la fase indicada. Se listan para ser conscientes y no descubrirlas tarde.

# Cuestión Tipo Bloquea Recomendación / nota
1 Captura de gastos: ¿cómo se registran los ProductExpense? (manual, OCR de factura de proveedor, import CSV/banco, e-mail) Producto P4 (gastos) Sin gastos registrados no hay contra qué conciliar DEBIT. MVP mínimo: alta manual; OCR/import como mejora posterior.
2 Pricing/coste TrueLayer (por conexión activa / por llamada / por usuario) Negocio Go-live (P7) Pedir condiciones a TrueLayer; afecta a unit economics y al diseño de frecuencia de sync.
3 Estrategia de rollout (beta cerrada → GA; por organización) Producto P6/P7 Más allá del kill-switch reconciliationEnabled; feature flag por organización + cohorte beta.
4 Operativo TrueLayer: activación del scope verification (beta ES), condiciones del auth dialog en producción ES, host/path exactos de Verification Operativo P4b / Go-live Confirmar en consola/soporte en paralelo a P0–P6.
5 Frontend/UX y peso dashboard vs Eve Producto (diferido) Decidir al tener la funcionalidad delante; el backend ya sirve ambos (§1.5). No bloquea.
6 Política de auto-confirmación de asignaciones de alta confianza (umbral de score, ¿requiere validación humana?) Producto P5 Empezar conservador: todo sugerido requiere confirmación; auto-confirmar solo match determinista exacto si el cliente lo activa.
7 Retención de datos (GDPR): cuánto tiempo guardamos transacciones tras revocar/cancelar Legal P7 Definir política + purga programada; alinear con asesoría legal.
8 Cobertura de bancos ES de la base real de usuarios (7 soportados; falta cola larga) Producto/negocio Go-live Encuestar bancos de los clientes objetivo; activar plan B (GoCardless/Tink) si falta cobertura.
9 Multidivisa: hoy EUR; ¿soportar cuentas no-EUR? (Revolut/Wise multi-divisa) Producto (futuro) MVP solo EUR; Money ya lleva divisa, así que es extensible sin rediseño.
10 SLOs de K6 (L10) y lista de journeys E2E (L09) Técnico P7 Cerrar umbrales p95/RPS/error-rate y el set de recorridos críticos.

¿Te ha resultado útil esta página?