Skip to content

Arquitectura General - Portal de Clientes

Vision General

El Portal de Clientes es un sistema donde cada tenant tiene su propia instancia Docker del frontend configurada via .env, mientras que el backend es compartido (bautista-backend) con un modulo DDD Modules/Portal/. No existe resolucion por dominio: el frontend envia tenant_id y sucursal_id en cada request, y el backend resuelve la conexion a la base de datos correspondiente via ini.sistema.

Estado: Planificado

Modelo de Deployment

mermaid
graph LR
    subgraph "Infraestructura por Tenant"
        direction TB
        D1["Docker Tenant A<br/>.env:<br/>BACKEND_URL=https://api.bautista.com<br/>TENANT_ID=1<br/>SUCURSAL_ID=1<br/>APP_NAME=Portal Empresa A<br/>LOGO_URL=...<br/>PRIMARY_COLOR=#1e40af"]
        D2["Docker Tenant B<br/>.env:<br/>BACKEND_URL=https://api.bautista.com<br/>TENANT_ID=2<br/>SUCURSAL_ID=3<br/>APP_NAME=Portal Club XYZ<br/>LOGO_URL=...<br/>PRIMARY_COLOR=#c41e3a"]
    end

    subgraph "Backend Unico"
        BE["bautista-backend<br/>Modules/Portal/<br/>Auth | Account | Payment | Cupon"]
    end

    D1 -->|REST + JWT| BE
    D2 -->|REST + JWT| BE

Cada instancia Docker del frontend:

  • Sirve la misma app React (repo portal-usuarios)
  • Se configura unicamente via .env al momento del deploy
  • La URL del frontend es irrelevante para la logica -- puede ser cualquier dominio
  • El branding (nombre, logo, colores) se define en .env y opcionalmente se complementa con data_config del tenant

Separacion Frontend y Backend

Frontend (Repo portal-usuarios)

  • Repositorio: independiente, submodulo git en /var/www/Bautista/portal-usuarios
  • Tecnologia: React PWA
  • Deploy: una instancia Docker por tenant
  • Configuracion: .env con BACKEND_URL, TENANT_ID, SUCURSAL_ID, variables de branding
  • Independencia: completamente separado de bautista-app (ERP administrativo)

