Appearance
Arquitectura Backend - Portal API
Resumen
El backend del Portal PWA extiende el servidor Slim Framework existente con nuevos endpoints bajo el namespace /portal/*, reutilizando modelos y servicios existentes.
Estructura de Capas
┌─────────────────────────────────────────────────────────┐
│ Routes (/portal/*) │
│ - Definición de endpoints │
│ - Middleware: PortalAuth → Connection │
└──────────────────────────┬──────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────┐
│ Controllers │
│ - PortalAuthController │
│ - PortalCtaCteController │
│ - PortalPagosController │
│ - PortalCuponesController │
│ Solo manejo HTTP, delega a Services │
└──────────────────────────┬──────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────┐
│ Services (Lógica de Negocio) │
│ - ClientIdentificationService │
│ - PortalCtaCteService (reutiliza CuentaCorriente) │
│ - PaymentGatewayService (multi-gateway) │
│ - CuponPagoService │
│ Lógica + transacciones + auditoría │
└──────────────────────────┬──────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────┐
│ Models (Acceso a Datos) │
│ Nuevos: │
│ - TenantDomainModel │
│ - PortalClientModel │
│ - PortalPaymentModel │
│ - PortalCuponModel │
│ Reutilizados: │
│ - Cliente │
│ - CuentaCorriente │
└──────────────────────────┬──────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────┐
│ Database (PostgreSQL Multi-Tenant) │
│ - DB ini: tenant_domains │
│ - DB {tenant}: portal_clients, portal_payments, etc. │
└─────────────────────────────────────────────────────────┘Middleware
PortalAuthMiddleware
Responsabilidad: Resolver tenant por dominio + autenticar cliente
Flujo:
- Extraer host del request (ej:
ctacte.empresaA.com.ar) - Buscar en
tenant_domainspor dominio - Inyectar
tenant_contexten request (tenant_id, database, schema) - Si ruta protegida, verificar sesión/token del cliente
- Inyectar
cliente_contexten request - Delegar a ConnectionMiddleware
Ventajas:
- Más simple que JWT de empresa (no requiere machine tokens)
- Reutiliza ConnectionMiddleware existente
- Session PHP o JWT ligero de cliente
ConnectionMiddleware
Reutiliza middleware existente: Configura ConnectionManager con database y schema del tenant.
Endpoints
Autenticación
POST /portal/auth/identify-client
Propósito: Identificar cliente sin contraseña (DNI/CUIT/ID).
Request:
json
{
"identifier": "12345678",
"identifier_type": "dni" // dni, cuit, cliente_id
}Response:
json
{
"success": true,
"data": {
"cliente_id": 123,
"nombre": "Juan Pérez",
"email": "juan@example.com",
"has_ctacte": true
},
"session_token": "..."
}Cuenta Corriente
GET /portal/mi-cuenta
Propósito: Resumen del estado de cuenta del cliente.
Parámetros: cliente_id (query param)
Response:
json
{
"cliente_id": 123,
"nombre": "Juan Pérez",
"saldo_total": 15000.00,
"facturas_vencidas": 3,
"ultimo_pago": {
"fecha": "2026-01-15",
"monto": 5000.00
}
}GET /portal/deudas
Propósito: Listar todas las facturas pendientes del cliente.
Parámetros: cliente_id (query param)
Response:
json
{
"data": [
{
"id": "uuid-1234",
"tipo": "Factura A",
"numero": 123,
"fecha": "2026-01-01",
"vencimiento": "2026-01-31",
"monto": 10000.00,
"saldo": 10000.00,
"dias_vencido": 0,
"esta_vencido": false
}
]
}Pagos
POST /portal/pagos/iniciar
Propósito: Iniciar pago online y obtener URL de redirección al gateway.
Request:
json
{
"cliente_id": 123,
"facturas": [
{"id": "uuid-1234", "monto": 10000.00},
{"id": "uuid-5678", "monto": 5000.00}
],
"total": 15000.00
}Response:
json
{
"payment_id": "uuid-payment-123",
"redirect_url": "https://gateway.com/checkout?...",
"payment_method": "mercadopago"
}Flujo:
- Crear registro en
portal_paymentscon estado "pending" - Llamar adapter del gateway configurado
- Obtener URL de checkout del gateway
- Retornar URL para redirigir al cliente
POST /portal/pagos/webhook
Propósito: Recibir notificaciones del gateway cuando el pago cambia de estado.
Request: Variable según gateway (MercadoPago, PagoTIC, etc.)
Headers importantes:
x-signature: Firma del webhook (validación de seguridad)x-request-id: ID único del request (idempotencia)
Response:
json
{
"success": true
}Flujo:
- Validar firma del webhook según adapter del gateway
- Verificar idempotencia (no procesar dos veces)
- Si pago aprobado:
- Actualizar
portal_payments.status = 'approved' - Crear recibo en
ordctausandoReciboRelationsService - Vincular recibo con payment
- Actualizar
- Si pago rechazado:
- Actualizar
portal_payments.status = 'rejected'
- Actualizar
GET /portal/pagos/historial
Propósito: Ver historial de pagos del cliente.
Parámetros: cliente_id (query param)
Response:
json
{
"data": [
{
"id": "uuid-payment-123",
"fecha": "2026-01-20",
"metodo": "online",
"monto": 15000.00,
"estado": "approved",
"facturas_pagadas": [
{"tipo": "Factura A", "numero": 123, "monto": 10000.00}
],
"recibo_numero": "REC-00123"
}
]
}Cupones
POST /portal/cupones/generar
Propósito: Generar cupón de pago con código de barras para pago físico.
Request:
json
{
"cliente_id": 123,
"facturas": [
{"id": "uuid-1234", "tipo": "Factura A", "numero": 123, "monto": 10000.00}
],
"total": 10000.00,
"dias_vencimiento": 30
}Response:
json
{
"cupon_id": "uuid-cupon-123",
"codigo_barras": "0001056789202601274",
"monto": 10000.00,
"fecha_vencimiento": "2026-02-27",
"facturas": [...]
}Flujo:
- Generar código de barras único (ITF - 19 dígitos)
- Calcular fecha de vencimiento
- Crear registro en
portal_cupones - Retornar datos del cupón
Formato código de barras:
SUCU(4) + CLIENTE(6) + TIMESTAMP(8) + DV(1) = 19 dígitos
0001 + 056789 + 20260127 + 4 = 0001056789202601274GET /portal/cupones/cliente/
Propósito: Listar cupones del cliente con filtros.
Parámetros (query):
estado: pending, used, expired, cancelled (opcional)limit: Número de registros (default: 50)offset: Offset para paginación (default: 0)
Response:
json
{
"cupones": [
{
"cupon_id": "uuid-cupon-123",
"codigo_barras": "0001056789202601274",
"monto": 10000.00,
"estado": "pending",
"fecha_generacion": "2026-01-27",
"fecha_vencimiento": "2026-02-27",
"recibo_id": null
}
],
"total": 10,
"has_more": false
}GET /portal/cupones/
Propósito: Obtener detalle de cupón por código de barras.
Response:
json
{
"cupon_id": "uuid-cupon-123",
"cliente_id": 123,
"cliente_nombre": "Juan Pérez",
"codigo_barras": "0001056789202601274",
"monto": 10000.00,
"estado": "pending",
"fecha_generacion": "2026-01-27",
"fecha_vencimiento": "2026-02-27",
"facturas": [...]
}Servicios (Lógica de Negocio)
ClientIdentificationService
Responsabilidad: Identificar y autenticar clientes.
Flujo de identificación:
- Conectar a DB del tenant
- Buscar en
portal_clientspor DNI/CUIT - Si no existe, buscar en
ordcon(tabla existente de clientes) - Crear registro en
portal_clientssi es primera vez - Verificar si está bloqueado por intentos fallidos
- Actualizar
last_login - Retornar datos del cliente
Validaciones:
- El identificador debe existir en
ordcon - El cliente debe pertenecer al tenant actual
- No debe estar bloqueado temporalmente
PortalCtaCteService
Responsabilidad: Consultar cuenta corriente del cliente.
Métodos principales:
getDeudas(): Obtener facturas pendientesgetMiCuenta(): Resumen de cuenta
Reutilización:
- Usa
CuentaCorrientemodel existente - Enriquece datos con
dias_vencido,esta_vencido - Calcula saldo total y cantidad de facturas vencidas
PaymentGatewayService
Responsabilidad: Orquestación de pagos online con múltiples gateways.
Métodos principales:
iniciarPago(): Crear pago y obtener URL de checkoutprocesarWebhook(): Procesar notificaciones del gatewaygetHistorial(): Obtener historial de pagos
Características:
- Patrón Adapter para múltiples gateways (MercadoPago, PagoTIC, etc.)
- Factory pattern para seleccionar adapter según configuración
- Validación de firma de webhook específica por gateway
- Idempotencia: verifica
portal_payments.external_id - Acreditación automática: llama
ReciboRelationsServicecuando se aprueba - Configuración por tenant en
ini.sistemas
CuponPagoService
Responsabilidad: Generación y validación de cupones de pago.
Métodos principales:
generarCupon(): Crear cupón con código de barrasvalidarCupon(): Validar cupón antes de usar
Generación de código ITF (19 dígitos):
SUCU(4) + CLIENTE(6) + TIMESTAMP(8) + DV(1)- SUCU: Código de sucursal (4 dígitos)
- CLIENTE: ID del cliente (6 dígitos)
- TIMESTAMP: Fecha actual AAAAMMDD (8 dígitos)
- DV: Dígito verificador calculado (1 dígito)
Seguridad
Autenticación
Opción recomendada: Session PHP
Después de identificar cliente:
- Se guarda en
$_SESSION['portal_cliente'] - Incluye:
cliente_id,tenant_id,nombre,expires
Alternativa: JWT Ligero
- Payload simple con
cliente_id,tenant_id,exp - Firmado con HS256
Validaciones
- Propiedad del cliente: Verificar que cliente pertenece al tenant actual
- Rate Limiting:
- Max 5 intentos de login/minuto por IP
- Max 100 requests/minuto por dominio
- Bloqueo automático después de 3 intentos fallidos (15 minutos)
- Webhook Security:
- Validar firma específica por gateway
- Verificar idempotencia (no procesar dos veces)
- Loggear todos los payloads recibidos
- Responder en < 5 segundos
Headers Requeridos
Todos los endpoints protegidos requieren:
Authorization: Bearer {session_token}: Token de sesión del clienteX-Client-Domain: Dominio actual (para resolver tenant)
Reutilización de Código Existente
El portal reutiliza ampliamente el backend existente:
Modelos reutilizados:
Cliente: Tabla existente de clientesCuentaCorriente: Movimientos de cuenta corrienteReciboRelationsService: Creación de recibos
Middleware reutilizado:
ConnectionMiddleware: Configuración de conexión multi-tenant
Servicios reutilizados:
ReciboRelationsService: Crear recibos cuando se aprueba un pago
Próximos Pasos
- Ver middleware/portal-auth-middleware.md
- Revisar services/ para detalles de cada servicio
- Consultar database/schema.md para estructura de datos