Saltar a contenido

0004. Modelo de datos: empresas, sucursales y aplicaciones

Estado: Propuesto Fecha: 2026-09-04 Autor: Duval Alcivar Módulo(s) afectado(s): svc-identidad (core_sigfa)

Contexto

El ADR 0002 define la tenencia (empresas, sucursales) y el aislamiento por RLS. El ADR 0001 asigna a svc-identidad la gestión de empresas y sucursales. Este ADR define las tablas de empresa, sus sucursales y qué aplicaciones tiene habilitadas cada empresa (rel_empresa_aplicacion, ADR 0006). Las tablas de usuario están en el ADR 0005; los catálogos de país/ciudad/tipo de documento en el ADR 0003.

Convenciones generales (ADR 0004 global)

Convención Valor
PK id UUID — catálogo excepcional mae_empresas → BIGINT identity (mae_sucursales usa UUID)
empresa_id BIGINT — refiere a mae_empresas (ADR 0011)
Columnas estándar created_at, updated_at, deleted_at, created_by, updated_by
Booleanos is_<...>is_activo
RLS empresa_id en toda tabla con datos de empresa (ADR 0002/0012)
Prefijos mae_, rel_ (ADR 0004 global)

1. mae_empresas — catálogo de empresas

Catálogo pequeño (~5-50 registros). id es BIGINT autogenerado (GENERATED BY DEFAULT AS IDENTITY) porque es finito (ADR 0011); la secuencia permite insertar ids explícitos en seeds o propagaciones. Es la fuente de empresa_id que viaja en el token y usa RLS. No tiene empresa_id propia ni RLS.

Campo Tipo Notas
id BIGINT PK identity autogenerado
nombre VARCHAR(200) NOT NULL nombre legal de la empresa
nombre_comercial VARCHAR(200) nombre corto / marca
identificacion VARCHAR(20) UNIQUE NOT NULL identificación tributaria (RUC en Ecuador)
pais_id UUID FK → cat_paises catálogo global (ADR 0003)
ciudad_id UUID FK → cat_ciudades catálogo global (ADR 0003)
tipo_identificacion_id UUID FK → cat_tipos_identificacion catálogo global (ADR 0003)
direccion VARCHAR(300)
telefono VARCHAR(20)
email VARCHAR(150)
is_activo BOOLEAN DEFAULT TRUE
created_at TIMESTAMPTZ DEFAULT now()
updated_at TIMESTAMPTZ DEFAULT now()
deleted_at TIMESTAMPTZ NULL
created_by UUID
updated_by UUID
CREATE TABLE core.identidad.mae_empresas (
    id               BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    nombre           VARCHAR(200) NOT NULL,
    nombre_comercial VARCHAR(200),
    identificacion   VARCHAR(20) NOT NULL,
    pais_id          UUID,
    ciudad_id        UUID,
    tipo_identificacion_id UUID,
    direccion        VARCHAR(300),
    telefono         VARCHAR(20),
    email            VARCHAR(150),
    is_activo        BOOLEAN DEFAULT TRUE,
    created_at       TIMESTAMPTZ DEFAULT now(),
    updated_at       TIMESTAMPTZ DEFAULT now(),
    deleted_at       TIMESTAMPTZ NULL,
    created_by       UUID,
    updated_by       UUID,

    CONSTRAINT uq_mae_empresas_identificacion UNIQUE (identificacion),
    CONSTRAINT fk_mae_empresas_pais
        FOREIGN KEY (pais_id) REFERENCES core.identidad.cat_paises(id),
    CONSTRAINT fk_mae_empresas_ciudad
        FOREIGN KEY (ciudad_id) REFERENCES core.identidad.cat_ciudades(id),
    CONSTRAINT fk_mae_empresas_tipo_identificacion
        FOREIGN KEY (tipo_identificacion_id) REFERENCES core.identidad.cat_tipos_identificacion(id)
);
class MaeEmpresa(Base):
    __tablename__ = "mae_empresas"
    __table_args__ = {"schema": "core.identidad"}

    id: Mapped[int] = mapped_column(BigInteger, Identity(), primary_key=True)
    nombre: Mapped[str] = mapped_column(String(200), nullable=False)
    nombre_comercial: Mapped[str | None] = mapped_column(String(200))
    identificacion: Mapped[str] = mapped_column(String(20), unique=True, nullable=False)
    pais_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("core.identidad.cat_paises.id"))
    ciudad_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("core.identidad.cat_ciudades.id"))
    tipo_identificacion_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("core.identidad.cat_tipos_identificacion.id"))
    direccion: Mapped[str | None] = mapped_column(String(300))
    telefono: Mapped[str | None] = mapped_column(String(20))
    email: Mapped[str | None] = mapped_column(String(150))
    is_activo: Mapped[bool] = mapped_column(Boolean, default=True)
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
    updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
    deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
    created_by: Mapped[uuid.UUID | None] = mapped_column(as_uuid=True)
    updated_by: Mapped[uuid.UUID | None] = mapped_column(as_uuid=True)

2. mae_sucursales — unidades de la empresa

Una sucursal pertenece a una empresa (empresa_id); una empresa puede tener varias sucursales. id es UUID: la sucursal se referencia entre servicios (selector de sucursal en el token / consumidores) y desde rel_usuario_sucursal.

