Saltar a contenido

0022. Patrón party: mae_entidades, personas natural y jurídica

Estado: Propuesto Fecha: 2026-09-14 Autor: Duval Alcivar Módulo(s) afectado(s): svc-identidad (mae_usuarios, mae_personas), todos los servicios que referencian personas

Contexto

mae_personas centraliza los datos de personas naturales (cédula) y la usan varios módulos (nómina, promotores internos, usuarios). mae_usuarios tenía un personal_id NOT NULL hacia mae_personas.

El problema: un usuario también puede representar a una persona jurídica externa (empresa cliente o consultor que factura con RUC), y ese registro no debe ir en mae_personas (exclusiva de personas naturales con cédula), porque necesita su propio conjunto de campos (identificación fiscal, razón social, ubicación). "Externo" es el rol/tipo del usuario, no el tipo de entidad: un externo puede ser persona natural (cédula) o jurídica (RUC).

Condicionar con if/else cada consulta de usuario según el tipo de entidad es costoso y frágil. El ADR 0005 (usuarios) y el 0007 (tipos de usuario) no cubrían este caso.

Decisión

Se aplica el patrón party con herencia por tabla (joined table inheritance):

  1. mae_entidades — raíz polimórfica. id UUID + tipo_entidad (PERSONA_NATURAL | PERSONA_JURIDICA). El id nace aquí, una sola vez.
  2. mae_personas — persona natural. Hereda de mae_entidades: su PK es FK a mae_entidades.id (mismo UUID, sin DEFAULT gen_random_uuid()).
  3. mae_personas_juridicas — persona jurídica externa (nombre, nombre_comercial, identificacion e identificación geográfica/fiscal vía catálogos). Misma regla de herencia. No es una empresa del grupo: esas siguen en mae_empresas.
  4. mae_usuarios.entidad_id — reemplaza a personal_id y apunta a mae_entidades. Un solo FK, sin condicionales.
  5. cat_tipos_usuario — catálogo de tipos de cuenta (usuario, root, externo). mae_usuarios.tipo_usuario (VARCHAR + CHECK) pasa a tipo_usuario_id (FK). Ver el ADR 0007, que se actualiza con este.

Cada subtipo expone nombre_mostrar (nombres + apellidos o nombre), de modo que el código consume usuario.nombre_mostrar sin saber el tipo.

CREATE TABLE core.identidad.mae_entidades (
    id           UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tipo_entidad VARCHAR(20) NOT NULL
        CHECK (tipo_entidad IN ('PERSONA_NATURAL', 'PERSONA_JURIDICA'))
);

CREATE TABLE core.identidad.mae_personas (
    id UUID PRIMARY KEY REFERENCES core.identidad.mae_entidades(id),
    identificacion VARCHAR(20) NOT NULL,
    nombres        VARCHAR(100) NOT NULL,
    apellidos      VARCHAR(100) NOT NULL,
    -- ...resto de columnas y auditoría
);

CREATE TABLE core.identidad.mae_personas_juridicas (
    id UUID PRIMARY KEY REFERENCES core.identidad.mae_entidades(id),
    nombre                 VARCHAR(200) NOT NULL,
    nombre_comercial       VARCHAR(200),
    identificacion         VARCHAR(20)  NOT NULL,
    pais_id                UUID REFERENCES core.identidad.cat_paises(id),
    ciudad_id              UUID REFERENCES core.identidad.cat_ciudades(id),
    tipo_identificacion_id UUID REFERENCES core.identidad.cat_tipos_identificacion(id)
);

CREATE TABLE core.identidad.mae_usuarios (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    entidad_id      UUID NOT NULL REFERENCES core.identidad.mae_entidades(id),
    tipo_usuario_id UUID NOT NULL REFERENCES core.identidad.cat_tipos_usuario(id),
    -- ...
);

Regla clave: el id no se genera en las hijas; nace en mae_entidades y se copia a la hija en la misma transacción. Solo mae_entidades y mae_usuarios tienen DEFAULT gen_random_uuid().

Alternativas descartadas

  • Dos columnas en mae_usuarios (persona_id y persona_juridica_id, mutuamente excluyentes) — obliga a if/else en cada consulta y no escala a nuevos subtipos.
  • Unificar todo en mae_personas con campos opcionales — mezcla personas naturales con empresas y ensucia mae_personas, que es exclusiva de cédulas.
  • Guardar la persona jurídica externa en mae_empresasmae_empresas es la raíz de tenencia del grupo (BIGINT, RLS); una empresa cliente no lo es.
  • Mantener el CHECK de tipo_usuario — agregar externo exigiría ALTER TABLE revalidando la tabla; un catálogo + FK solo inserta una fila.

Consecuencias

  • mae_usuarios referencia una entidad y no condiciona por tipo; los módulos consumen nombre_mostrar sin ramas.
  • mae_personas y mae_personas_juridicas mantienen su auditoría propia; el id de ambas es el mismo de mae_entidades.
  • Los catálogos y la identidad no llevan empresa_id ni RLS (son globales, como mae_personas); se declara explícitamente para no romper el ADR 0004.
  • La migración 0005_entidades migra los datos existentes con el mismo UUID y es reversible (downgrade()).
  • Agregar un subtipo nuevo (p. ej. persona jurídica interna) es una tabla hija más y su polymorphic_identity, sin tocar mae_usuarios.