Arquitectura del frontend¶
El frontend es una aplicación Next.js (App Router) que sirve tres audiencias desde un mismo proyecto: el comensal (flujo QR → menú → comanda → pago), el personal de sala/administración y una zona de demos. Esta página explica cómo está organizada la app, cómo habla con el backend (capa de API tipada), dónde vive el estado de cliente y cómo se sincroniza en tiempo real mediante SSE.
No es el Next.js que ya conoces
El proyecto usa una versión de Next con breaking changes respecto a versiones anteriores: APIs, convenciones y estructura de ficheros pueden diferir. Antes de escribir código, consulta la guía correspondiente en node_modules/next/dist/docs/ y atiende a los avisos de deprecation (ver frontend/AGENTS.md).
Zonas de la aplicación (app/)¶
El App Router organiza las rutas por audiencia:
| Zona | Ruta | Propósito |
|---|---|---|
| Landing | app/(landing)/ |
Página de marketing pública (grupo de ruta sin segmento de URL), con components/ y sections/. |
| Comensal | app/menu/[restaurantId]/ |
Carta del restaurante (MenuClient.tsx). |
| Comensal (QR) | app/menu/[restaurantId]/table/[sessionToken]/ |
Punto de entrada al escanear el QR de mesa: resuelve el session_token y abre el chat/comanda de la sesión. |
| Comensal | app/checkout/ |
Funnel de cuenta y pago. |
| Comensal | app/order/[orderId]/ |
Detalle/seguimiento de un pedido. |
| Cliente (auth) | app/auth/ |
login, register y callback de OAuth del comensal. |
| Admin | app/admin/ |
Panel de gestión: login, auth, onboarding, restaurants, super. |
| Demo | app/demo/ |
Pantallas de demostración (p. ej. error-system). |
| Internas | app/api/ |
Rutas de servidor (incluye app/api/admin/). |
El árbol raíz incluye además layout.tsx, page.tsx, error.tsx, not-found.tsx y globals.css.
Distinción cliente / sala
La zona del comensal se sincroniza por el canal de sesión (token de mesa). La zona admin incluye el dashboard de sala, que usa un canal SSE distinto autenticado con el token de administrador. Ver Control de sala.
Capa de API¶
Toda la comunicación HTTP con el backend pasa por una capa tipada en frontend/lib/api/.
Cliente del comensal: client.ts¶
frontend/lib/api/client.ts exporta el objeto api, un wrapper sobre fetch organizado por dominios: auth, customers, menu, tables, recommendations, sessions, cart, comanda, tab, bill, review, chat, managerCall y admin. Características:
- Maneja autenticación opcional por bearer token (
requireAuth), guardado ensessionStoragevíasetAccessToken/getAccessToken. - Normaliza errores en
APIClientError(constatusydata) y ofrecesafeFetchque devuelve unAPIResponse<T>({ success, data | error }). - Convierte
204 No Contenten un objeto vacío y serializa el body a JSON automáticamente.
Endpoints orders deprecados
Las operaciones de pedido legadas en api.orders (create, createDraft, get, updateStatus) lanzan error a propósito. Usa los endpoints de comanda (api.comanda.*): GET /sessions/{token}/draft, POST /sessions/{token}/lines, POST /sessions/{token}/submit, etc. Ver Comanda.
Cliente base y cliente de administración: baseClient.ts / adminClient.ts¶
frontend/lib/api/baseClient.tsdefine la clase abstractaBaseApiClient, que centraliza la construcción de URL, cabeceras,request<T>,requestBlob(descargas) ysafeFetch. Las subclases implementangetAuthHeaders()ygetCacheMode().frontend/lib/api/adminClient.tsdefineAdminApiClient extends BaseApiClient(instancia exportadaadminApiClient). Inyecta el token desdeuseAdminAuthStore, fuerzacache: 'no-store'y reporta errores conreportApiError.
El cliente admin compone módulos de endpoints por dominio (de lib/api/endpoints/admin/) en namespaces de solo lectura, entre otros:
products,categories,menus,chat,promotions,knowledge,restaurantsrecommendations,flows,users,audit,analyticstables,rooms,batches,managerCalls,errors,ordersmanager(Control de sala):getSessions,getKitchenLoad,deliver,courtesyRound,setKitchenOverload.
Tipos: generados a mano y por codegen¶
- Tipos del contrato OpenAPI: se generan con
npm run codegen(scriptopenapi-typescript), que lee el snapshot del contrato (backend/tests/contract/openapi_snapshot.json, configurable víaOPENAPI_SNAPSHOT) y escribelib/api/generated/openapi.d.ts. Los helpers derivados viven enlib/api/generated/helpers.ts(p. ej.TabView,BillResponse,PayBillResponse,ReviewResponse). - Tipos SSE escritos a mano: las superficies SSE no están en OpenAPI, por lo que se mantienen manualmente en
frontend/lib/api/sse-types.ts. Su fuente de verdad esbackend/tests/contract/sse_events_inventory.md. Ver Eventos SSE.
Evolucionar el contrato
Cuando cambie el contrato del backend, regenera los tipos con npm run codegen y cablea el frontend en el mismo cambio. Ver Evolucionar el contrato.
Configuración de la URL base¶
frontend/lib/config/api.ts centraliza el cálculo de la URL base de la API mediante getApiBaseUrl() (y la constante API_CONFIG.baseUrl). El orden de resolución es:
- Variable de entorno de build
NEXT_PUBLIC_API_URL, si existe. - Detección en runtime (solo en navegador): host conocido de demo → API de demo; en otro caso, mismo origen +
/api/v1. - Por defecto en desarrollo:
http://localhost:8000/api/v1.
Ver Configuración.
Estado de cliente (stores Zustand)¶
El estado se gestiona con Zustand, un store por dominio en frontend/store/:
- Comensal:
cartStore,chatStore,sessionStore,batchStore,tabStore,billStore. - Admin:
adminAuthStore,adminRestaurantStore,adminUserStore,adminUIStore. - Transversales:
authStore,uiStore(con helpertoast).
store/index.ts reexporta los stores de uso más común; tabStore y billStore se importan directamente desde su módulo. Los hooks de tiempo real despachan los eventos SSE a estos stores (carrito, chat, batch, tab, bill, sesión).
Tiempo real (hooks SSE)¶
El frontend consume dos streams SSE distintos, ambos con el sobre { type, data, timestamp }. Ambos hooks reconectan con backoff exponencial (máx. 30 s) y fuerzan reconexión en visibilitychange / online / pageshow, porque los navegadores móviles cierran EventSource al pasar a segundo plano.
Canal del comensal: useSessionSync¶
frontend/lib/hooks/useSessionSync.ts conecta a GET /sessions/{token}/events (vía EventSource) y reparte los eventos a los stores correspondientes. Tipos de evento que maneja, entre otros:
- Sesión/carrito/chat:
connected,cart_updated,chat_message,chat_streaming,typing_started,typing_stopped,session_closed,guest_count_updated,heartbeat. - Ciclo de vida del batch:
batch_submitted,batch_ai_result,batch_manager_result,batch_approved,batch_in_progress,batch_served,batch_ready,batch_updated. - Llamada al responsable:
manager_call_created,manager_call_responded.
Al reconectar (onopen en una reconexión) hace una llamada REST de catch-up (api.comanda.getActive) para recuperar eventos perdidos mientras estuvo desconectado, ya que la cola del backend es por suscriptor y no reenvía histórico.
Canal de sala: useManagerSessionSync¶
frontend/lib/hooks/useManagerSessionSync.ts alimenta el dashboard de sala. Conecta a GET /admin/manager/restaurants/{id}/events y, como EventSource no puede enviar cabeceras de autenticación, el token de admin viaja como query param (?token=<admin jwt>, tomado de useAdminAuthStore). El hook:
- Siembra el estado con una llamada REST (
adminApiClient.manager.getSessions). - Fusiona los frames
sala_snapshot,table_state_changedysilent_table_alert(tipados ensse-types.ts). - Devuelve
sessions,status(connecting | connected | disconnected),lastUpdated,silentAlert,loadingy unrefetchmanual.