Arquitectura y diseño
1. El patrón dominante: RSC + hidratación con SWR
Casi todas las pantallas siguen el mismo esquema de tres capas, que conviene entender antes de tocar cualquier página:
flowchart LR
P["page.tsx (RSC)<br/>Suspense + loading.tsx"] -->|await action| A["actions/*<br/>fetchAuth → robo-api"]
P -->|initialData| T["Tabla/formulario (cliente)"]
T -->|"useSWR(fallbackData)"| H["hooks/*"]
H -->|fetch| R["app/api/* (route handler)"]
R -->|reutiliza| A
page.tsx(Server Component) llama directamente a una función deactions/y pasa el resultado comoinitialData. Va envuelto en<Suspense>con suloading.tsx, así la navegación muestra un skeleton inmediato.- El componente de cliente monta un hook de
hooks/que haceuseSWR(..., { fallbackData: initialData }). El primer render no pide nada: usa lo que ya vino del servidor. - A partir de ahí SWR revalida (
revalidateOnFocus: trueen casi todos los hooks) llamando a un route handler deapp/api/, que a su vez reutiliza la misma función deactions/.
Es decir: la misma llamada a robo-api está disponible por dos caminos — server-side en el primer render y vía /api en las revalidaciones y mutaciones.
En el detalle de una devolución, la carga es aún más agresiva: getReturn(id) primero (necesario para conocer el país del pedido) y después Promise.allSettled de métodos, estados, estados de línea, orígenes y almacenes, de forma que un catálogo caído degrada esa parte de la UI a un array vacío en lugar de tumbar la página.
2. Capa de acceso a datos (actions/)
Una carpeta por dominio, una función por operación. Todas comparten el mismo patrón:
const response = await fetchAuth(`${process.env.ROBO_API_URL}/return-method/info`, { method: "GET" });
if (!response.ok) throw new Error(DEFAULT_ERROR_MESSAGE);
return await response.json();
| Carpeta | Operaciones |
|---|---|
actions/returns/ | get-returns (filtrado y paginado), get-return, edit, delete |
actions/methods/ | get-methods, get-method, search-methods, new, edit, delete |
actions/reasons/ | get-reasons, get-reason, new, edit, delete |
actions/users/ | get-users, get-user, new, edit |
actions/warehouses/ | get-warehouses, edit |
actions/statuses/ | get-statuses, get-line-statuses |
actions/origins/, actions/sources/, actions/payment-methods/, actions/logs/ | catálogos e historial |
actions/getUserRoles.ts | Único "use server" real: lee los roles del ID token |
Dos de ellas ordenan en cliente lo que la API no ordena: getWarehouses por name y getSources por cd_source_type.
3. Route handlers (app/api/)
Unos 30 handlers, casi todos envoltorios finos de la acción equivalente (try/catch + Response.json). Existen para que SWR pueda llamarlos desde el navegador. Los que hacen algo más que envolver:
| Ruta | Particularidad |
|---|---|
/api/returns/getReturns | Traduce pageIndex, pageSize y columnFilters (JSON) a los parámetros de robo-api |
/api/export/csv/returns | Genera el CSV paginando el listado completo (ver Gestión de devoluciones) |
/api/get-order | Recupera el pedido y asigna un crypto.randomUUID() a cada línea |
/api/set-return | Crea la devolución añadiendo customer: <sub de Auth0> del empleado |
/api/get-pdf-url | Firma la URL del PDF (HMAC) |
/api/trigger-journey | Publica el evento en Marketing Cloud según la acción |
/api/token | Devuelve el access token de la sesión |
4. Estado
No hay store global. El estado se reparte en tres mecanismos:
| Mecanismo | Qué guarda |
|---|---|
| SWR | Caché de datos del servidor. Es la fuente de verdad de todas las listas y detalles |
react-hook-form | Estado de edición de los formularios; se resetea con form.reset() cada vez que SWR revalida |
RefundContext | Alta manual de devoluciones, persistida en localStorage (refundProcess) |
sessionStorage | Filtros y paginación del listado de devoluciones (RETURNS_TABLE_STATE) |
RoleContext | Roles del usuario, inyectados desde el layout |
Las mutaciones van siempre por useSWRMutation (trigger) seguido de mutate() para revalidar la caché, y notifican con toasts de sonner.
5. Diccionario de UI
No hay framework de i18n: la interfaz está en español fijo mediante constants/dictionary.ts, un objeto plano con ~140 claves (dictionary.warehouse, dictionary.error.required_fields, dictionary.success.return_updated…). Los mensajes de error y de éxito de todo el proyecto salen de ahí, incluidos los que consumen los esquemas de zod.
Ojo con la distinción: la UI es monolingüe, pero el contenido que administra es multiidioma. constants/languages.ts define los 7 idiomas (en, es, fr, de, it, pt, el) en los que se editan los textos de métodos y motivos, y en los que se puede descargar el PDF de una devolución.
6. Tablas de datos
Base común en components/admin/data-table/, construida sobre TanStack Table. La DataTable acepta manualPagination + rowCount: solo el listado de devoluciones lo activa (paginación y filtrado en servidor, con totalElements de robo-api como total); los listados de métodos, motivos, usuarios y almacenes traen todo el catálogo y paginan/filtran en cliente. Además:
data-table-column-selector— mostrar/ocultar columnas.data-table-quick-filtersypopover-filter— filtros por catálogo con búsqueda (cmdk).usePinColums— fija columnas a izquierda/derecha cuando la tabla desborda horizontalmente, reaccionando aresizeyscroll.status-badge— badge de color por estado, reutilizado en listado y detalle.
Cada dominio aporta su propio columns.tsx con las cabeceras tomadas del diccionario.
7. Componentes destacados
| Componente | Función |
|---|---|
components/returns/table.tsx | Listado de devoluciones con filtros, búsqueda, CSV y persistencia de estado |
components/returns/return/content.tsx | Detalle: formulario de edición completo con la matriz de editabilidad |
components/returns/return/table.tsx | Tabla de líneas editables + acciones masivas Aprobar todas / Reembolsar todas |
components/returns/return/logs.tsx | Historial de cambios con diff entre entradas consecutivas |
components/returns/return/pdf-button.tsx | Desplegable que firma y abre el PDF en cualquiera de los 7 idiomas |
components/admin/language-selector.tsx | Añade/quita idiomas en los campos translations de métodos y motivos |
components/rich-text-editor/default.tsx | Editor WYSIWYG de instrucciones y descripciones (con botón de HTML crudo) |
8. Selectores para automatización
El markup expone atributos data-test-id en los puntos de interacción relevantes (return-create-button, return-edit-button, return-status-selector, return-store-selector, return-claimed-goods-selector, return-final-price-input, return-pdf-button, return-log-button, method-create-button…).
Es una convención distinta a la del portal del cliente, que usa clases qa-*. Conviene mantenerlos al refactorizar componentes.