07. Flujo de Pedidos: El Ciclo de Vida de una Orden

Este documento es el corazón del manual. Aquí se describe el ciclo de vida completo de un pedido en el sistema ZEUMAX (ZEUMAX). Un pedido no es solo un registro en una base de datos: es una historia que involucra a múltiples personas, dispositivos y sistemas, todas conectadas por un mismo backend que orquesta cada paso en tiempo real.


Interfaz visual y diagramas de flujo

A continuación se muestran los diagramas y capturas principales del flujo de pedidos.


Introducción

Desde el momento en que un cliente siente hambre y abre la web de su restaurante favorito, hasta que el último plato llega a su mesa o su puerta, decenas de eventos se suceden en milisegundos. Un pedido en ZEUMAX es un objeto vivo: nace con un estado, crece a través de transiciones, envía notificaciones a humanos y máquinas, consume recursos (stock, tiempo, dinero), y finalmente se archiva cuando su misión termina.

Este documento explica cómo se siente hacer un pedido desde cada perspectiva: la del cliente en su sofá, la del cocinero frente a la plancha, la del cajero en el mostrador, la del manager en la tablet del recepción, y la del repartidor en su moto. Veremos los cinco tipos de pedido que el sistema soporta, los diagramas de comunicación entre apps, las tablas maestras de estados, y las excepciones que pueden interrumpir el flujo.

Consejo de lectura: Si eres implementador o administrador, lee este documento completo. Si eres usuario de una app específica (TPV, Orden Receiver, etc.), salta directamente a la sección de tu flujo.


Tipos de Pedido

ZEUMAX clasifica los pedidos en cinco tipos según su origen y su destino. Cada tipo activa un flujo ligeramente diferente, pero todos comparten el mismo backend, los mismos estados y la misma base de datos.

Tipo Origen Destino ¿Quién paga? ¿Quién entrega? App principal del cliente
Delivery Web Cliente (Next.js) Domicilio del cliente Online (MONEI / Stripe / Wallet) Repartidor (hotpack_rider) Web Delivery
Takeaway Web Cliente (Next.js) Local del restaurante Online o en local Cliente recoge Web Delivery
Mesa App QR (Vite + React) Mesa del restaurante En efectivo o datáfono Mesero (servido en mesa) App QR móvil
TPV TPV (React Native + Expo) Mesa o mostrador Efectivo, tarjeta, wallet Cajero / Mesero TPV (operado por staff)
Sinqro Marketplace externo (Uber Eats, Glovo, etc.) Domicilio o local Externo (marketplace) Repartidor externo o propio Web del marketplace

Nota clave: Todos los tipos de pedido terminan en la misma tabla orders del backend. La diferencia está en el campo order_type y en el flujo de estados que se activa.


Diagrama General del Ecosistema

El siguiente diagrama muestra todos los sistemas involucrados en un pedido y cómo se comunican entre sí. Las flechas representan llamadas API, eventos Socket.IO, notificaciones FCM, polling nativo o impresiones térmicas.

graph TD
    subgraph Clientes
        C[  Cliente final]
        WD[  Web Delivery<br/>Next.js + React 19]
        QR[  App QR Mesas<br/>Vite + React 19]
    end

    subgraph Backend
        API[  API del sistema<br/>v1 REST + Socket.IO]
        DB[(  Base de Datos<br/>MySQL + MongoDB)]
        FCM[  Firebase Cloud Messaging<br/>Topics: customer, kitchen, admin, deliveryman]
        SIO[  Socket.IO<br/>Tracking en tiempo real]
    end

    subgraph Staff
        OR[  Orden Receiver<br/>React Native + Expo<br/>Polling 10s]
        KA[ ‍  Kitchen App<br/>React Native<br/>auth:kitchen_api]
        TPV[  TPV Terminal<br/>React Native + Expo]
        IMP[  Impresora Térmica<br/>Ethernet / Bluetooth]
    end

    subgraph Delivery
        RIDER[  Repartidor<br/>hotpack_rider App]
    end

    subgraph Admin
        AW[  Panel Admin Web]
        BW[  Panel Branch Web]
    end

    subgraph Externo
        SINQ[Sinqro Webhook<br/>Integración marketplace]
    end

    C -->|Navega / Compra| WD
    C -->|Escanea QR| QR

    WD -->|Axios + TanStack Query| API
    QR -->|fetch nativo| API
    SINQ -->|Webhook POST| API

    API -->|Guarda / Lee| DB
    API -->|Emit events| SIO
    API -->|FCM Push| FCM

    OR -->|Polling nativo| API
    OR -->|Socket.IO rooms| SIO
    KA -->|API + FCM| API
    TPV -->|API endpoints| API

    API -->|Kitchen Call + print| IMP
    API -->|Push notification| RIDER
    RIDER -->|GPS + Socket.IO| SIO

    AW -->|Admin endpoints| API
    BW -->|Branch endpoints| API

Comunicación en tiempo real: El backend usa tres mecanismos para notificar eventos:
1. FCM Push Notifications → para alertas urgentes (nuevo pedido, repartidor asignado).
2. Socket.IO → para tracking en vivo (posición del repartidor en el mapa) y rooms por branch.
3. Polling nativo → en Orden Receiver, cada 10 segundos, como respaldo robusto para pedidos críticos.


A. Flujo Delivery: De la Web al Domicilio

Este es el flujo más completo y el que más actores involucra. Un pedido Delivery atraviesa la web del cliente, el backend, el Orden Receiver del manager, la Kitchen App, la impresora térmica, la app del repartidor, y finalmente vuelve al cliente con una notificación de entrega.

Diagrama del Flujo Delivery

