Arquitectura y diseño
1. Patrón general
La aplicación sigue un patrón BFF (Backend For Frontend): el navegador nunca habla directamente con robo-api. Todas las llamadas van a route handlers propios en app/api/*, que se ejecutan en el servidor de Next.js, obtienen el token de Auth0 y reenvían la petición.
Esto mantiene las credenciales (AUTH0_*, MC_*, PDF_SIGNATURE_SECRET) exclusivamente en el servidor.
flowchart TD
U[Cliente / navegador] --> PX[proxy.tsx<br/>Basic Auth opcional]
PX --> APP[App Router]
APP --> W["/ — Wizard 4 pasos"]
APP --> I["/info — Consulta de estado"]
APP --> D["/doc/· /status/·<br/>PDF con URL firmada"]
W --> API["/api/* — Route handlers BFF"]
I --> API
D --> LIB["lib/returns/fetchReturn"]
API --> AUTH["lib/auth<br/>token Auth0 en caché"]
LIB --> AUTH
AUTH --> A0[(Auth0<br/>robo-hawkers.eu.auth0.com)]
API --> ROBO[(robo-api<br/>robo-api.hawkersco.net)]
LIB --> ROBO
API --> MC[(Salesforce<br/>Marketing Cloud)]
2. Providers y jerarquía
app/layout.tsx es un Server Component que resuelve el locale con getLocale() y monta el árbol de providers. El orden importa, porque StepContext consume RefundContext y OrderContext, y ReturnMethodContext consume OrderContext y LanguageContext:
NextIntlClientProvider
└── LanguageProvider (language)
└── OrderProvider
└── ReturnMethodsProvider
└── RefundProvider
├── StepProvider
│ ├── Header
│ └── {children}
├── Footer
└── Toaster (sonner, bottom-center, richColors)
3. Gestión de estado
No hay Redux/Zustand: el estado es React Context + sessionStorage, de forma que un refresco de página no pierde la devolución en curso pero cerrar la pestaña sí la descarta.
| Contexto | Clave en sessionStorage | Contenido y responsabilidad |
|---|---|---|
OrderContext | currentOrder | Pedido recuperado de /api/get-order. Expone order, setOrder, destroyOrder |
RefundContext | currentRefund | Devolución en construcción: artículos, método, subtotal, total. Expone setRefundItems, setReturnMethod, destroyRefund |
StepContext | currentStep | Paso actual (1–5, maxSteps = 5), sincronizado con el historial del navegador |
ReturnMethodContext | — | Métodos de devolución del país del pedido; se recarga al cambiar order o language |
LanguageContext | — | Locale resuelto en servidor, inyectado desde el layout |
Cálculo del importe (RefundContext)
subtotal = Σ price de los artículos seleccionados (2 decimales)
total = subtotal + shippingCost − precio del método (2 decimales)
shippingCostsolo se suma sishowShippingCostestrue.- El precio del método se ignora (cuenta como 0) si el método es
priceless. RefundProviderno renderiza nada hasta hidratarse (if (!hydrated) return null), para evitar desajustes entre servidor y cliente al leersessionStorage.
Navegación e historial (StepContext)
- Al montar, lee el paso de
sessionStorage, lo valida contra1..5y hacehistory.replaceState({ step }). setCurrentStephacehistory.pushState, así que el botón atrás del navegador retrocede de paso en lugar de salir del portal.- Escucha
popstate: si se vuelve atrás desde el paso 5 (pantalla de éxito), destruye pedido, devolución y paso — no se puede reabrir una devolución ya confirmada.
4. Componentes
Dos familias claramente separadas:
app/components/ui/— primitivas de shadcn/ui generadas sobre Radix (button,form,select,sheet,alert-dialog,radio-group,checkbox,table,sonner…). Configuración encomponents.json.app/components/— componentes de negocio.
| Componente | Función |
|---|---|
Stepper | Indicador de progreso de 4 pasos con iconos y estado completado/actual |
StepsNavigator | Botones Atrás/Continuar; acepta onBeforeContinue que puede vetar el avance devolviendo false |
Step1–Step4 | Contenedores de cada paso (ver Flujo de devolución) |
Step2ReturnForm / Step3ReturnMethodForm | Formularios de selección de artículos y de método |
ReturnItem / ReasonSelector | Tarjeta de artículo (checkbox, imagen, precios) y selector de motivo + texto libre |
ReturnResume / OrderResume | Resúmenes de devolución y de pedido |
ThankYouPage | Paso 5: confirmación, QR, enlace al PDF y disparo del journey de Marketing Cloud |
ReturnDetails | Línea temporal de estados en /info + descarga de PDFs |
ReturnConditions | Condiciones de devolución (colapsable en móvil), con enlace de contacto según idioma |
PdfDocument / PdfStatusDocument | Documentos @react-pdf/renderer renderizados en servidor |
RichText | Renderiza traducciones con etiquetas <p>, <strong>, <i>, <a> de next-intl |
LabelStatus | Badge de color por estado (CREATED, IN_REVIEW, APPROVED, REFUNDED, CANCELLED) |
ButtonReset | Destruye pedido + devolución + paso y vuelve al inicio |
Error, LoaderPage, LoaderSpinner | Estados de error y carga |
Price, ProductImage | Formateo de importes por locale/currency e imagen de producto |
app/page.tsx carga los pasos con next/dynamic y un LoaderSpinner como fallback, de modo que solo se descarga el código del paso visible.
5. Estilos y UI
- Tailwind CSS v4 vía
@tailwindcss/postcss;app/globals.cssimportatailwindcssytw-animate-cssy define los tokens de shadcn (--color-primary,--radius…) con@theme inline. - shadcn/ui estilo
new-york, color baseneutral,cssVariables: true, iconoslucide. - Marca: cabecera negra (
bg-gray-900) con logo blanco; verdeemerald-500como color de acción y progreso; fondobg-gray-50; tarjetas blancas conshadow-md. - Tipografía: Roboto (peso 400, subset latin) vía
next/font/googleen el layout. Los PDFs usan NotoSans local (ver PDFs). - Selectores de QA: el markup incluye clases
qa-*(qa-form-search-order-input-order-id,qa-return-method-radio-button,qa-steps-navigator-next-button,qa-details-step-1…) usadas por los tests de Playwright. No deben eliminarse ni renombrarse sin revisar ese repositorio.
6. Imágenes
next.config.ts desactiva la optimización de imágenes (unoptimized: true) y permite remotas solo desde https://*.hawkersco.*, que es de donde llegan las imágenes de producto del pedido.