Observabilidad de errores — diagnóstico y estado deseado¶
Fecha: 2026-07-28
Alcance: backend/src/instrument.ts, backend/src/common/filters/*, backend/src/common/sentry/*, backend/src/common/sap-client/*, backend/src/modules/*/**.service.ts, frontend/src/utils/omsBackend/omsBackend.ts, stack de infraestructura (GlitchTip, logs).
Documento hermano: plan-implementacion.md — mapa técnico de trabajo, fase por fase.
1. Tesis¶
Cuando un usuario reporta un error del OMS, el equipo busca en GlitchTip y no lo encuentra, o encuentra un evento genérico sin contexto. No es un problema de GlitchTip: la instrumentación del backend está bien montada en su esqueleto (SDK inicializado antes de Nest, filtro global, interceptor de scope con usuario y sucursal), pero tres decisiones puntuales hacen que:
- Los errores que los usuarios efectivamente reportan (errores de negocio de SAP, status 4xx) se descartan antes de enviarse a GlitchTip.
- El ID de referencia que ve el usuario no existe como dato buscable en GlitchTip ni en ningún otro lado.
- El contexto rico (payload enviado a SAP, respuesta de SAP, duración) se queda en la consola y el evento de GlitchTip llega desnudo y mal agrupado.
A esto se suma un faltante estructural: no hay logs estructurados ni agregados — no existe forma de reconstruir "todo lo que pasó en el request X".
2. Cómo funciona hoy la cadena de captura¶
Usuario → Frontend (Axios) → Backend NestJS → SAP Service Layer
│
┌────────────────┼──────────────────────┐
│ │ │
SentryScopeInterceptor SapErrorFilter Logger (consola)
(user, branch, (@SentryExceptionCaptured (texto plano,
request_id tag) → GlitchTip, filtrado stdout, sin
por beforeSend) agregación)
- Init:
backend/src/instrument.ts— SDK@sentry/nestjsse inicializa como primera línea demain.ts. Correcto. - Scope:
backend/src/common/sentry/sentry-scope.interceptor.ts— seteauser, tagsbranch,position,request_id. Correcto en intención. - Filtro:
backend/src/common/filters/sap-error.filter.ts— catch-all global; transforma AxiosError de SAP enSapBusinessException, responde JSON seguro conreferenceId. - Cliente SAP:
backend/src/common/sap-client/sap-client.module.ts— interceptores que loguean cada request/response a SAP con duración, y en error el body de respuesta y el body enviado. - Servicios: ~90 catch que envuelven el error de SAP en
BadRequestExceptioncon el mensaje interpolado a mano.
3. Los problemas, con evidencia¶
| # | Problema | Dónde | Consecuencia |
|---|---|---|---|
| P1 | beforeSend descarta todo status < 500. Los servicios envuelven los errores de SAP en BadRequestException (400) y SapBusinessException usa 400 por defecto. |
instrument.ts:24-31, inventory.service.ts:161 (patrón ×90 en modules/), sap-business.exception.ts:44 |
Los errores de negocio de SAP —los que los usuarios reportan— nunca llegan a GlitchTip. Se busca donde el error, por diseño, no está. |
| P2 | Dos IDs de correlación desconectados. El interceptor genera request_id (tag en GlitchTip); el filtro genera un referenceId distinto con Math.random() que es el que ve el usuario en el JSON de error. Ninguno se devuelve como header ni viaja desde el frontend. |
sentry-scope.interceptor.ts:24-25, sap-error.filter.ts:170-172 |
El ID que el usuario podría reportar no es buscable en GlitchTip; el ID buscable nunca se le muestra. No hay hilo reporte → evento → logs. |
| P3 | El evento llega crudo y mal agrupado. @SentryExceptionCaptured() captura el AxiosError original ("Request failed with status code 400"), sin mensaje real de SAP, sin payload, sin fingerprint. GlitchTip agrupa peor que Sentry: todos los errores SAP caen en 1-2 issues genéricos por status code. |
sap-error.filter.ts:37 |
Imposible distinguir "stock insuficiente" de "cliente bloqueado" en el listado de issues. Triaje manual leyendo eventos uno por uno. |
| P4 | El contexto rico muere en stdout. El sap-client loguea el mensaje real de SAP, el body de respuesta y el body enviado — pero vía Logger de Nest (escribe a process.stdout directo; Sentry no lo captura como breadcrumb). |
sap-client.module.ts:216-242 |
La información que resolvería el diagnóstico existe, pero solo en la consola del contenedor, sin búsqueda ni retención. |
| P5 | El wrapping manual pierde la causa. new BadRequestException(msg) sin { cause } descarta stack y error original. Patrón repetido ~90 veces con interpolación manual de error.response?.data?.error?.message?.value. |
inventory.service.ts:159-164 y ~90 sitios más |
Aun si el evento llegara a GlitchTip, no tendría el stack real. Además: 90 lugares para mantener el mismo boilerplate. |
| P6 | Errores tragados en silencio. Catches que solo hacen logger.warn y continúan; schedulers y listeners (Bancard QR, notificaciones) capturan sus propios errores sin reportar a GlitchTip. |
inventory.service.ts:97-103, bancard-qr.cancel.scheduler.ts, notifications/*.listener.ts |
Fallas de background jobs invisibles salvo lectura de consola en vivo. |
| P7 | Sin logs estructurados ni agregación. Logger default de Nest, texto plano, sin request_id automático, retención = vida del contenedor. |
todo el backend | No se puede responder "¿qué pasó en el request abc-123?" ni "¿cuántas veces falló X esta semana?". |
| P8 | Sin alertas. GlitchTip soporta alertas por issue nuevo / umbral; no están configuradas hacia ningún canal del equipo. | infra GlitchTip | El equipo se entera por el usuario, nunca antes. |
| P9 | Frontend y backend no correlacionan. El frontend tiene su propio GlitchTip (instrumentation-client.ts, sentry.server.config.ts) pero no comparte ningún ID con el backend; el interceptor Axios (omsBackend.ts:78) no envía X-Request-Id. |
frontend/src/utils/omsBackend/omsBackend.ts |
Un error visto en el frontend no se puede unir con su contraparte backend. |
4. Estado deseado¶
Principio rector: de cualquier reporte de usuario al diagnóstico completo en menos de 2 minutos, con un solo dato: el ID de referencia.
| Dimensión | Hoy | Deseado |
|---|---|---|
| Cobertura | Solo 5xx y excepciones no controladas llegan a GlitchTip | Todo error que un usuario percibe llega a GlitchTip (errores de negocio SAP como warning, 5xx como error). Se filtra solo ruido real: validación de DTOs, 401/403/404. |
| Correlación | 2 IDs desconectados, ninguno cierra el círculo | Un solo request_id: nace en el frontend (o en el edge), viaja en header X-Request-Id, es tag en ambos GlitchTip (front y back), aparece en el JSON de error, en el header de respuesta, en el toast que ve el usuario, y en cada línea de log del request. |
| Contexto por evento | AxiosError genérico sin payload | Cada evento lleva: mensaje real de SAP, código SAP, endpoint SAP llamado, body enviado (truncado/redactado), body de respuesta, duración, usuario, sucursal. |
| Agrupación | 1-2 issues genéricos por status code | Fingerprint por errorCode + ruta → un issue por tipo de problema real. |
| Causa raíz | Stack perdido al envolver | Cadena de causas preservada ({ cause }); los servicios lanzan SapBusinessException.fromSapError() en vez de formatear a mano. |
| Background jobs | Errores invisibles | Helper captureJobError() en schedulers/listeners con tag job:<nombre>. |
| Logs | Texto plano, efímero | JSON estructurado (pino) con request_id automático, agregado en Grafana Loki, retención ≥ 30 días, buscable por request_id/usuario/endpoint. |
| Alertas | Ninguna | Issue nuevo o pico de frecuencia → notificación al canal del equipo. |
| UX del error | Mensaje + ref que no sirve para buscar | Toast/pantalla de error muestra Ref: <request_id> con botón "copiar detalles". El usuario reporta con el ID; soporte pega el ID en GlitchTip o Loki y ve todo. |
5. Qué NO está en alcance (por ahora)¶
- Tracing distribuido / performance (
tracesSampleRate > 0): se evalúa como segunda etapa, cuando la captura de errores esté sana. La duración de llamadas SAP ya se loguea y pasará a Loki, que cubre el 80 % de la necesidad. - Migrar de GlitchTip a Sentry SaaS: no hace falta. Los problemas son de instrumentación, no de la herramienta. El fingerprint manual compensa la agrupación más débil de GlitchTip.
- Reemplazar el patrón de manejo de errores de los 90 catch de una sola vez: se hace incremental, módulo por módulo, empezando por los de mayor tráfico de reportes (ver plan).
6. Resultado esperado¶
- Un reporte de usuario ("me dio error, ref
abc-123") se resuelve buscando el tagrequest_id:abc-123en GlitchTip → evento con contexto SAP completo → si hace falta más, misma búsqueda en Loki reconstruye el request entero. - El equipo detecta errores nuevos por alerta antes de que lleguen 3 reportes.
- Métrica de éxito propuesta: tiempo mediano de diagnóstico (reporte → causa identificada) y % de reportes con evento encontrado en GlitchTip (hoy ≈ bajo; objetivo > 95 %).