Appearance
Migracion: portal_users
Informacion
- Database:
{tenant}(cada DB de tenant) - Schema: Dinamico — mismo schema que
ordcon - Level:
LEVEL_SUCURSALoLEVEL_EMPRESAsegun configuracion del tenant
Proposito
Tabla de credenciales y seguridad para usuarios del portal. Vincula un cliente existente en ordcon con su cuenta de acceso.
Regla de Nivel de Migracion
La migracion debe ejecutarse en el mismo nivel que ordcon para el tenant:
- Si
ordconesta configurado comoLEVEL_SUCURSAL(por defecto): La migracion corre en cada schemasucXXXX, creando una tablaportal_userspor sucursal. - Si
ordconesta configurado comoLEVEL_EMPRESA: La migracion corre solo enpublic, creando una unica tablaportal_userscompartida.
La migracion debe consultar configuracion_niveles_tablas para determinar el nivel de ordcon y aplicar el mismo nivel a portal_users.
php
public function getLevel(): array
{
// Respetar el mismo nivel que ordcon
$ordconLevel = $this->getTableLevel('ordcon');
return [$ordconLevel]; // LEVEL_SUCURSAL o LEVEL_EMPRESA
}Definicion DDL
sql
CREATE TABLE portal_users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
cliente_id INTEGER NOT NULL,
dni_cuit VARCHAR(20) NOT NULL,
email VARCHAR(255) NOT NULL,
password_hash VARCHAR(255) NOT NULL,
last_login TIMESTAMP NULL,
refresh_token VARCHAR(255) NULL,
refresh_token_expires TIMESTAMP NULL,
failed_login_attempts INTEGER NOT NULL DEFAULT 0,
locked_until TIMESTAMP NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
CONSTRAINT fk_portal_users_ordcon
FOREIGN KEY (cliente_id)
REFERENCES ordcon(cnro)
ON DELETE CASCADE
);Indices
sql
-- Unicidad de DNI/CUIT dentro del schema (credencial de login)
CREATE UNIQUE INDEX uq_portal_users_dni_cuit
ON portal_users (dni_cuit);
-- Busqueda por cliente vinculado
CREATE INDEX idx_portal_users_cliente_id
ON portal_users (cliente_id);
-- Busqueda por email (password reset)
CREATE INDEX idx_portal_users_email
ON portal_users (email);
-- Unicidad de refresh token (lookup rapido + una sesion por usuario)
CREATE UNIQUE INDEX uq_portal_users_refresh_token
ON portal_users (refresh_token)
WHERE refresh_token IS NOT NULL;Campos
| Campo | Tipo | Restricciones | Descripcion |
|---|---|---|---|
id | UUID | PK, default gen_random_uuid() | Identificador unico — usado como portal_user_id en JWT |
cliente_id | integer | FK → ordcon.cnro, NOT NULL | Vinculacion con cliente existente del ERP |
dni_cuit | varchar(20) | UNIQUE, NOT NULL | DNI o CUIT del usuario (credencial de login) |
email | varchar(255) | NOT NULL | Email del usuario (para password reset) |
password_hash | varchar(255) | NOT NULL | Hash bcrypt del password |
refresh_token | varchar(255) | UNIQUE (parcial), nullable | UUID del refresh token activo |
refresh_token_expires | timestamp | nullable | Expiracion del refresh token |
last_login | timestamp | nullable | Timestamp del ultimo login exitoso |
failed_login_attempts | integer | NOT NULL, default 0 | Contador de intentos fallidos consecutivos |
locked_until | timestamp | nullable | Si no es null y es futuro, la cuenta esta bloqueada |
created_at | timestamp | NOT NULL, default now() | Fecha de creacion del registro |
updated_at | timestamp | NOT NULL, default now() | Ultima modificacion |
Restricciones
- FK a ordcon:
cliente_idreferenciaordcon.cnrocon CASCADE. Si se elimina el cliente del ERP, se elimina su acceso al portal. - UNIQUE dni_cuit: Solo un usuario del portal por DNI/CUIT dentro del schema. Esto es natural porque un DNI/CUIT identifica a una persona fisica/juridica unica.
- password_hash: Almacena hash bcrypt (no texto plano). El campo es NOT NULL porque el portal usa autenticacion con password obligatorio.
Consideraciones de Seguridad
failed_login_attemptsse incrementa en cada login fallido y se resetea a 0 en cada login exitosolocked_untilse establece aNOW() + 15 minutoscuandofailed_login_attemptsalcanza 5- La aplicacion debe verificar
locked_untilantes de intentar validar el password
Rollback
sql
DROP TABLE IF EXISTS portal_users CASCADE;