Especificación Técnica: mae_usuarios¶
Esquema: core.identidad
Base de Datos: core
Servicio: svc-identidad
Tabla Legacy Origen: core_sigfa.mae_usuarios
Propósito: Maestro de usuarios del núcleo (acceso a aplicaciones del dominio identidad). Referencia una entidad (persona natural o jurídica).
1. Justificación y Mejoras de Arquitectura¶
- Credenciales centralizadas con hash (password) y primer ingreso forzado (password_temp).
- Vinculado a una entidad (
mae_entidades): persona natural o jurídica, sin condicionales en el código (entidad_id). tipo_usuario_ides FK acat_tipos_usuario(usuario|root|externo); ya no es unCHECK.- Evolución del legacy
core_sigfa.mae_usuariosencore.identidad.
2. Definiciones de Implementación¶
Table core.identidad.mae_usuarios {
id uuid [pk, default: `gen_random_uuid()`]
username varchar(50) [not null]
email varchar(150) [not null]
password varchar(255) [not null]
password_temp boolean [not null, default: `true`]
entidad_id uuid [not null, ref: > mae_entidades.id]
tipo_usuario_id uuid [not null, ref: > cat_tipos_usuario.id]
// Auditoría General
is_activo boolean [not null, default: `true`, note: 'Estado lógico del registro']
created_at timestamptz [not null, default: `now()`, note: 'Fecha de creación']
updated_at timestamptz [not null, default: `now()`, note: 'Fecha de última actualización']
deleted_at timestamptz [note: 'Fecha de eliminación lógica']
created_by uuid [note: 'Usuario creador']
updated_by uuid [note: 'Usuario modificador']
Indexes {
(username) [unique, name: 'uq_mae_usuarios_username']
}
}
CREATE TABLE IF NOT EXISTS core.identidad.mae_usuarios (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(150) NOT NULL,
password VARCHAR(255) NOT NULL,
password_temp BOOLEAN NOT NULL DEFAULT true,
entidad_id UUID NOT NULL,
tipo_usuario_id UUID NOT NULL,
-- Auditoría General
is_activo BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ,
created_by UUID,
updated_by UUID,
CONSTRAINT uq_mae_usuarios_username UNIQUE (username),
CONSTRAINT fk_mae_usuarios_entidad FOREIGN KEY (entidad_id) REFERENCES core.identidad.mae_entidades (id),
CONSTRAINT fk_mae_usuarios_tipo_usuario FOREIGN KEY (tipo_usuario_id) REFERENCES core.identidad.cat_tipos_usuario (id)
);
CREATE INDEX ix_mae_usuarios_entidad_id ON core.identidad.mae_usuarios (entidad_id);
CREATE INDEX ix_mae_usuarios_tipo_usuario_id ON core.identidad.mae_usuarios (tipo_usuario_id);
-- Comentarios
COMMENT ON COLUMN core.identidad.mae_usuarios.username IS 'Nombre de usuario';
COMMENT ON COLUMN core.identidad.mae_usuarios.email IS 'Correo electrónico';
COMMENT ON COLUMN core.identidad.mae_usuarios.password IS 'Hash de la contraseña';
COMMENT ON COLUMN core.identidad.mae_usuarios.password_temp IS 'Indica contraseña temporal';
COMMENT ON COLUMN core.identidad.mae_usuarios.entidad_id IS 'Entidad asociada (persona natural o jurídica)';
COMMENT ON COLUMN core.identidad.mae_usuarios.tipo_usuario_id IS 'Tipo de usuario (catálogo)';
from datetime import datetime, date
from typing import Optional
import uuid
from sqlalchemy import (
BigInteger,
Boolean,
CheckConstraint,
Date,
DateTime,
ForeignKey,
Identity,
Index,
Integer,
SmallInteger,
String,
Text,
UniqueConstraint,
text,
)
from sqlalchemy.dialects import postgresql
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
class Base(DeclarativeBase):
pass
class MaeUsuario(Base):
__tablename__ = 'mae_usuarios'
__table_args__ = (
UniqueConstraint('username', name='uq_mae_usuarios_username'),
Index('ix_mae_usuarios_entidad_id', 'entidad_id'),
Index('ix_mae_usuarios_tipo_usuario_id', 'tipo_usuario_id'),
{"schema": 'core.identidad'},
)
id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
primary_key=True,
server_default=text("gen_random_uuid()"),
)
username: Mapped[str] = mapped_column(
String(50),
nullable=False,
comment='Nombre de usuario',
)
email: Mapped[str] = mapped_column(
String(150),
nullable=False,
comment='Correo electrónico',
)
password: Mapped[str] = mapped_column(
String(255),
nullable=False,
comment='Hash de la contraseña',
)
password_temp: Mapped[bool] = mapped_column(
Boolean,
nullable=False,
server_default=text("true"),
comment='Indica contraseña temporal',
)
entidad_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
nullable=False,
ForeignKey("mae_entidades.id"),
comment='Entidad asociada (persona natural o jurídica)',
)
tipo_usuario_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
nullable=False,
ForeignKey("cat_tipos_usuario.id"),
comment='Tipo de usuario (catálogo)',
)
entidad: Mapped["MaeEntidad"] = relationship(lazy="joined")
tipo_usuario: Mapped["CatTipoUsuario"] = relationship(lazy="joined")
# Auditoría General
is_activo: Mapped[bool] = mapped_column(
Boolean,
nullable=False,
server_default=text("true"),
comment='Estado lógico del registro',
)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
nullable=False,
server_default=text("now()"),
comment='Fecha de creación',
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
nullable=False,
server_default=text("now()"),
comment='Fecha de última actualización',
)
deleted_at: Mapped[datetime | None] = mapped_column(
DateTime(timezone=True),
comment='Fecha de eliminación lógica',
)
created_by: Mapped[uuid.UUID | None] = mapped_column(
UUID(as_uuid=True),
comment='Usuario creador',
)
updated_by: Mapped[uuid.UUID | None] = mapped_column(
UUID(as_uuid=True),
comment='Usuario modificador',
)
"""revision: core_id_0009
create table core.identidad.mae_usuarios
Nota: personal_id -> entidad_id y tipo_usuario -> tipo_usuario_id (FK a
cat_tipos_usuario) los aplica la migración 0005_entidades de svc-identidad.
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
revision: str = 'core_id_0009'
down_revision: str | None = 'core_id_0008'
branch_labels: str | None = None
depends_on: str | None = None
def upgrade() -> None:
op.create_table(
'mae_usuarios',
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()")),
sa.Column("username", sa.String(length=50), nullable=False),
sa.Column("email", sa.String(length=150), nullable=False),
sa.Column("password", sa.String(length=255), nullable=False),
sa.Column("password_temp", sa.Boolean(), nullable=False, server_default=sa.text("true")),
sa.Column("entidad_id", postgresql.UUID(as_uuid=True), nullable=False),
sa.Column("tipo_usuario_id", postgresql.UUID(as_uuid=True), nullable=False),
sa.Column("is_activo", sa.Boolean(), nullable=False, server_default=sa.text("true")),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")),
sa.Column("deleted_at", sa.DateTime(timezone=True)),
sa.Column("created_by", postgresql.UUID(as_uuid=True)),
sa.Column("updated_by", postgresql.UUID(as_uuid=True)),
sa.UniqueConstraint('username', name='uq_mae_usuarios_username'),
sa.ForeignKeyConstraint(['entidad_id'], ['core.identidad.mae_entidades.id'], name='fk_mae_usuarios_entidad'),
sa.ForeignKeyConstraint(['tipo_usuario_id'], ['core.identidad.cat_tipos_usuario.id'], name='fk_mae_usuarios_tipo_usuario'),
schema='core.identidad',
)
op.create_index('ix_mae_usuarios_entidad_id', 'mae_usuarios', ['entidad_id'], schema='core.identidad')
op.create_index('ix_mae_usuarios_tipo_usuario_id', 'mae_usuarios', ['tipo_usuario_id'], schema='core.identidad')
def downgrade() -> None:
op.drop_table('mae_usuarios', schema='core.identidad')