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.
Mockups visuales de Flujo de Pedidos
Diagrama y flujo de procesos del sistema ZEUMAX.
Gestion de pedidos: listado, detalle, estados y acciones desde el panel de administracion.
Gestion de perfiles, roles y accesos de usuarios del sistema.
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
ordersdel backend. La diferencia está en el campoorder_typey 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: cookingodone). 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: cashocard).
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 cooking → done. 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 cooking → done. 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 eventkitchen_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+fetchnativo 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
confirmedydone. - 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 | Sí | 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 |
Sí | 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 |
Sí | Repartidor o Cliente | Manager + Admin |
| Imposible preparar post-confirmar | canceled |
Sí | 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
pendingdirectamente adelivered. Si se intenta, el backend devuelve422 Unprocessable Entitycon 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.