Skip to content

Jobs de Recuperación de Pagos del Portal

⚠️ DOCUMENTACIÓN RETROSPECTIVA — Generada a partir de código implementado el 2026-06-24

Módulo: Portal de Clientes Tipo: Process Estado: Implementado Fecha: 2026-06-24


Descripción

Dos scripts CLI ejecutados via cron resuelven automáticamente los pagos que quedan en estado intermedio sin resolución del gateway o con recibo_error de TX2.

El diseño prioriza el aislamiento por tenant: un fallo en un tenant no aborta el sweep global.


Scripts

ScriptFrecuenciaPropósito
cli/sweep-portal-payments.phpCada 30 minBarrer filas issued/pending stale (>2h). Issued: consultar gateway → reconciliar o cancelar. Pending: cancelar directamente.
cli/retry-recibo-errors.phpCada 10 minReintentar filas approved con recibo_error (TX2 falló antes). Cap: 3 intentos. AMOUNT_MISMATCH excluido siempre.

Entradas de crontab (solo referencia — no instaladas automáticamente):

cron
*/30 * * * * php /var/www/Bautista/server/cli/sweep-portal-payments.php >> /var/www/Bautista/server/logs/sweep-portal-payments.log 2>&1
*/10 * * * * php /var/www/Bautista/server/cli/retry-recibo-errors.php >> /var/www/Bautista/server/logs/retry-recibo-errors.log 2>&1

Ver referencia completa en portal-payment-recovery-crontab.md.


Flujo de sweep-portal-payments.php

Para cada tenant con modulo_portal_clientes=1:
  Filas issued (>2h, sin resolución):
    → getPaymentStatus(gateway_payment_id)
    → APPROVED → AutoReconciliacionService::reconciliar()
    → cualquier otro estado → markCancelled() en TX
  Filas pending (>2h):
    → markCancelled() en TX (sin consultar gateway — pending nunca tiene gateway_payment_id)

Argumentos disponibles:

ArgumentoDefaultDescripción
--dry-runImprime lo que haría, sin mutar nada
--ttl-hours=N2Umbral de antigüedad para considerar una fila stale

Códigos de salida: 0 = éxito (errores por fila se loguean pero no abortan), 1 = error fatal en setup.


Flujo de retry-recibo-errors.php

Para cada tenant con modulo_portal_clientes=1:
  Filas approved con recibo_error IS NOT NULL
  (excluye AMOUNT_MISMATCH a nivel SQL):
    sweep_retries < 3:
      → AutoReconciliacionService::reconciliar()
      → En éxito: clearReciboError() + incrementSweepRetry() en TX
    sweep_retries >= 3:
      → AlertService::sendAlert(type='portal_payment_retry_cap_reached')
      → skip

sweep_retries se persiste en portal_payments.gateway_response JSONB como {"sweep_retries": N}.


Reglas de negocio

RN-001: Aislamiento por tenant Cada tenant se envuelve en try/catch con continue. Un fallo de bootstrap en tenant A no afecta el procesamiento del tenant B.

RN-002: AMOUNT_MISMATCH nunca se reintenta Filas con recibo_error LIKE 'AMOUNT_MISMATCH:%' se excluyen a nivel SQL en la query de findApprovedWithReciboError() y con un guard en RetryReciboErrorsService::processRow(). Requieren intervención humana obligatoria.

RN-003: Cap de reintentos = 3RetryReciboErrorsService usa retryCap = 3 (configurable por constructor). Al alcanzar el cap, AlertService::sendAlert() emite alerta con type='portal_payment_retry_cap_reached'. La fila no se vuelve a procesar hasta que el sweep_retries sea reseteado manualmente.

RN-004: Transacciones CUD Toda operación de escritura en los services (markCancelled, incrementSweepRetry, clearReciboError) se envuelve en transacción sobre la conexión principal. AutoReconciliacionService gestiona su propia TX2 internamente.

RN-005: Dry-run seguro Ambos scripts respetan --dry-run: iteran y loguean lo que harían pero no ejecutan ningún CUD ni llamada al gateway.


Clases involucradas

ClaseArchivoRol
SweepPortalPaymentsServiceModules/Portal/Application/Sweep/SweepPortalPaymentsService.phpLógica unitesteable de decisión para filas issued/pending
RetryReciboErrorsServiceModules/Portal/Application/Sweep/RetryReciboErrorsService.phpLógica unitesteable de retry con cap y alertas
AutoReconciliacionServiceModules/Portal/Application/Reconciliacion/Services/AutoReconciliacionService.phpTX2 de conciliación, usada por ambos scripts
AlertServiceApp/Core/Services/AlertService.phpEnvío de alertas al webhook configurado

Ver también


⚠️ NOTA IMPORTANTE: Validar con stakeholders antes de considerar final.