Saltar a contenido

Estructura de carpetas — 14 microservicios (Sigfa / SigfaPro)

Patrón: monolito modular por dominio (vertical slice) dentro de cada servicio

Cada microservicio organiza su código por dominio de negocio, no por capa técnica. Router, service, repository, models y schemas de un mismo dominio viven juntos en su propia carpeta. Esto vale para los 14, incluidos los de un solo dominio — así el criterio es único en todo el proyecto y cualquier dominio se puede mover/extraer sin desarmar nada.

Patrón común

svc-<nombre>/
├── app/
│   ├── main.py                    # arranque FastAPI, registro de routers de cada dominio
│   ├── core/
│   │   ├── config.py              # variables de entorno (Pydantic Settings)
│   │   ├── security.py            # valida JWT emitido por svc-identidad
│   │   ├── permissions.py         # require_permission() por endpoint
│   │   ├── exceptions.py          # excepciones de negocio -> HTTP errors
│   │   └── logging.py             # logging estructurado (JSON)
│   ├── api/
│   │   └── deps.py                # get_db(), get_current_user() — transversal, no es un dominio
│   ├── domains/
│   │   ├── <dominio_1>/
│   │   │   ├── router.py          # endpoints del dominio
│   │   │   ├── service.py         # lógica de negocio, sin SQL
│   │   │   ├── repository.py      # único lugar que toca SQL de este dominio
│   │   │   ├── models.py          # entidades SQLAlchemy
│   │   │   └── schemas.py         # contratos Pydantic
│   │   ├── <dominio_2>/
│   │   │   └── ... (misma forma)
│   │   └── ...
│   ├── shared/                    # SOLO lo que de verdad cruza 2+ dominios de ESTE servicio
│   │   └── ...                    # (si un dominio importa de otro dominio, es señal de alerta)
│   └── events/
│       ├── publisher.py           # outbox: publica eventos de dominio hacia otros servicios
│       └── subscriber.py          # consume eventos de otros servicios (si aplica)
├── alembic/
│   ├── versions/                  # migraciones versionadas (una por cambio)
│   └── env.py
├── tests/
│   ├── unit/
│   │   └── <dominio>/             # mismo árbol que domains/, por dominio
│   └── integration/
│       └── <dominio>/
├── docs/
│   └── adr/                       # 0001-titulo.md — decisiones de arquitectura de ESTE servicio
├── pyproject.toml
├── .env.example                   # nunca .env real committeado
└── Dockerfile

Regla dura: un archivo dentro de domains/nomina/ no importa directo de domains/asistencia/. Si dos dominios necesitan comunicarse, es a través de shared/ (síncrono, dentro del mismo servicio) o de events/ (asíncrono, entre servicios). Si notás que la única forma de resolver algo es importar cruzado entre dominios, esa es la señal de que en realidad son un solo dominio (o de que falta un evento).


CORE DE PLATAFORMA (BD: core_sigfa)

1. svc-identidad

app/domains/
├── usuarios/       # login, tokens, credenciales
├── empresas/
├── sucursales/
└── permisos/       # tipos de usuario y permisos individuales, SSO

2. svc-workflow

app/domains/
└── casos/          # estado y aprobaciones/seguimiento (no el efecto de negocio)

3. svc-notificaciones (ya existe)

app/domains/
└── notificaciones/ # WhatsApp, marcaciones biométricas

4. svc-documents (generación de documentos/PDF)

app/domains/
├── pdf/             # facturas, etiquetas, reportes (WeasyPrint)
├── excel/           # exportaciones contables e inventariales (openpyxl)
└── word/            # plantillas editables (python-docx)
Capacidad transversal: ERP y CRM lo consumen por igual; ningún dominio de negocio lo posee en exclusiva (ADR 0001 de svc-documents).


ERP — sigfa.com.ec (BD: erp_sigfa)

5. svc-operaciones (6 dominios, 1 transacción)

app/domains/
├── inventario/
├── recepcion_mp/
├── produccion/         # planificación + ejecución
├── envasado/
├── etiquetado/
└── calidad/
La "1 transacción" que amarra a los 6 no vive dentro de ningún dominio individual — va en app/shared/produccion_orquestador.py (o similar), que sí puede tocar los 6 repositories para garantizar la atomicidad. Vale un ADR (0001-transaccion-cruzada-operaciones.md) documentando por qué este es el único lugar del servicio donde se permite cruzar dominios.

6. svc-pedidos

app/domains/
├── pedidos/         # pedido de despacho
├── canal_app/        # canal B2C / app
└── tienda/            # catálogo (hoy fusionado con la app)

7. svc-contable

app/domains/
├── facturacion/       # facturación, CxC
└── contabilidad/
Las integraciones contables (Aylen/QBO/Siigo) no viven acá — son el servicio svc-integraciones (#10), para aislar sus fallas del resto de la contabilidad.

8. svc-compras

app/domains/
└── compras/           # órdenes, proveedores, cuentas por pagar

9. svc-rrhh (RRHH / Nómina — incluye faena pesquera)

app/domains/
├── personal/           # legajo/ficha
├── nomina/             # cálculo/pago de nómina
├── asistencia/         # manual + biométrica (evento desde svc-notificaciones)
├── empleados/          # comisariato, deducciones
└── faena_pesquera/     # liquidación de faena (se calcula junto a nómina, pero es su propio dominio)

10. svc-activos (Mantenimiento de activos)

app/domains/
├── mantenimiento/      # preventivo/correctivo
├── odt/                 # órdenes de trabajo
└── gastos/               # gastos por activo

11. svc-integraciones (Integraciones contables — "falla afuera")

app/domains/
├── aylen/
├── qbo/
└── siigo/
Cada proveedor externo es su propio dominio a propósito: si Aylen cae, no debe arrastrar a QBO ni a Siigo. Cada domains/<proveedor>/service.py maneja su propio circuito de reintentos/timeouts, y app/shared/ puede tener un circuit_breaker.py genérico que los tres reutilizan.

12. svc-reportes (Reportes y analítica — "solo lectura")

app/domains/
└── gerencial/          # consolidados gerenciales
gerencial/repository.py solo lee — arma proyecciones a partir de eventos consumidos de los demás servicios, nunca escribe hacia atrás a ningún otro dominio.


CRM — sigfapro.com.ec (BD: crm_sigfapro, desglose interno según el plan del CRM)

13. svc-clientes

app/domains/
└── clientes/           # cartera, contactos, historial

14. svc-fuerza-ventas

app/domains/
└── fuerza_ventas/       # promotores, preventa

Qué queda fuera de los domains/ (infraestructura transversal, no es negocio)

En el monolito, shared/ tenía comunas_service.py y pdf_header_service.py (usado por 10+ módulos). Al partir en microservicios, esto no debe copiarse dentro de cada uno — conviene sacarlo a una librería interna compartida (paquete pip privado o git submodule) que cada svc-* importe, para no duplicar lógica ni sincronizar 14 copias del mismo encabezado de PDF. Vale un ADR (0002-libreria-compartida.md) definiendo dónde vive y cómo se versiona — y otro (0003-organizacion-por-dominio.md) dejando asentado que la organización interna de todos los servicios es por dominio, no por capa, ya que es un patrón que se repite en más de un módulo.