Appearance
Diseno de Base de Datos - Portal de Clientes
Estrategia Multi-Tenant
Las tablas del portal viven en LEVEL_EMPRESA — siempre en el schema public del tenant, independientemente de cómo tenga configurado ordcon.
Regla de Ubicación de Schema
| Tabla | Level | Schema |
|---|---|---|
portal_users | LEVEL_EMPRESA | public |
portal_payments | LEVEL_EMPRESA | public |
Las referencias a ordcon (via ordcon_id) son FK lógicas sin constraint de integridad referencial — las consultas cross-schema se resuelven en runtime via sucursal_id del JWT. No hay FK declarada en DDL porque ordcon puede vivir en otro schema.
Resolucion en Runtime
JWT { tenant_id, sucursal_id }
| |
v v
ini.sistema schema resolution
| |
v v
database name sucXXXX o publictenant_iddel JWT se busca enini.sistemapara obtener el nombre de la base de datossucursal_iddel JWT determina el schema (sucXXXX)- Si el tenant tiene ordcon en
public, el schema espublicindependientemente delsucursal_id
Diagrama Entidad-Relacion
mermaid
erDiagram
ordcon ||--o{ portal_users : "cnro = ordcon_id (lógica)"
ordcon ||--o{ portal_payments : "cnro = ordcon_id (lógica)"
ordcon ||--o{ ordcta : "cnro = cliente_id"
portal_users ||--o{ portal_payments : "id = portal_user_id"
portal_payments o|--o| ordcta : "recibo_id = ordcta.id (UUID)"
ordcon {
int cnro PK
string cnom
string ccui
string cemail
string ctel
}
portal_users {
serial id PK
int tenant_id
int sucursal_id
int ordcon_id
string dni
string cuit
string email
string telefono
string password_hash
string refresh_token
timestamp refresh_token_expires_at
string reset_code
int failed_login_attempts
timestamp locked_until
timestamp created_at
timestamp deleted_at
}
portal_payments {
serial id PK
int portal_user_id FK
int ordcon_id
int tenant_id
int sucursal_id
string gateway
numeric monto
string status
string gateway_payment_id
string reference
jsonb facturas_json
string recibo_id
timestamp recibo_at
text recibo_error
timestamp issued_at
timestamp approved_at
timestamp rejected_at
timestamp created_at
}
ordcta {
uuid id PK
int cliente_id
string tipo_movimiento
numeric monto
numeric saldo
date fecha
date vencimiento
}Nota: portal_users y portal_payments viven en public (LEVEL_EMPRESA). ordcon y ordcta viven en el schema de sucursal (sucXXXX). Las referencias entre tablas de distintos schemas son FK lógicas — sin constraint DDL.
Tablas Nuevas
1. portal_users
Proposito: Credenciales de acceso y seguridad de usuarios del portal. Un usuario se identifica por DNI o CUIT (al menos uno). Vinculado lógicamente a ordcon via ordcon_id.
Ver esquema completo: migrations/portal-users.md
Campos clave:
| Campo | Tipo | Restricciones | Descripcion |
|---|---|---|---|
id | serial | PK | Identificador secuencial |
ordcon_id | integer | nullable, FK lógica → ordcon.cnro | Cliente ERP vinculado |
sucursal_id | integer | nullable | Null para portales multi-sucursal |
dni | varchar(20) | nullable | DNI del usuario |
cuit | varchar(20) | nullable | CUIT del usuario |
email | varchar(255) | nullable | Email (reset de password, contacto) |
telefono | varchar(30) | nullable | Teléfono (editable desde Perfil) |
password_hash | varchar(255) | NOT NULL | Hash bcrypt |
refresh_token | varchar(500) | nullable | Token de sesión activo |
reset_code | varchar(64) | nullable | Código de reset de password |
failed_login_attempts | integer | NOT NULL, default 0 | Intentos fallidos consecutivos |
locked_until | timestamp | nullable | Bloqueo hasta esta fecha |
Unicidad: Partial unique indexes por (tenant_id, sucursal_id, dni/cuit) para sucursal fija, y por (tenant_id, dni/cuit) cuando sucursal_id IS NULL.
Relacion con ordcon: FK lógica sin constraint DDL. nombre del cliente se lee de ordcon.cnom en runtime. No hay CASCADE — eliminar un cliente en ordcon no elimina el usuario del portal.
2. portal_payments
Proposito: Ciclo de vida completo de cada pago online. Desde la iniciación hasta la aprobación del gateway y la conciliación automática en ctacte.
Ver esquema completo: migrations/portal-payments.md
Campos clave:
| Campo | Tipo | Restricciones | Descripcion |
|---|---|---|---|
id | serial | PK | Identificador secuencial |
portal_user_id | integer | FK → portal_users.id, NOT NULL | Usuario del portal que inició el pago |
ordcon_id | integer | nullable, FK lógica → ordcon.cnro | Cliente del ERP |
monto | numeric(15,2) | nullable | Monto total del pago |
status | varchar(20) | NOT NULL, CHECK | Estado: pending/issued/approved/rejected/refunded/cancelled |
facturas_json | jsonb | NOT NULL | Array de facturas incluidas |
gateway_payment_id | varchar(255) | nullable | ID del pago en el gateway externo |
reference | varchar(255) | nullable | Referencia de correlación webhook |
recibo_id | varchar(36) | nullable | UUID del recibo en ordcta (post auto-reconciliación) |
recibo_at | timestamptz | nullable | Timestamp de conciliación automática exitosa |
recibo_error | text | nullable | Mensaje de error si TX2 (auto-reconciliación) falló |
issued_at | timestamptz | nullable | Redirect al gateway |
approved_at | timestamptz | nullable | Aprobación del gateway |
rejected_at / cancelled_at / refunded_at | timestamptz | nullable | Timestamps de terminal states |
Pago aprobado: Cuando el webhook del gateway notifica status = approved, TX1 commitea el estado y TX2 genera el recibo en ordcta automáticamente (sin intervención del operador). Ver auto-reconciliación técnico.
Tablas Reutilizadas del ERP
ordcon (Clientes)
Proposito: Tabla maestra de clientes del sistema ERP. El portal NO crea clientes nuevos; solo vincula clientes existentes con cuentas de acceso.
Campos usados por el portal:
cnro: ID del cliente (PK, referenciado porportal_users.cliente_idyportal_payments.cliente_id)cnom: Nombre del cliente (mostrado en la UI del portal)ccui: CUIT/DNI del cliente (usado para validar auto-registro)cemail: Email (opcional, puede diferir del email deportal_users)ctel: Telefono (opcional)
ordcta (Cuenta Corriente)
Proposito: Movimientos de cuenta corriente (facturas, recibos, notas de credito).
Campos usados por el portal:
id: UUID del movimientocliente_id: Cliente del movimientotipo_movimiento: "Factura", "Recibo", "Nota de Credito", etc.monto: Monto del movimientosaldo: Saldo pendientefecha: Fecha del movimientovencimiento: Fecha de vencimiento (para facturas)
Uso en portal:
- Consulta de deudas: Buscar facturas con saldo > 0
- Generacion de recibos: Crear nuevo movimiento tipo "Recibo" durante la reconciliacion manual de pagos aprobados
Tablas Eliminadas del Diseno Original
| Tabla eliminada | Razon |
|---|---|
tenant_domains | La resolucion de tenant se configura en .env al momento del deploy Docker. No se necesita tabla de mapeo. |
portal_cupones | Se reutiliza el sistema de cupones existente del ERP. No se crea tabla nueva. |
portal_clients | Reemplazada por portal_users que incluye password_hash para autenticacion con password. |
Flujos de Datos
Auto-Registro de Usuario
mermaid
sequenceDiagram
participant U as Usuario
participant API as Portal API
participant DB as Database
U->>API: POST /register {dni_cuit, email, password}
API->>DB: SELECT FROM ordcon WHERE ccui = dni_cuit
alt ordcon encontrado
API->>DB: SELECT FROM portal_users WHERE dni_cuit = dni_cuit
alt ya registrado
API-->>U: 409 Conflict - Usuario ya existe
else no registrado
API->>DB: INSERT portal_users {cliente_id, dni_cuit, email, password_hash}
API-->>U: 201 Created + JWT
end
else ordcon no encontrado
API-->>U: 404 - DNI/CUIT no encontrado en el sistema
endConsulta de Deudas
- Buscar en
ordctamovimientos del cliente autenticado - Filtrar por tipo "Factura" y saldo > 0
- Ordenar por fecha de vencimiento
Proceso de Pago Online con Auto-Reconciliación
mermaid
sequenceDiagram
participant U as Usuario
participant API as Portal API
participant GW as Gateway
participant DB as Database
U->>API: POST /pagos/iniciar {facturas}
API->>DB: INSERT portal_payments (status=pending)
API->>GW: Crear preferencia de pago
GW-->>API: URL de pago
API-->>U: Redirect a gateway
Note over GW: Usuario paga en gateway
GW->>API: Webhook (payment approved)
Note over API: TX1: registrar aprobación
API->>DB: UPDATE portal_payments SET status=approved
Note over API: TX2: auto-reconciliación (independiente de TX1)
API->>DB: INSERT ordcta (recibo)
API->>DB: INSERT movimi (caja)
API->>DB: UPDATE portal_payments SET recibo_id, recibo_at