graph TD
    subgraph Fase_1_Cliente
        A1[  Cliente abre web<br/>Home con banners]
        A2[  Navega categoría<br/>/categories/[slug]]
        A3[  Ve producto<br/>/products/[id]]
        A4[  Selecciona variaciones/addons<br/>Añade al carrito]
        A5[  Revisa carrito<br/>/cart + cupón]
        A6[  Checkout<br/>Dirección + MONEI + notas]
        A7[  Confirma pedido]
    end

    subgraph Fase_2_Backend
        B1[  POST /api/v1/customer/order/place]
        B2[OrderCreationService]
        B3[Reserva stock en BD]
        B4[FCM → admin + manager]
    end

    subgraph Fase_3_Manager
        M1[  Orden Receiver<br/>Polling 10s → sonido + vibración]
        M2[Dashboard: badge "Nuevo"]
        M3[Manager toca "Aceptar"]
        M4[Estado: confirmed]
        M5[  Ticket en cocina]
    end

    subgraph Fase_4_Cocina
        K1[ ‍  Kitchen App<br/>Lista de pedidos]
        K2[Marca: cooking]
        K3[Marca: done]
    end

    subgraph Fase_5_Handover
        H1[Manager recibe notificación]
        H2[OrderDetail: "Listo para recogida"]
        H3[Toca → estado: handover]
        H4[FCM → repartidores disponibles]
    end

    subgraph Fase_6_Delivery
        D1[  hotpack_rider App<br/>Push: "Nuevo pedido"]
        D2[Repartidor acepta]
        D3[POST /manager/orders/{id}/assign-delivery]
        D4[Estado: picked_up]
        D5[Cliente ve nombre y foto del repartidor]
        D6[Repartidor marca: Recogido]
        D7[Estado: out_for_delivery]
        D8[  Web tracking<br/>Mapa en tiempo real Socket.IO]
        D9[Repartidor entrega → foto de prueba]
        D10[Estado: delivered]
    end

    subgraph Fase_7_Cierre
        C1[  Cliente deja review]
        C2[Backend: completed<br/>archiva pedido]
    end

    A7 --> B1
    B1 --> B2 --> B3 --> B4 --> M1
    M1 --> M2 --> M3 --> M4 --> M5 --> K1
    K1 --> K2 --> K3 --> H1 --> H2 --> H3 --> H4 --> D1
    D1 --> D2 --> D3 --> D4 --> D5 --> D6 --> D7 --> D8 --> D9 --> D10 --> C1 --> C2

Paso 1: El cliente descubre

El cliente abre su navegador y accede a la web de delivery del restaurante (/). La Home carga banners promocionales, categorías de productos (pizzas, hamburguesas, postres), y una sección de "más populares". Redux Toolkit gestiona el estado global; TanStack Query precarga datos en caché.

Pantalla: Home con carrusel de banners, grid de categorías, productos estrella con precio y foto. Barra de búsqueda en la parte superior. Icono de carrito con badge de cantidad en la esquina superior derecha.

Paso 2: El cliente navega y elige

El cliente toca una categoría. La app navega a /categories/[slug], donde se listan todos los productos de esa categoría. Toca un producto y entra a /products/[id]. Aquí ve la foto grande, descripción, precio base, y selecciona variaciones (tamaño: mediana / grande / familiar) y addons (extra queso, champiñones, bebida). Cada cambio actualiza el precio en tiempo real.

Pantalla: ProductDetail con selector de tabs para variaciones, lista de checkboxes para addons, precio total dinámico, botón grande naranja "Añadir al carrito".

API: GET /api/v1/customer/products/{id} (detalle del producto con variaciones y addons).

Paso 3: El carrito

El cliente toca el carrito flotante. Navega a /cart. Revisa los items: imagen miniatura, nombre, variaciones elegidas, cantidad (con +/-), precio unitario y subtotal. Puede aplicar un cupón de descuento introduciendo el código; el backend valida (GET /api/v1/customer/coupon/apply) y recalcula totales. Toca "Proceder al checkout".

Pantalla: Lista de items en carrito. Campo de cupón con botón "Aplicar". Resumen de totales: subtotal, descuento, impuestos, delivery fee, total. Botón "Checkout".

Paso 4: Checkout y pago

El cliente llega a /checkout. El checkout drawer se abre con pasos:
1. Tipo de orden: selecciona "Delivery".
2. Dirección: autocomplete de Google Maps, campo de notas para el repartidor ("Timbre roto, llamar al llegar").
3. Método de pago: MONEI (tarjeta), Stripe, wallet, o efectivo (si la sucursal lo permite).
4. Resumen final: items, dirección, pago, total. Checkbox de términos.
5. Botón: "Confirmar pedido".

Pantalla: Checkout stepper. Mapa miniatura con pin de dirección. Formulario de tarjeta integrado (MONEI SDK). Lista de métodos de pago con iconos. Botón de confirmación grande y verde.

API: GET /api/v1/customer/address (direcciones guardadas), POST /api/v1/customer/payment-intent (inicia pago MONEI).

Paso 5: El backend da vida al pedido

El cliente confirma. La web envía POST /api/v1/customer/order/place. El backend ejecuta OrderCreationService:
- Valida stock de cada item (incluyendo fraccionamiento si aplica).
- Reserva stock temporalmente (bloqueo hasta entrega o cancelación).
- Calcula totales: subtotal, impuestos (porcentaje según configuración del backend, no hardcoded), descuentos, delivery fee.
- Crea el registro Order en MySQL con estado pending.
- Genera la relación OrderDetail con cada item, variación y addon.
- Guarda el pago en transactions si fue online.
- Envía FCM push al topic admin y manager de la sucursal: "Nuevo pedido #12345".
- Emite evento Socket.IO a la room de la branch.

Timing: Todo este proceso toma entre 500ms y 2 segundos.

Notificación: El manager recibe un push en su dispositivo (si la app está en background) o un sonido inmediato (si está en foreground).

Paso 6: Orden Receiver despierta

El Orden Receiver (React Native + Expo) está en la tablet del manager. Aunque la pantalla esté apagada, el polling nativo corre cada 10 segundos en segundo plano (BackgroundFetch o setInterval con keep-awake).

Cuando detecta un nuevo pedido:
- Reproduce sonido new-order.mp3 (alto y repetitivo).
- Vibra intensamente.
- Enciende la pantalla (wake lock).
- Muestra badge "Nuevo" en el Dashboard.
- Muestra un toast flotante: "Pedido #12345 recibido — Delivery".

Pantalla: Dashboard con tarjetas de resumen (ventas del día, pedidos activos, empleados en turno). Lista de pedidos con color de estado. Badge rojo "Nuevo" parpadeante en la esquina superior.

API: GET /api/v1/manager/orders?branch_id=X&status=pending (polling nativo).

