Saltar a contenido

0001. Microservicio de identidad y acceso (svc-identidad)

Estado: Propuesto Fecha: 2026-08-31 Autor: Duval Alcivar

Contexto

El ADR 0003 asigna a svc-identidad los dominios de usuarios, empresas, sucursales, permisos y SSO ERP+CRM, y lo ubica dentro del core de plataforma (core_sigfa), común a los dos productos. El ADR 0006 ya define cómo se autentican los 14 servicios (JWT emitido por identidad). Las funciones de identidad están hoy dispersas: el ERP duplica la gestión de usuarios/permisos y el CRM su propio login, cada uno contra su propia base, lo que produce usuarios y roles inconsistentes entre productos.

Es necesario definir qué es exactamente svc-identidad, qué domina y qué fronteras respeta frente a los dominios de negocio.

Decisión

Se crea el microservicio svc-identidad como dueño único de la identidad y el acceso en todo el ecosistema ERP+CRM.

Stack tecnológico (consistente con el resto del backend — ADR 0001 y 0005):

  • Lenguaje: Python 3.12+.
  • Framework: FastAPI — estándar del proyecto; es quien emite el JWT (ADR 0006) y expone GET /.well-known/jwks.json con las claves públicas.
  • Despliegue: contenedor Docker independiente, con su propio pipeline, panel de salud y runbook (regla 9 del ADR 0003).

Dominios que posee (cada uno con dueño de entidad, ADR 0003):

Dominio Qué cubre Dueño de
Usuarios cuentas, credenciales, sesiones, refresh con rotación y revocación (ADR 0006) mae_usuarios
Empresas el multiempresario: mae_empresas y su ciclo de vida; alimenta empresa_id del token (ADR 0002) mae_empresas
Sucursales unidades de la empresa: mae_sucursales, ubicación, vigencia mae_sucursales
Permisos tipos de usuario (usuario/root/externo), permisos finos y asignación individual usuario → permiso/módulo; scopes/aud del token mae_usuarios.tipo_usuario_id, cat_tipos_usuario, cat_permisos
SSO ERP+CRM login único que actúa a la vez sobre erp_sigfa y crm_sigfapro; vive dentro de permisos/ sesión/logon entre productos

Reglas de frontera:

  • svc-identidad es el único que emite tokens (ADR 0006); ningún otro servicio firma JWT.
  • Identifica al usuario y la empresa, y viajan en el token (sub, empresa_id, aud, jti). No decide qué hace el usuario dentro de un dominio de negocio: eso lo resuelve el servicio dueño de la regla.
  • Correlaciona "quién es el usuario" por UUID hacia los demás servicios; las empresas/sucursales (catálogos) se correlacionan por BIGINT (empresa_id) — sin FK ni consultas cruzadas (ADR 0003 global, ADR 0011).
  • El aislamiento por empresa (RLS, ADR 0002) aplica dentro de este servicio sobre sus propias tablas.

Estructura interna — monolito modular por dominio (vertical slice):

El servicio se organiza 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 (ver 0003-organizacion-por-dominio.md).

svc-identidad/
├── app/
│   ├── main.py                    # arranque FastAPI; registra los routers de cada dominio
│   ├── core/
│   │   ├── config.py              # variables de entorno (Pydantic Settings)
│   │   ├── security.py            # emite y firma el JWT (RS256, ADR 0006)
│   │   ├── 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
│   ├── domains/
│   │   ├── usuarios/
│   │   │   ├── router.py          # /auth, /users, /sessions
│   │   │   ├── service.py         # login, tokens, credenciales
│   │   │   ├── repository.py      # único lugar que toca SQL de usuarios
│   │   │   ├── models.py          # entidades SQLAlchemy
│   │   │   └── schemas.py         # contratos Pydantic
│   │   ├── empresas/
│   │   │   └── ... (misma forma)  # empresas, empresa_id del multiempresario
│   │   ├── sucursales/
│   │   │   └── ... (misma forma)  # sucursales, ubicación, vigencia
│   │   └── permisos/
│   │       └── ... (misma forma)  # tipos de usuario y permisos, SSO
│   ├── shared/                    # SOLO lo que cruza 2+ dominios de ESTE servicio
│   └── events/
│       ├── publisher.py           # outbox: emite eventos de usuarios/empresas
│       └── 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 ESTE servicio
├── pyproject.toml
├── .env.example                   # nunca .env real committeado
└── Dockerfile

Dominios de svc-identidad:

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

Regla dura de frontera entre dominios: un archivo dentro de domains/usuarios/ no importa directo de domains/permisos/. 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 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).

Alternativas descartadas

  • Mantener identidad duplicada dentro de ERP y CRM — descartado: dos fuentes de verdad para usuarios/roles crean cuentas y permisos inconsistentes entre productos, y rompe la regla "un servicio dueño por entidad" (regla 6 del ADR 0003).
  • Separar cada dominio en su propio servicio (usuarios, empresas, permisos por separado) — descartado: fragmentar estos dominios acoplados exige transacciones distribuidas para operaciones triviales (crear usuario + asignar rol + enlazar empresa); se deja como evolución solo si demuestra necesitarlo.
  • Usar un IdP de terceros (Auth0, Keycloak SaaS) — descartado por depender de un externo en el núcleo de tenencia y seguridad, por el control de datos sensibles de identidad, y por integrarse difícilmente con la base multiempresa propia (ADR 0002).
  • Autenticación intra-servicio (cada servicio autentica su propia sesión) — descartado: duplica mecanismos y rompe el SSO ERP+CRM; ya lo resuelve el ADR 0006.

Consecuencias

  • Ganancia: una sola fuente de verdad para usuarios, empresas, sucursales y permisos, compartida por ERP y CRM; el SSO es natural porque ambos autentican contra el mismo dueño. Es el servicio que centraliza cómo cada microservicio identifica al usuario y la empresa seleccionada, su tipo (usuario/root) y sus permisos (ADR 0007/0009/0010).
  • Ganancia: la rotación de claves y la revocación de sesiones viven en un solo lugar; los demás servicios solo validan el token (ADR 0006).
  • Ganancia: toda la tenencia (mae_empresas, mae_sucursales) se centraliza y nutre empresa_id, reforzando el aislamiento RLS del ADR 0002.
  • Resuelto: el modelo de tipos de usuario y de permisos individuales está en el ADR 0007; el catálogo inicial de permisos por módulo/acción en el ADR 0014 y las acciones finas por página en el 0015.
  • Resuelto: el flujo de alta de empresa que propaga el empresa_id y sus sucursales a los demás servicios (evento de outbox) está en el ADR 0008.
  • Modelo de datos: el esquema completo de las tablas que posee este servicio está repartido en los ADRs de svc-identidad: 0003 (catálogos y maestros), 0004 (empresas y sucursales: mae_empresas, mae_sucursales, rel_empresa_aplicacion), 0005 (usuarios, sesiones y credenciales: mae_usuarios, mae_personas, rel_usuario_empresa, rel_usuario_sucursal, trx_refresh_tokens, log_sesiones), 0006 (navegación: cat_aplicaciones, cfg_modulos, cfg_menu, cfg_menu_acciones), 0007 (permisos: cat_permisos, rel_usuario_permiso, rel_usuario_menu) y 0022 (patrón party: mae_entidades, mae_personas_juridicas, cat_tipos_usuario y mae_usuarios.entidad_id/tipo_usuario_id).

Microservicio dentro de core/ porque es la capacidad de identidad que ningún producto posee en exclusiva: ERP y CRM se autentican contra el mismo dueño de usuarios, empresas, sucursales y permisos.