Estado Handler
Autor: Iker Acevedo¶
Fecha creacion: 2026-08-26
Estado: produccion
Handler: EstadoHandler¶
Ruta: GET /devolucionesMasivo/estado
Ubicación: Handlers/EstadoHandler.cs
Timeout: 30s — corre detrás de API Gateway
¿Qué hace?¶
La ruta más llamada de todo el módulo: el front la consulta cada 2.5 segundos por cada operario con un lote activo (polling). Por eso está diseñada para ser barata — un findOne por _id y nada más, sin joins ni agregaciones.
Tiene 3 comportamientos según los query params:
| Query | Comportamiento |
|---|---|
sin jobId |
Jobs abiertos (Pendiente/EnProceso) del usuario — para reenganchar el polling si recargó la página |
?jobId=X |
Estado de un job puntual, sin el detalle de rechazadas |
?jobId=X&detalle=true |
Igual, más el array Rechazadas completo — se pide una sola vez, cuando el job termina |
Response — 200 OK (con jobId)¶
{
"Error": false,
"JobId": "a3f9c1e2...",
"Estado": "EnProceso",
"GuiasTotal": 812,
"GuiasProcesadas": 350,
"GuiasPendientes": 462,
"Resueltas": 300,
"Rechazadas": 20,
"YaProcesadas": 30,
"Porcentaje": 43,
"FechaInicio": "2026-08-26T00:24:32Z",
"FechaUltimaActividad": "2026-08-26T00:31:02Z",
"FechaFin": null,
"WorkerDetenido": false,
"MensajeError": null,
"Detalle": null
}
| Código | Cuándo |
|---|---|
401 |
Token inválido, o usuario sin tiendas asignadas |
404 |
El jobId no existe, o existe pero pertenece a otro usuario (ver seguridad más abajo) |
500 |
Excepción no controlada |
Flujo interno¶
EjecutarAsync(peticion, contexto)
identidad = LectorTokenCognito.Leer(headers)
usuario = UsuarioRepository.ObtenerContextoAsync(identidad) el email REAL sale de la BD, no del token
jobId = query["jobId"]
si no hay jobId:
ConsultarJobUseCase.ObtenerAbiertosAsync(usuario.Email)
JobRepository.ObtenerJobsAbiertosAsync filtro Estado in [Pendiente, EnProceso], limit 10
responde 200 con la lista
si hay jobId:
conDetalle = query["detalle"] == "true"
ConsultarJobUseCase.EjecutarAsync(jobId, usuario.Email, conDetalle)
JobRepository.ObtenerAsync(jobId)
valida job.Usuario == usuario.Email (ver seguridad)
Mapear(job, conDetalle) arma la respuesta, incluye WorkerDetenido
responde segun EstadoConsulta: Ok / NoEncontrado / NoAutorizado
WorkerDetenido — cómo se calcula¶
ToleranciaLatido = 2 minutos
enCurso = job.Estado in [Pendiente, EnProceso]
latidoViejo = DateTime.UtcNow - job.FechaUltimaActividad > ToleranciaLatido
WorkerDetenido = enCurso && latidoViejo
El Worker actualiza FechaUltimaActividad en cada lote (ActualizarProgresoAsync). Dos minutos de silencio son mucho más de lo que tarda cualquier lote normal — si pasan y el job sigue abierto, se asume que el Worker murió sin completar ni fallar explícitamente (por ejemplo, un cold start que agotó las 3 continuaciones sin que el código llegara a marcar Fallido, o un corte de red total).
Seguridad: un job solo lo puede ver su dueño¶
Se responde "no encontrado" y no "no autorizado" a propósito: decir "existe pero no es tuyo" ya confirma que ese jobId existe, información que el solicitante no debería tener. Mismo criterio que usa GitHub con los repositorios privados — un 404 no distingue entre "no existe" y "no tienes permiso".
Observaciones¶
- El
Detalle(arrayRechazadas) solo se pide condetalle=true, y el front lo pide una sola vez al terminar el job — pedirlo en cada poll con 3.000 guías serían megabytes por consulta cada 2.5 segundos. - El detalle de las guías resueltas nunca viaja por esta ruta — vive en
InventarioDevolucion, consultado directo por el front víametodoGenerico(veringreso-devoluciones-repository). - Ver
ConsultarJobUseCasepara el mapeo completo del documento a la respuesta.