Manejo de errores y explicación UX
1) Por qué es importante
El error no es «texto rojo», sino una continuación de la secuencia de comandos. Buenos errores UX:- explica qué pasó y qué hacer después,
- almacena los datos introducidos y evita que se pierda progreso,
- da una repetición segura o una ruta alternativa,
- permanece disponible (SR/teclado) y no revela ningún exceso.
2) Tipología de errores (para la interfaz)
1. Validación de datos (cliente 4xx): campos vacíos/incorrectos, formato, longitud, conflicto de reglas.
2. Reglas de negocio: límites, restricciones geográficas, KYC/KYB, dobles, ranuras inaccesibles.
3. Derechos/permisiones: función, acceso al recurso, limitaciones de edad.
4. Red/servidor: tiempo de espera, fuera de línea, 5xx, sobrecarga, límite de velocidad.
5. Conflictos/estado: 409/412 (los datos han cambiado), carreras, bloqueos.
6. Sin recurso: 404/410, eliminado/migrado.
7. Pago y riesgo: desviación bancaria/PSP, antifraude, límites de juego responsable.
3) Canales y nivel de visualización
Seleccionamos el «volumen» bajo contexto:Regla: no esconda lo crítico en tostadas/hover. Donde el usuario está mirando, también hay un mensaje.
4) Redacción de errores
Estructura: causa → consecuencia → acción.
Tono: honesto, neutral, sin culpa.
Especificación: especifique el campo/condición, evite códigos y pilas.
Botón de acción: Repetir, Cambiar mapa, Restablecer filtros, Abrir chat.
Datos sensibles: no mostrar (enmascaramiento PAN, atributos personales).
Bien: "El pago no ha pasado: el banco ha rechazado la operación. Pruebe otra manera o repita más tarde".
Mal: "Error 500. Algo salió mal".
Bien: "Se ha alcanzado el límite de gasto diario. Establezca un nuevo límite o inténtelo mañana".
Bien: "El archivo es demasiado grande (máx. 25 MB). Comprime o descarga varios archivos".
5) Comportamiento y enfoque (A11y)
El error se muestra en el contexto focal: ponemos el foco en el primer campo erróneo.
Regiones vivas: 'role =' status '(polite) para info,' role = 'alert' (assertive) - para críticos.
Visible ': focus-visible', contraste ≥ AA, alternativas de color (icono/texto).
El mensaje se enlaza al campo a través de 'aria-describedby'.
html
<label for = "pwd "> Password </label>
<input id="pwd" name="password" aria-describedby="pwd-err" aria-invalid="true">
<p id = "pwd-err" role = "alert"> Minimum 8 characters </p>
6) Retraídas, retroceso e idempotencia
La repetición se ofrece si hay alguna posibilidad de éxito (fallas de red, 5xx, límite de velocidad).
Backoff exponencial 1-2-4-8 s, límite de intento, botón claro «Repetir».
Operaciones críticas (apuestas/pagos): Idempotency-Key obligatorio → excluye las tomas.
Retroceder las actualizaciones optimistas - retorno visual claro + explicación.
js async function retry(fn, attempts=3){
let wait=1000; for(let i=0; i<attempts; i++){
try{ return await fn(); }catch(e){ if(i===attempts-1) throw e; await new Promise(r=>setTimeout(r,wait)); wait=2; }
}
}
7) Offline, temporizadores y contenido parcial
Fuera de línea: muestra el banner «Sin conexión», acceso a caché (sólo lectura), cola de sincronización.
Timauts: UI-Timaut (3-5 s) → estado «Esperamos confirmación»... con repetición/cancelación segura.
Éxito parcial: mantenemos lo que hemos logrado; etiquetamos «no sincronizado».
8) Conflictos y competencia
409/412: datos obsoletos. Sugerir «Actualizar» y mostrar el diff (que ha cambiado).
Bloqueos: le informamos quién mantiene el bloque, y cuánto tiempo, el botón «Solicitar acceso».
9) Plantillas de UI de muestra
Banner de página:html
<div class="banner banner--error" role="alert">
<strong> Connection failed. </strong> Shows cached data.
<button class =" btn btn--ghost" id = "retry "> Retry </button>
</div>
Modal de error crítico:
html
<div role="alertdialog" aria-labelledby="err-title" aria-describedby="err-desc">
<h2 id = "err-title "> Session expired </h2>
<p id = "err-desc "> Sign in again to continue. </p>
<button class = "btn "> Sign in </button>
<button class =" btn btn--ghost"> Home </button>
</div>
Aprox ErrorBoundary (con ID de correlación):
tsx function Fallback({ id, onRetry }: { id: string; onRetry: ()=>void }) {
return (
<div role="alert" className="banner banner--error">
<strong> We couldn't load the page. </strong>
<div> Try again. Код: <code>{id}</code> <button onClick={()=>navigator. clipboard. writeText (id)}> Copy </button> </div>
<button onClick = {onRetry}> Retry </button>
</div>
);
}
10) Tokens de error (sistema de diseño)
json
{
"error": {
"tones": { "danger": "#", "warning": "#", "info": "#" },
"aria": { "polite": true, "assertive": true },
"timing": { "toastMs": 3500, "retryBackoffMs": [1000,2000,4000] },
"layout": { "fieldGap": 8, "bannerIcon": 20 }
}
}
Presets CSS:
css
.banner--error { background: var(--bg-danger); color: var(--on-danger); padding: 12px 16px; border-radius: 12px; }
.field-error { color: var(--role-danger); margin-top: 6px; font-size:.875rem; }
11) Seguridad y privacidad
No sacamos stack-tracks, ID interno, rutas de BD.
Enmascaramos valores sensibles (mapas, documentos).
Los mensajes no deben indicar al atacante (por ejemplo, que la cuenta existe).
Para soporte - ID de correlación en lugar de piezas.
json
{"level":"error","event":"payment_fail","correlation_id":"c-8f1...","user_id":"u-","route":"/pay","psp_code":"DO_NOT_EXPOSE_TO_USER"}
12) Métricas y controles
INP y una fracción de Long Tasks en el momento del error (el error no debe «colgar» IU).
Tasa de éxito retráctil, errores en 1000 acciones, tiempo de recuperación.
CTR en «Ayuda/Chat», porcentaje de formularios abandonados.
Tarjetas térmicas: donde los errores de campo ocurren con mayor frecuencia.
13) Lista de comprobación de QA
Disponibilidad
- Foco en el primer lanzamiento erróneo; 'aria-describedby '/' aria-invalid' se exponen.
- Mensajes críticos - 'role =' alert '; contraste ≥ AA.
- Los datos del formulario no se pierden en caso de error.
- Hay un 'Retry' comprensible y un backoff correcto.
- El modo offline/caché funciona; la pancarta se ve.
Copyright
- Causa → acción; sin jerga técnica ni cargos.
- Los textos se localizan y no rompen la cuadrícula.
- No hay filtración de PII/secretos; sólo mostramos códigos/ID seguros.
- La idempotencia está habilitada para operaciones críticas.
14) Especificidad de iGaming
Apuesta:- IU fija inmediatamente 'busy'; con un retraso> 3 s - «Esperamos confirmación»....
- Con fail: estado honesto («mercado cerrado», «coeficiente cambiado») + 'Retry' seguro.
- Una clave idempotente para eliminar la apuesta doble.
- Distinguimos entre «fallo del banco/PSP» vs «error del servidor». Para el primero, «Elige otra manera», para el segundo, «Retry».
- Pasos transparentes KYC/AML; Referencias "¿Por qué es necesario? ».
- El tono es cariñoso, sin presión. «Se ha alcanzado el límite - haga una pausa o actualice el límite».
- Sin brotes/neón; contraste AAA, disponibilidad con SR.
- Explique claramente las limitaciones y sugiera «Leer las reglas/soporte».
15) Anti-patrones
«Algo salió mal» sin acción y contexto.
Restablece el formulario después del error.
Esconder lo crítico en un brindis durante 3 segundos.
Sólo color sin texto/iconos.
Retratos infinitos sin posibilidad de cancelación.
Mostrar códigos internos/stack-tracks.
16) Documentación en el sistema de diseño
Компоненты: `FieldError`, `FormError`, `PageBanner`, `AlertDialog`, `ErrorBoundary`.
Fichas de tono/contraste/tiempo, presets a11y y ejemplos ARIA.
Mapa de scripts tipo (validación, red, derechos, pagos) con plantillas de texto.
"Do/Don 't': pantallas reales antes/después con métricas de fallas/aciertos.
Resumen breve
Haga que los errores sean comprensibles y manejables: hable un lenguaje humano, guarde los datos introducidos, ofrezca una repetición y alternativas seguras, respete la accesibilidad y la privacidad. Entonces, incluso las situaciones no estándar mantienen la confianza y no interrumpen el camino del usuario - especialmente en escenarios críticos de apuestas y pagos.