Códigos de error consistentes: una pequeña taxonomía y registros más seguros
Aprende a diseñar códigos de error consistentes, mapear excepciones a mensajes seguros para usuarios y registrar lo suficiente para depurar sin exponer secretos.

Por qué los errores lanzados causan caos para usuarios y equipos
Cuando cada parte de una app lanza su propio error, la gente recibe mensajes distintos para el mismo problema. Un fallo de inicio de sesión puede mostrar “Algo salió mal” en móvil, “401” en la web y una traza roja en una pantalla de administración. El usuario no aprende nada útil y tu bandeja de soporte se llena de capturas que no coinciden.
Esa inconsistencia es la razón por la que los usuarios reportan problemas como “se rompió” o “no funcionó”. No pueden saber si escribieron mal la contraseña, perdieron conexión o hay una caída temporal. Sin una señal estable que compartir, adivinan, reintentan o se van.
Los ingenieros lo notan también. Si los errores se lanzan y se muestran tal cual, los registros se vuelven ruidosos: texto distinto para la misma causa raíz, contexto faltante y ninguna forma limpia de agrupar incidentes. Un bug puede parecer 20 problemas distintos, así que tarda más encontrar patrones, reproducir el fallo y confirmar una solución.
El texto de error en bruto también crea riesgo de seguridad. Las excepciones suelen incluir detalles que nunca deberías mostrar a usuarios (o almacenar en registros sin cifrar): claves secretas, cadenas de conexión, rutas internas de archivos, fragmentos SQL o datos de usuarios. Un error no manejado puede filtrar más de lo que esperas.
Un sistema simple de códigos de error consistentes arregla esto. Los usuarios ven un mensaje claro y seguro más un código corto que pueden compartir. Tu equipo ve registros estructurados vinculados al mismo código, de modo que la depuración se acelera sin exponer datos sensibles.
Lo que vas a crear: códigos, mensajes y registros útiles
Estás creando un contrato simple para las fallas:
- Un código estable que identifica qué pasó
- Un mensaje seguro para el usuario que explica qué hacer a continuación
- Registros que te ayudan a arreglar la causa raíz
Un buen código de error no es una frase. Es un identificador que permanece igual aunque reescribas el mensaje, cambies frameworks o refactorices toda la funcionalidad. Esa estabilidad es lo que hace útiles a los códigos para soporte, paneles y reportes de bugs.
Mantén las capas separadas. Los usuarios reciben lenguaje claro y pasos a seguir. Los ingenieros obtienen contexto detallado en los registros. Esa separación también reduce el riesgo de filtraciones porque el mensaje al usuario nunca incluye secretos, trazas, errores SQL o IDs internas.
Ejemplo: si el inicio de sesión falla porque la base de datos está caída, el usuario debería ver “No podemos iniciar sesión ahora. Inténtalo de nuevo en unos minutos.” El código podría ser AUTH.SERVICE_UNAVAILABLE. Los registros capturan el timeout de la base de datos, la dependencia que falló y un request ID.
Para una herramienta interna pequeña, los errores lanzados simples pueden bastar. Una vez que tengas usuarios reales, tickets de soporte o requisitos de cumplimiento, esta estructura paga rápido.
Empieza con una pequeña taxonomía de errores que puedas mantener
Una buena taxonomía es aburrida a propósito. Si no puedes recordarla sin consultar un doc, es demasiado grande. El objetivo son códigos de error consistentes que indiquen qué tipo de problema ocurrió sin exponer cómo está organizado tu código.
Agrupa por impacto para el usuario, no por capas internas. “DB” es útil porque suele significar “intenta más tarde”. “RepositoryError” es solo la estructura de carpetas filtrando en tu API.
Aquí tienes un conjunto inicial simple (6 grupos) que cubre la mayoría de apps:
| Grupo | Qué pertenece aquí (una frase) | ¿Reintentable? |
|---|---|---|
| AUTH | Problemas de inicio de sesión/sesión como credenciales inválidas, tokens expirados o falta de autenticación. | A veces (refresh de token), a menudo No |
| VALIDATION | La petición está mal: campos faltantes, formatos incorrectos, valores fuera de rango. | No |
| PAYMENT | Cobros, estado de suscripción o rechazos del proveedor de facturación. | A veces (timeouts), a menudo No |
| DB | Base de datos no disponible, timeouts, deadlocks o migraciones fallidas. | A menudo Sí |
| INTEGRATION | Un servicio de terceros falló (email, SMS, mapas, webhooks). | A menudo Sí |
| INTERNAL | Cualquier cosa inesperada que deba investigarse (bugs, nulls, estados imposibles). | A veces, por defecto No |
Dos reglas mantienen esto manejable:
- Cada grupo necesita una frase clara de “pertenece aquí si...”, para que la gente no debata casos límite durante una hora.
- Decide el comportamiento de reintento a nivel de grupo como valor por defecto, y anula solo cuando sea necesario.
Si un usuario hace clic en “Guardar” y la app no puede alcanzar la base de datos, eso es DB (reintentable) incluso si el error vino de una librería de acceso a datos. Mientras tanto, “el email no tiene @” es siempre VALIDATION (no reintentable), aunque se haya detectado en lo profundo del código.
Diseña códigos de error que se mantengan estables en el tiempo
Los códigos solo ayudan si significan lo mismo la semana que viene que hoy. Trátalos como una API pública: mejora el mensaje y la solución, pero mantén el código estable una vez lanzado.
Elige un formato legible y lo bastante corto para pegar en chats de soporte:
- Usa
AREA-NNN(ejemplos:AUTH-001,DB-003,PAY-012) - Haz que el prefijo coincida con tu taxonomía (AUTH, DB, FILE, RATE, PERM)
- Reserva rangos numéricos si tienes subsistemas grandes (AUTH-100+ para OAuth, AUTH-200+ para sesiones)
- Nunca reutilices números, aunque el problema esté “resuelto”
- Mantén los códigos en una única fuente de verdad (un archivo o tabla en el repo)
Decide qué debe mantenerse estable entre versiones: el código, su significado (una frase) y la categoría. Lo que puede cambiar: el texto visible para el usuario, los pasos sugeridos y los detalles internos que registras.
También separa el código del correlation ID. El usuario ve AUTH-001. Tus registros obtienen correlation_id=8f3c... más traza y contexto. Soporte puede pedir el código y el correlation ID sin exponer internos sensibles.
Mapea fallos internos a mensajes seguros para el usuario
Los usuarios no necesitan saber qué se rompió dentro de tu app. Necesitan saber qué pasó, qué hacer después y cómo pedir ayuda si se quedan atascados.
Un mensaje seguro para el usuario normalmente necesita tres partes:
- Qué pasó (breve)
- Qué hacer a continuación
- Qué compartir con soporte (el código)
Ejemplo:
“Error en inicio de sesión. Por favor intenta de nuevo o restablece tu contraseña. Comparte este código con soporte: AUTH-002.”
Mantén el texto del usuario separado de los detalles para desarrolladores. El mensaje al usuario nunca debe incluir trazas, nombres de tablas, rutas internas, claves API, correos ni tokens en bruto. Esos detalles pertenecen solo a los registros y, aun ahí, deberían ser saneados.
Un ejemplo realista: tu base de datos lanza UniqueViolation porque un email ya existe. Internamente eso puede incluir detalles SQL. Externamente, devuelve: “Ese correo ya está en uso. Intenta iniciar sesión en su lugar. Comparte este código con soporte: ACC-001.” Registra el tipo de excepción y el request ID, pero no guardes el email completo.
Mantén registros útiles sin filtrar datos
Los registros son para ti, no para tus usuarios. El objetivo es simple: cuando algo falla, puedas encontrar la petición exacta, ver qué falló y arreglarlo rápido, sin almacenar contraseñas, tokens o datos de clientes en el proceso.
Un buen predeterminado es registrar tres anclas cada vez: el código de error, un correlation ID y la ubicación (servicio, módulo, ruta o handler). Eso convierte una traza aleatoria en algo que puedes buscar y agrupar.
Una lista de verificación simple que funciona para la mayoría de apps:
- Registra siempre:
error_code,correlation_id, ruta de la petición y dónde ocurrió (endpoint más función/módulo). - Registra identidad de forma segura: un ID interno de usuario suele ser suficiente; evita emails, nombres o IPs completas salvo que sea necesario.
- Redacta o hashea campos sensibles: contraseñas, tokens, claves API, cookies, encabezados de auth y secretos de variables de entorno.
- Usa niveles de logs con intención: WARN para fallos esperados (entrada inválida, auth denegado), ERROR para caídas inesperadas.
- Decide retención y acceso: conserva logs solo el tiempo necesario y limita quién puede verlos.
Un patrón común de fallo es el “debugging útil” que imprime peticiones completas o variables de entorno. Eso puede filtrar secretos en logs y backups. Si heredaste código así, arregla el logging primero. Reduce el riesgo de inmediato, incluso antes de resolver cada bug.
Paso a paso: implementar errores consistentes en tu app
Empieza eligiendo una única forma de error que todas las partes de la app puedan devolver. Esto es lo que hace posibles los códigos consistentes aun cuando las fallas vengan de librerías distintas.
Una forma simple y práctica se ve así:
{
"code": "AUTH_INVALID_CREDENTIALS",
"message": "Email or password is incorrect.",
"status": 401,
"correlationId": "b3f1d2..."
}
Luego impleméntalo en pasos pequeños:
- Crea un
AppError(o similar) y un helper para construirlo. Hazcodeystatusobligatorios, y manténmessageseguro para el usuario. - Añade un handler global que capture excepciones no controladas (middleware del servidor, handler del API gateway o boundary de la app). Debe devolver siempre la misma forma.
- Centraliza el mapeo de excepciones en un solo lugar. Ejemplo: mapea un error de restricción única de la base de datos a
CONFLICT_EMAIL_TAKENen vez de dejar que el texto en bruto se filtre. - Estandariza códigos de estado HTTP para que los clientes reaccionen de forma predecible.
- Añade tests que afirmen tanto el
codecomo elmessage, no solo el status.
Para códigos de estado, mantén el conjunto pequeño y predecible:
- 400 para entrada inválida
- 401 para no autenticado o credenciales incorrectas
- 403 para autenticado pero no autorizado
- 404 para recursos inexistentes
- 409 para conflictos (ya existe)
Un escenario realista: un endpoint de login lanza “Cannot read properties of undefined” porque falta el body de la petición. Con un mapper, eso se convierte en INPUT_MISSING_FIELDS con un 400 y un mensaje claro, mientras los logs guardan la traza ligada a un correlationId.
Fuentes comunes de error y cómo clasificarlas
La mayoría de apps fallan en los mismos pocos lugares. Si nombras esos lugares y los manejas de la misma forma cada vez, obtienes tickets de soporte más claros y menos mensajes de “se rompió”.
Una regla útil: clasifica por lo que el usuario puede hacer después, no por el texto exacto de la excepción. Dos trazas pueden verse distintas pero significar lo mismo: “vuelve a iniciar sesión” o “intenta más tarde”.
Auth y permisos deben separar “necesita iniciar sesión” de “acceso denegado”, para que la UI sepa si debe pedir login o mostrar un mensaje de permisos. Input y validación deben devolver códigos que el usuario pueda arreglar y, cuando sea posible, apuntar al campo, sin exponer detalles del servidor.
Las fallas de base de datos y almacenamiento deben separar “no encontrado” de “consulta fallida”, porque llevan a acciones distintas (mostrar un estado vacío vs reintentar más tarde). Los servicios externos deben clasificar si reintentar es seguro ahora, más tarde o nunca. Archivos y subidas deben ser lo bastante específicos para que el usuario elija otro archivo, pero nunca deben volcar el contenido del archivo en los logs.
Errores comunes que empeoran el manejo de errores
La mayoría de manejos de error fracasan porque los equipos optimizan por “hacer que funcione” en vez de “mantenerlo soportable”. Unas pocas prácticas convierten silenciosamente bugs pequeños en horas de adivinanza y pueden filtrar datos sensibles.
Errores que dañan soporte y confianza
Usar el mensaje humano como “código” es una trampa común. Los mensajes cambian cuando reescribes copy, traduces o añades detalle. Soporte no puede buscar de forma confiable y los clientes no pueden construir comportamientos estables.
Otra trampa es empezar con un catálogo enorme. Si creas 200+ códigos el primer día, nadie los mantiene y la gente inventa nuevos al azar. Un conjunto pequeño y claro que reutilices vence a una lista larga que ignoras.
El texto de excepción en bruto nunca debe llegar a usuarios o clientes de API. A menudo contiene trazas, nombres de tablas, rutas de archivos o pistas que ayudan a un atacante.
Registrar cuerpos de petición completos es otro riesgo evitable. Es una forma fácil de capturar contraseñas, tokens, claves API o datos personales. Los logs deben ayudarte a depurar sin convertirse en una brecha de datos.
Por último, no mezcles tipos de fallo distintos. Tratar “no encontrado” como “error de servidor” oculta problemas reales de fiabilidad y hace que las alertas sean ruidosas.
Lista rápida antes de enviar a producción
Antes del release, haz una pasada que revise la experiencia de usuario, el contrato de la API y lo que verá tu equipo a las 2 a.m.
- Vista del usuario: cada fallo muestra un mensaje claro más un paso siguiente (intentar de nuevo, iniciar sesión de nuevo, contactar soporte). No hay trazas crudas.
- Vista de la API: cada respuesta de error incluye un código de error estable y un correlation ID que también aparece en los logs del servidor.
- Seguridad en logs: los registros no contienen secretos (claves API), tokens, contraseñas, códigos de un solo uso ni datos completos de tarjeta de pago. Si debes registrar un identificador, enmáscalo.
- Comportamiento de reintento: los fallos reintentables están claramente marcados y se manejan con suavidad (backoff corto, reintentos limitados y un mensaje claro de “intenta de nuevo”).
- Flujo de soporte: alguien debe ser capaz de diagnosticar el problema usando solo el código de error y el correlation ID, sin pedir al usuario que pegue la salida de herramientas de desarrollador.
Una prueba simple: provoca tres fallos comunes (contraseña incorrecta, sesión expirada, timeout del servidor). Si el usuario sabe qué hacer y tu equipo puede encontrar la línea de log exacta con el correlation ID, estás listo.
Un ejemplo realista: convertir un crash de login desordenado en un código claro
Un problema común de prototipo: un flujo de login funciona el lunes, luego llega una actualización rápida el martes y de pronto todos quedan bloqueados. Algunos usuarios ven “Algo salió mal”, otros una página en blanco y tu equipo ve una traza que menciona un error de base de datos y una librería de auth en la misma respiración.
Con códigos de error consistentes, decides qué significa esa falla para usuarios y soporte. Aquí, la app falla al crear una sesión después de validar credenciales. La clasificas como un problema de autenticación y la mapeas a AUTH-003 (por ejemplo: “Fallo en la creación de sesión”).
Lo que ve el usuario cuando ocurre AUTH-003:
- “No hemos podido iniciar sesión. Por favor intenta de nuevo en un minuto.”
- “Si sigue ocurriendo, contacta a soporte y comparte este código: AUTH-003 y ID: 7F2K9.”
Ese mensaje es honesto, corto y seguro. No menciona tablas, tokens, providers u otros internos.
Mientras tanto, tus logs guardan los detalles, pero solo lo que puedes almacenar con seguridad: el código, el correlation ID, la ruta de la petición, la versión del build y la causa interna raíz (por ejemplo, “DB timeout al escribir la fila de sesión”). Los campos sensibles deben ser redactados (email hasheado, IP truncada, sin contraseñas, sin tokens completos).
Ahora soporte puede priorizar rápido. Un usuario envía “AUTH-003, 7F2K9” y tu equipo puede extraer una única traza completa en vez de adivinar.
Próximos pasos para limpiar una base de código existente
Empieza con evidencia. Extrae tus logs de las últimas 1–2 semanas y los tickets de soporte más comunes, luego lista los 20 mensajes de error más frecuentes que realmente ven los usuarios. Normalmente verás repeticiones: timeouts, fallos de auth, registros faltantes y crashes genéricos de “Algo salió mal”.
Añade estructura en pequeñas porciones. Elige un endpoint de alto tráfico o una pantalla (login, checkout, subida de archivos) e introduce códigos de error consistentes ahí primero. Una vez estable, expande al siguiente segmento.
Un plan de rollout que funciona en equipos reales:
- Agrupa los errores principales en 5 a 8 categorías (auth, validation, dependency, permissions, not found, internal).
- Añade una capa de mapeo que convierta excepciones lanzadas en
{code, userMessage, logContext}. - Mantén un doc pequeño para los códigos: qué significan, quién los posee y cuándo se puede añadir uno nuevo.
- Añade tests para el mapeo (un test por código es suficiente al principio).
- Revisa logs para confirmar que mantienes contexto útil mientras eliminas secretos y datos personales.
Si heredaste una base de código generada por IA, espera lanzamientos de excepciones impredecibles, formas de error mezcladas y problemas ocultos de seguridad (como secretos en logs o errores de base de datos filtrándose en respuestas). En esa situación, estabilizar el boundary de errores y limpiar el logging suele dar la ganancia más rápida.
Si quieres una segunda opinión sobre un código generado por IA que filtra errores o produce logs desordenados, FixMyMess (fixmymess.ai) se centra en diagnosticar y reparar problemas como auth roto, logging riesgoso y arquitectura enmarañada. Su auditoría de código gratuita puede ayudarte a identificar los puntos de fallo más grandes antes de comprometerte con cambios.
Preguntas Frecuentes
¿Por qué los errores lanzados son tan inconsistentes en toda la app?
Los errores lanzados suelen variar según el dispositivo, el framework y la ruta de código, por lo que la misma causa raíz puede aparecer con mensajes totalmente distintos. Los códigos de error estables te dan una etiqueta compartida para soporte, analítica y depuración, incluso cuando el texto visible para el usuario cambia.
¿Con cuántas categorías de códigos de error debo empezar?
Empieza con un conjunto pequeño que puedas recordar y aplicar, normalmente 5–8 grupos. Agrupa por lo que el usuario puede hacer después (corregir la entrada, volver a iniciar sesión, intentar más tarde) en lugar de por capas internas como “repository” o “service”.
¿Cuál es un buen formato para códigos de error que no falle con el tiempo?
Usa un formato legible y estable como AUTH-001 o DB-003 y mantiene el significado de cada código fijo una vez que se publique. Puedes cambiar el texto del mensaje con el tiempo, pero no reutilices códigos para nuevos significados.
¿El mensaje visible para el usuario debe ser el mismo que el código de error?
No. El código es un identificador, no una frase; debe permanecer igual aunque reescribas el texto de la UI o traduzcas la app. Pon la explicación amigable para humanos en el campo de mensaje y mantenla segura para los usuarios.
¿Qué debe incluir como mínimo una respuesta de error de la API?
Mantén el código estable, el mensaje claro y evita exponer detalles internos. Un buen valor predeterminado es devolver una explicación corta, qué hacer a continuación y el código que el usuario puede compartir con soporte.
¿Dónde debo mapear excepciones a códigos y mensajes?
Mapea las excepciones internas a mensajes seguros para el usuario en un único lugar central, como un handler global de errores o middleware. Ahí puedes convertir un timeout de base de datos en un código consistente y un mensaje simple de “intenta de nuevo”, mientras que la traza se guarda solo en los registros.
¿Qué debo registrar para que la depuración sea rápida pero los datos se mantengan seguros?
Registra un código de error y un correlation ID, y añade justo el contexto suficiente para reproducir el problema sin capturar secretos ni datos personales. Evita registrar contraseñas, tokens, claves API y cuerpos completos de petición, incluso al depurar.
¿Cómo decido si un error debe ser reintentable?
Usa el comportamiento predeterminado de la categoría: los errores de validación normalmente no son reintentables, y las caídas de dependencias (base de datos o servicios externos) a menudo sí. Si reintentas, hazlo con suavidad y límite para no saturar al usuario ni al servicio.
¿Por qué es un problema tratar 404 y 500 igual?
Mezclarlos empeora tanto la experiencia de usuario como las operaciones: “no encontrado” suele ser un resultado normal mientras que “error interno” es un problema de fiabilidad. Dales códigos y estados distintos para que la UI muestre el estado correcto y las alertas sean significativas.
¿Cuál es la forma más rápida de limpiar esto en un prototipo generado por IA?
Si heredaste código generado por IA, empieza añadiendo un boundary global de errores y estandarizando la forma de error, luego limpia los registros para evitar filtraciones. Si quieres ayuda, FixMyMess (fixmymess.ai) puede ejecutar una auditoría gratuita de código y normalmente dejar prototipos rotos listos para producción en 48–72 horas.