Taxonomía de errores para aplicaciones empresariales: UI y monitoreo coherentes
Una taxonomía de errores para apps empresariales te ayuda a clasificar validación, auth, límites de tasa y fallos de dependencias para que alertas y respuestas de UI sean consistentes.

Qué resuelve una taxonomía de errores en aplicaciones reales de negocio
Una taxonomía de errores es una forma compartida de nombrar y agrupar errores para que todos los manejen de la misma forma. En lugar de que cada pantalla y cada API inventen sus propios mensajes, defines un conjunto pequeño de categorías (como validation o auth) y reglas sobre cómo aparecen ante los usuarios y en el monitoreo.
Sin esa estructura compartida, el mismo problema aparece en formas distintas. Un campo obligatorio faltante puede mostrarse como “Bad Request” en móvil, “Algo salió mal” en web y una traza en los logs. Los usuarios no saben qué hacer, y los equipos on-call pierden tiempo adivinando si es error de usuario, un ataque o una caída.
El objetivo es consistencia: el mismo tipo de error provoca el mismo comportamiento en la UI y el mismo comportamiento de alerta. Los problemas de validación deberían apuntar al campo exacto. Los problemas de permiso deben detener la acción y explicar qué acceso falta. Los fallos de dependencias deben ofrecer un reintento seguro, mientras que el monitoreo genera la alarma correcta.
Un ejemplo realista: un representante de ventas intenta crear un registro de cliente, pero el servicio de pagos está caído. Si tu app devuelve un 500 genérico, volverá a intentarlo y puede crear duplicados después. Con una categoría clara de fallo de dependencia, la UI puede indicar que el servicio está temporalmente fuera, evitar envíos duplicados y el monitoreo puede avisar al equipo correcto.
Este tipo de alineación importa sobre todo cuando un backend alimenta varios clientes. Si la API, la app web, la app móvil y las herramientas internas usan las mismas categorías y códigos, las fallas dejan de parecer aleatorias.
Un modelo simple: categoría, código, mensaje, detalles
Las taxonomías son más fáciles de mantener cuando separas cuatro cosas que a menudo se mezclan: la categoría (qué tipo de problema es), el código (un identificador estable), el mensaje (texto para humanos) y los detalles (contexto estructurado). El status HTTP sigue importando, pero no debería ser toda la historia.
La categoría responde: “¿Cómo debe comportarse la UI y el monitoreo?” Un 403 puede significar “auth” en un sitio, mientras que otro 403 podría ser “policy” si más tarde añades reglas. La categoría trata sobre comportamiento, no transporte.
El código responde: “¿Qué pasó exactamente?” Los códigos deben ser estables y aburridos. Si renombras un botón o refactorizas un servicio, el código no debería cambiar. Dashboards, alertas y scripts de soporte dependen de esto.
El mensaje responde: “¿Qué le decimos a una persona?” Decide para quién es el mensaje. Un mensaje destinado al usuario debe ser corto y amable. Un mensaje para soporte puede incluir pasos siguientes. Los logs pueden ser más técnicos.
Los detalles responden: “¿Qué necesitamos para arreglarlo?” Mantén los detalles estructurados para que la UI pueda reaccionar. Para un error de formulario eso pueden ser nombres de campo. Para un problema de dependencia, puede ser el nombre del servicio upstream y un valor retry-after.
Aquí hay una forma compacta que muchos equipos usan:
{
"category": "validation",
"code": "CUSTOMER_EMAIL_INVALID",
"message": "Enter a valid email address.",
"details": { "field": "email", "rule": "email" }
}
A medida que cambian las funcionalidades, mantén las categorías pequeñas y estables, y añade códigos nuevos en lugar de reutilizar los antiguos. Eso mantiene el comportamiento de la UI, las tendencias del monitoreo y los playbooks de soporte fiables a medida que el producto evoluciona.
Categorías principales: validation, auth, rate limits, dependencies
La mayoría de las aplicaciones empresariales pueden empezar con cuatro categorías que aparecen en todas partes. Si las nombras y las tratas igual en backend, web y móvil, tu UI podrá responder consistentemente y tu monitoreo será legible.
Validation (esperado)
Los errores de validación ocurren cuando una entrada de usuario o una regla de negocio falla. Son normales y deben ser fáciles de arreglar: campos requeridos faltantes, formatos inválidos o reglas como “el descuento no puede superar el 20%” o “el total del pedido debe ser > $0”. La UI debe resaltar el campo o la regla exacta, no mostrar una alerta genérica.
Autenticación vs autorización (esperado)
Los errores de auth suelen dividirse en dos casos: no autenticado (no ha iniciado sesión, sesión expirada, token faltante) y no autorizado (ha iniciado sesión, pero le falta permiso). Trátalos diferente. “Por favor, inicia sesión de nuevo” encaja en el primer caso. Para el segundo, evita revelar detalles sensibles, pero sé claro: “No tienes acceso para aprobar facturas.”
Límites de tasa (esperado, con componente temporal)
Rate limiting significa “demasiadas solicitudes, inténtalo más tarde”. Suele aparecer durante importaciones, dashboards concurridos o reintentos repetidos. Incluye una pista retry-after (aunque sea “espera 30 segundos”), y haz que la UI retroceda en lugar de martillar el servidor.
Fallos de dependencias (a menudo inesperados)
Los fallos de dependencia provienen de servicios upstream, timeouts o caídas: proveedores de pagos, email/SMS, bases de datos o servicios internos. Los usuarios no pueden arreglarlos, así que la UI debería ofrecer una alternativa segura (guardar borrador, intentar más tarde, contactar soporte).
La diferencia clave es el comportamiento: los errores esperados forman parte del flujo normal y merecen feedback preciso; los errores inesperados señalan inestabilidad y deben disparar alertas, correlation IDs y logging cuidadoso.
Paso a paso: construye tu taxonomía en un taller
Una taxonomía debe ser lo suficientemente pequeña como para recordarla, pero estricta suficiente para que dos equipos etiqueten el mismo problema igual.
1) Limita el tiempo y elige un conjunto pequeño
Empieza con un taller de 60 a 90 minutos. Lista los errores que más ves (input incorrecto, problemas de login, demasiadas peticiones, caídas de terceros, bugs inesperados) y luego agrúpalos en 6 a 12 categorías que cualquiera pueda decir en voz alta sin mirar un doc.
2) Acepta un esquema de códigos estable
Elige un patrón de nombres que siga siendo legible en logs y tickets. Manténlo corto, evita números de versión y trata los códigos como permanentes una vez liberados. Un patrón común es prefijo de categoría más un slug claro, como AUTH_INVALID_TOKEN o DEP_PAYMENT_TIMEOUT.
Antes de salir, decide qué debe incluir cada error: categoría, código, mensaje seguro, detalles estructurados y un trace o request ID.
3) Escribe una regla para categoría vs código
Los equipos se atascan cuando las categorías se convierten en un vertedero. Una regla simple ayuda: la categoría responde “¿Cómo deben reaccionar la UI y el monitoreo?”; el código responde “¿Qué pasó exactamente?”. Si dos fallos necesitan distinto comportamiento en la UI, no deberían compartir categoría.
4) Define comportamiento UI por defecto por categoría
Decide qué ven los usuarios por defecto. Validation resalta campos. Auth envía a iniciar sesión o muestra un mensaje de acceso. Rate limits muestran “intenta de nuevo en X segundos”. Los fallos de dependencia muestran una pantalla de reintento calmada. Cuando existen estos comportamientos por defecto, las nuevas funcionalidades pueden seguirlos en lugar de inventar manejos puntuales.
5) Prueba con escenarios reales
Ejecuta cinco flujos comunes (registro, checkout, búsqueda, edición admin, subida de archivos) y etiqueta cada fallo. Si el grupo discute, normalmente necesitas una regla más clara, no veinte códigos más.
Errores de validación: hazlos accionables para usuarios
Validation es el tipo de fallo que normalmente quieres mostrar inmediatamente. Debe ser predecible: le dice al usuario qué corregir, y nunca debe disparar un bucle de reintentos.
Errores a nivel de campo y a nivel de formulario son problemas diferentes. Los errores a nivel de campo se mapean a una sola entrada (email, teléfono, importe). Los errores a nivel de formulario tratan sobre la combinación de entradas (fecha inicio debe ser anterior a fecha fin) o prerequisitos faltantes (no se seleccionó método de envío). Tu respuesta de API debe dejar clara esa diferencia para que la UI reaccione correctamente.
Un fallo común de regla de negocio es “límite de crédito excedido”. El usuario puede haber introducido un número válido, pero la acción no está permitida por el estado de la cuenta. Trata esto como un error de validación a nivel de formulario con una razón clara y una pista segura, por ejemplo: “Tu límite disponible es $500. Reduce el monto o solicita un aumento.” Evita exponer nombres internos como campos de BD, modelos de scoring o pasos del motor de reglas.
Una respuesta accionable suele incluir un código estable (no solo una frase en inglés), un mensaje amigable para el usuario, punteros de campo opcionales para errores de campo y pequeñas pistas seguras (ejemplos de formato, rangos permitidos). Si necesitas un nombre de regla para ingenieros, colócalo en los logs, no en la UI.
Registra los fallos de validación de forma distinta a los errores del sistema. Quieres suficiente contexto para detectar patrones sin almacenar datos sensibles. Registra user ID, request ID, el nombre o código de la regla y qué campos fallaron. Para valores, registra sólo lo necesario (a menudo “presente/faltante” o longitud) y enmascara cualquier dato sensible.
En la UI, céntrate en arreglar, no en reintentar. Resalta campos, conserva lo que el usuario escribió, desplázate al primer error y desactiva reintentos automáticos. Los errores de validación no son temporales, así que “inténtalo de nuevo” es pérdida de tiempo.
Errores de auth y permisos: mantén seguridad y claridad
Los fallos de autenticación y autorización se ven similares para los usuarios, pero significan cosas distintas para seguridad, flujo UI y monitoreo. Separarlos hace que el comportamiento sea consistente en web, móvil y clientes API.
No autenticado significa que la app no puede probar quién es el usuario. Causas típicas: credenciales faltantes, token inválido o sesión expirada. Forbidden significa que se conoce al usuario, pero no tiene permiso para la acción.
La sesión expirada es el caso límite más común. Si soportas refresh tokens, intenta un refresh silencioso una vez y luego reintenta la petición original. Si el refresh falla, devuelve un error de no autenticado y envía al usuario a iniciar sesión. Evita bucles: tras un intento de refresh, para y muestra el siguiente paso claro.
El comportamiento UI debería ser predecible:
- No autenticado: pedir iniciar sesión y preservar lo que el usuario intentaba hacer
- Prohibido: permanecer en la página y mostrar un mensaje de acceso, más una acción segura como “solicitar acceso”
- Cuenta deshabilitada o revocada: cerrar sesión y mostrar un mensaje corto indicando que soporte puede ayudar
Para auditoría, registra lo suficiente para responder “quién intentó qué y por qué fue bloqueado” sin exponer secretos. Un registro útil incluye user ID (si se conoce), tenant o workspace, nombre de la acción, identificador del recurso, timestamp, request ID y el resultado de la comprobación de la política (allowed/denied). Mantén tokens y contraseñas fuera de los logs.
En mensajes para usuarios, no reveles nombres de roles, reglas de permisos o estructura interna de políticas. “No tienes acceso para aprobar facturas” es más seguro que “Solo FinanceAdmin puede aprobar facturas.”
Errores por límites de tasa: comportamiento predecible bajo carga
Los límites de tasa no son bugs. Son una barandilla de seguridad. Trátalos como una categoría de primera clase para que la UI, los logs y las alertas reaccionen consistentemente cuando el tráfico sube.
Los rate limits suelen presentarse en varias formas: por usuario (una persona clicando muy rápido), por IP (muchos usuarios detrás de una red de oficina) o por API key (una integración ejecutándose sin control). La causa importa porque la solución es distinta.
Qué incluye una buena respuesta por rate-limit
Los clientes necesitan dos cosas: que están limitados y cuándo intentar de nuevo. Devuelve HTTP 429 más un tiempo claro de espera (por ejemplo Retry-After: 30). También incluye un código de error estable (como RATE_LIMITED) para que los dashboards agrupen eventos.
Mantén el mensaje calmado y específico. “Too many requests” es técnicamente cierto pero poco útil. “Por favor espera 30 segundos e inténtalo de nuevo” fija expectativas y reduce clics repetidos.
En la UI, evita reintentos rápidos. Un patrón simple es desactivar la acción durante el periodo de espera, mostrar una cuenta regresiva corta y luego ofrecer un reintento seguro cuando termine el temporizador. Evita formulaciones que hagan pensar al usuario que los datos se perdieron.
En monitoreo es donde los equipos suelen sobrerreaccionar. No hagas paging por cada 429. Observa tasas y alerta en picos inusuales: un salto repentino en un endpoint, tenant o API key es accionable.
El backend también debe comportarse de forma predecible. Usa backoff exponencial para reintentos automáticos y haz que los reintentos sean idempotentes. Una acción “Crear factura” no debería crear dos facturas si la primera petición realmente tuvo éxito.
Fallos de dependencias: maneja caídas sin caos
Los fallos de dependencias son aquellos que los usuarios no pueden arreglar con mejor input. El usuario hizo todo correctamente, pero una pasarela de pagos tardó, una conexión a BD cayó o un servicio upstream devolvió 5xx. Trátalos como una categoría separada para que UI y monitoreo actúen de forma predecible.
Empieza por nombrar las formas comunes de fallo: timeout, error de conexión (DNS, TLS, rechazado) y 5xx upstream (bad gateway, service unavailable). Aunque no puedas conocer la causa raíz, puedes capturar qué ocurrió y responder de forma consistente.
Reintentar vs fallar rápido
Los reintentos ayudan en micro-caídas, pero también pueden empeorar una caída general. Usa reglas simples para que todos los equipos tomen la misma decisión.
- Reintentar cuando el error probablemente es temporal: timeouts, resets de conexión, 502/503
- Fallar rápido para casos causados por el usuario o permanentes: 4xx desde la dependencia, credenciales inválidas, recurso faltante
- Limitar reintentos (por ejemplo 2 a 3 intentos) y añadir un pequeño backoff
- Nunca reintentar acciones no idempotentes a menos que tengas una clave de idempotencia
Comportamiento UI y alternativas seguras
Cuando falla una dependencia, indica qué puede hacer el usuario a continuación sin culparlo: “Problema temporal. Inténtalo de nuevo.” Si hay una alternativa segura, ofrécela. Ejemplo: si Stripe está caído, permite guardar el pedido como “Pago pendiente” y enviar un correo de confirmación en lugar de perder el carrito.
También protege a los usuarios de envíos dobles. Si el usuario pulsa “Pagar” dos veces durante una respuesta lenta, tu sistema debe detectarlo. Usa claves de idempotencia para flujos crear-y-cobrar, o comprobaciones de estado como “orden ya pagada” antes de ejecutar la acción de nuevo.
Para monitoreo, registra campos que respondan rápido a una pregunta: “¿Qué dependencia falla y qué tan grave es?” Captura nombre de dependencia, endpoint u operación, duración y resultado final (timeout, connect, upstream 5xx). Esto hace que las alertas y dashboards sean significativos en lugar de ruidosos.
Haz consistente el monitoreo y la UI en todos los canales
Las taxonomías sólo funcionan cuando cada canal habla el mismo idioma: la API, la UI web, la app móvil y tus logs. Si no, el mismo problema aparece como cinco mensajes distintos y nadie sabe si es error de usuario o una caída real.
Trata los códigos de estado HTTP como una capa secundaria. Ayudan con proxies y comportamientos básicos de clientes, pero tu categoría y código deben llevar el significado. Un timeout de dependencia puede seguir siendo un 503, pero la categoría indica a la UI ofrecer “Intentar de nuevo” y al monitoreo hacer paging al on-call.
Haz que cada API devuelva una forma estándar de error, incluso cuando la fuente sea distinta (base de datos, módulo de auth, API de terceros). Una forma simple como esta mantiene el manejo UI y los dashboards consistentes:
{
"category": "dependency",
"code": "PAYMENTS_TIMEOUT",
"message": "Payment service is not responding.",
"details": {"provider": "stripe"},
"correlation_id": "9f2c2c3a-6a2b-4a0a-9e9d-0b0c0c8b2b10"
}
Los correlation IDs son el puente entre “un usuario vio un error” y “podemos rastrearlo”. Muestra el correlation_id en la UI (un botón para copiar ayuda) y siempre regístralo en el backend para poder seguir una petición a través de servicios.
Acordad qué es seguro mostrar en la UI frente a lo que debe quedar sólo en logs. Una separación práctica es: la UI recibe categoría, un mensaje claro y un siguiente paso; los logs reciben detalles técnicos y contexto de la petición; ambos comparten correlation_id y el código de error estable.
Lista rápida para un sistema de errores consistente
La consistencia es aburrida en el mejor sentido: cada canal se comporta igual y el monitoreo dice la verdad.
Revisa primero el backend, incluyendo jobs en background y webhooks. Si cualquier campo es opcional, la gente lo omitirá y la consistencia se rompe.
- Cada error incluye categoría, código estable, mensaje seguro para el usuario y trace ID.
- Los problemas de validación son esperados, por lo que no disparan alertas de paging.
- Los problemas de auth y permisos se rastrean por patrones de seguridad, pero no se tratan como caídas.
- Las respuestas por límites de tasa incluyen una pista de reintento (por ejemplo, segundos a esperar) y no generan alertas masivas.
- Los fallos de dependencia incluyen el nombre de la dependencia más detalles de timeout o estado.
Luego revisa las reglas UI. Cada categoría debe mapear a un comportamiento de pantalla predecible para que los usuarios no tengan que adivinar el siguiente paso: validation resalta campos, auth pide iniciar sesión o muestra acceso, rate limits muestran una espera calmada, dependencies ofrecen reintento y una alternativa cuando sea posible.
Una prueba simple es provocar un error de cada categoría en staging y verificar que obtienes el mismo resultado en la web, la app móvil y el panel admin.
Errores comunes y pasos prácticos siguientes
La forma más rápida de romper un sistema de errores es tratarlo como algo secundario. Equipos distintos terminan usando palabras distintas, códigos distintos y comportamientos UI distintos para el mismo problema. El trabajo de taxonomía rinde cuando se mantiene consistente.
Patrones de fallo comunes:
- Filtrar texto de excepción interno a los usuarios. Confunde y puede exponer datos sensibles.
- Etiquetar todo 4xx como “validation”. Falta de permiso no es lo mismo que campo faltante.
- Inventar códigos nuevos por característica sin revisión. Terminas con 200 códigos que significan las mismas 5 cosas.
- Reintentar los fallos equivocados. Reintentar un error de permiso o un email inválido sólo crea ruido.
Un ejemplo simple: un representante envía un formulario “Crear cliente” y recibe un 403. Si la UI trata todos los 4xx como validación, resaltará campos al azar y pedirá “corrige inputs” en vez de decir que necesita permisos. El monitoreo mostrará entonces un pico en “problemas de validación” cuando el problema real son roles.
Pasos prácticos que caben en un taller corto: escribe un doc de una página con la taxonomía (categorías, cuándo usarlas, 5 a 10 códigos canónicos), define reglas de mensaje (qué ve el usuario vs qué va a logs), añade una puerta ligera de revisión para nuevos códigos, establece reglas de reintento por categoría y luego implementa end-to-end (respuesta backend, mapeo UI y dashboards de monitoreo).
Si estás construyendo con AppMaster (appmaster.io), ayuda centralizar estas reglas en un único sitio para que el mismo comportamiento de categoría y código se aplique en backend, web y apps móviles nativas.
FAQ
Empieza cuando el mismo backend atiende a más de un cliente (web, móvil, herramientas internas) o cuando soporte y on-call siguen preguntando “¿es esto error del usuario o del sistema?”. Una taxonomía rinde frutos rápido si tienes flujos repetidos como registro, pago, importaciones o ediciones administrativas donde el manejo consistente importa.
Un buen punto de partida son 6–12 categorías que la gente pueda recordar sin consultar docs. Mantén las categorías estables y amplias (por ejemplo validation, auth, rate_limit, dependency, conflict, internal) y expresa la situación específica con un código, no con una nueva categoría.
La categoría marca el comportamiento; el código identifica la situación exacta. La categoría le dice a la UI y al monitoreo qué hacer (resaltar campos, pedir inicio de sesión, moderar peticiones, ofrecer reintento), mientras que el código se mantiene estable para dashboards, alertas y scripts de soporte aunque cambie el texto visible.
Trata los mensajes como contenido, no como identificadores. Devuelve un mensaje corto y seguro para el usuario en la UI, y usa el código estable para agrupar y automatizar. Si necesitas texto técnico, déjalo en los logs y asócialo al mismo correlation ID.
Incluye categoría, código estable, un mensaje seguro para usuarios, detalles estructurados y un correlation o request ID. Los detalles deben permitir que el cliente actúe, por ejemplo qué campo falló o cuánto esperar, sin volcar el stack trace.
Devuelve punteros a nivel de campo cuando sea posible para que la UI resalte la entrada exacta y conserve lo que el usuario escribió. Usa un error a nivel de formulario cuando el problema sea la combinación de entradas o una regla de negocio (por ejemplo límites de crédito), para que la UI no adivine el campo equivocado.
No autenticado significa que la app no puede probar quién es el usuario; lo lógico es enviarlo a iniciar sesión y preservar su tarea. Prohibido (forbidden) significa que está autenticado pero sin permiso; la UI debería permanecer en la página y mostrar un mensaje de acceso sin revelar reglas internas.
Devuelve un tiempo explícito de espera (por ejemplo un valor retry-after) y mantiene el código estable para que los clientes apliquen backoff consistentemente. En la UI, desactiva clics repetidos y muestra el siguiente paso claro, porque los reintentos automáticos suelen empeorar los límites de tasa.
Reintenta sólo cuando el fallo probablemente sea temporal (timeouts, resets de conexión, 502/503) y limita los reintentos con un pequeño backoff. Para acciones no idempotentes, exige una clave de idempotencia o una comprobación de estado; de lo contrario un reintento puede crear duplicados si el primer intento realmente tuvo éxito.
Muestra el correlation ID al usuario (para que soporte se lo pida) y siempre regístralo en el servidor junto al código y detalles clave. Esto permite rastrear una falla a través de servicios y clientes; en proyectos con AppMaster, centralizar esta forma ayuda a alinear backend, web y móvil nativo.


