Appearance
Roadmap de Desarrollo — Portal de Clientes
Módulo: Portal de Clientes Tipo: Hoja de ruta de implementación Estado: Implementado — 7 fases completadas, pendiente merge a develop
Visión del Roadmap
El Portal de Clientes se desarrolla en 4 fases secuenciales, cada una testeada y verificada antes de iniciar la siguiente. La feature vive en una rama paraguas (feature/portal-backend) aislada del ciclo de releases de develop, para que las fases intermedias no bloqueen otras entregas.
Estado Actual por Fase
| Fase | Nombre | Contenido | Estado | Rama de trabajo |
|---|---|---|---|---|
| 1 | Backend Foundation | Auth JWT + scaffold DDD + migrations | ✅ Completada | feature/portal-backend (squash) |
| 2 | Account & Cupon | Consulta ctacte + cupones | ✅ Completada | feature/portal-backend_phase-2-account |
| 3 | Payment Backend | Adapters + webhooks (reconciliacion manual) | ✅ Completada | feature/portal-backend_phase-3-payments |
| 4 | Config Gateway (ERP) | Vista config gateway en ERP por sucursal | ✅ Completada | feature/portal-backend_phase-4-gateway-config |
| 5 | Vista Reconciliacion (ERP) | Vista pagos portal + generacion recibo manual | ✅ Completada | feature/portal-backend_phase-5-reconciliacion |
| 6 | Frontend PWA | React SPA en portal-usuarios | ✅ Completada | feature/portal-usuarios_phase-6-pwa |
| 7 | Perfil Backend | Endpoints GET/PUT /portal/perfil | ✅ Completada | feature/portal-backend (squash) |
Diagrama de Fases
mermaid
gantt
title Portal de Clientes — Roadmap
dateFormat YYYY-MM-DD
axisFormat %b %d
section Fase 1 — Foundation
Auth JWT + Scaffold DDD :done, f1a, 2026-04-01, 2026-04-10
Migrations portal_users/payments :done, f1b, 2026-04-01, 2026-04-10
Tests PHPUnit (45 passing) :done, f1c, 2026-04-08, 2026-04-10
section Fase 2 — Account & Cupon
Sub-módulo Account/ (ctacte) :active, f2a, 2026-04-22, 10d
Sub-módulo Cupon/ : f2b, after f2a, 5d
Tests + verify : f2c, after f2b, 3d
section Fase 3 — Payment Backend
Adapters MercadoPago + PagoTic : f3a, after f2c, 10d
Webhooks (reconciliacion manual) : f3b, after f3a, 5d
Tests + verify : f3c, after f3b, 3d
section Fase 4 — Config Gateway (ERP)
Vista config gateway por sucursal : f4a, after f3c, 5d
Tests + verify : f4b, after f4a, 2d
section Fase 5 — Reconciliacion (ERP)
Vista pagos portal + recibo : f5a, after f4b, 7d
PortalReciboCreatorService : f5b, after f4b, 7d
Tests + verify : f5c, after f5b, 3d
section Fase 6 — Frontend PWA
Setup React + auth flow : f6a, after f5c, 7d
Vistas ctacte + deudas : f6b, after f6a, 7d
Flujo de pago + polling : f6c, after f6b, 7d
Tests Vitest + Playwright : f6d, after f6c, 5d
section Fase 7 — Perfil Backend
Endpoints GET/PUT /portal/perfil :done, f7a, after f6d, 5d
Tests + verify :done, f7b, after f7a, 2d
section Integracion Final
Squash merge → develop :milestone, crit, m1, after f7b, 1dEstrategia de Branching
El portal usa un modelo de rama paraguas con squash merge por fase, que garantiza historial limpio en develop y aísla el ciclo de desarrollo del resto del ERP.
mermaid
gitGraph commit id: "develop baseline" branch feature/portal-backend checkout feature/portal-backend commit id: "fase 1: auth JWT, DDD scaffold, migrations" tag: "done" commit id: "sync: merge develop" branch feature/portal-backend_phase-2-account checkout feature/portal-backend_phase-2-account commit id: "feat: Account + Cupon sub-modules" commit id: "test: coverage + verify" checkout feature/portal-backend merge feature/portal-backend_phase-2-account id: "fase 2: account & cupon (squash)" commit id: "sync: merge develop" branch feature/portal-backend_phase-3-payments checkout feature/portal-backend_phase-3-payments commit id: "feat: MercadoPago + PagoTic adapters" commit id: "feat: webhooks reconciliacion manual" commit id: "test: coverage + verify" checkout feature/portal-backend merge feature/portal-backend_phase-3-payments id: "fase 3: payment backend (squash)" commit id: "sync: merge develop" branch feature/portal-backend_phase-4-gateway-config checkout feature/portal-backend_phase-4-gateway-config commit id: "feat: gateway config view ERP" checkout feature/portal-backend merge feature/portal-backend_phase-4-gateway-config id: "fase 4: config gateway (squash)" branch feature/portal-backend_phase-5-reconciliacion checkout feature/portal-backend_phase-5-reconciliacion commit id: "feat: vista reconciliacion + PortalReciboCreatorService" checkout feature/portal-backend merge feature/portal-backend_phase-5-reconciliacion id: "fase 5: reconciliacion (squash)" commit id: "sync: merge develop" branch feature/portal-backend_phase-7-perfil checkout feature/portal-backend_phase-7-perfil commit id: "feat: perfil endpoints" checkout feature/portal-backend merge feature/portal-backend_phase-7-perfil id: "fase 7: perfil backend (squash)" commit id: "sync: merge develop" checkout develop merge feature/portal-backend id: "feature/portal completa → develop (squash)"
Nota sobre naming: Git no permite que
feature/portal-backend(rama paraguas) yfeature/portal-backend/phase-Ncoexistan en el mismo repo, porque el slash crea un conflicto de paths en el filesystem de refs. Por eso las sub-ramas usan underscore como separador:feature/portal-backend_phase-N-nombre.
Reglas del modelo
| Regla | Detalle |
|---|---|
| Una rama paraguas | feature/portal-backend — integration branch, nunca toca develop hasta el final |
| Sub-ramas por fase | feature/portal-backend_phase-N-nombre — nacen de la paraguas, mueren al squash |
| Squash por fase | Cada fase aporta un solo commit a la paraguas — historial intencional, sin ruido WIP |
| Sync periódico con develop | git merge develop --no-ff a la paraguas cada vez que develop avanza |
| Sync = merge, no rebase | Rebase rompería las sub-ramas activas; merge agrega un commit de sync barato y seguro |
| Integración final | Squash merge de la paraguas completa a develop — un único commit de feature |
Detalle por Fase
Fase 1 — Backend Foundation ✅
Objetivo: Establecer la base del módulo DDD Modules/Portal/ con autenticación completa.
Entregables:
- Migrations:
portal_usersyportal_payments(multi-tenant, Phinx) - Scaffold
Modules/Portal/siguiendo patrón DDD deModules/Membresia - Sub-módulo
Auth/: register, login, forgot-password, reset-password, refresh-token PortalJwtMiddleware(RSA-signed, claims{portal_user_id, tenant_id, sucursal_id})- Hash bcrypt + lockout por intentos fallidos + reset por código de email
- Auto-registro validado contra
ordcon(DNI/CUIT debe existir)
Métricas de calidad:
- 45 tests PHPUnit — 0 fallos
- Cobertura ≥ 90% en Service + Middleware
- PHPStan sin errores nuevos, PHPCS PSR-12 limpio
- Code review GGA: ✅ PASSED
Rama: feature/portal-backend (squash de feature/portal-backend-foundation)
Fase 2 — Account & Cupon ✅
Objetivo: Exponer la cuenta corriente del cliente y sus cupones de pago.
Sub-módulo Account/:
- Endpoint: listado de deudas con indicadores de vencimiento
- Endpoint: saldo actual del cliente
- Integración con
CuponPagoServiceyCuponValidacionServiceexistentes - Filtrado por
sucursal_iddel JWT
Sub-módulo Cupon/:
- Endpoint: listado de cupones por cliente
- Endpoint: descarga de PDF (proxy hacia servicio de
informesen puerto 9999) - Reutilización de
ReciboRelationsService
Criterios de completitud:
- [x] Endpoints documentados en OpenAPI via
PortalOpenApiService— saldo, movimientos, deudas, cupon/membresia, cupon/pdf - [x] Cobertura PHPUnit ≥ 80% — 47 unit tests + 14 integration tests, todos PASS
- [x] SDD verify: PASS — flujos end-to-end verificados (auth, autorización, aislamiento por cliente)
- [x] Squash merge a
feature/portal-backend— incluido en el squash de Fase 3 (Phase 3 fue construida sobre Phase 2; el squash0800e761contiene ambas fases)
Rama: feature/portal-backend_phase-2-account
Fase 3 — Payment Backend ⏳
Objetivo: Habilitar pagos online. El webhook acredita en portal_payments; la acreditacion en ctacte es manual (ver Fase 5).
Sub-módulo Payment/:
- Adapter
MercadoPagoAdapter(interfacePaymentGatewayAdapterInterface) - Adapter
PagoTicAdapter PaymentGatewayFactory— seleccion por config de sucursal endata_config- Endpoint: iniciar pago (crea preferencia en gateway, retorna URL de redireccion)
- Webhook: recibe notificacion de gateway → actualiza
portal_payments.status = 'approved'(sin crear recibo automaticamente) - Idempotencia de webhooks (tabla
portal_payments+ status tracking) - Polling endpoint: estado de pago para frontend
Consideraciones criticas:
- Webhooks deben ser idempotentes — el mismo evento no puede actualizar el status dos veces
- Timeout de gateway: manejar estados
pending/approved/rejected/expired - Configuracion de gateway por sucursal en
data_config(no hardcodeada, no enini.sistema) - La creacion del recibo en
ordctaes responsabilidad de la Fase 5 (manual via operador)
Criterios de completitud:
- [x] Tests de idempotencia de webhooks —
test_process_is_idempotent_when_payment_already_in_final_state - [x] Tests de cada adapter con mocks — PagoTic implementado, MercadoPago stub documentado (sin demanda de cliente)
- [x] Cobertura PHPUnit ≥ 80% — 83.63% en
Application/Payment/ - [x] SDD verify: PASS — 271/271 tests, 0 failures
- [x] Squash merge a
feature/portal-backend— commit0800e761
Rama: feature/portal-backend_phase-3-payments
Fase 4 — Config Gateway (ERP) ✅
Objetivo: Vista en el modulo Config/Admin del ERP para que el operador configure el gateway de pagos por sucursal.
Entregables:
- Vista de configuracion de gateway en ERP (modulo Config/Admin)
- Persistencia de
portal.gateway.nombre,portal.gateway.api_key,portal.gateway.api_secret,portal.gateway.webhook_tokenendata_configa nivel SUCURSAL - Soporte para gateways:
paypertic,mercadopago, y "sin pagos online" (vacio) - Campos de credenciales enmascarados en lectura, limpiados al editar
Criterios de completitud:
- [x] Vista implementada y funcional en ERP
- [x] Credenciales persistidas en
data_configde la sucursal - [ ] Sin gateway configurado →
POST /portal/pagos/iniciarretorna422 GATEWAY_NOT_CONFIGURED(validado en Fase 3) - [x] SDD verify: PASS (sin CRITICAL)
- [ ] Squash merge a
feature/portal-backend(pendiente al cerrar la feature completa)
Documentacion: gateway-config-view.md
Rama: feature/portal-backend_phase-4-gateway-config
Fase 5 — Vista Reconciliacion (ERP) ✅
Objetivo: Vista en CtaCte del ERP para que el operador genere el recibo de ctacte a partir de pagos online aprobados.
Entregables:
- Vista "Pagos Portal" en CtaCte del ERP
- Tab "Pendientes":
portal_paymentsconstatus = 'approved'yrecibo_id IS NULL - Tab "Historial": pagos ya conciliados
- Filtros: sucursal, periodo, cliente
- Accion "Generar Recibo" por fila
- Tab "Pendientes":
PortalReciboCreatorServiceenModules/Portal/Application/Services/- Crea recibo en
ordcta(SUCURSAL level) - Crea
movimien caja siportal.caja_idesta configurado (CAJA level) - Actualiza
portal_payments.recibo_id - Idempotente: verifica
recibo_id IS NULLantes de proceder
- Crea recibo en
Gap abierto:
- Definir
portal.caja_idendata_config(caja destino para movimientos de Tesoreria) — movimi diferido a fase posterior
Criterios de completitud:
- [x] Vista implementada — PagosPortalView en
ts/ctacte/PagosPortal/ - [x] PortalReciboCreatorService — transacción atómica con nrocomp desde mulcta
- [x] Flujo completo: pago aprobado → generar recibo → recibo en ordcta → recibo_id actualizado
- [x] Idempotencia: guard WHERE recibo_id IS NULL + 409 Conflict en race condition
- [x] SDD verify: PARTIAL (no CRITICAL) — 299 backend + 24 frontend tests
- [x] Squash merge a
feature/portal-backend
Documentacion: reconciliacion-pagos-process.md
Rama: feature/portal-backend_phase-5-reconciliacion
Fase 6 — Frontend PWA 📋
Objetivo: Aplicacion React en portal-usuarios lista para deploy Docker por tenant.
Vistas:
/login— formulario con validacion Zod (DNI/CUIT + password)/register— registro con confirmacion de email + password match/forgot-password+/reset-password— flujo completo por codigo de email/dashboard— resumen: deudas vencidas + proximas, acceso rapido a pagar/deudas— listado con filtros, indicadores de vencimiento, seleccion para pagar/pagar— flujo de checkout: seleccion → confirmacion → redirect a gateway/pagar/resultado— polling de estado + mensaje final/cupones— listado + descarga PDF
Stack:
- React 19 + TypeScript + Vite
- TanStack Query (data fetching + polling)
- React Hook Form + Zod (validaciones)
- shadcn/ui (componentes)
- BrandingContext (colores/logo desde
import.meta.env)
Configuracion Docker por tenant:
VITE_BACKEND_URL, VITE_TENANT_ID, VITE_SUCURSAL_ID
VITE_APP_NAME, VITE_LOGO_URL, VITE_PRIMARY_COLOR, VITE_THEME_COLORCriterios de completitud:
- [x] Tests Vitest + Testing Library — 104/104 PASS, cobertura 93-100% en scope
- [x] Tests Playwright: login, register, flujo de pago
- [x] Build Docker sin errores — multi-stage, nginx SPA routing
- [x] SDD verify: PARTIAL (sin CRITICAL) — warnings W1-W5 corregidos
- [x] Squash merge a
feature/portal-usuarios
Rama: feature/portal-usuarios_phase-6-pwa (repo portal-usuarios/)
Fase 7 — Perfil Backend ✅
Objetivo: Exponer y permitir actualización de datos de contacto del cliente autenticado.
Entregables implementados:
GET /backend/portal/account/perfil— retorna{ portal_user_id, tenant_id, sucursal_id, nombre, dni, cuit, email, telefono }PUT /backend/portal/account/perfil— actualizaemaily/otelefonoenportal_users- Migración: columna
telefono VARCHAR(30) NULLenportal_users PortalPerfilServicecongetPerfil()yupdatePerfil()(transacción + audit log)OrdconLookupInterface::findNombreByOrdconId()— lookup de nombre del cliente desdeordcon.cnom
Decisiones tomadas (consultas Q1/Q2/Q3 resueltas):
| Consulta | Decisión implementada |
|---|---|
| Q1 — Relación portal_user ↔ ordcon | N:1 — múltiples portal_users pueden apuntar al mismo ordcon |
| Q2 — Origen de nombre y email | Mixto: nombre viene de ordcon.cnom (read-only desde portal); email y telefono son de portal_users y actualizables |
| Q3 — ¿ordcon_id nullable? | Sí — ordcon_id es nullable; nombre retorna null cuando no hay ordcon vinculado |
Criterios de completitud:
- [x] Endpoints documentados en OpenAPI via
PortalOpenApiService - [x] Cobertura PHPUnit: 11 unit tests Service + 13 unit tests Controller — 100% PASS
- [x] GGA: ✅ PASSED (sin violations)
- [x] Squash merge a
feature/portal-backend
Rama: feature/portal-backend (squash de feature/portal-backend_phase-7-perfil)
Procedimiento de Sync con develop
Ejecutar cada vez que develop avanza (hotfixes, otras features, releases):
bash
# Detectar qué cambió en develop que puede afectar el portal
git log feature/portal-backend..develop --oneline -- bautista-backend/bootstrap/
git log feature/portal-backend..develop --oneline -- bautista-backend/migrations/
# Sincronizar la paraguas
git checkout feature/portal-backend
git merge develop --no-ff -m "chore/portal: sync con develop"Archivos de mayor riesgo de conflicto:
| Archivo | Riesgo | Motivo |
|---|---|---|
index.php | Alto | DI container + rutas — cada feature agrega entries |
container/shared-definitions.php | Alto | Definiciones compartidas |
migrations (submodule) | Medio | Puntero de commit cambia con cada release |
Integración Final a develop
Cuando las 7 fases esten completas y verificadas:
bash
# Sync final con develop
git checkout feature/portal-backend
git merge develop --no-ff -m "chore/portal: sincronización final con develop"
# Resolver conflictos si los hay
# Squash merge a develop
git checkout develop
git merge --squash feature/portal-backend
git commit -m "feature/portal: portal de clientes completo — auth, account, cupones, payments, gateway config, reconciliacion, pwa y perfil"
git push origin developEl resultado en develop: un único commit atómico que representa todo el portal.