0003. Plataforma multiservicio: separación ERP/CRM con core común¶
Estado: Aceptado Fecha: 2026-08-28 Autor: Duval Alcivar
Contexto¶
Gerencia decidió separar las bases de datos del ERP y del CRM, construir un core común entre ambos, y separar en servicios todo lo que tenga sentido separar. Este enfoque no reemplaza el patrón de capas Router → Service → Repository (ver ADR 0001), que sigue rigiendo dentro de cada servicio — pero sí define dónde van las fronteras entre servicios y entre empresas.
Hoy el sistema es un monolito compartido entre ERP y CRM sobre una sola base de datos. Hay que decidir cuántos servicios crear, qué base de datos usa cada uno, y qué regla decide de qué servicio es cada tabla.
Decisión¶

Quién queda en cada lado — 14 microservicios en total, 3 bases de datos:
- Core de plataforma (
core_sigfa) — 4 servicios comunes a ambos productos: svc-identidad— usuarios, empresas, sucursales, permisos, SSO ERP+CRM.svc-workflow— motor genérico de casos/aprobaciones.svc-notificaciones— WhatsApp, marcaciones biométricas.-
svc-documents— generación de documentos/PDF (facturas, etiquetas). -
ERP (
erp_sigfa) — 8 servicios: svc-operaciones— inventario, recepción de materia prima, producción, calidad, envasado/etiquetado (comparten la misma transacción sobre el stock).svc-pedidos— pedido de despacho, tienda/app.svc-contable— facturación, CxC, contabilidad, caja (el asiento se deriva de la factura).svc-compras— órdenes, proveedores, cuentas por pagar.svc-rrhh— nómina, asistencia, faena pesquera.svc-activos— activos, GDT, gastos por activo.svc-integraciones— Aylen, QBO, Siigo (capa de solo escritura).-
svc-reportes— consolidados gerenciales (solo lectura, ninguna tabla propia, solo vistas sobre otros servicios). -
CRM (
crm_sigfapro) — 2 servicios: svc-clientes— cartera, contactos, historial.svc-fuerza-ventas— promotores, presupuesto de cliente.
Regla que asigna cada tabla a un servicio: el dueño es quien tiene la regla de negocio que la modifica, no quien la lee. De 1.162 tablas referenciadas en el código, solo 69 (6%) requieren una decisión difícil (usadas por 5+ áreas); el resto se asigna por consenso mecánico según cuántas áreas la usan hoy.
Reglas no negociables — baratas ahora, imposibles de agregar después. Cada una queda como ADR antes de la primera línea de código:
empresa_id+ RLS en cada tabla, con test de aislamiento en CI.- Ninguna clave foránea entre servicios — se correlaciona por UUID.
- Cero consultas cruzadas entre bases:
dblinky FDW prohibidos. - Outbox en cada productor, idempotencia en cada consumidor.
- Contrato antes que código: OpenAPI versionado + contract tests en CI.
- Un servicio dueño por entidad, sin excepciones "temporales".
trace_ideempresa_iden cada log, desde el día uno.- Backup y restauración por empresa, probada.
- Ningún servicio nace sin dueño, pipeline, panel de salud y runbook.
Por qué se correlaciona por UUID entre servicios¶
Una FK es una garantía de integridad dentro de una misma base: apunta a
una tabla de ese motor y ese esquema. Entre servicios las bases son
distintas (cada una la migra y la respalda su propio pipeline — ADR 0003 y
0004), así que una FK no puede cruzar esa frontera. La pregunta entonces es:
con qué identificador se correlaciona una fila de erp con una de crm?
No con el id autoincremental, porque ese número es local a su tabla y
base, se genera al insertar y no significa nada fuera del servicio que lo
creó. Se usa UUID, que se genera en el origen y es global:
independiente de la base, opaco (no revela volúmenes) y permite al
consumidor saber si un evento ya lo procesó (idempotencia).
Ejemplo corto. svc-pedidos (base erp) registra un pedido de un
cliente cuyo registro vive en svc-clientes (base crm). No hay FK: la
capa de "quién es el cliente" no se duplica y svc-pedidos no consulta la
base de crm. Solo guarda el cliente_id como UUID que apuntó a
crm.clientes cuando se generó. Si mañana el cliente cambia de nombre en
crm, el pedido sigue con su UUID y no se rompe.
sequenceDiagram
participant App as Tienda/App
participant Ped as svc-pedidos (erp)
participant Cli as svc-clientes (crm)
App->>Cli: crear cliente
Cli-->>App: cliente_id = UUID
App->>Ped: crear pedido (cliente_id=UUID)
Ped->>Ped: guarda pedido + cliente_id UUID<br>(no consulta la base de crm)
Ped-->>App: pedido_id = UUID
Lo que se evita guardar un id numérico o una FK cruzada:
flowchart TD
A["¿Hay que referenciar una entidad de otro servicio?"] --> B{Son el mismo servicio?}
B -- No --> C{"¿Cómo se correlaciona?"}
C -- "id autoincremental" --> D["Mal:<br>número local de otra base,<br>sin significado ni garantía"]
C -- "FK cruzada" --> E["Mal:<br>la FK no cruza bases; obliga a consulta cruzada"]
C -- "UUID" --> F["Bien:<br>global, generable en origen,<br>idempotente, sin fuga de info"]
B -- Sí --> G["FK normal intra-servicio<br>(ADR 0004)"]
Dentro de un mismo servicio la lógica no cambia: ahí sí hay FK normal, con su índice (ADR 0004), porque es la única integridad relacional real. El UUID es lo que correlaciona a través de las fronteras de servicio.
Alcance del primer año (no de los 14 desde el arranque): con la capacidad actual del equipo, en 18 meses se alcanzan de forma realista 6 a 8 servicios. El core de plataforma, operaciones de planta y contable-comercial van primero.
Orden recomendado de arranque:
1. Cuatro ADR de fundación: modelo de tenencia (ver ADR 0002), dueño por
entidad ERP/CRM (este ADR), plataforma de despliegue, mensajería y
contratos.
2. Core de plataforma (identidad + tenencia), sirviendo todavía al ERP y
al CRM actuales, para validarlo contra el sistema real.
3. Cortar la base: proyecciones por evento y eliminar las consultas
cruzadas.
4. Núcleo de operaciones sobre el diseño de svc-operaciones, ya
existente.
5. Servicios de borde (integraciones contables, reportes) y el resto por
olas.
Alternativas descartadas¶
- Una base de datos por servicio individual, sin agrupar por producto
(16 bases en vez de 3) — descartada: los dos servicios que agrupan
varios dominios (
svc-operaciones,svc-contable) lo hacen porque comparten una misma transacción — separarlos habría exigido transacciones distribuidas entre servicios para una sola operación de negocio (ej. una venta que descuenta stock y genera factura). - Servicio único por dominio de negocio (20 servicios, uno por cada
bounded context de la auditoría) — descartada por ahora: fragmenta
transacciones que hoy son atómicas; se deja como evolución posible si un
dominio agrupado (
svc-operacionesosvc-contable) demuestra necesitar separarse más adelante. - Arrancar directamente con los 14 servicios del diagrama — descartada: con la capacidad de equipo actual son el destino a 18 meses, no la meta del primer año; arrancar con más servicios de los que el equipo puede operar (pipeline, panel de salud, runbook, dueño) deja servicios sin operación real.
- Consultas cruzadas directas entre bases (dblink/FDW) como atajo temporal — descartada explícitamente como regla no negociable: un atajo "temporal" entre bases es indistinguible en producción de un acoplamiento permanente, y bloquea después separar o escalar el servicio por su cuenta.
Consecuencias¶
- El asiento de qué servicio es dueño de qué tabla debe resolverse tabla por tabla para las 69 tablas de 5+ áreas antes de escribir el código de ese dominio — quedan documentadas en un ADR de mapeo aparte cuando se aborde cada servicio.
- Toda comunicación entre servicios pasa por evento (outbox) o por API contractual — nunca por SQL cruzado ni FK entre bases.
- Un servicio no puede arrancar en producción sin: pipeline propio, panel de salud, runbook y dueño asignado — aunque el diagrama ya lo contemple.
- El aislamiento por empresa (ver ADR 0002) aplica dentro de cada uno de estos 14 servicios, no lo reemplaza.
- Ambición correcta es la cantidad de servicios bien delimitados, no la cantidad total: un dominio mal delimitado extraído demasiado pronto es más caro de corregir después que mantenerlo dentro de un servicio más grande unas semanas más.