Paso 7: Manager acepta el pedido

El manager abre el pedido. Ve el detalle completo: lista de items, variaciones, addons, dirección del cliente, notas, método de pago, total. Toca el botón "Aceptar" (o si tiene auto-aceptar activado, el sistema lo acepta solo tras 30 segundos).

El backend:
- Cambia estado a confirmed.
- Envía FCM push al cliente: "Tu pedido #12345 fue confirmado".
- Envía Kitchen Call a la impresora térmica de cocina: ticket con items, mesa (no aplica en Delivery, pero sí indicador), variaciones, notas, tiempo estimado.
- Emite Socket.IO a la room de la cocina.

Pantalla: OrderDetail con lista de items, dirección del cliente en mapa miniatura, botones de acción: "Aceptar" (verde), "Rechazar" (rojo), "Imprimir" (gris). Timer desde recepción.

API: POST /api/v1/manager/orders/{id}/update-status (body: status: confirmed).

Paso 8: La cocina se pone en marcha ‍

El cocinero abre la Kitchen App (React Native, login con auth:kitchen_api). Ve la lista de pedidos con filtro confirmed. El pedido nuevo aparece en la lista con:
- Número de pedido
- Lista de items simplificada (sin precios, solo cantidad y variaciones)
- Notas del cliente en rojo
- Tiempo transcurrido desde confirmación

El cocinero toca el pedido y marca "Cocinando" → estado cooking. El backend notifica al manager. Cuando termina, marca "Listo" → estado done.

Pantalla: Kitchen App — lista de tarjetas de pedido. Cada tarjeta tiene botones "Cocinando" (naranja) y "Listo" (verde). Timer visible. Filtros por estado en la parte superior.

API: POST /api/v1/kitchen/orders/{id}/status (body: status: cooking o done). Guardia: auth:kitchen_api.

Paso 9: Handover — Listo para salir

El manager recibe notificación de cocina lista (FCM + Socket.IO). En Orden Receiver, el OrderDetail del pedido ahora muestra el botón "Listo para recogida" (o "Empaquetar" según configuración). El manager toca el botón.

Backend:
- Cambia estado a handover.
- Envía FCM push a todos los repartidores disponibles (topic deliveryman) de la sucursal: "Nuevo pedido disponible para entrega".
- Notifica al cliente: "Tu pedido está listo para salir".

Pantalla: OrderDetail ahora muestra estado en amarillo "Listo para recogida". Botón de acción actualizado. Timeline del pedido visible: pending → confirmed → processing → handover.

API: POST /api/v1/manager/orders/{id}/update-status (body: status: handover).

Paso 10: Repartidor entra en escena

El repartidor tiene abierta la app hotpack_rider (fuera de este workspace, pero conectada al mismo backend). Recibe un FCM push: "Nuevo pedido disponible — $12.50 — 2.3 km".

El repartidor toca "Aceptar". El backend:
- Asigna repartidor al pedido (POST /manager/orders/{id}/assign-delivery).
- Cambia estado a picked_up.
- Envía FCM push al cliente con nombre, foto y teléfono del repartidor.
- Envía FCM push al manager: "Repartidor asignado".

Pantalla (hotpack_rider): Mapa con pin de recogida y pin de entrega. Tarjeta de pedido con botón "Aceptar" (verde) o "Rechazar" (rojo). Lista de items para verificar.

API: POST /manager/orders/{id}/assign-delivery (body: delivery_man_id).

Paso 11: En camino

El repartidor llega al restaurante, recoge el pedido empaquetado, y marca "Recogido" en su app. Estado cambia a out_for_delivery.

Backend:
- Envía FCM push al cliente: "Tu pedido está en camino ".
- Activa Socket.IO tracking: la web del cliente, en /orders/tracking/[id], muestra el mapa en tiempo real. El GPS del repartidor se transmite cada 3-5 segundos vía Socket.IO al backend, que retransmite a los clientes suscritos a esa room.

Pantalla (Web tracking): Mapa centrado en la dirección del cliente. Pin del repartidor se mueve en vivo. ETA estimada. Timeline: confirmed → picked_up → out_for_delivery. Foto del repartidor y botón de llamada.

API: GET /api/v1/customer/orders/{id}/tracking (datos iniciales). Socket.IO event: deliveryman_location_update.

Paso 12: Entrega en puerta

El repartidor llega al domicilio. Entrega el pedido. Si la política de la empresa lo requiere, toma una foto de prueba (pedido en la puerta, código de entrega). Marca "Entregado" en su app.

Backend:
- Cambia estado a delivered.
- Envía FCM push al cliente: "Tu pedido #12345 fue entregado. ¡Buen provecho! ".
- Libera stock reservado (confirma consumo definitivo).
- Cierra transacción de pago si estaba en hold.

Pantalla (hotpack_rider): Botón "Entregado" con confirmación modal. Opción de foto de prueba. Pantalla de confirmación con estrellas para auto-evaluación.

API: POST /api/v1/deliveryman/orders/{id}/update-status (body: status: delivered, opcional: proof_photo_url).

Paso 13: Review y cierre

El cliente recibe la notificación. Abre la web, puede dejar una review del producto (/products/[id]) y una review del repartidor (estrellas + comentario). El backend guarda la review en reviews y delivery_reviews.

Tras un período de gracia (configurable, típicamente 24-48 horas), un cron job o evento automático marca el pedido como completed. El pedido se archiva: aparece en historial, estadísticas, reportes de admin, pero ya no es editable.

Pantalla: Modal de review con 5 estrellas, campo de texto, botón "Enviar". Mensaje de agradecimiento.

API: POST /api/v1/customer/reviews, POST /api/v1/customer/delivery-reviews.

Timing total típico:
- Pasos 1-4 (cliente): 2-5 minutos
- Paso 5 (backend): < 2 segundos
- Paso 6-7 (recepción + aceptación): 1-3 minutos (o 30s si auto-aceptar)
- Paso 8 (cocina): 10-25 minutos según complejidad
- Paso 9 (handover): 2-5 minutos
- Paso 10-11 (asignación + recogida): 5-15 minutos
- Paso 12 (entrega): 5-30 minutos según distancia
- Total: 25-80 minutos desde pedido hasta puerta


