362 lines
6.5 KiB
Markdown
362 lines
6.5 KiB
Markdown
|
|
# Tratamiento de errores HTTP en Frontend ERP
|
||
|
|
|
||
|
|
## 1. Objetivo
|
||
|
|
|
||
|
|
Cualquier error que cruce `IDataSource` desde el frontend ERP queda normalizado como `ApiClientError`.
|
||
|
|
|
||
|
|
Esto evita que los módulos consumidores dependan de `AxiosError` o inspeccionen `response.data` directamente.
|
||
|
|
|
||
|
|
## 2. Flujo
|
||
|
|
|
||
|
|
```txt
|
||
|
|
Axios
|
||
|
|
→ interceptor
|
||
|
|
→ IDataSource
|
||
|
|
→ normalizeAxiosError
|
||
|
|
→ ApiClientError
|
||
|
|
→ TanStack Query
|
||
|
|
→ controller
|
||
|
|
→ UI
|
||
|
|
```
|
||
|
|
|
||
|
|
## 3. Responsabilidades
|
||
|
|
|
||
|
|
```txt
|
||
|
|
setupInterceptors
|
||
|
|
→ auth, headers y refresh
|
||
|
|
|
||
|
|
normalizeAxiosError
|
||
|
|
→ normalización HTTP/transporte
|
||
|
|
|
||
|
|
ApiClientError
|
||
|
|
→ contrato transversal
|
||
|
|
|
||
|
|
TanStack Query
|
||
|
|
→ transporte del error al consumidor
|
||
|
|
|
||
|
|
Controller/Page Controller
|
||
|
|
→ decisión UX
|
||
|
|
|
||
|
|
UI
|
||
|
|
→ render
|
||
|
|
```
|
||
|
|
|
||
|
|
## 4. Clasificación
|
||
|
|
|
||
|
|
| Caso | kind |
|
||
|
|
| ---- | ---- |
|
||
|
|
| 400 | `badRequest` |
|
||
|
|
| 401 | `unauthorized` |
|
||
|
|
| 403 | `forbidden` |
|
||
|
|
| 404 | `notFound` |
|
||
|
|
| 409 | `conflict` |
|
||
|
|
| 422 | `validation` |
|
||
|
|
| 429 | `rateLimited` |
|
||
|
|
| 500-599 | `server` / `unavailable` |
|
||
|
|
| red | `network` |
|
||
|
|
| timeout | `timeout` |
|
||
|
|
| cancelación | `cancelled` |
|
||
|
|
| resto | `unknown` |
|
||
|
|
|
||
|
|
## 5. Problem Details
|
||
|
|
|
||
|
|
El frontend soporta Problem Details parciales y completos.
|
||
|
|
|
||
|
|
Campos reconocidos:
|
||
|
|
|
||
|
|
```txt
|
||
|
|
type
|
||
|
|
title
|
||
|
|
status
|
||
|
|
detail
|
||
|
|
instance
|
||
|
|
method
|
||
|
|
correlationId
|
||
|
|
code
|
||
|
|
source
|
||
|
|
errors
|
||
|
|
```
|
||
|
|
|
||
|
|
`errors` puede estar ausente, vacío o contener elementos parciales válidos.
|
||
|
|
|
||
|
|
## 6. Mensajes
|
||
|
|
|
||
|
|
```txt
|
||
|
|
ApiClientError.message
|
||
|
|
→ diagnóstico técnico estable
|
||
|
|
|
||
|
|
getUserErrorMessage
|
||
|
|
→ mensaje seguro genérico
|
||
|
|
|
||
|
|
controller/page controller
|
||
|
|
→ mensaje específico del caso de uso
|
||
|
|
```
|
||
|
|
|
||
|
|
`ApiClientError.message` no debe tratarse como texto UX por defecto.
|
||
|
|
|
||
|
|
## 7. Reglas para nuevos módulos
|
||
|
|
|
||
|
|
```txt
|
||
|
|
No comprobar AxiosError en controllers.
|
||
|
|
No leer error.response.data en UI.
|
||
|
|
No normalizar errores dentro de hooks.
|
||
|
|
No mostrar problem.detail automáticamente.
|
||
|
|
No crear clases de error HTTP por módulo.
|
||
|
|
No usar error.message como mensaje UX por defecto.
|
||
|
|
```
|
||
|
|
|
||
|
|
## 8. Ejemplo de consumo
|
||
|
|
|
||
|
|
```ts
|
||
|
|
if (isApiClientError(error) && error.kind === "notFound") {
|
||
|
|
// El controller decide el estado específico del recurso.
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## 9. Niveles de error
|
||
|
|
|
||
|
|
La presentación distingue cuatro niveles:
|
||
|
|
|
||
|
|
```txt
|
||
|
|
1. Error de validación/formulario
|
||
|
|
2. Error inline o de mutation
|
||
|
|
3. Error bloqueante de página
|
||
|
|
4. Error inesperado de routing/render
|
||
|
|
```
|
||
|
|
|
||
|
|
Regla crítica:
|
||
|
|
|
||
|
|
```txt
|
||
|
|
PageErrorState
|
||
|
|
no conoce Axios
|
||
|
|
no conoce TanStack Query
|
||
|
|
no conoce React Router
|
||
|
|
no conoce ErrorBoundary
|
||
|
|
```
|
||
|
|
|
||
|
|
## 10. Decisiones no adoptadas
|
||
|
|
|
||
|
|
En esta fase no se introduce:
|
||
|
|
|
||
|
|
```txt
|
||
|
|
ErrorProvider global
|
||
|
|
toasts globales automáticos
|
||
|
|
framework de errores no HTTP
|
||
|
|
registro dinámico de errores
|
||
|
|
```
|
||
|
|
|
||
|
|
## 11. Estados visuales bloqueantes
|
||
|
|
|
||
|
|
Para errores que impiden renderizar una página completa, la UI debe usar un view model explícito y un componente dedicado de página.
|
||
|
|
|
||
|
|
```txt
|
||
|
|
ApiClientError
|
||
|
|
→ builder específico del caso de uso
|
||
|
|
→ PageErrorViewModel
|
||
|
|
→ Page Controller
|
||
|
|
→ PageErrorState
|
||
|
|
```
|
||
|
|
|
||
|
|
Reglas:
|
||
|
|
|
||
|
|
```txt
|
||
|
|
ErrorAlert
|
||
|
|
→ error inline y no bloqueante
|
||
|
|
|
||
|
|
PageErrorState
|
||
|
|
→ error que impide renderizar la página
|
||
|
|
|
||
|
|
componentes locales
|
||
|
|
→ errores embebidos en una subárea, como PDFViewer
|
||
|
|
```
|
||
|
|
|
||
|
|
`NotFoundCard` queda retirado. Su sustitución es:
|
||
|
|
|
||
|
|
```txt
|
||
|
|
404 real
|
||
|
|
→ PageErrorState variant="notFound"
|
||
|
|
|
||
|
|
success sin entidad válida
|
||
|
|
→ PageErrorState variant="unknown"
|
||
|
|
|
||
|
|
colección vacía
|
||
|
|
→ empty state
|
||
|
|
```
|
||
|
|
|
||
|
|
## 12. Listados paginados
|
||
|
|
|
||
|
|
En listados con `placeholderData`, el estado visual debe distinguir entre fallo bloqueante y error de refetch:
|
||
|
|
|
||
|
|
```txt
|
||
|
|
error inicial + sin data utilizable
|
||
|
|
→ PageErrorState
|
||
|
|
|
||
|
|
error durante refetch + data previa utilizable
|
||
|
|
→ mantener tabla
|
||
|
|
→ ErrorAlert inline
|
||
|
|
```
|
||
|
|
|
||
|
|
Reglas aplicadas en proformas, customers e issued-invoices:
|
||
|
|
|
||
|
|
```txt
|
||
|
|
cancelled
|
||
|
|
→ sin feedback visual
|
||
|
|
|
||
|
|
notFound en listados
|
||
|
|
→ no se interpreta como empty
|
||
|
|
→ se trata como error remoto anómalo
|
||
|
|
→ mapeado a unknown en esta fase
|
||
|
|
```
|
||
|
|
|
||
|
|
## 13. Detalle y edición de recursos
|
||
|
|
|
||
|
|
En pantallas de detalle o edición de una entidad:
|
||
|
|
|
||
|
|
```txt
|
||
|
|
ApiClientError.kind === notFound
|
||
|
|
→ ausencia real del recurso
|
||
|
|
→ PageErrorState específico de notFound
|
||
|
|
|
||
|
|
respuesta exitosa sin entidad válida
|
||
|
|
→ inconsistencia del contrato
|
||
|
|
→ variant unknown
|
||
|
|
|
||
|
|
cancelled
|
||
|
|
→ no renderiza pantalla bloqueante
|
||
|
|
```
|
||
|
|
|
||
|
|
La decisión no debe derivarse de `!entity` a secas. El page controller debe distinguir entre:
|
||
|
|
|
||
|
|
```txt
|
||
|
|
error HTTP real
|
||
|
|
inconsistencia de datos
|
||
|
|
cancelación
|
||
|
|
```
|
||
|
|
|
||
|
|
El patrón se aplica ya en:
|
||
|
|
|
||
|
|
```txt
|
||
|
|
proformas/detail
|
||
|
|
proformas/list
|
||
|
|
proformas/update
|
||
|
|
customers/list
|
||
|
|
customers/view
|
||
|
|
customers/update
|
||
|
|
issued-invoices/list
|
||
|
|
```
|
||
|
|
|
||
|
|
## 14. Queries HTTP
|
||
|
|
|
||
|
|
Para queries normales, el flujo estable es:
|
||
|
|
|
||
|
|
```txt
|
||
|
|
ApiClientError
|
||
|
|
→ builder específico del caso de uso
|
||
|
|
→ PageErrorState / ErrorAlert
|
||
|
|
```
|
||
|
|
|
||
|
|
Reglas:
|
||
|
|
|
||
|
|
```txt
|
||
|
|
error inicial sin datos
|
||
|
|
→ PageErrorState
|
||
|
|
|
||
|
|
error de refetch con datos previos
|
||
|
|
→ conservar contenido
|
||
|
|
→ ErrorAlert inline
|
||
|
|
|
||
|
|
mutation error
|
||
|
|
→ ErrorAlert o feedback local
|
||
|
|
|
||
|
|
cancelled
|
||
|
|
→ sin pantalla de error
|
||
|
|
```
|
||
|
|
|
||
|
|
En `issued-invoices` solo existe actualmente el listado principal y la descarga de PDF. No hay detalle, create ni update cableados en web; por eso la migración de esta fase cubre:
|
||
|
|
|
||
|
|
```txt
|
||
|
|
issued-invoices/list
|
||
|
|
→ PageErrorState para fallo bloqueante
|
||
|
|
→ ErrorAlert para refetch con datos previos
|
||
|
|
|
||
|
|
issued-invoices/download-pdf
|
||
|
|
→ feedback local mediante toast
|
||
|
|
→ no promueve el error a pantalla completa
|
||
|
|
```
|
||
|
|
|
||
|
|
## 15. Routing
|
||
|
|
|
||
|
|
Los errores del router no se interpretan como `ApiClientError`.
|
||
|
|
|
||
|
|
Flujo:
|
||
|
|
|
||
|
|
```txt
|
||
|
|
Route error
|
||
|
|
→ clasificador de app
|
||
|
|
→ RouteErrorPage
|
||
|
|
→ PageErrorState
|
||
|
|
```
|
||
|
|
|
||
|
|
Cobertura actual:
|
||
|
|
|
||
|
|
```txt
|
||
|
|
404
|
||
|
|
403
|
||
|
|
500
|
||
|
|
503
|
||
|
|
unknown
|
||
|
|
```
|
||
|
|
|
||
|
|
La clasificación vive en `apps/web` y usa `errorElement` en las ramas del router que montan shell autenticado, fullscreen y auth. Las rutas placeholder antiguas (`dashboard`, `settings`, `catalog`, `quotes`) pasan a reutilizar la misma base visual de `PageErrorState`.
|
||
|
|
|
||
|
|
## 16. Render inesperado
|
||
|
|
|
||
|
|
Los errores inesperados de render de React quedan separados de Query y Routing.
|
||
|
|
|
||
|
|
Flujo:
|
||
|
|
|
||
|
|
```txt
|
||
|
|
React render error
|
||
|
|
→ ErrorBoundary
|
||
|
|
→ RenderErrorFallback
|
||
|
|
→ PageErrorState
|
||
|
|
```
|
||
|
|
|
||
|
|
Reglas:
|
||
|
|
|
||
|
|
```txt
|
||
|
|
resetErrorBoundary()
|
||
|
|
→ retry real del boundary
|
||
|
|
|
||
|
|
Ir al inicio
|
||
|
|
→ acción explícita
|
||
|
|
|
||
|
|
no stack
|
||
|
|
no error.message
|
||
|
|
no problem.detail
|
||
|
|
```
|
||
|
|
|
||
|
|
## 17. Legacy
|
||
|
|
|
||
|
|
Estado final de piezas legacy relacionadas:
|
||
|
|
|
||
|
|
```txt
|
||
|
|
ErrorPage
|
||
|
|
→ eliminado y sustituido por RouteErrorPage / StaticRouteErrorPage
|
||
|
|
|
||
|
|
error-fallbacks
|
||
|
|
→ eliminado
|
||
|
|
|
||
|
|
ErrorOverlay
|
||
|
|
→ eliminado por ausencia de consumidores
|
||
|
|
|
||
|
|
NotFoundCard
|
||
|
|
→ eliminado globalmente
|
||
|
|
|
||
|
|
ErrorState
|
||
|
|
→ se mantiene como compatibilidad sobre PageErrorState
|
||
|
|
|
||
|
|
ErrorAlert
|
||
|
|
→ se mantiene para feedback inline no destructivo
|
||
|
|
```
|