05 - Catálogo de Colas y Procesos Asíncronos
Validated Corrections
ProcesarPagareDigitalno hace una validacion dura de consistencia denumero_cuotascuando hay multiples ventas; solo registra un warning y sigue.- Existe riesgo real de duplicacion de cuotas porque
VentaServiceyProcesarPagareDigitalpueden generar cuotas sobre la misma compra. - La ausencia de
failed()enGenerarCreditoDeVentasigue siendo critica: un fallo por excepcion puede dejar ventas enPENDIENTEsin limpieza completa. - La cadena de jobs debe leerse con cautela por idempotencia: webhooks repetidos o jobs tardios pueden reabrir inconsistencias de estado e inventario.
- Este catalogo es correcto como mapa de procesos, pero no como garantia de comportamiento seguro ante reintentos o fallos.
- Conteo revisado: existen 6 clases en
app/Jobs, pero solo 5 implementanShouldQueue.ProcesarOrdenesAbandonadases scheduled, no un job realmente en cola.
Nombres de Colas
| Nombre de Cola | Jobs | Propósito |
|---|---|---|
creditos | GenerarCreditoDeVenta, ValidarPagareDigital, ProcesarPagareDigital | Generación de créditos y procesamiento de pagaré |
procesar_documento_productos | IniciarCargaMasivaProducto | Inicio de carga masiva de productos |
procesar_fila_producto | ProcesarFilaProducto | Procesamiento individual de filas de productos |
| N/A (programado, no encolado) | ProcesarOrdenesAbandonadas | Limpieza programada (no encolada, se ejecuta vía scheduler) |
Jobs
1. GenerarCreditoDeVenta
| Campo | Valor |
|---|---|
| Archivo | app/Jobs/GenerarCreditoDeVenta.php |
| Cola | creditos |
| Implementa ShouldQueue | Sí |
| Intentos | 3 |
| Timeout | 900s (15 min) |
| Backoff | 60s entre reintentos |
| Constructor | Empresa, Cliente, Venta |
| Inyectado | CreditoService |
Despachado por:
ProcesarPagareDigital::handle()— después de generar cuotas (el camino real usado en producción)VentaService::crearVenta()—dispatch()condicional enapp/Services/VentaService.php:245(venta única) y:403(multi-empresa) controlado por$tienePagare && !is_null($cliente->pagare_firmado_en). Actualmente es código muerto: el helperregistrarEnCerticamara()termina conreturn false;(app/Services/VentaService.php:499), por lo que$tienePagaresiempre esfalsey estos dispatches nunca se ejecutan
Lógica:
- Valida que
cupo_disponiblesea suficiente - Llama a
creditoService->crearClienteEnCredito()para registrar al cliente en el core bancario - Llama a
creditoService->generarCredito()para crear el crédito con la cantidad de cuotas - En éxito: actualiza OrdenCompra → PROCESADA, Venta → APROBADA, decrementa
cupo_disponible - En fallo: rechaza la venta, devuelve el inventario al stock
Advertencia: No se define un método
failed(). Después de 3 reintentos fallidos, el job se marca como fallido en el sistema de colas pero la Venta permanece en estado PENDIENTE sin restauración de inventario. La limpieza derechazarVenta()solo se ejecuta en rechazos explícitos del servicio de crédito (no excepción), no en excepciones no manejadas.
2. ValidarPagareDigital
| Campo | Valor |
|---|---|
| Archivo | app/Jobs/ValidarPagareDigital.php |
| Cola | creditos |
| Implementa ShouldQueue | Sí |
| Intentos | por defecto |
| Timeout | por defecto |
| Constructor | Cliente, array $webhookPayload |
| Inyectado | VentaService |
Despachado por:
CerticamaraController::__invoke()— endpoint del webhook de Certicámara (POST /api/v1/webhooks/certicamara)
Lógica:
- Recibe el payload del webhook con el estado del pagaré (
signed,blocked,expired) - Si NO está firmado: rechaza todas las órdenes/ventas PENDIENTE del cliente, devuelve inventario, limpia el UUID de certicámara, establece timestamp de reintento
- Si está firmado: despacha
ProcesarPagareDigitalpara cada orden PENDIENTE, actualizapagare_firmado_en
Encadena: → ProcesarPagareDigital (uno por cada orden PENDIENTE)
3. ProcesarPagareDigital
| Campo | Valor |
|---|---|
| Archivo | app/Jobs/ProcesarPagareDigital.php |
| Cola | creditos |
| Implementa ShouldQueue | Sí |
| Intentos | por defecto |
| Timeout | por defecto |
| Constructor | OrdenCompra |
| Inyectado | VentaService |
Despachado por:
ValidarPagareDigital::procesarOrdenesPendientes()— después de que se firma el pagaré
Lógica:
- Carga las ventas con sus relaciones (empresa, cliente)
- Valida que todas las ventas tengan datos completos
- Venta única: genera cuotas → despacha
GenerarCreditoDeVenta - Múltiples ventas: valida
numero_cuotasconsistente, genera cuotas por orden → despachaGenerarCreditoDeVentapara cada una - Registra el estado final esperado (PROCESADA) pero NO actualiza OrdenCompra → PROCESADA. La actualización real a PROCESADA ocurre en
GenerarCreditoDeVenta::handle().
Encadena: → GenerarCreditoDeVenta (uno por cada venta)
4. ProcesarOrdenesAbandonadas
| Campo | Valor |
|---|---|
| Archivo | app/Jobs/ProcesarOrdenesAbandonadas.php |
| Cola | por defecto (no encolada explícitamente) |
| Implementa ShouldQueue | No |
| Programación | Cada 15 minutos (routes/console.php) |
| Constructor | ninguno |
| Inyectado | VentaService |
Despachado por:
- Laravel Scheduler —
Schedule::job(new ProcesarOrdenesAbandonadas)->everyFifteenMinutes()
Lógica:
- Encuentra todas las OrdenCompra en estado PENDIENTE con más de 60 minutos
- Procesa en bloques de 10
- Por cada orden: devuelve el inventario al stock, establece el estado de la venta como ABANDONADA con la razón “El firmante abandonó la firma del pagaré”
- Limpia
certicamara_uuiddel cliente si aplica - Actualiza OrdenCompra → ABANDONADA
- Captura errores por orden (no los re-lanza)
5. IniciarCargaMasivaProducto
| Campo | Valor |
|---|---|
| Archivo | app/Jobs/Productos/IniciarCargaMasivaProducto.php |
| Cola | procesar_documento_productos |
| Implementa ShouldQueue | Sí |
| Intentos | por defecto |
| Timeout | por defecto |
| Constructor | string $filePath, int $empresaId |
| Inyectado | ProductoService |
Despachado por:
Market\ProductoController::massiveStore()— endpoint de carga masiva de productos (POST /aliados/productos/masivos)
Lógica:
- Obtiene la ruta local del archivo desde el almacenamiento
- Llama a
productoService->procesarCarga()que lee la hoja de cálculo y despacha jobs por fila - Elimina el archivo subido del almacenamiento después del procesamiento
Encadena: → ProcesarFilaProducto (uno por cada fila de la hoja de cálculo, vía ProductoService)
6. ProcesarFilaProducto
| Campo | Valor |
|---|---|
| Archivo | app/Jobs/Productos/ProcesarFilaProducto.php |
| Cola | procesar_fila_producto |
| Implementa ShouldQueue | Sí |
| Intentos | por defecto |
| Timeout | por defecto |
| Constructor | int $empresaId, array $row, array $mapper, string $batchId |
| Inyectado | ProductoService |
Despachado por:
ProductoService::procesarCarga()— despachado por cada fila (index > 0) en la hoja de cálculo
Lógica:
- Obtiene el progreso del lote desde la caché de archivos
- Llama a
productoService->procesarFila()para crear/actualizar el producto - Incrementa el contador de procesados, registra éxitos/errores
- Cuando se procesan todas las filas (missing llega a 0): registra la finalización, limpia la caché
Coordinación de lotes: Usa caché basada en archivos (clave batchId) para rastrear el progreso entre jobs paralelos
Flujo de Encadenamiento de Jobs
Certicámara Webhook │ ▼ ValidarPagareDigital ┌─────┴─────┐ │ │ if signed if blocked/expired │ │ ▼ ▼ ProcesarPagareDigital Reject all orders (per PENDIENTE order) Return inventory │ ▼ GenerarCreditoDeVenta (per sale in order) │ ┌─────┴─────┐ │ │ success failure │ │ ▼ ▼ APROBADA RECHAZADA -cupo +inventory
Bulk Product Upload │ ▼ IniciarCargaMasivaProducto │ ▼ ProcesarFilaProducto × N (parallel, per row)
Scheduler (every 15m) │ ▼ ProcesarOrdenesAbandonadas │ ▼ PENDIENTE > 60min → ABANDONADAResumen del Comportamiento de Reintentos y Errores
| Job | Intentos | Timeout | Backoff | En Fallo |
|---|---|---|---|---|
| GenerarCreditoDeVenta | 3 | 900s | 60s | En rechazo explícito del crédito: rechaza la venta, devuelve inventario. En excepción tras 3 reintentos: el job falla silenciosamente — la venta queda PENDIENTE, sin restauración de inventario, sin notificación (sin método failed()). |
| ValidarPagareDigital | por defecto | por defecto | por defecto | Excepción re-lanzada |
| ProcesarPagareDigital | por defecto | por defecto | por defecto | Excepción re-lanzada |
| ProcesarOrdenesAbandonadas | N/A | N/A | N/A | Error registrado por orden, continúa |
| IniciarCargaMasivaProducto | por defecto | por defecto | por defecto | Excepción re-lanzada |
| ProcesarFilaProducto | por defecto | por defecto | por defecto | Error registrado en la caché del lote |
Nota: Los nombres de las colas se asignan en el sitio de despacho (vía
->onQueue(...)) en lugar de en la clase del job. Esto significa que la asignación de colas está distribuida en múltiples archivos, lo que podría conducir a inconsistencias.