B. Flujo Takeaway: Web → Recogida en Local

El flujo Takeaway es idéntico al Delivery hasta el paso 9 (handover). La diferencia radica en que no hay repartidor.

Diagrama del Flujo Takeaway

graph TD
    T1[  Cliente pide en web<br/>Delivery / Takeaway] --> T2[Checkout → selecciona Takeaway]
    T2 --> T3[Backend crea orden<br/>pending]
    T3 --> T4[Manager acepta → confirmed]
    T4 --> T5[Cocina prepara → done]
    T5 --> T6[Manager marca handover]
    T6 --> T7[FCM al cliente<br/>"Tu pedido está listo para recoger"]
    T7 --> T8[Cliente llega al local<br/>Muestra código o dice nombre]
    T8 --> T9[Manager marca delivered<br/>en Orden Receiver]
    T9 --> T10[Backend: completed]

Pasos 1-9: Idénticos a Delivery

Cliente navega web, checkout, selecciona Takeaway en lugar de Delivery, paga online, backend crea orden, manager acepta, cocina prepara, manager marca handover.

Paso 10: Cliente recoge

Cuando el pedido está en handover, el backend envía FCM push al cliente: "Tu pedido #12345 está listo para recoger. Te esperamos en [dirección de la sucursal]".

El cliente llega al local. Puede mostrar el código de pedido (QR o número) desde su app, o simplemente decir su nombre. El manager o cajero lo identifica en el Orden Receiver o TPV.

Paso 11: Entregado en mostrador

El manager abre el pedido en Orden Receiver y marca "Entregado" (o el cajero lo marca en TPV). Estado cambia a delivered. Backend notifica al cliente: "Pedido entregado. ¡Gracias por tu visita!".

Tras el período de gracia, el pedido pasa a completed.

Pantalla (Orden Receiver): OrderDetail con botón "Entregado al cliente" (azul). Campo para nota de entrega. Opción de requerir firma o código.

API: POST /api/v1/manager/orders/{id}/update-status (body: status: delivered).

Timing total típico: 15-40 minutos (sin tiempo de reparto).


C. Flujo Mesa: QR en la Mesa

Este flujo es el más ágil para el comensal en el restaurante. No requiere descargar app: solo escanear un QR, elegir, y pedir. El pago puede ser en efectivo al final.

Diagrama del Flujo Mesa

graph TD
    M1[  Cliente en mesa] --> M2[  Escanea QR<br/>?table=12&branch=3&token=abc123]
    M2 --> M3[App QR SplashPage<br/>Lee params → Zustand]
    M3 --> M4[Redirige a /menu]
    M4 --> M5[MenuPage<br/>Categorías tabs + productos grid]
    M5 --> M6[BottomSheet<br/>Variaciones + addons]
    M6 --> M7[Carrito flotante → añade items]
    M7 --> M8[CheckoutPage<br/>Ve pedidos acumulados de la mesa]
    M8 --> M9["Opción: Pedir la cuenta" o "Pagar en efectivo"]
    M9 --> M10[POST /api/v1/table/order/place]
    M10 --> M11[Backend crea orden<br/>Notifica OR + Kitchen]
    M11 --> M12[Manager acepta en OR]
    M12 --> M13[Cocina prepara<br/>Kitchen App]
    M13 --> M14[Cocina marca done]
    M14 --> M15[Mesero lleva a mesa]
    M15 --> M16[Cliente pide la cuenta<br/>CheckoutPage]
    M16 --> M17[Cajero cobra en TPV<br/>Efectivo / tarjeta / wallet]
    M17 --> M18[Pedido marcado como pagado<br/>completed]

Paso 1: Escaneo del QR

El cliente está sentado en la mesa 12. Escanea el QR impreso en la mesa o en la servilleta. La URL contiene parámetros: ?table=12&branch=3&token=abc123. El token valida que el QR es legítimo y no ha expirado.

Paso 2: SplashPage y persistencia

La App QR (Vite + React 19, muy ligera) carga la SplashPage. Lee los parámetros URL, los guarda en Zustand (con persistencia en localStorage para que si recarga la página no pierda la mesa). Redirige a /menu.

Pantalla: SplashPage con logo del restaurante, animación de carga, texto "Cargando menú de la mesa 12...". Fondo limpio, sin distracciones.

Paso 3: Menú interactivo

El MenuPage carga categorías como tabs horizontales (Entrantes, Principales, Postres, Bebidas). Cada categoría muestra un grid de productos con foto, nombre, precio. El cliente toca un producto y se abre un BottomSheet con:
- Selector de variaciones (tabs)
- Lista de addons (checkboxes)
- Precio dinámico
- Cantidad (+/-)
- Botón "Añadir"

Los items se acumulan en el carrito flotante (badge en esquina inferior).

Pantalla: MenuPage con tabs de categorías, grid de cards de producto. BottomSheet modal con foto grande, descripción, selector de variaciones. Carrito flotante redondo en la esquina inferior derecha con contador.

API: GET /api/v1/table/menu?branch_id=3&table_id=12 (menú con precios y disponibilidad para esa mesa).

Paso 4: Checkout de mesa

El cliente toca el carrito. Va a CheckoutPage. Aquí ve:
- Sus items: lista de lo que ha pedido él.
- Todos los pedidos de la mesa: si otra persona en la misma mesa escaneó el mismo QR y pidió, aparecen acumulados. Esto permite "pago grupal".
- Total acumulado de la mesa.
- Botón "Pedir la cuenta" → envía pedido al backend y muestra opción de pago.
- Botón "Pagar en efectivo" → envía pedido y notifica al mesero que cobrará en efectivo.

Pantalla: CheckoutPage con lista de items. Tabs: "Mi pedido" / "Todos en la mesa". Resumen de totales. Botones grandes: "Pedir la cuenta" (naranja) y "Pagar en efectivo" (gris).

API: POST /api/v1/table/order/place (body: table_id, branch_id, items[], payment_method: cash o card).

Paso 5: Backend procesa

El backend recibe el POST. Crea la orden con order_type: table. Estado inicial pending. Notifica:
- Orden Receiver: via polling nativo (nuevo pedido en Dashboard).
- Kitchen App: via FCM push ("Nuevo pedido mesa 12").
- Impresora térmica: ticket de cocina con "MESA 12" en grande.

