Buscar en la documentación

Cliente HTTP y errores

Cómo la SPA consume la API, maneja el token y convierte cualquier error en un mensaje claro.

Módulo shared5 min de lecturaVer en GitHub

Toda la comunicación con la API pasa por un único cliente (shared/http). Ninguna vista lo usa directamente: cada módulo lo envuelve en sus repositorios.

Qué hace en cada petición

sequenceDiagram
  participant M as Repositorio del módulo
  participant C as Cliente HTTP
  participant A as API
  M->>C: petición
  C->>C: agrega Authorization (si hay sesión) y X-Request-Id
  C->>A: envía
  A-->>C: respuesta o error RFC 9457
  alt error
    C->>C: lo convierte en ApiError
    C->>C: UNAUTHENTICATED → borra el token y avisa "session-expired"
    C->>C: 503 → avisa "maintenance"
    C-->>M: lanza ApiError
  else éxito
    C-->>M: datos
  end
AspectoComportamiento
Base/api/v1, mismo origen que la SPA (configurable con VITE_API_BASE_URL)
TokenAuthorization: Bearer …, guardado en memoria y en sessionStorage; nunca en localStorage
Identificador de peticiónX-Request-Id generado en el navegador, para cruzar un error con los logs
Conexión de tiempo realX-Socket-ID de la pestaña (si está conectada), para que la API no le avise de sus propios cambios (socketId.ts)
Tiempo de espera15 segundos
ReintentosSolo lecturas (GET) ante fallas de red, 502 o 504; hasta 2 reintentos. Una escritura nunca se repite.

ApiError

Cualquier falla llega a la app con la misma forma, sin importar si vino de la API, de la red o de un tiempo de espera:

CampoContenido
kindhttp, network, timeout o canceled
codeCódigo de la API (VALIDATION_FAILED…) o del cliente (NETWORK_ERROR, TIMEOUT, REQUEST_CANCELED, SERVER_UNREACHABLE, SCREEN_LOAD_FAILED, UNKNOWN_ERROR)
statusEstado HTTP, si hubo respuesta
paramsDatos para el mensaje
fieldErrorsErrores por campo
traceIdCódigo para soporte
retryAfterSegundos de espera en un 429 o un 423 (cabecera Retry-After)

Si la respuesta no trae el formato de error de la API (por ejemplo, una página HTML del gateway porque la API está caída), el código sale del estado: 502 y 504 dan SERVER_UNREACHABLE y 503 da SERVICE_UNAVAILABLE. Así la persona ve un mensaje claro en lugar de uno genérico.

Descargas. Una petición con responseType: 'blob' (la exportación) recibe también el error como archivo. Antes de armar el ApiError, el cliente lee ese archivo si es JSON, y así se conserva el código de la API (EXPORT_TOO_LARGE, TOO_MANY_REQUESTS…). El archivo bueno se guarda con saveFile (shared/download/saveFile.ts), con el nombre que propone el servidor en Content-Disposition (filenameFrom). Con el token en la cabecera no sirve un enlace directo, y un token en la URL quedaría en el historial y en los logs.

Modo mantenimiento. Un 503 emite maintenance y muestra “Trazo está en mantenimiento”. Quien puede seguir trabajando recibe la cabecera X-Maintenance en cada respuesta: el cliente la refleja en maintenanceNotice (shared/http/maintenance.ts) y el shell muestra el aviso mientras siga llegando (Módulo de sistema).

Pantalla que no se descarga. Cada pantalla se descarga al abrirla por primera vez. Si esa descarga falla, isScreenLoadError lo reconoce por el mensaje de cada navegador. Lo normal es que haya una versión nueva desplegada (los archivos de la anterior ya no existen) o que se haya cortado la conexión. Pasan dos cosas:

  • installScreenLoadNotice (app/router/screenLoadNotice.ts), instalado en App.vue, escucha router.onError y muestra un aviso de error con Recargar, que abre la pantalla que se buscaba con la versión nueva. Cubre también los enlaces del menú, que de otro modo descartarían el error en silencio.
  • toApiError lo convierte en SCREEN_LOAD_FAILED, así las pantallas que esperan una navegación (el login, por ejemplo) muestran el mismo mensaje claro en vez del genérico.

Reautenticación. needsCode(error) dice si la API pidió el código de la aplicación (REAUTHENTICATION_REQUIRED). useReauth (shared/ui/feedback) abre el diálogo del código y reintenta con él; lo usan la administración y el sistema.

Sesión vencida

Solo el código UNAUTHENTICATED cierra la sesión en el navegador. Un 401 con otro código, como INVALID_CREDENTIALS en el login, lo maneja la pantalla que hizo la petición. Si no fuera así, una contraseña mal escrita se trataría como una sesión vencida.

Catálogo de mensajes

shared/errors convierte códigos en texto claro, sin tecnicismos:

  • messageFor(error): mensaje general (notificación o banner).
  • fieldMessageFor(fieldError) / fieldMessagesFor(error): mensaje junto a cada campo del formulario.
  • registerErrorMessages({...}): registra códigos de negocio. Cada módulo los declara en modules/<módulo>/errors.ts y el arranque de la app los registra solos.

Las plantillas pueden usar {traceId} (código de soporte) y {wait}, la espera de Retry-After en texto natural (“30 segundos”, “5 minutos”, “1 hora”). Cualquier otro parámetro numérico se muestra con separador de miles (“50.000”).

Los mensajes de campo no repiten el nombre del campo porque se muestran a su lado. Un código desconocido nunca deja la pantalla en blanco: muestra un mensaje útil con el código de soporte (UNKNOWN_ERROR_TRACED). Si la falla no trae código de soporte (no llegó al servidor), cualquier mensaje que lo pida se cambia por UNKNOWN_ERROR, que no lo menciona: pedir un código que no existe no le sirve a nadie. La lista completa de mensajes está en el catálogo de errores.

Listados ligados a la URL

useRouteList es la base de todo listado: los criterios salen de la URL, cada cambio la actualiza y eso dispara la consulta. Cancela la consulta anterior si llega otra, descarta respuestas viejas, y cambiar un filtro vuelve a la primera página. Cada módulo le pasa:

  • fromQuery y toQuery: cómo se leen y se escriben sus criterios. Se apoyan en shared/routing/queryValues.ts: queryOne, queryList, queryPositive, queryChoice y compactQuery, que ignoran lo que no reconocen y dejan la URL corta.
  • fetch: la llamada de su repositorio.
  • ready (opcional): cuándo todavía no consultar, por ejemplo una búsqueda incompleta.

La usan los listados de incidentes y de usuarios.

useCursorList es la variante para listas que crecen sin fin, como la auditoría. Los criterios también salen de la URL y cada cambio vuelve a empezar. La diferencia es que no hay páginas: “cargar más” pide la siguiente tanda con el cursor que entregó la API y la agrega al final. El cursor no va en la URL, así que una recarga vuelve al principio. Recibe fromQuery, toQuery y un fetch que, además de los criterios, recibe el cursor.

Revisiones visuales compartidas

shared/validation/email.ts (looksLikeEmail) es la única revisión de la forma de un correo antes de enviarlo: la usan el login, la recuperación de contraseña y la administración. Solo evita un viaje innecesario; la API valida de verdad.

Dónde está en el código