Backend (Modulo DDD en bautista-backend)

  • Ubicacion: Modules/Portal/ dentro de bautista-backend
  • NO usa arquitectura legacy (App\Controller\Portal\)
  • Sub-modulos:
    • Auth/ -- login, registro, reset password
    • Account/ -- consulta cuenta corriente, deudas
    • Payment/ -- pagos online, webhooks
    • Cupon/ -- generacion de cupones (reutiliza CuponPagoService y CuponValidacionService)
  • Endpoints: /portal/*
  • Reutiliza: modelos y servicios existentes del ERP

Flujo de Autenticacion

mermaid
sequenceDiagram
    participant C as Cliente (Browser)
    participant F as Frontend Docker
    participant B as Backend (Modules/Portal/Auth)
    participant I as ini.sistema
    participant DB as PostgreSQL (tenant DB)

    C->>F: Accede al portal
    F->>C: Muestra login

    C->>F: Ingresa DNI/CUIT + password
    F->>B: POST /portal/auth/login<br/>{dni, password, tenant_id, sucursal_id}

    B->>I: Validar tenant_id existe
    I-->>B: DB name del tenant

    B->>I: Validar sucursal_id pertenece al tenant
    I-->>B: OK

    B->>DB: Buscar en portal_users por DNI
    DB-->>B: Usuario encontrado

    B->>B: Verificar bcrypt hash

    B-->>F: JWT {portal_user_id, tenant_id, sucursal_id}
    F->>F: Almacena JWT
    F-->>C: Dashboard

Auto-registro

mermaid
sequenceDiagram
    participant C as Cliente
    participant F as Frontend
    participant B as Backend (Auth)
    participant DB as PostgreSQL

    C->>F: Formulario de registro
    F->>B: POST /portal/auth/register<br/>{dni, email, password, tenant_id, sucursal_id}

    B->>DB: Buscar DNI/CUIT en ordcon
    alt DNI existe en ordcon
        B->>DB: Crear portal_users con bcrypt hash
        B-->>F: Registro exitoso
    else DNI NO existe en ordcon
        B-->>F: Error: debe ser cliente existente
    end

El auto-registro requiere que el DNI/CUIT coincida con un registro existente en ordcon. Si no hay match, el registro falla. Esto garantiza que solo clientes reales de la empresa puedan crear cuentas en el portal.

Reset de Password

  • El usuario solicita reset via email
  • El backend genera un codigo temporal y lo envia por email
  • El usuario ingresa el codigo y define nueva password
  • Seguridad moderada: el portal es mayormente lectura de deudas + pagos

Flujo de Request (Request Autenticado)

mermaid
sequenceDiagram
    participant F as Frontend Docker<br/>(tenant_id=1, suc_id=1)
    participant MW as JWT Middleware
    participant P as Modules/Portal/Account
    participant I as ini.sistema
    participant DB as PostgreSQL

    F->>MW: GET /portal/deudas<br/>Authorization: Bearer JWT

    MW->>MW: Validar JWT
    MW->>MW: Extraer: portal_user_id=42,<br/>tenant_id=1, sucursal_id=1

    MW->>I: tenant_id=1 -> DB name?
    I-->>MW: "empresa_a"

    MW->>I: sucursal_id=1 -> schema?
    I-->>MW: "suc0001"

    MW->>MW: Configurar conexion:<br/>DB=empresa_a, schema=suc0001

    MW->>P: Request con contexto inyectado

    P->>DB: Query deudas del portal_user
    DB-->>P: Resultados

    P-->>F: JSON {success: true, data: [...]}

Flujo de Schema Resolution

El backend NO recibe nombres de DB o schemas. Solo recibe IDs numericos y los resuelve:

mermaid
flowchart TD
    A[JWT contiene<br/>tenant_id + sucursal_id] --> B{tenant_id valido<br/>en ini.sistema?}
    B -->|Si| C[Obtener DB name<br/>de ini.sistema]
    B -->|No| X[401 Unauthorized]
    C --> D{sucursal_id pertenece<br/>al tenant?}
    D -->|Si| E[Obtener schema name]
    D -->|No| X
    E --> F[Configurar conexion:<br/>DB + schema]
    F --> G[Procesar request]
  1. Frontend .env tiene TENANT_ID y SUCURSAL_ID (IDs numericos)
  2. En login, frontend envia estos IDs junto con credenciales
  3. Backend valida que el tenant existe en ini.sistema
  4. Backend valida que la sucursal pertenece al tenant
  5. JWT generado con: portal_user_id, tenant_id, sucursal_id (solo IDs)
  6. En cada request: tenant_id -> DB name (de ini.sistema), sucursal_id -> schema

Flujo de Pago Online (Automatico)

mermaid
sequenceDiagram
    participant C as Cliente
    participant F as Frontend
    participant B as Backend (Payment)
    participant GW as Payment Gateway
    participant DB as PostgreSQL

    C->>F: Selecciona facturas a pagar
    F->>B: POST /portal/pagos/iniciar<br/>{facturas: [...], monto}

    B->>GW: Crear preferencia de pago
    GW-->>B: URL de pago + payment_id

    B->>DB: INSERT portal_payments<br/>status='pending'
    B-->>F: {redirect_url: "..."}

    F->>C: Redirige a gateway
    C->>GW: Completa el pago

    GW->>B: POST /portal/pagos/webhook<br/>{payment_id, status}

    B->>B: Validar webhook
    B->>DB: UPDATE portal_payments<br/>status='approved'

    B->>DB: AUTO crear recibo en ctacte<br/>(via ReciboRelationsService)

    C->>F: Retorna a pagina de exito
    F->>B: GET /portal/pagos/{id}
    B-->>F: Pago confirmado

El flujo es completamente automatico:

  1. El cliente selecciona facturas y el backend crea el pago en el gateway
  2. El cliente completa el pago en el gateway externo
  3. El gateway notifica via webhook al backend
  4. El backend actualiza portal_payments y auto-crea el recibo en ctacte usando servicios existentes como ReciboRelationsService

Decisiones Arquitectonicas Clave

Frontend como Docker por tenant (no app generica multi-tenant)

AspectoDocker por tenantApp unica multi-tenant
Configuracion.env simple por instanciaResolucion por dominio, tabla tenant_domains
BrandingVariables de entornoLogica runtime compleja
DNSCada tenant maneja su dominioWildcard DNS, certificados complejos
ComplejidadBaja -- cada instancia es autonomaAlta -- resolucion de tenant en runtime
EscalabilidadAgregar Docker, nueva .envAgregar registro en BD, DNS
AislamientoCompleto a nivel de procesoCompartido, aislamiento logico

Modulo DDD vs Legacy

AspectoModules/Portal/ (DDD)App\Controller\Portal\ (Legacy)
OrganizacionBounded contexts clarosPlano, sin estructura
DependenciasExplicitas entre sub-modulosAcoplamiento implicito
TestingAislado por sub-moduloDificil de aislar
ConsistenciaPatron moderno del proyectoPatron en desuso

JWT con IDs numericos vs datos de conexion

AspectoIDs numericosNombres DB/schema
SeguridadNo expone infraestructuraRevela nombres internos
FlexibilidadRenombrar DB sin afectar tokensTokens invalidos si cambia nombre
ResolucionBackend resuelve via ini.sistemaDirecto pero inseguro

Componentes del Sistema

Tablas Nuevas

TablaSchema levelProposito
portal_usersMismo que ordconCredenciales portal (bcrypt hash)
portal_paymentsMismo que ordconRegistro de pagos online

Servicios Reutilizados

ServicioUso en Portal
CuponPagoServiceGeneracion de cupones de pago
CuponValidacionServiceValidacion de cupones
ReciboRelationsServiceAuto-creacion de recibos post-pago

Servicios Nuevos (en Modules/Portal/)

Sub-moduloServicioResponsabilidad
Auth/PortalAuthServiceLogin, registro, reset password
Account/PortalAccountServiceConsulta deudas, datos de cuenta
Payment/PortalPaymentServiceInicio de pago, webhooks
Cupon/PortalCuponServiceWrapper sobre CuponPagoService

Documentos Relacionados

  • Multi-Tenancy -- Estrategia Docker por tenant con resolucion via ini.sistema
  • ADR -- Registro completo de decisiones arquitectonicas
  • Backend -- Modulo DDD Modules/Portal/
  • Frontend -- Repo portal-usuarios