Skip to content

Bloqueo de Repago por Estado — Portal de Clientes

Módulo: Portal de Clientes Tipo: Process Estado: Implementado Fecha: 2026-05-27


Descripción

El portal bloquea el inicio de un nuevo pago si el cliente ya tiene un pago en estado no-terminal para la misma combinación (ordcon_id, sucursal_id). El bloqueo es permanente — no existe ventana de tiempo (TTL) — y se aplica tanto en el backend como en el frontend de forma proactiva.


Estados bloqueantes vs. no bloqueantes

Estadorecibo_id¿Bloquea?
pendingcualquiera✅ Sí
issuedcualquiera✅ Sí
deferredcualquiera✅ Sí
objectedcualquiera✅ Sí
reviewcualquiera✅ Sí
validatecualquiera✅ Sí
approvedNULL✅ Sí (recibo pendiente)
approvedNOT NULL❌ No (ciclo completo)
cancelledcualquiera❌ No
rejectedcualquiera❌ No
refundedcualquiera❌ No

Flujo backend

El método PortalPaymentRepository::findBlockingPaymentByOrdconAndSucursal() ejecuta una única query que cubre todos los estados bloqueantes sin restricción de tiempo:

sql
SELECT * FROM portal_payments
WHERE ordcon_id   = :ordcon_id
  AND sucursal_id = :sucursal_id
  AND (
    status IN ('pending','issued','deferred','objected','review','validate')
    OR (status = 'approved' AND recibo_id IS NULL)
  )
ORDER BY created_at DESC
LIMIT 1

Si esta query retorna un resultado, PortalPaymentService::iniciar() lanza ActivePaymentExistsException → HTTP 409 ACTIVE_PAYMENT_EXISTS.


Endpoint de pre-check

GET /backend/portal/account/pago-activo?sucursal_id={id}

  • Autenticado con JWT del portal (el ordcon_id se extrae del token, no de la query)
  • Sin JWT válido → 401
  • Sin sucursal_id en query → 400

Respuesta cuando no hay pago activo

json
{ "activo": false }

Respuesta cuando hay pago activo

json
{
  "activo": true,
  "payment_id": "uuid-del-pago",
  "estado": "pending",
  "created_at": "2026-05-27T10:00:00+00:00"
}

Flujo frontend (PagarView)

Al montar PagarView, el hook usePagoActivo llama al endpoint de pre-check con staleTime: 10s (sin polling). Si activo: true:

  • Se muestra un banner no-dismissible en la parte superior de la vista
  • El botón Pagar queda deshabilitado

Texto del banner:

"Tenés un pago en proceso. Si necesitás asistencia, contactá a soporte."

El cliente no puede cancelar pagos desde el portal. La resolución de pagos colgados (por ejemplo, si el gateway no notifica el resultado) es responsabilidad del operador desde el backoffice.


Limitaciones conocidas

  • Sin cancelación para el usuario: si un pago queda en estado no-terminal indefinidamente (gateway sin respuesta), el cliente queda bloqueado hasta que un operador resuelva el pago en el backoffice.
  • Herramienta de backoffice: la funcionalidad para cancelar pagos del portal desde el backoffice está pendiente de implementación (scope separado).

Archivos relevantes

ArchivoRol
bautista-backend/Modules/Portal/Infrastructure/Persistence/Repositories/PortalPaymentRepository.phpfindBlockingPaymentByOrdconAndSucursal()
bautista-backend/Modules/Portal/Application/Payment/Services/PortalPaymentService.phpGuard en iniciar() + getPagoActivo()
bautista-backend/Modules/Portal/Presentation/Account/Controllers/PortalAccountController.phppagoActivo() action
portal-usuarios/src/features/pagos/hooks/usePagoActivo.tsHook de pre-check
portal-usuarios/src/views/PagarView.tsxBanner + botón bloqueado