Paso 6: Aceptación y preparación

Manager acepta en Orden Receiver. Cocina ve en Kitchen App. Cocinero marca cookingdone. No hay repartidor ni tracking GPS.

Paso 7: Servicio en mesa

El mesero lleva el pedido a la mesa 12. No hay cambio de estado explícito en el sistema para "servido", aunque el manager puede marcarlo manualmente en Orden Receiver si la sucursal lo requiere.

Paso 8: Pago y cierre

Al final, el cliente pide la cuenta en la App QR (o llama al mesero). El cajero abre el TPV, localiza la mesa 12, y procesa el cobro:
- Efectivo: teclado numérico en TPV, introduce monto recibido, calcula cambio automáticamente, abre cajón de dinero.
- Tarjeta: integración con datáfono.
- Wallet: si el cliente tiene saldo en su cuenta.

El TPV marca el pedido como pagado. Backend cambia estado a completed.

Pantalla (TPV): MesaView con lista de pedidos activos por mesa. Toca mesa 12 → lista de items, botón "Cobrar". Checkout TPV con teclado numérico, display de cambio, botones de método de pago.

API: POST /api/v1/tpv/orders/{id}/pay (body: payment_method, amount_received).

Timing total típico: 5-15 minutos para preparación. Pago al final del servicio.


D. Flujo TPV: El Local como Origen

En este flujo, el cliente no interactúa digitalmente. Todo empieza en el mostrador o en la mesa, operado por el cajero o mesero.

Diagrama del Flujo TPV

graph TD
    TPV1[  Cajero abre TPV<br/>Login PIN + sesión de caja] --> TPV2[Cliente llega al mostrador]
    TPV2 --> TPV3[Cajero toca mesa o "Para llevar"]
    TPV3 --> TPV4[Toma pedido: categorías → productos → variaciones → addons]
    TPV4 --> TPV5[Comanda lateral<br/>Items acumulados]
    TPV5 --> TPV6[Botón "Enviar a cocina"]
    TPV6 --> TPV7[  Impresión ticket en cocina]
    TPV7 --> TPV8[Cocina prepara<br/>Kitchen App]
    TPV8 --> TPV9[Cocina marca done]
    TPV9 --> TPV10[TPV recibe Kitchen Call<br/>"Cocina lista"]
    TPV10 --> TPV11[Cajero marca "Servido"]
    TPV11 --> TPV12[Checkout: cobro]
    TPV12 --> TPV13[  Ticket de caja + abre cajón]
    TPV13 --> TPV14[Mesa liberada / Pedido cerrado]

Paso 1: Apertura de caja

El cajero abre la app TPV. Ingresa su PIN de empleado. Abre una sesión de caja (CashSession), registrando monto inicial en efectivo. El TPV sincroniza configuración: productos, precios, impuestos, impresoras, mesas.

Pantalla: Login con teclado numérico grande. Selección de empleado (foto + nombre). Apertura de caja: campo de monto inicial. Pantalla principal con grid de mesas (tocaquí) o botón "Para llevar".

API: POST /api/v1/tpv/login (PIN), POST /api/v1/tpv/cash-session/open.

Paso 2: Toma de pedido

El cliente llega. El cajero toca una mesa (si es servicio en mesa) o el botón "Para llevar" (si es takeaway en local). Abre el catálogo de productos: categorías en tabs, productos en grid. Toca un producto, selecciona variaciones y addons en un modal. Los items se añaden a la comanda lateral (lista acumulada en la derecha de la pantalla).

Pantalla: TPV principal. Izquierda: grid de categorías y productos. Derecha: comanda lateral con items, subtotales, botones de cantidad. Botón grande naranja "Enviar a cocina".

API: GET /api/v1/tpv/products?branch_id=X (catálogo local cacheado).

Paso 3: Envío a cocina

El cajero toca "Enviar a cocina". El TPV envía el pedido al backend (POST /api/v1/tpv/order/place). Backend:
- Crea orden con order_type: dine_in (o takeaway).
- Estado pending (o confirmed directamente si el TPV está configurado para auto-confirmar).
- Envía comando de impresión a la impresora térmica de cocina.
- Notifica Kitchen App vía FCM.

Pantalla: Confirmación modal "¿Enviar a cocina?". Después, animación de éxito. Ticket se imprime en cocina con sonido de impresora.

API: POST /api/v1/tpv/order/place, POST /api/v1/printer/print-kitchen.

Paso 4: Cocina y Kitchen Call ‍

Cocina prepara. Marca cookingdone. Cuando marca done, el backend envía Kitchen Call al TPV: notificación en pantalla "Pedido [número] de cocina listo". El cajero ve el pedido resaltado en la lista de comandas.

Pantalla (TPV): Banner amarillo en la parte superior: "Cocina lista: Pedido #12345". Lista de comandas con indicador de estado. Toca para ver detalle.

API: POST /api/v1/kitchen/orders/{id}/status, Socket.IO event kitchen_call.

Paso 5: Checkout y cobro

El cajero entrega el pedido (si fue para llevar) o el mesero lo lleva a la mesa. Cuando el cliente pide pagar, el cajero va a Checkout en el TPV:
- Lista de items (no editable en checkout, solo vista).
- Método de pago: Efectivo, Tarjeta, Wallet.
- Efectivo: teclado numérico, introduce monto recibido, el TPV calcula cambio automáticamente. Botón "Abrir cajón".
- Tarjeta: selecciona, integra con pasarela o datáfono externo.
- Split bill: si la mesa quiere dividir, el TPV permite dividir por items o por monto.
- Hold orders: si el cliente pide esperar, se puede poner en hold.

Al confirmar:
- Imprime ticket de caja en impresora térmica de recepción (con detalle, impuestos, método de pago, cambio, mensaje de agradecimiento).
- Abre cajón de dinero (si hay conexión).
- Backend marca pedido como pagado y eventualmente completed.

Pantalla: Checkout view. Resumen de items. Selector de método de pago. Teclado numérico grande para efectivo. Display de cambio. Botón "Confirmar pago" (verde). Opción "Dividir cuenta".

API: POST /api/v1/tpv/orders/{id}/pay, POST /api/v1/printer/print-receipt.

