Skip to main content

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.

ContextoClave en sessionStorageContenido y responsabilidad
OrderContextcurrentOrderPedido recuperado de /api/get-order. Expone order, setOrder, destroyOrder
RefundContextcurrentRefundDevolución en construcción: artículos, método, subtotal, total. Expone setRefundItems, setReturnMethod, destroyRefund
StepContextcurrentStepPaso actual (1–5, maxSteps = 5), sincronizado con el historial del navegador
ReturnMethodContextMétodos de devolución del país del pedido; se recarga al cambiar order o language
LanguageContextLocale 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)
  • shippingCost solo se suma si showShippingCost es true.
  • El precio del método se ignora (cuenta como 0) si el método es priceless.
  • RefundProvider no renderiza nada hasta hidratarse (if (!hydrated) return null), para evitar desajustes entre servidor y cliente al leer sessionStorage.
  • Al montar, lee el paso de sessionStorage, lo valida contra 1..5 y hace history.replaceState({ step }).
  • setCurrentStep hace history.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 en components.json.
  • app/components/ — componentes de negocio.
ComponenteFunción
StepperIndicador de progreso de 4 pasos con iconos y estado completado/actual
StepsNavigatorBotones Atrás/Continuar; acepta onBeforeContinue que puede vetar el avance devolviendo false
Step1Step4Contenedores de cada paso (ver Flujo de devolución)
Step2ReturnForm / Step3ReturnMethodFormFormularios de selección de artículos y de método
ReturnItem / ReasonSelectorTarjeta de artículo (checkbox, imagen, precios) y selector de motivo + texto libre
ReturnResume / OrderResumeResúmenes de devolución y de pedido
ThankYouPagePaso 5: confirmación, QR, enlace al PDF y disparo del journey de Marketing Cloud
ReturnDetailsLínea temporal de estados en /info + descarga de PDFs
ReturnConditionsCondiciones de devolución (colapsable en móvil), con enlace de contacto según idioma
PdfDocument / PdfStatusDocumentDocumentos @react-pdf/renderer renderizados en servidor
RichTextRenderiza traducciones con etiquetas <p>, <strong>, <i>, <a> de next-intl
LabelStatusBadge de color por estado (CREATED, IN_REVIEW, APPROVED, REFUNDED, CANCELLED)
ButtonResetDestruye pedido + devolución + paso y vuelve al inicio
Error, LoaderPage, LoaderSpinnerEstados de error y carga
Price, ProductImageFormateo 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.css importa tailwindcss y tw-animate-css y define los tokens de shadcn (--color-primary, --radius…) con @theme inline.
  • shadcn/ui estilo new-york, color base neutral, cssVariables: true, iconos lucide.
  • Marca: cabecera negra (bg-gray-900) con logo blanco; verde emerald-500 como color de acción y progreso; fondo bg-gray-50; tarjetas blancas con shadow-md.
  • Tipografía: Roboto (peso 400, subset latin) vía next/font/google en 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.