Skip to content

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

TablaLevelSchema
portal_usersLEVEL_EMPRESApublic
portal_paymentsLEVEL_EMPRESApublic

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 public
  1. tenant_id del JWT se busca en ini.sistema para obtener el nombre de la base de datos
  2. sucursal_id del JWT determina el schema (sucXXXX)
  3. Si el tenant tiene ordcon en public, el schema es public independientemente del sucursal_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:

CampoTipoRestriccionesDescripcion
idserialPKIdentificador secuencial
ordcon_idintegernullable, FK lógica → ordcon.cnroCliente ERP vinculado
sucursal_idintegernullableNull para portales multi-sucursal
dnivarchar(20)nullableDNI del usuario
cuitvarchar(20)nullableCUIT del usuario
emailvarchar(255)nullableEmail (reset de password, contacto)
telefonovarchar(30)nullableTeléfono (editable desde Perfil)
password_hashvarchar(255)NOT NULLHash bcrypt
refresh_tokenvarchar(500)nullableToken de sesión activo
reset_codevarchar(64)nullableCódigo de reset de password
failed_login_attemptsintegerNOT NULL, default 0Intentos fallidos consecutivos
locked_untiltimestampnullableBloqueo 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:

CampoTipoRestriccionesDescripcion
idserialPKIdentificador secuencial
portal_user_idintegerFK → portal_users.id, NOT NULLUsuario del portal que inició el pago
ordcon_idintegernullable, FK lógica → ordcon.cnroCliente del ERP
montonumeric(15,2)nullableMonto total del pago
statusvarchar(20)NOT NULL, CHECKEstado: pending/issued/approved/rejected/refunded/cancelled
facturas_jsonjsonbNOT NULLArray de facturas incluidas
gateway_payment_idvarchar(255)nullableID del pago en el gateway externo
referencevarchar(255)nullableReferencia de correlación webhook
recibo_idvarchar(36)nullableUUID del recibo en ordcta (post auto-reconciliación)
recibo_attimestamptznullableTimestamp de conciliación automática exitosa
recibo_errortextnullableMensaje de error si TX2 (auto-reconciliación) falló
issued_attimestamptznullableRedirect al gateway
approved_attimestamptznullableAprobación del gateway
rejected_at / cancelled_at / refunded_attimestamptznullableTimestamps 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 por portal_users.cliente_id y portal_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 de portal_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 movimiento
  • cliente_id: Cliente del movimiento
  • tipo_movimiento: "Factura", "Recibo", "Nota de Credito", etc.
  • monto: Monto del movimiento
  • saldo: Saldo pendiente
  • fecha: Fecha del movimiento
  • vencimiento: 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 eliminadaRazon
tenant_domainsLa resolucion de tenant se configura en .env al momento del deploy Docker. No se necesita tabla de mapeo.
portal_cuponesSe reutiliza el sistema de cupones existente del ERP. No se crea tabla nueva.
portal_clientsReemplazada 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
    end

Consulta de Deudas

  1. Buscar en ordcta movimientos del cliente autenticado
  2. Filtrar por tipo "Factura" y saldo > 0
  3. 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