Appearance
Diseno de Base de Datos - Portal de Clientes
Estrategia Multi-Tenant
El Portal reutiliza la arquitectura multi-tenant existente de Bautista ERP con una regla especifica: las tablas del portal viven en el mismo schema que ordcon.
Regla de Ubicacion de Schema
La ubicacion de portal_users y portal_payments es dinamica, determinada por la configuracion del tenant en ini.sistema:
- Si el tenant tiene ordcon por sucursal (configuracion tipica): las tablas van en el schema
sucXXXXcorrespondiente - Si el tenant tiene ordcon compartido en public: las tablas van en el schema
public
Esta regla garantiza que las FK a ordcon.cnro siempre sean locales al schema, sin necesidad de cross-schema JOINs.
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 = cliente_id"
ordcon ||--o{ portal_payments : "cnro = cliente_id"
ordcon ||--o{ ordcta : "cnro = cliente_id"
portal_payments o|--o| recibo : "recibo_id"
ordcon {
int cnro PK
string cnom
string ccui
string cemail
string ctel
}
portal_users {
uuid id PK
int cliente_id FK
string dni_cuit UK
string email
string password_hash
string refresh_token UK
timestamp refresh_token_expires
timestamp last_login
int failed_login_attempts
timestamp locked_until
timestamp created_at
timestamp updated_at
}
portal_payments {
uuid id PK
int cliente_id FK
int tenant_id
int sucursal_id
string gateway
string payment_method
numeric amount
enum status
string external_id UK
jsonb external_response
jsonb facturas_pagadas
int recibo_id FK
timestamp payment_date
timestamp created_at
timestamp updated_at
}
ordcta {
uuid id PK
int cliente_id
string tipo_movimiento
numeric monto
numeric saldo
date fecha
date vencimiento
}Nota: Todas las entidades del diagrama viven en el mismo schema (determinado por la regla de ubicacion). ordcon, ordcta y recibo son tablas existentes del ERP.
Tablas Nuevas
1. portal_users
Proposito: Credenciales de acceso y datos de seguridad de usuarios del portal. Vincula un cliente existente en ordcon con su cuenta de acceso al portal.
Campos:
| Campo | Tipo | Restricciones | Descripcion |
|---|---|---|---|
id | UUID | PK | Identificador unico |
cliente_id | integer | FK → ordcon.cnro, NOT NULL | Vinculacion con cliente existente |
dni_cuit | varchar(20) | UNIQUE, NOT NULL | Credencial de identificacion (login) |
email | varchar(255) | NOT NULL | Email del usuario (para reset de password) |
password_hash | varchar(255) | NOT NULL | Hash bcrypt del password |
refresh_token | varchar(255) | UNIQUE, nullable | UUID del refresh token activo (una sesion por usuario) |
refresh_token_expires | timestamp | nullable | Expiracion del refresh token |
last_login | timestamp | nullable | Ultimo acceso exitoso |
failed_login_attempts | integer | NOT NULL, default 0 | Contador de intentos fallidos |
locked_until | timestamp | nullable | Bloqueo temporal tras 5 intentos fallidos |
created_at | timestamp | NOT NULL, default now() | Fecha de creacion |
updated_at | timestamp | NOT NULL, default now() | Ultima actualizacion |
Indices:
uq_portal_users_dni_cuit(UNIQUE) — Login por DNI/CUITuq_portal_users_refresh_token(UNIQUE) — Lookup rapido de refresh tokenidx_portal_users_cliente_id— Busqueda por cliente vinculadoidx_portal_users_email— Busqueda por email (para password reset)
Relacion con ordcon:
- El registro en
portal_usersse crea durante el auto-registro - El cliente debe existir previamente en
ordconcon DNI/CUIT coincidente - FK con ON DELETE CASCADE: si se borra el cliente en
ordcon, se borra su acceso al portal
Flujo de auto-registro:
- Usuario ingresa DNI/CUIT + email + password
- Se busca en
ordconun registro conccuiigual al DNI/CUIT ingresado - Si existe match, se crea el registro en
portal_usersvinculandocliente_id = ordcon.cnro - Si no existe match, se rechaza el registro
2. portal_payments
Proposito: Registro de pagos realizados via portal. Almacena el estado completo del pago incluyendo la respuesta del gateway, y se vincula automaticamente con el recibo generado.
Campos:
| Campo | Tipo | Restricciones | Descripcion |
|---|---|---|---|
id | UUID | PK | Identificador unico |
cliente_id | integer | FK → ordcon.cnro, NOT NULL | Cliente que realizo el pago |
tenant_id | integer | NOT NULL | ID del tenant (para resolucion en webhook) |
sucursal_id | integer | NOT NULL | ID de la sucursal (para resolucion en webhook) |
gateway | varchar(50) | NOT NULL | Adapter de gateway usado (paypertic, mercadopago). Para webhook routing y reportes |
payment_method | varchar(50) | NOT NULL | Metodo de pago (mercadopago, pagotic, etc.) |
amount | numeric(15,2) | NOT NULL | Monto total pagado |
status | varchar(20) | NOT NULL, default 'pending' | Estado del pago |
external_id | varchar(255) | UNIQUE | ID del pago en el gateway externo |
external_response | jsonb | nullable | Respuesta completa del gateway |
facturas_pagadas | jsonb | NOT NULL | Array de facturas incluidas en el pago |
recibo_id | integer | FK → recibo, nullable | Recibo generado automaticamente tras aprobacion |
payment_date | timestamp | NOT NULL | Fecha/hora del pago |
created_at | timestamp | NOT NULL, default now() | Fecha de creacion |
updated_at | timestamp | NOT NULL, default now() | Ultima actualizacion |
Valores de status: pending, approved, rejected, refunded, cancelled
Indices:
uq_portal_payments_external_id(UNIQUE) — Idempotencia: evita procesar el mismo pago dos vecesidx_portal_payments_cliente_id— Pagos de un clienteidx_portal_payments_status— Filtrado por estadoidx_portal_payments_gateway— Filtrado por gateway (reportes, estadisticas por proveedor)
Estructura de facturas_pagadas:
json
[
{
"id": "uuid-factura-1",
"tipo": "Factura A",
"numero": 123,
"monto": 10000.00
},
{
"id": "uuid-factura-2",
"tipo": "Factura B",
"numero": 456,
"monto": 5000.00
}
]Estructura de external_response (ejemplo MercadoPago):
json
{
"payment_id": 1234567890,
"status": "approved",
"status_detail": "accredited",
"payment_type_id": "credit_card",
"transaction_amount": 15000.00,
"date_approved": "2026-01-20T14:30:00Z"
}Pago automatico: Cuando el webhook del gateway notifica status = approved, el sistema automaticamente crea un recibo en ordcta y vincula recibo_id en el registro de portal_payments. No hay intervencion manual.
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" automaticamente al aprobar pago
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 (automatico)
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)
API->>DB: UPDATE portal_payments SET status=approved
API->>DB: INSERT recibo en ordcta (automatico)
API->>DB: UPDATE portal_payments SET recibo_id=nuevo_recibo