Saltar al contenido principal

Cola de Impresion y Reintentos

Cuando una impresion falla en el cliente (impresora apagada, sin papel, sin red, navegador sin permiso, etc.), Core360 puede encolar el trabajo en una cola persistente del backend que reintenta automaticamente con backoff exponencial. Un supervisor puede consultar, reintentar manualmente o cancelar trabajos pendientes.

Endpoint API: /api/printjobs

Worker: PrintQueueWorker (BackgroundService de la API).


Cuando se encola un trabajo

La WebApp / app movil intenta primero las estrategias configuradas en la terminal. Si todas fallan, el cliente puede pedirle al backend que persista el trabajo:

POST /api/printjobs
{
"posTerminalId": 5,
"invoiceId": 12345,
"payloadJson": "{...ReceiptPayloadDTO serializado...}",
"strategy": "webbluetooth",
"lastError": "GATT operation failed"
}

El job se crea con status Pending y queda disponible para retry.


Lifecycle de un PrintJob

   Pending  ──primer intento exitoso──→  Sent

└──fallo─→ Retrying (AttemptCount++, NextAttemptAt = now + backoff)

├── 5 intentos fallidos ──→ Failed (sin mas reintentos automaticos)

└── operador cancela ─────→ Cancelled
StatusSignificado
PendingRecien creado, sin intentos aun
SentImpresion exitosa
RetryingFalló, esperando proxima ventana de reintento
FailedAgotó los 5 intentos automaticos
CancelledOperador decidió descartarlo

Backoff exponencial

IntentoEspera tras fallo
130 segundos
21 minuto
35 minutos
415 minutos
51 hora
6+(status pasa a Failed)

El campo NextAttemptAt indica cuando el worker considerará el job nuevamente.


El worker

PrintQueueWorker es un BackgroundService que se ejecuta dentro del proceso de la API. Cada 30 segundos:

  1. Consulta jobs con Status IN (Failed, Retrying) AND AttemptCount < 5 AND NextAttemptAt <= NOW().
  2. Marca cada uno como Retrying.
  3. Emite el evento SignalR printJobReady al grupo de la empresa (company-{id}).
  4. Los clientes POS conectados (Blazor o MAUI) reciben el evento y ejecutan la impresion con la estrategia configurada en la terminal.
  5. Tras imprimir, el cliente llama POST /api/printjobs/{id}/retry (exito) o el worker volvera a intentar.

El worker tambien notifica a supervisores via SignalR cuando un job pasa a Failed definitivo.


API

GET /api/printjobs/bycompany

Lista jobs de la empresa autenticada, opcionalmente filtrados por status.

Query params:

ParamTipoDefault
statusPending / Sent / Failed / Retrying / Cancelled(todos)
takeint100

Response 200: array de PrintJob.

POST /api/printjobs

Encola un nuevo trabajo. El CompanyId se toma del JWT.

Body:

{
"posTerminalId": 5,
"invoiceId": 12345,
"payloadJson": "{...}",
"strategy": "qztray",
"lastError": "WebSocket disconnected"
}

Response 200: el PrintJob creado con Status = Pending.

POST /api/printjobs/{id}/retry

Reactiva un job para reintento inmediato. Util cuando el operador resolvió el problema (puso papel, conecto la impresora) y quiere reimprimir sin esperar el backoff.

POST /api/printjobs/{id}/cancel

Marca el job como Cancelled. No se intentará mas. Util cuando la factura fue anulada o el cliente ya no requiere recibo.


Modelo de datos

Tabla PrintJobs:

ColumnaTipoNotas
Idint (PK)Identidad
CompanyIdint (FK Companies)Cascade delete
POSTerminalIdint?Terminal origen
InvoiceIdint?Factura origen (si aplica)
PayloadJsontextReceiptPayloadDTO serializado
Strategystring(40)?Ultima estrategia intentada
Statusenum (int)PrintJobStatus
AttemptCountintNumero de intentos
LastAttemptAttimestamptz?Ultimo intento
NextAttemptAttimestamptz?Proximo intento programado
LastErrorstring(2000)?Ultimo error reportado
CompletedAttimestamptz?Cuando paso a Sent o Cancelled
CreatedAt, UpdatedAt, CreationUser, ModificationUser(heredado de AuditEntity)

Indices:

NombreColumnasUso
IX_PrintJobs_Company_Status_Created(CompanyId, Status, CreatedAt)Listado por empresa filtrado por status
IX_PrintJobs_Status_NextAttempt(Status, NextAttemptAt)Worker selecciona retryables

Buenas practicas

Cliente: cuando encolar

Solo encolar tras agotar todas las estrategias del cliente:

const result = await PrintService.PrintInvoiceAsync(invoiceId, terminal);
if (!result.Success && result.Strategy === "none") {
// Solo aqui — el cliente probó todo y fallo
await Repository.PostAsync("/api/printjobs", {
posTerminalId: terminal.Id,
invoiceId: invoiceId,
payloadJson: JSON.stringify(payload),
strategy: "all-failed",
lastError: result.Message
});
}

Operador: dashboard de monitoreo

Una pagina recomendada (no incluida en Phase 1, pero el endpoint esta listo):

  • Listar jobs Failed y Retrying de la empresa.
  • Mostrar columna Intento (AttemptCount/5).
  • Boton Reintentar (POST .../retry).
  • Boton Cancelar (POST .../cancel).
  • Notificacion SignalR cuando aparece un nuevo Failed.

Limpieza periodica

Jobs Sent o Cancelled con CompletedAt > 30 dias se pueden eliminar para mantener la tabla pequeña. Implementar con un job nocturno (no incluido en Phase 1).


Lecturas relacionadas