Timing total típico: Inmediato para el cliente (el pedido se toma en el momento). Cocina: 5-20 minutos.


E. Flujo Sinqro: Marketplace Externo

Sinqro es la integración que permite recibir pedidos desde marketplaces externos (Uber Eats, Glovo, Rappi, etc.) directamente en el sistema ZEUMAX, sin que el manager tenga que operar múltiples tablets.

Diagrama del Flujo Sinqro

graph TD
    S1[  Cliente pide en Uber Eats / Glovo] --> S2[Sinqro Platform]
    S2 --> S3[Webhook POST<br/>SinqroWebhookController]
    S3 --> S4[Backend: NewOrderHandler]
    S4 --> S5[OrderCreationService<br/>Crea orden interna]
    S5 --> S6[Orden Receiver<br/>Polling → nuevo pedido]
    S6 --> S7[Manager acepta<br/>confirmed]
    S7 --> S8[Flujo continúa como Delivery<br/>Desde paso 8]
    S8 --> S9[Estado cambia en OR]
    S9 --> S10[SinqroOrderSyncService<br/>Sincroniza vuelta a Sinqro]
    S10 --> S11[Sinqro notifica al marketplace<br/>Cliente ve actualización]

Paso 1: Pedido externo

El cliente pide en la app de Uber Eats, Glovo, o similar. El restaurante aparece listado en el marketplace porque está integrado vía Sinqro.

Paso 2: Webhook al backend

Sinqro recibe el pedido y envía un webhook POST al backend. Llega a SinqroWebhookController, que delega a NewOrderHandler.

API: POST /api/v1/webhooks/sinqro (endpoint público con validación de firma).

Paso 3: Creación interna

NewOrderHandler mapea los datos del marketplace al formato interno de ZEUMAX:
- Items del marketplace → items del catálogo interno (por SKU o nombre fuzzy).
- Dirección del cliente → formato interno.
- Precios → pueden ser diferentes a los precios propios; se guarda como "precio externo".
- Crea orden interna con order_type: sinqro y source: ubereats (o glovo, etc.).

El backend ejecuta OrderCreationService igual que un pedido normal. Estado inicial: pending.

Paso 4: Flujo interno estándar

Desde este punto, el flujo es idéntico al Delivery desde el paso 6: Orden Receiver recibe, manager acepta, cocina prepara, handover, repartidor (propio o externo), entrega.

Paso 5: Sincronización de vuelta

Cuando el manager cambia estado en Orden Receiver (por ejemplo, de confirmed a processing), el backend ejecuta SinqroOrderSyncService:
- Mapea estado interno a estado del marketplace.
- Envía actualización a Sinqro vía API.
- Sinqro retransmite al marketplace original.
- El cliente en Uber Eats ve: "Tu pedido está en preparación".

API: POST /api/v1/sinqro/orders/{id}/sync (interno, llamado por eventos de estado).

Timing: El webhook llega en segundos. El flujo interno es idéntico. La sincronización de vuelta es casi inmediata (< 1s).

Consideraciones:
- Los precios en marketplace pueden incluir comisiones. El backend guarda el precio real recibido y el precio interno esperado para conciliación.
- Si un item no existe en el catálogo interno, puede marcarse como "manual" o rechazarse según configuración.
- Las cancelaciones desde el marketplace se reciben por webhook y se propagan internamente.


Tabla Maestra de Estados del Pedido

Esta tabla es la referencia definitiva. Cada estado, su significado, color en UI, quién lo cambia, en qué app, qué notificación se envía, y el endpoint API.

Estado Significado Color UI Quién lo cambia App Notificación Endpoint API
pending Pedido recién creado, esperando aceptación Rojo Cliente (crea) / Sistema (Sinqro) Web, QR, TPV, Sinqro FCM a admin/manager: "Nuevo pedido" POST /api/v1/customer/order/place POST /api/v1/table/order/place POST /api/v1/tpv/order/place Webhook Sinqro
confirmed Manager aceptó el pedido Amarillo Manager (auto/manual) Orden Receiver FCM a cliente: "Pedido confirmado". Kitchen Call + impresión. POST /api/v1/manager/orders/{id}/update-status
processing En preparación en cocina Naranja Manager o Cocina Orden Receiver, Kitchen App FCM a cliente: "En preparación". Socket.IO a room. POST /api/v1/manager/orders/{id}/update-status POST /api/v1/kitchen/orders/{id}/status
cooking Cocinero empezó a cocinar Morado Cocina Kitchen App FCM a manager: "Cocina iniciada". POST /api/v1/kitchen/orders/{id}/status
done Cocina terminó la preparación Azul claro Cocina Kitchen App FCM a manager: "Cocina lista". Kitchen Call. POST /api/v1/kitchen/orders/{id}/status
handover Listo para recogida/entrega Verde Manager Orden Receiver FCM a repartidores: "Nuevo pedido disponible". FCM a cliente: "Listo para salir". POST /api/v1/manager/orders/{id}/update-status
picked_up Repartidor asignado y recogió Verde oscuro Repartidor (acepta) hotpack_rider FCM a cliente: foto y nombre del repartidor. POST /manager/orders/{id}/assign-delivery
out_for_delivery En camino al cliente Verde + mapa Repartidor hotpack_rider FCM a cliente: "En camino". Socket.IO tracking activado. POST /api/v1/deliveryman/orders/{id}/update-status
delivered Entregado al cliente Negro/Gris Repartidor hotpack_rider FCM a cliente: "Entregado". Liberación de stock. POST /api/v1/deliveryman/orders/{id}/update-status
completed Pedido cerrado y archivado Gris claro Sistema automático (cron) — (silencioso) Automático tras período de gracia
canceled Cancelado por cliente o manager Gris tachado Cliente o Manager Web, Orden Receiver FCM a contraparte: "Pedido cancelado". Refund si aplica. POST /api/v1/customer/orders/{id}/cancel POST /api/v1/manager/orders/{id}/cancel
returned Devuelto por cliente o repartidor Marrón Repartidor/Cliente hotpack_rider, Web FCM a manager: "Devolución". Re-stock. POST /api/v1/deliveryman/orders/{id}/return
failed Fallido (no entregado, no pagado) Rojo oscuro Sistema automático FCM a manager y cliente. Automático por timeout o rechazo de pago

