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 tenerempresa_iden el token:svc-identidadla 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 | sí | Sucursales |
rel_empresa_aplicacion |
core.identidad | UUID | (empresa_id) | — | Apps por empresa |
Consecuencias¶
mae_empresasalimentaempresa_id(BIGINT) del token; toda tabla de negocio depende de él.mae_sucursaleses el alcance de permisos del usuario dentro de una empresa (rel_usuario_sucursal, ADR 0005).rel_empresa_aplicaciondecide el catálogo de aplicaciones que ve cada empresa en el login (ADR 0006).- El flujo de alta de empresa que propaga
empresa_idy sucursales a los demás servicios (outbox, ADR transversal 0003) se define en el ADR 0008.