ADR-009 · Cashpaya como primer cliente DJ — modelo de integración
| Campo | Valor |
|---|---|
| Status | Accepted |
| Date | 2026-06-05 (Draft) → 2026-06-05 (Accepted) |
| Authors | tech-lead agent + Jonhjar Guerrero |
| Supersedes | — |
| Superseded by | — |
Context
Sección titulada «Context»Cashpaya — la fintech CO que jguerrero ya opera en producción legacy con ~50 usuarios reales — pasa a ser el primer tenant productivo de Digital Jungle. La pregunta arquitectónica de fondo no es técnica sino de frontera de producto: ¿qué hace DJ y qué retiene cashpaya?
El gap analysis cierra esa pregunta con datos
(docs/conceptos/integracion-cashpaya-gap-analysis.md,
secciones 1.6 + “Decisiones confirmadas por jguerrero 2026-06-05”):
- De 73 funcionalidades inventariadas, 8 son
🔵 Out-of-scopepara DJ y todas son financieras: saldos, transferencias P2P, recargas, comisiones, integración bancaria/PSE, pasarela de pago, QR. Decisión jguerrero #1: “cashpaya maneja dinero real e integra Mercado Pago → la lógica financiera queda en cashpaya. DJ no la replica”. - De las otras 65, 27 están equivalentes en DJ, 21 son parciales y 12 no existen — distribuidas en identidad, KYC, SARLAFT, firma electrónica y wallet SSI. Todo eso es lo que DJ provee como servicio.
- Decisión jguerrero #4: “los componentes deben servir para 5-10 tenants sin re-trabajo — nada cashpaya-specific en módulos DJ”.
La consecuencia es clara: cashpaya no es un fork ni una variante de
DJ. Es un cliente que consume DJ vía REST, igual que como mañana lo
hará otro fintech, una EPS o un operador de telcos. Si en algún módulo de
DJ aparece la palabra cashpaya hardcoded, hicimos algo mal.
Este ADR formaliza la frontera para que el resto de Waves (A → D) tenga una referencia única sobre qué endpoint corresponde a qué dominio y qué credencial autentica qué llamada.
Decision
Sección titulada «Decision»Vamos a tratar cashpaya como un tenant más de DJ, sin asimetrías de código. Específicamente:
1. División de dominio
Sección titulada «1. División de dominio»| Dominio | Owner | Notas |
|---|---|---|
| Identidad legal (cédula, OCR, hash de doc) | DJ — bmonkey | persiste identity_subjects, RLS por tenant |
| KYC orchestration multi-step | DJ — bmonkey | flow_engine, sesiones con cursor en jsonb |
| Biometría (liveness + face match + face enroll) | DJ — bmonkey + services/arcface | arcface default (decisión #1), no Rekognition Collection |
| Screening SARLAFT (PEP + sanctions) | DJ — bhawk | reglas versionadas en validations |
| Firma electrónica de docs | DJ — bseal | KMS tenant + circuits multi-firmante |
| Wallet SSI + VCs + passkeys | DJ — bmonkey (global) | invisible al usuario hasta uso cross-tenant |
| Saldo de cuenta cashpaya | Cashpaya | clientes.tx_realizadas queda en cpy.* |
| Transferencias P2P / depósitos / retiros | Cashpaya | toda la lógica fiat |
| Mercado Pago marketplace cashpaya | Cashpaya | distinta de ADR-010 (que es topup Açaí) |
| Comisiones + integración bancaria + PSE + QR | Cashpaya | OOS por diseño |
| Créditos Açaí (cuota de uso de DJ) | DJ — platform | cashpaya compra Açaí vía MP topup público (ADR-010) |
2. Contratos de credencial
Sección titulada «2. Contratos de credencial»Cashpaya consume DJ por dos canales distintos, cada uno con su credencial:
2.a · Server-to-server con X-API-Key
Sección titulada «2.a · Server-to-server con X-API-Key»El backend Fiber de cashpaya (mientras siga vivo en Wave A→D) o cualquier servicio futuro que reemplace a Fiber, llama a DJ con la API key del tenant cashpaya:
POST /v1/flows/sessions (bmonkey)POST /v1/screenings/dry-run (bhawk)POST /v1/signatures (bseal)POST /v1/acai/topup/checkout (platform — ver ADR-010)Header obligatorio: X-API-Key: dj_live_<...>. Scopes mínimos al issue
de la key (ADR-004):
bmonkey:flows:read,bmonkey:flows:write,bmonkey:sessions:create,bmonkey:sessions:readbhawk:screenings:read,bhawk:screenings:writebseal:signatures:read,bseal:signatures:writeacai:write(para topup; opcionalacai:readpara balance)
Sin *. Sin scope admin. Sin acceso cross-tenant.
2.b · OIDC end-user con Authorization: Bearer
Sección titulada «2.b · OIDC end-user con Authorization: Bearer»Cuando un usuario final de cashpaya se loguea en la app cashpaya y
ésta quiere actuar en nombre del usuario contra DJ — por ejemplo,
para que el usuario consulte su wallet o consienta una presentación —
cashpaya actúa como RP (Relying Party) del IdP nativo de bmonkey
(implementado en Fase 3, ADR-003 / sign-in-with-bjungle).
Flujo:
[Usuario en app cashpaya] │ │ click "Iniciar sesión con bjungle" ▼[bmonkey OIDC IdP] ── PKCE + nonce ──▶ id_token + access_token │ ▼[Cashpaya guarda access_token del usuario] │ │ Authorization: Bearer <user_access_token> ▼[DJ wallet endpoints — /v1/wallet/me/*]X-API-Key y Authorization: Bearer no se mezclan en la misma
request. Si la operación es “en nombre del usuario” → Bearer. Si la
operación es “del tenant sobre sus subjects” → X-API-Key.
3. Datos sensibles viven en DJ
Sección titulada «3. Datos sensibles viven en DJ»identity_subjects, face_patterns, verifiable_credentials,
wallet_passkeys, hashes de documento — todo eso vive en DJ.
Cashpaya solo persiste:
cpy.users.dj_subject_id(UUID, FK lógica al subject en DJ)cpy.users.dj_wallet_id(UUID, opcional, lookup del wallet del subject)- Cualquier cosa estrictamente financiera (saldo, ledger, tx)
Cashpaya no copia la cédula, ni el OCR completo, ni la selfie, ni el
embedding facial. Si necesita el nombre o la fecha de nacimiento para UI,
los pide a DJ vía GET /v1/subjects/{dj_subject_id} con su X-API-Key.
Esto reduce el alcance regulatorio de cashpaya: el día que SuperFinanciera audite custodia de PII, el inventario de cashpaya es cortísimo.
4. Wallet SSI invisible hasta cross-tenant
Sección titulada «4. Wallet SSI invisible hasta cross-tenant»Decisión jguerrero #3: “wallet visible + educativa”. Cuando un usuario
hace onboarding por cashpaya, el flow incluye un step wallet_create con
copy explícito (“respaldamos tu identidad para usarla en otros servicios
bjungle”) y un step passkey_enroll opcional (decisión #4).
Pero la navegación al wallet en sí (UI wallet.bjungle.com,
gestionar passkeys, ver VCs, revocar grants) no aparece en la app
cashpaya. El usuario que quiera usar su wallet cross-tenant lo
descubre cuando un segundo RP — fintech distinta, EPS, telcos — le
ofrezca “Iniciá con bjungle”. Ahí recién entra a la UI del wallet.
Esto evita que cashpaya tenga que diseñar UI de gestión de wallet que no es de su dominio.
5. Ningún módulo de DJ conoce a cashpaya por nombre
Sección titulada «5. Ningún módulo de DJ conoce a cashpaya por nombre»Regla dura del PR review: si en una migration, un handler, un service
o un test de bmonkey/bhawk/bseal/platform aparece la string cashpaya
(salvo en seeds explícitamente etiquetados como “ejemplo cashpaya”), el
PR se rechaza.
Los únicos lugares legítimos donde puede aparecer cashpaya:
- Seeds de SARLAFT rules y onboarding flow ejecutados vía API contra el tenant, no como código embebido (Wave A2 + A6).
- Templates de bseal “Términos Cashpaya” subidos al S3 bucket del tenant cashpaya (Wave A7).
- Doc del repo (este ADR, gap analysis, backlog).
Esto garantiza la reusabilidad (decisión jguerrero #4) por construcción y no por buena voluntad.
Consequences
Sección titulada «Consequences»Positivas
Sección titulada «Positivas»- Scope claro por PR: cuando un dev abre PR en bmonkey, sabe que NO está tocando lógica de saldos. Cuando uno abre PR en cashpaya, sabe que NO está tocando KYC. La frontera reduce el blast radius de cada cambio.
- Reusabilidad por construcción: el tenant #2 (otra fintech, EPS, telcos) onboardea con el mismo flow.seed + rules.seed que cashpaya, solo cambiando el branding y el set de pasos. Cero refactor.
- Audit regulatorio acotado: cashpaya audita lo financiero, DJ audita lo identidad/compliance. Cada uno responde por su slice. La SFC ya no tiene que mirar dos sistemas mezclados.
- Multi-canal de credenciales explícito: X-API-Key vs Bearer no se ambigua. Cada endpoint declara en su doc OpenAPI cuál acepta.
- Migración de Wave C trivial: los 50 users hacen re-onboarding ellos mismos (decisión jguerrero #9). No hay ETL bcrypt-cost-mismatch porque no se copian passwords — cada user setea password nueva en DJ.
Negativas / trade-offs
Sección titulada «Negativas / trade-offs»- Doble red round-trip en hot path de cashpaya: cuando cashpaya
necesita decidir “puede este user transferir $X?”, hace al menos 2
llamadas: (1)
GET /v1/subjects/{id}para confirmar KYC approved, (2) según política,POST /v1/flows/sessions {type:step_up}para forzar face_match. Latencia adicional ~150-300ms vs monolito. Mitigación: el step-up se cachea viastep_up_token5 min (ADR-002) y el subject status se cachea en cashpaya con TTL corto (≤ 60s). - Datos parcialmente duplicados: nombre del subject lo vamos a leer desde cashpaya frecuente para UI. Cashpaya puede cachearlo, pero si DJ lo actualiza (corrección admin de OCR) cashpaya queda stale. Documentar invariante: el nombre canónico vive en DJ; cualquier UI cashpaya que muestre nombre lo refrescá desde DJ cuando el usuario abre su perfil.
- Dependencia operacional bidireccional: si DJ se cae, cashpaya no puede registrar usuarios nuevos ni firmar docs. Si cashpaya se cae, DJ funciona pero los users no tienen frontend para usarlo. Mitigación: monitoreo Sintético por endpoint pareado + circuit breakers en cashpaya con fallback “intentá luego”.
- Costo de Açaí transparente al tenant: cada
screening,signature_pdf,face_step_updebita Açaí de cashpaya. Cashpaya tiene que comprar topups (ADR-010) y ajustar su pricing al usuario final. No hay “todo incluido”. Esto es positivo regulatoriamente pero requiere que el equipo cashpaya entienda la unit economics.
Neutrales
Sección titulada «Neutrales»- Wave C de migración de 50 users (re-onboarding auto-servicio) es consecuencia directa de no compartir DB. Si compartiéramos schema sería instantáneo, pero rompería la independencia regulatoria.
- bseal-tenant signing certs (ADR-013) permiten que la firma corporativa de cashpaya use el cert que cashpaya ya tenga, sin que DJ se entere de quién emitió el cert.
Alternatives considered
Sección titulada «Alternatives considered»| Alternativa | Por qué se descartó |
|---|---|
| DJ absorbe pagos (saldos, transferencias, MP marketplace) | Convertiría a DJ en fintech regulada por SFC bajo SEDPE/EFNV. Cierra puertas para que DJ sea consumida por otras fintechs que compiten con cashpaya — ninguna se integraría a su competidor. Decisión jguerrero #1 lo cerró. |
| Cashpaya hace KYC + SARLAFT propios (DJ solo da wallet) | Replicar OCR Bedrock + arcface + opensanctions + multi-step flow en Fiber es ~3 meses de trabajo y no agrega valor — ya está hecho en DJ. Además rompe el value-prop de DJ como identidad portable. |
| Monolito unificado (DJ + cashpaya en un solo backend) | Imposible: bjungle se vende como plataforma multi-tenant a 5-10 fintechs. Si cashpaya está fundida con la plataforma, los competidores nunca se integran. |
| Federation peer-to-peer (cashpaya y DJ se federan como pares iguales con DIDs cruzadas) | Over-engineering para 2026. Modelo hub-and-spoke (DJ = hub, tenants = spokes) es lo que la industria opera hoy (Stripe, Plaid, Truora). Federation P2P espera a que aparezca un segundo issuer de identidad de calibre comparable, lo que no va a pasar en CO en los próximos 24 meses. |
Compartir schema postgres cpy + bmonkey en una DB | Romperíamos RLS multi-tenant (cpy no tiene tenant_id) y el aislamiento regulatorio. Además complica la migración Wave C de 50 users — si compartiéramos schema, no haríamos re-onboarding, copiaríamos rows. Pero entonces el bcrypt cost mismatch ataca, los hashes de doc colisionan, etc. Peor en todo. |
Implementation notes
Sección titulada «Implementation notes»Esto es un ADR macro de frontera — no toca código directamente. Los siguientes ADRs implementan slices concretos:
- ADR-010 · Mercado Pago Açaí topup público
- ADR-011 · Multi-firma bseal
- ADR-012 · SARLAFT continuo parametrizable
- ADR-013 · X.509 cert bjungle + tenant opt-in
Y las waves del backlog
(docs/proyecto/cashpaya-integration-backlog.md):
- Wave A (A1..A11) — setup del tenant + flows + seeds
- Wave A+ (F1..F10) — MP Açaí topup
- Wave B (B1..B10) — frontend cashpaya consume DJ APIs
- Wave C (C1..C9) — re-onboarding auto-servicio 50 users
- Wave D (D1..D9) — SARLAFT continuo + decom legacy
Patrones que debe respetar todo PR de Wave A→D:
- RLS deny_all + SECURITY DEFINER para tablas globales (wallet,
audit_log, etc.) — ya canónico, ver
0005_wallet_global.up.sql. - Tx + tenant en cada handler
/v1/*:tenancy.Middlewareabre tx- setea
app.current_tenant. Toda query va por esa tx.
- setea
- Errores
application/problem+jsonvíawriteProblem. - Tests integration roundtrip contra postgres dev por cada handler nuevo de Wave A6, A7b, D1-D5.
Tests obligatorios:
- E2E de Wave A10 (cédula CO real → onboarding → KYC approved + wallet +
VC + passkey) corre sin que el código mencione
cashpaya— configurable por envTENANT_SLUG. - Smoke regression: tomar el seed de cashpaya, cambiarle el slug a
acme-fintech, ejecutar Wave A entero contra ese slug y verificar que funciona idéntico. Si falla, hay acoplamiento ilegal.
Riesgos a vigilar:
- Dev que copia código de bmonkey y deja un literal
if tenant.slug == "cashpaya": rechazo de PR + linter futuro. - Cashpaya empieza a almacenar PII “por convenience”: revisar
cpy.*schema en cada PR de cashpaya legacy para que no agregue columnasnombre,cedula,selfie_url.
Compliance / regulatory considerations
Sección titulada «Compliance / regulatory considerations»- SFC (Colombia): cashpaya queda dentro del perímetro SFC por saldos. DJ queda fuera del perímetro SFC porque no custodia dinero. DJ sí queda dentro de la Ley 1581 (HABEAS DATA) y Decreto 338 (entidades de certificación digital, cuando aplique firma legal — ver ADR-013).
- Ley 1581 / Decreto 1377 (HABEAS DATA CO): DJ es responsable del tratamiento de PII de los subjects. Cashpaya es encargado que delega en DJ ese tratamiento. Acuerdo de transferencia internacional no aplica (DJ + cashpaya = ambos en CO).
- PCI-DSS: no aplica directamente. cashpaya custodia tarjetas en su pasarela (o las pasa a MP); DJ no toca card data en ningún flow.
- UIAF (Unidad de Información y Análisis Financiero CO): reportes
ROS los hace cashpaya porque ve la transacción financiera. DJ provee
el evidence trail (
audit_log+ screenings result), cashpaya lo consume para armar el ROS. Automatización del ROS = backlog Wave E+ (decisión jguerrero #8).
Open questions — Resoluciones (2026-06-05)
Sección titulada «Open questions — Resoluciones (2026-06-05)»Decididas en review de ADR (Jonhjar Guerrero, 2026-06-05):
-
Sincronización de correcciones OCR DJ → cashpaya: ✅ Resuelto. DJ emite evento NATS
subject.updated(subjectbmonkey.subject.updated) cuando un campo del subject cambia (corrección manual, re-OCR, status change). Cashpaya consume con consumer durable. Shape del evento queda por definir en Wave B (frontend cashpaya) cuando se implemente el consumer; mientras tanto cashpaya consultaGET /v1/subjects/{id}al abrir perfil del usuario (pattern read-through cache). -
SLA DJ → cashpaya: ✅ Resuelto. Best-effort hasta que entre un tercer tenant productivo. SLA formal (99.9% uptime, RTO < 4h, RPO < 15min) se define cuando haya un cliente que lo exija contractualmente. Mientras tanto monitoreo Sintético + alertas internas son suficientes.
-
Pricing Açaí enterprise: ⏭️ Diferido a ADR-010 (ese ADR ya toca pricing tiers de topup, mejor centralizar la decisión ahí). El default Açaí USD 0.015/unit se mantiene; descuento volumen para cashpaya se evalúa cuando ADR-010 esté Accepted.
-
Revocación de wallet por fraude reportado por cashpaya: ✅ Resuelto. Flujo: cashpaya envía
POST /v1/subjects/{id}/report-fraud {reason, evidence_ref}(endpoint nuevo Wave D). DJ marca el VCidentity-kyc-loa3comosuspendeden la status list (ADR-008 blockchain anchoring lo soporta vía status list bitstring). El subject puede recuperaractivestatus tras revisión humana conjunta DJ + cashpaya — propuesta de endpointPOST /v1/subjects/{id}/restore-statuscon tag de auditoría dual (operator DJ + operator cashpaya). Detalle de ese flow queda como sub-tarea de Wave D backlog. -
Costo de SARLAFT continuo: ✅ Resuelto provisional. Sale del balance Açaí normal del tenant. Se monitorea durante los primeros 3 meses post-launch. Si el costo mensual de re-screening supera el 20% del consumo total Açaí del tenant, se introduce plan “compliance continuo” con tarifa flat (ADR-012 tiene la decisión técnica de frequency parametrizable; el modelo de pricing queda en este ADR-009 hasta tener datos).
Cross-references
Sección titulada «Cross-references»- Gap analysis:
integracion-cashpaya-gap-analysis.md - Backlog:
cashpaya-integration-backlog.md - ADR-001 Auth audiencias — define X-API-Key vs Bearer
- ADR-003 Sign-in with bjungle — OIDC flow consumido por cashpaya
- ADR-004 API key hardening — scopes que aplican a la key cashpaya
- ADR-006 Flow composition — bmonkey como workflow engine, no onboarding hardcoded
- ADR-010 MP Açaí topup público
- ADR-011 Multi-firma bseal
- ADR-012 SARLAFT continuo
- ADR-013 X.509 cert + opt-in