Saltar a contenido

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

Mapa de microservicios: core de plataforma, ERP y CRM

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:

  1. empresa_id + RLS en cada tabla, con test de aislamiento en CI.
  2. Ninguna clave foránea entre servicios — se correlaciona por UUID.
  3. Cero consultas cruzadas entre bases: dblink y FDW prohibidos.
  4. Outbox en cada productor, idempotencia en cada consumidor.
  5. Contrato antes que código: OpenAPI versionado + contract tests en CI.
  6. Un servicio dueño por entidad, sin excepciones "temporales".
  7. trace_id e empresa_id en cada log, desde el día uno.
  8. Backup y restauración por empresa, probada.
  9. 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-operaciones o svc-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.