Campo Tipo Notas
id UUID PK DEFAULT gen_random_uuid()
empresa_id BIGINT NOT NULL RLS
codigo VARCHAR(20) NOT NULL ej. SUC-01
nombre VARCHAR(150) NOT NULL
direccion VARCHAR(300)
is_activo BOOLEAN DEFAULT TRUE
created_at TIMESTAMPTZ DEFAULT now()
updated_at TIMESTAMPTZ DEFAULT now()
deleted_at TIMESTAMPTZ NULL
created_by UUID
updated_by UUID
CREATE TABLE core.identidad.mae_sucursales (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    empresa_id  BIGINT NOT NULL,
    codigo      VARCHAR(20) NOT NULL,
    nombre      VARCHAR(150) NOT NULL,
    direccion   VARCHAR(300),
    is_activo   BOOLEAN DEFAULT TRUE,
    created_at  TIMESTAMPTZ DEFAULT now(),
    updated_at  TIMESTAMPTZ DEFAULT now(),
    deleted_at  TIMESTAMPTZ NULL,
    created_by  UUID,
    updated_by  UUID,

    CONSTRAINT uq_mae_sucursales_codigo UNIQUE (empresa_id, codigo),
    CONSTRAINT fk_mae_sucursales_empresa
        FOREIGN KEY (empresa_id) REFERENCES core.identidad.mae_empresas(id)
);

-- RLS
ALTER TABLE core.identidad.mae_sucursales ENABLE ROW LEVEL SECURITY;
CREATE POLICY aislamiento_empresa ON core.identidad.mae_sucursales
    USING (empresa_id = NULLIF(current_setting('app.current_empresa_id', true), '')::BIGINT);
class MaeSucursal(Base):
    __tablename__ = "mae_sucursales"
    __table_args__ = (
        UniqueConstraint("empresa_id", "codigo", name="uq_mae_sucursales_codigo"),
        {"schema": "core.identidad"},
    )

    id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4)
    empresa_id: Mapped[int] = mapped_column(BigInteger, ForeignKey("core.identidad.mae_empresas.id"), nullable=False)
    codigo: Mapped[str] = mapped_column(String(20), nullable=False)
    nombre: Mapped[str] = mapped_column(String(150), nullable=False)
    direccion: Mapped[str | None] = mapped_column(String(300))
    is_activo: Mapped[bool] = mapped_column(Boolean, default=True)
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
    updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
    deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
    created_by: Mapped[uuid.UUID | None] = mapped_column(as_uuid=True)
    updated_by: Mapped[uuid.UUID | None] = mapped_column(as_uuid=True)

3. rel_empresa_aplicacion — aplicaciones habilitadas por empresa

Relación N:M empresa ↔ aplicación. Decide qué aplicaciones se muestran en el login de cada empresa (no todas las empresas tienen ERP y CRM). El catálogo cat_aplicaciones está en el ADR transversal 0013.

Campo Tipo Notas
id UUID PK
empresa_id BIGINT NOT NULL FK → mae_empresas empresa dueña
aplicacion_id UUID NOT NULL FK → cat_aplicaciones aplicación habilitada
is_activo BOOLEAN DEFAULT TRUE
created_at TIMESTAMPTZ DEFAULT now()
updated_at TIMESTAMPTZ DEFAULT now()
deleted_at TIMESTAMPTZ NULL
created_by UUID
updated_by UUID

UNIQUE (empresa_id, aplicacion_id). El login la consulta antes de tener empresa_id en el token: svc-identidad la lee con acceso privilegiado para resolver "qué aplicaciones tiene esta empresa".

CREATE TABLE core.identidad.rel_empresa_aplicacion (
    id             UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    empresa_id     BIGINT NOT NULL,
    aplicacion_id  UUID NOT NULL,
    is_activo      BOOLEAN DEFAULT TRUE,
    created_at     TIMESTAMPTZ DEFAULT now(),
    updated_at     TIMESTAMPTZ DEFAULT now(),
    deleted_at     TIMESTAMPTZ NULL,
    created_by     UUID,
    updated_by     UUID,

    CONSTRAINT uq_rel_empresa_aplicacion UNIQUE (empresa_id, aplicacion_id),
    CONSTRAINT fk_rel_empresa_aplicacion_empresa
        FOREIGN KEY (empresa_id) REFERENCES core.identidad.mae_empresas(id),
    CONSTRAINT fk_rel_empresa_aplicacion_aplicacion
        FOREIGN KEY (aplicacion_id) REFERENCES core.identidad.cat_aplicaciones(id)
);
class RelEmpresaAplicacion(Base):
    __tablename__ = "rel_empresa_aplicacion"
    __table_args__ = (
        UniqueConstraint("empresa_id", "aplicacion_id", name="uq_rel_empresa_aplicacion"),
        {"schema": "core.identidad"},
    )

    id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4)
    empresa_id: Mapped[int] = mapped_column(BigInteger, ForeignKey("core.identidad.mae_empresas.id"), nullable=False)
    aplicacion_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("core.identidad.cat_aplicaciones.id"), nullable=False)
    is_activo: Mapped[bool] = mapped_column(Boolean, default=True)
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
    updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
    deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
    created_by: Mapped[uuid.UUID | None] = mapped_column(as_uuid=True)
    updated_by: Mapped[uuid.UUID | None] = mapped_column(as_uuid=True)

Resumen de tablas (empresas)

Tabla Esquema PK empresa_id RLS Descripción
mae_empresas core.identidad BIGINT identity no Catálogo de empresas
mae_sucursales core.identidad UUID BIGINT Sucursales
rel_empresa_aplicacion core.identidad UUID (empresa_id) Apps por empresa

Consecuencias

  • mae_empresas alimenta empresa_id (BIGINT) del token; toda tabla de negocio depende de él.
  • mae_sucursales es el alcance de permisos del usuario dentro de una empresa (rel_usuario_sucursal, ADR 0005).
  • rel_empresa_aplicacion decide el catálogo de aplicaciones que ve cada empresa en el login (ADR 0006).
  • El flujo de alta de empresa que propaga empresa_id y sucursales a los demás servicios (outbox, ADR transversal 0003) se define en el ADR 0008.