Nota sobre colores: Los colores son aproximados según la UI actual. Pueden variar ligeramente entre apps (Orden Receiver vs Kitchen App vs TPV). La lógica de colores debe venir del backend (config('order_status_colors')) en lugar de estar hardcoded.


Notificaciones y Comunicación en Tiempo Real

Cada evento en un pedido desencadena una o más notificaciones. La elección del canal depende de la urgencia, el dispositivo receptor, y la disponibilidad de conexión.

Tabla de Eventos → Notificaciones

Evento Tipo de notificación Quién la recibe En qué app Detalle del mensaje Prioridad
Nuevo pedido creado FCM Push + Polling nativo Manager / Admin Orden Receiver, Panel Admin "Nuevo pedido #12345 — Delivery — $45.00" Alta
Nuevo pedido creado Kitchen Call + Impresión térmica Cocina Impresora, Kitchen App Ticket impreso con items y mesa Alta
Pedido confirmado FCM Push Cliente Web Delivery "Tu pedido fue confirmado. Tiempo estimado: 25 min." Media
Pedido en preparación FCM Push Cliente Web Delivery "Tu pedido está en la cocina." Baja
Cocina iniciada FCM Push Manager Orden Receiver "Cocina inició pedido #12345" Media
Cocina lista FCM Push + Kitchen Call Manager Orden Receiver "Cocina lista: Pedido #12345" Alta
Listo para recogida FCM Push Cliente (Takeaway) Web Delivery "Tu pedido está listo para recoger." Media
Nuevo pedido para repartidor FCM Push Repartidores disponibles hotpack_rider "Nuevo pedido: $12.50 — 2.3 km" Alta
Repartidor asignado FCM Push Cliente Web Delivery "Juan P. está en camino con tu pedido." Media
En camino FCM Push + Socket.IO Cliente Web Delivery "Tu pedido está en camino " Media
Tracking GPS Socket.IO (tiempo real) Cliente Web Tracking Actualización de coordenadas cada 3-5s Baja (continua)
Pedido entregado FCM Push Cliente Web Delivery "Tu pedido fue entregado. ¡Buen provecho!" Media
Pedido cancelado FCM Push Contraparte Web / OR / Rider "Pedido #12345 cancelado. Motivo: [X]" Alta
Pedido fallido FCM Push Manager + Cliente OR / Web "Pedido #12345 marcado como fallido" Alta
Pago exitoso FCM Push + Email Cliente Web "Confirmación de pago — $45.00" Baja
Pago fallido FCM Push + Email Cliente Web "Error en pago. Por favor reintenta." Alta
Recordatorio de carrito FCM Push (programado) Cliente Web "¿Olvidaste algo? Tu carrito te espera." Baja

Mecanismos de comunicación explicados

1. FCM Push Notifications

  • Tecnología: Firebase Cloud Messaging.
  • Topics: customer, deliveryman, kitchen, admin, manager.
  • Comportamiento: Si la app está en foreground, el push se muestra como toast/snackbar. Si está en background, aparece en la bandeja de notificaciones del sistema operativo. Si la app está cerrada, el push la despierta (depende del SO).
  • Payload: Incluye order_id, status, branch_id, title, body, sound (ej. new-order.mp3), y data extras (foto del repartidor, coordenadas, etc.).
  • Configuración: Topics se suscriben al login. Se desuscriben al logout.

2. Socket.IO

  • Tecnología: WebSocket con fallback a polling.
  • Rooms: Cada sucursal tiene una room (branch_{id}). Los clientes web se unen a la room de su sucursal al hacer login o abrir tracking.
  • Eventos principales: order_status_update, deliveryman_location_update, new_order, kitchen_call, payment_status_update.
  • Uso: Tracking GPS en tiempo real, actualización de estado en web sin recargar, notificaciones instantáneas en Panel Admin.
  • Limitación: Solo funciona cuando el dispositivo tiene conexión activa y la app/web está abierta. No es fiable para "despertar" dispositivos.

3. Polling Nativo

  • Tecnología: setInterval + fetch nativo en React Native (Orden Receiver).
  • Frecuencia: Cada 10 segundos (10000ms).
  • Ventajas: Funciona incluso si la app está en background o la pantalla apagada. No depende de FCM (que puede fallar o retrasarse). Garantiza que el manager nunca pierda un pedido.
  • Desventajas: Consume batería y datos. La respuesta puede tener hasta 10s de delay máximo.
  • Mejora: Se combina con wake lock y sonido/vibración para notificar inmediatamente cuando detecta nuevo pedido.

4. Kitchen Call + Impresión térmica

  • Tecnología: Impresoras térmicas Ethernet (ESC/POS) o Bluetooth.
  • Trigger: Eventos confirmed y done.
  • Comando: Backend envía comando de impresión a través de servicio interno (PrinterService) o cola de impresión.
  • Ticket: Incluye logo del restaurante, número de pedido, tipo (Delivery/Takeaway/Mesa), items con cantidad y variaciones, notas del cliente, dirección (si aplica), tiempo de impresión.

Cancelaciones y Excepciones

No todos los pedidos llegan a completed. El sistema debe manejar cancelaciones, fallos, reasignaciones y devoluciones de forma robusta.

Cancelación por Cliente ‍

El cliente puede cancelar desde la web (/orders/[id]). Las reglas son:
- Antes de confirmed: Cancelación libre. Si ya pagó online, se inicia refund automático vía gateway de pago (MONEI/Stripe). Stock reservado se libera.
- Después de confirmed: Depende de la política del restaurante. Si está configurado, puede cancelar con penalización. Si no, el botón de cancelación está deshabilitado y muestra: "Tu pedido ya está en preparación. Contacta al restaurante."

Pantalla: OrderDetail en web. Botón "Cancelar pedido" (rojo, solo si aplica). Modal de confirmación: "¿Estás seguro? Se reembolsará $X.".

API: POST /api/v1/customer/orders/{id}/cancel (valida estado actual antes de procesar).

Cancelación por Manager ‍

