Skip to content

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

FaseNombreContenidoEstadoRama de trabajo
1Backend FoundationAuth JWT + scaffold DDD + migrations✅ Completadafeature/portal-backend (squash)
2Account & CuponConsulta ctacte + cupones✅ Completadafeature/portal-backend_phase-2-account
3Payment BackendAdapters + webhooks (reconciliacion manual)✅ Completadafeature/portal-backend_phase-3-payments
4Config Gateway (ERP)Vista config gateway en ERP por sucursal✅ Completadafeature/portal-backend_phase-4-gateway-config
5Vista Reconciliacion (ERP)Vista pagos portal + generacion recibo manual✅ Completadafeature/portal-backend_phase-5-reconciliacion
6Frontend PWAReact SPA en portal-usuarios✅ Completadafeature/portal-usuarios_phase-6-pwa
7Perfil BackendEndpoints GET/PUT /portal/perfil✅ Completadafeature/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, 1d

Estrategia 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) y feature/portal-backend/phase-N coexistan 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

ReglaDetalle
Una rama paraguasfeature/portal-backend — integration branch, nunca toca develop hasta el final
Sub-ramas por fasefeature/portal-backend_phase-N-nombre — nacen de la paraguas, mueren al squash
Squash por faseCada fase aporta un solo commit a la paraguas — historial intencional, sin ruido WIP
Sync periódico con developgit merge develop --no-ff a la paraguas cada vez que develop avanza
Sync = merge, no rebaseRebase rompería las sub-ramas activas; merge agrega un commit de sync barato y seguro
Integración finalSquash 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_users y portal_payments (multi-tenant, Phinx)
  • Scaffold Modules/Portal/ siguiendo patrón DDD de Modules/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 CuponPagoService y CuponValidacionService existentes
  • Filtrado por sucursal_id del JWT

Sub-módulo Cupon/:

  • Endpoint: listado de cupones por cliente
  • Endpoint: descarga de PDF (proxy hacia servicio de informes en 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 squash 0800e761 contiene 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 (interface PaymentGatewayAdapterInterface)
  • Adapter PagoTicAdapter
  • PaymentGatewayFactory — seleccion por config de sucursal en data_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 en ini.sistema)
  • La creacion del recibo en ordcta es 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 — commit 0800e761

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_token en data_config a 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_config de la sucursal
  • [ ] Sin gateway configurado → POST /portal/pagos/iniciar retorna 422 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_payments con status = 'approved' y recibo_id IS NULL
    • Tab "Historial": pagos ya conciliados
    • Filtros: sucursal, periodo, cliente
    • Accion "Generar Recibo" por fila
  • PortalReciboCreatorService en Modules/Portal/Application/Services/
    • Crea recibo en ordcta (SUCURSAL level)
    • Crea movimi en caja si portal.caja_id esta configurado (CAJA level)
    • Actualiza portal_payments.recibo_id
    • Idempotente: verifica recibo_id IS NULL antes de proceder

Gap abierto:

  • Definir portal.caja_id en data_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_COLOR

Criterios 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 — actualiza email y/o telefono en portal_users
  • Migración: columna telefono VARCHAR(30) NULL en portal_users
  • PortalPerfilService con getPerfil() y updatePerfil() (transacción + audit log)
  • OrdconLookupInterface::findNombreByOrdconId() — lookup de nombre del cliente desde ordcon.cnom

Decisiones tomadas (consultas Q1/Q2/Q3 resueltas):

ConsultaDecisión implementada
Q1 — Relación portal_user ↔ ordconN:1 — múltiples portal_users pueden apuntar al mismo ordcon
Q2 — Origen de nombre y emailMixto: nombre viene de ordcon.cnom (read-only desde portal); email y telefono son de portal_users y actualizables
Q3 — ¿ordcon_id nullable?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:

ArchivoRiesgoMotivo
index.phpAltoDI container + rutas — cada feature agrega entries
container/shared-definitions.phpAltoDefiniciones compartidas
migrations (submodule)MedioPuntero 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 develop

El resultado en develop: un único commit atómico que representa todo el portal.


Ver también