|
| 1 | +# ADR-0066: Contrato de Errores Accionables para Usuarios y Diagnostico Correlacionado |
| 2 | + |
| 3 | +**Estado:** Aceptado |
| 4 | +**Fecha:** 2026-05-26 |
| 5 | +**Responsable:** Arquitectura |
| 6 | +**Disposicion Evolith:** Propuesto para adopcion como estandar universal de Evolith |
| 7 | +**Relacionados:** |
| 8 | +- [ADR-0053: Observabilidad con OpenTelemetry](./0053-opentelemetry-observability.md) |
| 9 | +- [ADR-0055: Patron Hibrido GraphQL/REST](./0055-graphql-rest-hybrid-api.md) |
| 10 | +- [ADR-0057: Estado con Zustand y TanStack Query](./0057-zustand-tanstack-query-state.md) |
| 11 | +- [ADR-0061: Execution Context Accessor](./0061-execution-context-accessor.es.md) |
| 12 | +- [ADR-0062: Configuracion Serilog Segura de PII](./0062-pii-safe-serilog-configuration.es.md) |
| 13 | +- [Arquitectura de Notificaciones y Feedback](../blueprints-es/notification-feedback-architecture.md) |
| 14 | + |
| 15 | +--- |
| 16 | + |
| 17 | +## Contexto |
| 18 | + |
| 19 | +Una revision de calidad del flujo de registro de modulos de System Suite revelo dos modos de falla opuestos: |
| 20 | + |
| 21 | +1. Mensajes tecnicos del API, detalles de excepcion y datos relacionados con la pila podian llegar al navegador. |
| 22 | +2. Al suprimir todos los mensajes del backend, las fallas esperadas de validacion quedaron demasiado genericas para que el usuario corrigiera la entrada. |
| 23 | + |
| 24 | +Por ejemplo, un usuario que envia una descripcion de modulo mayor al limite permitido debe saber como corregir sus datos. Ese usuario no debe ver un namespace, nombre de clase, detalle de base de datos, mensaje de excepcion ni stack trace. El equipo de soporte si necesita una referencia que localice el evento tecnico completo en Serilog y Grafana Loki. |
| 25 | + |
| 26 | +La decision aplica a todos los comandos iniciados por usuarios en UMS, no solo al registro de modulos. |
| 27 | + |
| 28 | +## Decision |
| 29 | + |
| 30 | +UMS adopta un contrato de errores con dos canales: |
| 31 | + |
| 32 | +1. **Canal de feedback para usuario:** expone solo informacion de negocio o validacion aprobada y accionable. |
| 33 | +2. **Canal de diagnostico:** conserva detalles tecnicos en logs estructurados y telemetria, correlacionados mediante un identificador de error generado por el servidor. |
| 34 | + |
| 35 | +### 1. Clasificacion de Errores |
| 36 | + |
| 37 | +| Clase de error | Ejemplo | Contenido visible para usuario | Logging tecnico | |
| 38 | +|---|---|---|---| |
| 39 | +| Falla de validacion | Longitud maxima excedida | Correccion accionable y codigo de seguimiento | Evento estructurado opcional | |
| 40 | +| Conflicto de negocio | Codigo de modulo duplicado | Razon segura de negocio y codigo de seguimiento | Evento estructurado cuando aporte valor operativo | |
| 41 | +| Autorizacion o autenticacion | Acceso rechazado | Declaracion segura de acceso y codigo de seguimiento | Evento estructurado con tratamiento de seguridad | |
| 42 | +| Infraestructura o falla inesperada | Base de datos, resolver, excepcion no controlada | Guia generica de reintento y codigo de seguimiento | Excepcion completa sanitizada con correlacion | |
| 43 | + |
| 44 | +### 2. Payload Publico de Error |
| 45 | + |
| 46 | +Las fallas de comandos REST usan Problem Details RFC 7807. El payload publico puede incluir: |
| 47 | + |
| 48 | +```json |
| 49 | +{ |
| 50 | + "type": "https://httpstatuses.io/422", |
| 51 | + "title": "Validation Error", |
| 52 | + "status": 422, |
| 53 | + "detail": "No se pudo completar la operacion porque existen campos invalidos.", |
| 54 | + "userMessage": "No se pudo registrar el modulo porque el campo Codigo tiene un formato invalido. Use solo letras, numeros y guion bajo, por ejemplo REPORTS_01. Valor ingresado: DDDD-!.", |
| 55 | + "errorCode": "validation.invalid_format", |
| 56 | + "messageKey": "system_suite.module.code_invalid_format", |
| 57 | + "messageParameters": { "invalidValue": "DDDD-!" }, |
| 58 | + "errorId": "0cd26dd6-d50e-4b3c-a662-8098a87569a4", |
| 59 | + "traceId": "<distributed-trace-id>" |
| 60 | +} |
| 61 | +``` |
| 62 | + |
| 63 | +Reglas: |
| 64 | + |
| 65 | +- `errorId` es un GUID generado por el servidor una sola vez por solicitud fallida. Es obligatorio en la respuesta, el header (`X-Error-Id`) y el evento Serilog correspondiente. |
| 66 | +- `traceId` y `X-Correlation-Id` permanecen como identificadores de trazabilidad distribuida y no reemplazan a `errorId`. |
| 67 | +- `userMessage`, cuando esta presente, es explicitamente seguro para presentacion. |
| 68 | +- `errorCode`, `messageKey` y `messageParameters` son el contrato objetivo para clientes localizados. |
| 69 | +- `detail` nunca debe contener stack traces, tipos de excepcion, namespaces, rutas de codigo fuente, sentencias SQL, tokens, secretos ni PII. |
| 70 | +- Los clientes no deben mostrar `detail` arbitrario, mensajes GraphQL crudos ni mensajes nativos de excepcion. Solo muestran campos aprobados o un fallback local. |
| 71 | + |
| 72 | +La implementacion actual de UMS produce `userMessage` mediante recursos de localizacion del API seleccionados por `X-Language` o `Accept-Language`. El soporte estable de `messageKey` sigue siendo la evolucion para clientes mas ricos. |
| 73 | + |
| 74 | +### 3. Limite GraphQL |
| 75 | + |
| 76 | +GraphQL se usa para lecturas segun ADR-0055. Los errores GraphQL expuestos al cliente usan mensajes genericos seguros localizados y transportan `errorId`. Las excepciones de resolvers se registran mediante Serilog con el mismo `errorId`, pero no se serializan al navegador. |
| 77 | + |
| 78 | +### 4. Observabilidad y Logging |
| 79 | + |
| 80 | +- Cada respuesta REST o GraphQL fallida obtiene un nuevo GUID `errorId` generado por el servidor. |
| 81 | +- Serilog registra `ErrorId` para fallas esperadas y excepciones sanitizadas completas con `ErrorId` para fallas inesperadas; Loki consulta esa propiedad. |
| 82 | +- `CorrelationId` y `TraceId` continuan enlazando operaciones distribuidas independientemente del `ErrorId` visible para soporte. |
| 83 | +- Las reglas de logging seguro de PII de ADR-0062 aplican antes de cualquier sink. |
| 84 | +- Un especialista de soporte usa el codigo visible para localizar el evento detallado del backend. |
| 85 | + |
| 86 | +### 5. Ciclo de Vida de la Notificacion |
| 87 | + |
| 88 | +El feedback para usuario se entrega mediante el mecanismo centralizado de notificaciones: |
| 89 | + |
| 90 | +- El feedback de validacion y negocio es accionable y puede incluir el codigo de seguimiento. |
| 91 | +- La expiracion automatica del toast elimina solo la presentacion efimera; el historial puede permanecer disponible. |
| 92 | +- El descarte manual mediante el control de cierre es una accion explicita del usuario y elimina la entrada correspondiente del estado activo de notificaciones. |
| 93 | + |
| 94 | +### 6. Ejemplos Requeridos de Mensaje |
| 95 | + |
| 96 | +| Categoria | Ejemplo seguro visible para usuario | |
| 97 | +|---|---| |
| 98 | +| Validacion | `No se pudo registrar el modulo porque el campo Codigo tiene un formato invalido. Use solo letras, numeros y guion bajo, por ejemplo REPORTS_01. Valor ingresado: DDDD-!.` | |
| 99 | +| Regla de negocio | `No se pudo registrar el modulo porque ya existe otro modulo con el mismo Codigo. Ingrese un codigo diferente.` | |
| 100 | +| Autorizacion | `No se pudo completar la operacion porque no tiene permisos suficientes. Solicite acceso al administrador.` | |
| 101 | +| Inesperado | `No se pudo completar la operacion debido a un error inesperado. Intente nuevamente mas tarde.` | |
| 102 | + |
| 103 | +Cada falla mostrada agrega: `Si necesitas mas detalles, consulta con el administrador e indica este ID de error: {errorId}.` |
| 104 | + |
| 105 | +## Alternativas Rechazadas |
| 106 | + |
| 107 | +### Mostrar todos los valores `detail` del backend |
| 108 | + |
| 109 | +Rechazada. Es conveniente, pero permite que detalles de implementacion o PII escapen cuando un endpoint esta mal configurado. |
| 110 | + |
| 111 | +### Mostrar solo mensajes genericos |
| 112 | + |
| 113 | +Rechazada. Impide que los usuarios corrijan condiciones esperadas de negocio o validacion, aumentando reintentos y solicitudes de soporte. |
| 114 | + |
| 115 | +### Mostrar detalles de pila solo en desarrollo o QA |
| 116 | + |
| 117 | +Rechazada. QA y ambientes compartidos siguen siendo superficies de usuario y pueden contener datos similares a produccion. |
| 118 | + |
| 119 | +## Consecuencias |
| 120 | + |
| 121 | +### Positivas |
| 122 | + |
| 123 | +- Los usuarios reciben informacion suficiente para corregir fallas esperadas y seguras. |
| 124 | +- Los diagnosticos tecnicos permanecen disponibles para soporte sin exponerse en la interfaz. |
| 125 | +- REST, GraphQL, logging y notificaciones frontend comparten un contrato auditable. |
| 126 | +- El contrato puede evolucionar desde texto `userMessage` servido por backend hacia claves de mensaje localizadas en cliente. |
| 127 | + |
| 128 | +### Compromisos |
| 129 | + |
| 130 | +- Los limites del backend deben clasificar que fallas son seguras para publicar. |
| 131 | +- Nuevas reglas de validacion requieren texto seguro de presentacion o metadata de localizacion. |
| 132 | +- Las pruebas deben cubrir tanto salida accionable como ausencia de filtracion tecnica. |
| 133 | + |
| 134 | +## Mapeo de Implementacion |
| 135 | + |
| 136 | +| Preocupacion | Implementacion UMS | |
| 137 | +|---|---| |
| 138 | +| Mapeo seguro REST | `Ums.Presentation/Extensions/ResultExtensions.cs` | |
| 139 | +| Generacion de Error ID | `Ums.Presentation/Extensions/UserFacingErrorContext.cs` | |
| 140 | +| Excepciones no controladas | `Ums.Presentation/Middleware/GlobalExceptionHandler.cs` | |
| 141 | +| Sanitizacion GraphQL | `Ums.Presentation/GraphQL/SafeGraphQlErrorFilter.cs` | |
| 142 | +| Recursos de localizacion | `Ums.Globalization/Resources/{language}/domain-errors.json` | |
| 143 | +| Extraccion segura web | `src/application/errors/http-error.ts` | |
| 144 | +| Composicion de notificacion | `src/application/hooks/use-notified-mutation.ts` | |
| 145 | +| Descarte manual | `src/application/stores/notification.store.ts` y `src/presentation/shared/components/ToastQueue.tsx` | |
| 146 | + |
| 147 | +## Requisitos de Verificacion |
| 148 | + |
| 149 | +- Las pruebas de integracion afirman campos de validacion accionables, `errorId` generado por servidor, header `X-Error-Id` coincidente y `traceId`. |
| 150 | +- Las pruebas de seguridad afirman que stack traces, nombres de excepcion y mensajes internos crudos estan ausentes. |
| 151 | +- Las pruebas frontend afirman que solo mensajes aprobados y codigos de seguimiento se renderizan. |
| 152 | +- Las pruebas de notificaciones afirman que el cierre manual elimina la notificacion seleccionada. |
| 153 | + |
| 154 | +--- |
| 155 | + |
| 156 | +**[Registro ADR](./index.md)** | **[Blueprint de Notificaciones y Feedback](../blueprints-es/notification-feedback-architecture.md)** |
0 commit comments