El manager puede cancelar desde Orden Receiver. Las reglas son:
- Antes de handover: Cancelación libre. Notificación a cliente. Refund si pagó online.
- Después de handover: No se puede cancelar desde Orden Receiver. Requiere intervención de admin en Panel Admin Web.
- Si el repartidor ya fue asignado, se le notifica que el pedido fue cancelado.

Pantalla: Orden Receiver, OrderDetail. Botón "Cancelar" (rojo, solo visible si estado lo permite). Motivo de cancelación (obligatorio): agotado, cerrado, cliente no responde, otro.

API: POST /api/v1/manager/orders/{id}/cancel (body: reason).

Repartidor no acepta / Reasignación

Si un repartidor rechaza o no responde en X minutos (configurable, típicamente 2-5 minutos), el backend:
- Desasigna el repartidor.
- Reenvía FCM a otros repartidores disponibles.
- Si ningún repartidor acepta en Y minutos, el pedido marca failed o notifica al manager para decisión manual.

Pantalla (Orden Receiver): Alerta amarilla: "Ningún repartidor aceptó. Asignar manualmente." Botón "Asignar repartidor" con lista de disponibles.

API: POST /manager/orders/{id}/assign-delivery (asignación manual por manager).

Pedido Fallido

Un pedido puede marcar como failed por:
- Timeout de reparto: repartidor no llega en tiempo máximo configurado.
- Cliente no responde: repartidor en puerta pero no contesta. Múltiples intentos de llamada.
- Pago rechazado: webhook de pago indica fallo después de que la orden se creó (raro, pero posible con tarjetas pre-autorizadas).
- Imposible de preparar: cocina no puede hacer el pedido (ingredientes agotados después de confirmar).

Backend:
- Cambia estado a failed.
- Envía FCM a cliente y manager.
- Inicia refund si aplica.
- Libera stock.
- Guarda log de fallo para análisis.

Estado: Automático por cron o manual por admin. No hay endpoint público para failed (solo admin).

Devolución (Returned) ↩

El repartidor o el cliente puede devolver el pedido después de entrega. Casos:
- Producto incorrecto: cliente recibe algo que no pidió.
- Producto en mal estado: comida fría, derramada, etc.
- Cliente no estaba: repartidor regresa con el pedido (aunque esto suele ser failed antes de delivered).

Backend:
- Cambia estado a returned.
- Notifica a manager.
- Re-stock de items (si son reutilizables; si no, se descartan y se ajusta inventario).
- Inicia refund.
- Guarda evidencia (fotos, notas del repartidor).

API: POST /api/v1/deliveryman/orders/{id}/return (body: reason, photos[]).

Tabla de Excepciones

Situación Estado resultante ¿Refund? ¿Quién decide? ¿Notificación?
Cliente cancela antes de confirmar canceled Sí, automático Cliente Cliente + Manager
Cliente cancela después de confirmar canceled Sí, si política lo permite Cliente (con validación) Cliente + Manager + Cocina
Manager cancela antes de handover canceled Sí, si pagado Manager Cliente + Repartidor (si asignado)
Manager cancela después de handover Requiere admin Admin (Panel Admin) Todos los involucrados
Repartidor no acepta Reasignación / failed No (aún no pagado delivery) Sistema Manager
Repartidor no llega (timeout) failed Sistema automático Cliente + Manager
Cliente no responde en puerta failed Sí (parcial según política) Repartidor → Sistema Cliente + Manager
Pago rechazado post-creación failed No se cobró Sistema (webhook) Cliente
Devolución post-entrega returned Repartidor o Cliente Manager + Admin
Imposible preparar post-confirmar canceled Manager Cliente

Seguridad y Guardias de API

Cada app se autentica con un guardia diferente del backend. Esto garantiza que un endpoint de cocina no pueda ser llamado por un cliente, y que un repartidor no pueda modificar estados de TPV.

Guardia Quién usa Endpoints principales
auth:api Cliente web, App QR customer/*, table/*
auth:kitchen_api Kitchen App kitchen/*
auth:tpv_api TPV Terminal tpv/*
auth:manager_api Orden Receiver, Panel Branch manager/*, branch/*
auth:admin Panel Admin Web admin/*
auth:deliveryman_api hotpack_rider deliveryman/*
Webhook (público) Sinqro, MONEI, Stripe webhooks/* (con firma)

Validación de estado: Todos los endpoints de cambio de estado validan que la transición sea lógica. Por ejemplo, no se puede cambiar de pending directamente a delivered. Si se intenta, el backend devuelve 422 Unprocessable Entity con mensaje: "Transición de estado no permitida: pending → delivered".


Glosario de Términos

Término Significado en este contexto
Order Pedido. Objeto central con cabecera (cliente, dirección, total, estado) y líneas de detalle (items).
OrderDetail Línea de pedido. Cada item con su variación, addons, cantidad, precio unitario.
Kitchen Call Notificación sonora/impresa que alerta a cocina o manager de un evento.
Handover Estado en que el pedido está listo para salir del local (recoger o entregar).
Polling Consulta periódica al servidor cada X segundos para detectar cambios.
FCM Firebase Cloud Messaging. Sistema de notificaciones push de Google.
Socket.IO Librería de comunicación en tiempo real vía WebSockets.
Sinqro Integración de marketplace. Permite recibir pedidos de Uber Eats, Glovo, etc.
Hotpack Rider App de repartidor, fuera de este workspace pero conectada al mismo backend.
OrderCreationService Servicio del backend que centraliza la creación de pedidos, validando stock, precios, impuestos y notificaciones.
Auto-aceptar Configuración en Orden Receiver que acepta pedidos automáticamente tras X segundos sin intervención del manager.
Split bill Dividir una cuenta en múltiples pagos (por items o por monto).
Hold order Poner un pedido en espera sin cancelarlo. Útil cuando el cliente pide esperar.
Cash session Sesión de caja en TPV. Registra apertura, ventas, cierre y arqueo.
Proof photo Foto que toma el repartidor como evidencia de entrega (ej. pedido en puerta).
Stock reservado Cantidad de producto bloqueada temporalmente cuando se crea un pedido. Se libera si se cancela o se confirma al entregar.

Documento escrito para el manual de usuario de ZEUMAX (ZEUMAX).
Última actualización: 2026-06-20.
Autor: Redacción técnica — Documentación de Software.
Versión: 1.0 — Revisión previa a producción.