Introducción
Restaurant Menu SaaS es una plataforma multi-tenant completa que permite a múltiples restaurantes gestionar todo su ciclo operativo digital: menús interactivos, gestión de pedidos en tiempo real, módulo de domiciliarios, chat en vivo, cupones, reseñas y analytics — todo desde un único sistema.
Multi-tenant
Un sistema, N restaurantes
Tiempo real
SSE para pedidos en vivo
Templates
Temas por restaurante
¿Qué es exactamente?
Cada restaurante registrado en la plataforma tiene su propio slug (ej. seven-burger) que actúa como identificador multi-tenant. El menú público es accesible en /[slug] y el panel admin en /dashboard (con sesión activa ligada al restaurantId).
restaurantId. No hay bases de datos separadas por tenant.Tech Stack
Next.js 14
Framework — App Router + Server Components
Prisma + SQLite
ORM / DB — SQLite local, PostgreSQL en prod
NextAuth.js v4
Autenticación — Credentials + JWT + Prisma adapter
Tailwind CSS
Estilos — Utility-first, dark mode
Framer Motion
Animaciones — Transiciones, AnimatePresence
Lucide React
Iconos — Iconos SVG por categoría y producto
SSE (EventSource)
Tiempo real — Server-Sent Events nativos de Next.js
bcryptjs
Seguridad — Hash de contraseñas
Zod
Validación — Schemas de validación de formularios
React Hook Form
Formularios — Con resolvers de Zod
Misión y Visión
Misión
Democratizar la transformación digital de los restaurantes, poniendo en manos de cualquier negocio gastronómico — desde una pequeña hamburguesería de barrio hasta una cadena regional — las mismas herramientas de pedidos, marketing y análisis que solo las grandes franquicias podían costear.
Visión
Ser la plataforma SaaS de referencia en Latinoamérica para la gestión operativa de restaurantes, reconocida por su facilidad de uso, personalización sin límites y capacidad de escalar desde 1 hasta miles de locales sin cambiar de sistema.
Valores
Simplicidad radical
Cualquier restaurantero debe poder usar la plataforma sin capacitación técnica. Si algo es confuso, lo rediseñamos.
Velocidad operativa
Cada segundo importa en la cocina. El sistema está diseñado para reducir clics y maximizar eficiencia.
Confiabilidad
Los pedidos son el corazón del negocio. Cero pedidos perdidos, actualizaciones en tiempo real, sin fallos.
Acompañamiento real
No somos solo un software. Somos socios del negocio. Ayudamos a configurar, crecer y optimizar.
¿Para quién es esta plataforma?
Restaurantes y cafeterías
Negocios gastronómicos que quieren digitalizar su menú y gestionar pedidos sin depender de apps de terceros que cobran comisiones.
Servicios con delivery
Negocios con repartidores propios que necesitan asignar entregas, hacer seguimiento y coordinar en tiempo real.
Desarrolladores y agencias
Equipos que quieren desplegar un SaaS de menú digital para sus clientes restauranteros, con personalización completa.
Precios y Membresías
La plataforma funciona bajo un modelo de suscripción mensual por restaurante. Cada restaurante elige su plan según su volumen de pedidos y las funcionalidades que necesita. No se cobra comisión por pedido — pagas una tarifa fija y todo lo que vendas es tuyo.
Planes disponibles
Ideal para probar la plataforma o negocios muy pequeños con pocos pedidos.
Para restaurantes en crecimiento con pedidos frecuentes y un pequeño equipo de reparto.
La opción más popular. Todo lo necesario para operar a pleno rendimiento.
Para cadenas, franquicias y negocios con múltiples sucursales bajo una misma marca.
Comparativa de funcionalidades
¿Qué cambia en tu negocio al integrar la plataforma?
Cuando un restaurante se integra, experimenta cambios concretos en su operación diaria y en la percepción de sus clientes desde el primer día:
Guía del Usuario
Esta sección explica cómo usar cada módulo de la plataforma desde el punto de vista del administrador del restaurante. No se requieren conocimientos técnicos.
Primeros pasos
Crea tu cuenta
Dirígete a /signup, ingresa el nombre de tu restaurante, elige un slug único (ej: mi-burger) y crea tu contraseña. Esto es lo único que necesitas para comenzar.
Configura tu restaurante
Desde Configuración, agrega tu teléfono, dirección, horarios de atención y si ofreces delivery, takeaway o servicio en mesa.
Crea tus categorías
Ve a Menú → Nueva categoría. Crea secciones como "Hamburguesas", "Bebidas", "Postres". Puedes elegir un ícono visual para cada una.
Agrega tus productos
Dentro de cada categoría, agrega tus productos con nombre, descripción, precio y un ícono representativo. Marca los más vendidos como "Popular".
Comparte tu menú
Desde la sección QR, descarga el código QR de tu menú y colócalo en tus mesas, en el empaque de delivery o compártelo por WhatsApp e Instagram.
Módulo: Gestionar el menú
El módulo de menú es el corazón de la plataforma. Desde aquí controlas todo lo que ven tus clientes cuando escanean el QR. Los cambios se reflejan al instante en el menú público sin necesidad de publicar ni aprobar nada.
Módulo: Gestionar pedidos
El dashboard de pedidos es el panel central de operaciones. Muestra todos los pedidos en tiempo real sin necesidad de recargar la página — suenan una alerta y aparecen automáticamente.
Módulo: Domiciliarios
Gestiona tu equipo de reparto desde un solo lugar. No se requiere app descargable — tus repartidores usan el navegador del celular con un enlace único.
Módulo: Cupones de descuento
Crea promociones y descuentos que tus clientes aplican en el checkout. Funciona como los cupones de las grandes apps de delivery, pero sin pagar comisiones a nadie.
Módulo: Reseñas
Las reseñas se generan automáticamente tras cada entrega. El cliente recibe el formulario en su pantalla de seguimiento sin necesidad de tener cuenta registrada.
Módulo: Analytics
El módulo de analytics te da visibilidad sobre el desempeño del restaurante: ventas, productos, horarios y tendencias. Todo en una sola pantalla, sin hojas de cálculo.
Módulo: Código QR
El QR es la puerta de entrada de los clientes al menú digital. Se genera automáticamente con el enlace único de tu restaurante desde el momento en que creas la cuenta.
Módulo: Configuración del restaurante
Desde Configuración controlas todos los parámetros operativos que afectan la experiencia del cliente final.
Arquitectura
Multi-tenant
El multi-tenancy se implementa a nivel de aplicación, no de base de datos. Cada tabla tiene un campo restaurantId que filtra los datos. Las sesiones de NextAuth incluyen el restaurantId del usuario en el JWT, lo que permite filtrar automáticamente en cada endpoint.
// lib/restaurant-context.ts// Patrón: siempre filtrar por restaurantId de la sesiónconst session = await getServerSession(authOptions)const { restaurantId } = session.userconst orders = await prisma.order.findMany({where: { restaurantId }, // ← multi-tenant filterorderBy: { createdAt: "desc" }})
Estructura de carpetas
restaurant-menu-saas/├── app/│ ├── [domain]/ # Menú público del restaurante│ │ ├── page.tsx # Menú por slug│ │ └── pedido/ # Formulario de pedido│ ├── dashboard/ # Panel admin (protegido)│ │ ├── menu/ # Gestión de menú│ │ ├── pedidos/ # Gestión de pedidos│ │ ├── domiciliarios/ # Gestión de repartidores│ │ ├── analytics/ # Métricas y reportes│ │ ├── cupones/ # Cupones de descuento│ │ ├── resenas/ # Reseñas de clientes│ │ ├── templates/ # Selección de template│ │ ├── qr/ # Generador de QR│ │ └── configuracion/ # Config del restaurante│ ├── admin/ # Super Admin panel│ ├── api/ # API Routes (REST)│ │ ├── auth/ # NextAuth handler│ │ ├── orders/ # CRUD pedidos│ │ ├── restaurants/ # CRUD restaurantes│ │ ├── delivery/ # Domiciliarios│ │ ├── chat/ # Mensajes chat│ │ ├── coupons/ # Cupones│ │ ├── reviews/ # Reseñas│ │ ├── push/ # Web Push│ │ └── sse/ # Server-Sent Events│ ├── delivery/ # App repartidor│ ├── login/ # Autenticación│ ├── docs/ # Esta página│ └── deploy/ # Guía de despliegue├── components/ # Componentes reutilizables├── hooks/ # Custom hooks├── lib/ # Utilidades y config├── prisma/ # Schema y seeds└── public/ # Archivos estáticos
Routing con App Router
El slug del restaurante se captura con el segmento dinámico [domain]. Esto no requiere middleware de subdominios — sólo navegar a /seven-burger ya sirve el menú de ese restaurante.
// app/[domain]/page.tsxexport default async function PublicMenuPage({params,}: {params: { domain: string }}) {const restaurant = await prisma.restaurant.findUnique({where: { slug: params.domain },include: { categories: true, products: true }})if (!restaurant) return notFound()return <MenuClient restaurant={restaurant} />}
Base de Datos (Prisma)
Modelos
prisma/schema.prismaDiagrama de relaciones
Restaurant (1) ──┬── (*) User├── (*) Category ── (*) Product├── (*) Order ──── (*) ChatMessage│ └─── (1?) DeliveryPerson├── (*) DeliveryPerson├── (*) Coupon├── (*) Review├── (1?) RestaurantConfig├── (1?) Subscription└── (1?) OrderCounter
Autenticación
Implementada con NextAuth.js v4 usando el provider CredentialsProvider y el adapter de Prisma. Las contraseñas se hashean con bcryptjs.
Sistema de roles
Acceso total a todos los restaurantes. Gestiona suscripciones, activa/desactiva tenants y ve estadísticas globales. Panel en /admin.
Admin del restaurante. Gestiona su menú, pedidos, domiciliarios, cupones, reseñas y configuración. Panel en /dashboard.
Sesión y JWT
// lib/auth.ts — callbackscallbacks: {async jwt({ token, user }) {if (user) {token.id = user.idtoken.role = user.roletoken.restaurantId = user.restaurantId}return token},async session({ session, token }) {session.user.id = token.idsession.user.role = token.rolesession.user.restaurantId = token.restaurantIdreturn session}}
app/dashboard/layout.tsx que redirige a /login si no hay sesión activa.API Reference
getServerSession). Las rutas públicas (/api/public/*) no requieren autenticación.Pedidos
/api/ordersLista pedidos del restaurante en sesión (paginado)
/api/ordersCrear nuevo pedido (desde menú público)
/api/orders/[id]Obtener detalle del pedido
/api/orders/[id]Actualizar estado del pedido o datos
/api/orders/[id]Cancelar / eliminar pedido
Menú público
/api/public/[slug]Info del restaurante por slug (nombre, colores, template)
/api/public/[slug]/menuCategorías y productos disponibles
/api/coupons/validateValidar y aplicar cupón
/api/reviewsEnviar reseña del pedido
/api/customer/ordersHistorial de pedidos del cliente por teléfono
Dashboard
/api/dashboard/statsEstadísticas del día (total, pendientes, ingresos, pedidos/hora)
/api/analytics/[range]Análisis histórico: ventas, productos top, horas pico
/api/restaurants/[id]/configConfiguración completa del restaurante
/api/restaurants/[id]/configActualizar configuración (horarios, delivery, WhatsApp)
/api/restaurants/[id]/categoriesLista categorías del restaurante
/api/restaurants/[id]/categoriesCrear categoría
/api/restaurants/[id]/productsLista productos
/api/restaurants/[id]/productsCrear producto
/api/restaurants/[id]/products/[pid]Editar producto (precio, stock, disponibilidad)
/api/restaurants/[id]/products/[pid]Eliminar producto
Domiciliarios
/api/deliveryLista domiciliarios del restaurante
/api/deliveryRegistrar nuevo domiciliario
/api/delivery/[id]Actualizar datos o estado del domiciliario
/api/delivery/[id]Eliminar domiciliario
/api/orders/[id]/assignAsignar domiciliario a un pedido
Dashboard Admin
Funcionalidades
Pedidos en tiempo real
Vista Kanban con SSE. Los pedidos aparecen sin refrescar.
Gestión de menú
CRUD completo de categorías y productos con íconos Lucide.
Domiciliarios
Registro, asignación de pedidos y seguimiento de estado.
Cupones
Crear cupones de porcentaje o monto fijo con expiración.
Reseñas
Listado de valoraciones con filtro por estrella.
Chat en vivo
Mensajes bidireccionales por pedido vía SSE.
Analytics
Ventas, productos top, horas pico, resumen semanal.
QR del menú
Generación y descarga del QR del menú público.
Templates
Selección del tema visual del restaurante.
Push Notifications
Suscripción web push para nuevos pedidos.
Rutas del dashboard
/dashboardResumen y pedidos activos en tiempo real/dashboard/pedidosHistorial completo de pedidos con búsqueda/dashboard/menuGestión de categorías y productos/dashboard/domiciliariosCRUD de repartidores/dashboard/analyticsMétricas y gráficas de ventas/dashboard/cuponesGestión de cupones de descuento/dashboard/resenasReseñas recibidas de clientes/dashboard/templatesCambiar el tema visual del menú público/dashboard/qrGenerar QR del menú/dashboard/configuracionHorarios, delivery fee, WhatsApp, impresoraEl menú público es la cara del restaurante hacia los clientes. Es un Server Component que carga el restaurante y su menú en el servidor, y entrega un Client Component interactivo para el carrito y los pedidos.
Carrito de compras
Estado global con Zustand (useCartStore). Persistido en localStorage.
Filtro por categorías
Pills animadas para filtrar productos por categoría.
Búsqueda de productos
Filtro en tiempo real por nombre.
Cupón de descuento
Campo para aplicar cupón antes del checkout.
Validación de cupón
API call a /api/coupons/validate con el código y total.
Pedido completo
Formulario con nombre, teléfono, dirección y método de pago.
Seguimiento
Página /track/[phone] para ver el estado del pedido.
Chat con restaurante
Chat en vivo con el restaurante desde el pedido activo.
Reseña post-entrega
Formulario de valoración 1-5 estrellas al recibir el pedido.
Template dinámico
El diseño cambia según el template configurado (burger, pizza…).
Tiempo Real con SSE
En lugar de WebSockets (que requieren servidor persistente), el proyecto usa Server-Sent Events (SSE) nativos de Next.js. Esto es compatible con Vercel Edge, Railway y cualquier plataforma serverless.
// app/api/sse/route.ts — Servidorexport async function GET(req: Request) {const encoder = new TextEncoder()const stream = new ReadableStream({start(controller) {// Registrar cliente en el storeconst id = sseStore.addClient(restaurantId, controller)req.signal.addEventListener("abort", () => {sseStore.removeClient(restaurantId, id)})}})return new Response(stream, {headers: {"Content-Type": "text/event-stream","Cache-Control": "no-cache","Connection": "keep-alive",}})}// lib/sse-store.ts — Emitir eventosseStore.emit(restaurantId, {type: "NEW_ORDER",payload: order})
// hooks/useSSE.ts — Clienteexport function useSSE(restaurantId: string) {useEffect(() => {const es = new EventSource(`/api/sse?restaurantId=${restaurantId}`)es.onmessage = (e) => {const { type, payload } = JSON.parse(e.data)if (type === "NEW_ORDER") {addOrder(payload) // Zustand storeplaySound() // Sonido de alertashowToast(payload) // Toast notification}}return () => es.close()}, [restaurantId])}
Sistema de Templates
Cada restaurante puede seleccionar un template visual desde el dashboard. El template se guarda en Restaurant.template y se aplica dinámicamente al menú público.
Default
Verde oscuro clásico
Burger
Amarillo cálido
Pizza
Rojo tomate
Sushi
Negro minimalista
// hooks/useTemplate.tsexport function useTemplate(template: string) {return TEMPLATES[template] ?? TEMPLATES.default}// lib/templates.tsexport const TEMPLATES = {default: { primaryColor: "#06C167", fontFamily: "sans-serif" },burger: { primaryColor: "#F59E0B", fontFamily: "serif" },pizza: { primaryColor: "#EF4444", fontFamily: "sans-serif" },sushi: { primaryColor: "#1F2937", fontFamily: "mono" },}
Módulo de Domiciliarios
El módulo de domiciliarios permite gestionar el ciclo completo de entrega: registrar repartidores, asignar pedidos y hacer seguimiento.
Registrar domiciliario
Desde /dashboard/domiciliarios: nombre, teléfono y tipo de vehículo. Queda asociado al restaurantId.
Pedido listo → Asignar
Cuando el pedido pasa a estado READY y es tipo DELIVERY, aparece el botón "Asignar domiciliario".
Vista del repartidor
El repartidor ve sus pedidos asignados en /delivery (sin login requerido, solo por enlace compartido).
Confirmación de entrega
El repartidor marca el pedido como entregado, actualizando el estado a DELIVERED.
Módulos Extra
Chat en vivo
Cada pedido tiene un canal de chat bidireccional entre el cliente y el restaurante. Los mensajes se entregan vía SSE en tiempo real. El modelo ChatMessage almacena cada mensaje con el campo sender (CUSTOMER|RESTAURANT).
Cupones de descuento
Los cupones pueden ser de tipo PERCENTAGE (ej: 15%) o FIXED (ej: $5000 de descuento). Soportan pedido mínimo, límite de usos y fecha de expiración. Se validan en el checkout del menú público.
Reseñas
Cuando un pedido se entrega (DELIVERED), el cliente recibe un enlace para calificar su experiencia de 1 a 5 estrellas. Las calificaciones se promedian y se actualizan en Restaurant.avgRating.
Push Notifications
Implementadas con la Web Push API. El admin puede suscribirse desde el dashboard y recibir notificaciones del navegador cuando llega un nuevo pedido, incluso con la pestaña cerrada. La suscripción se guarda en la base de datos.
// hooks/usePushNotifications.tsconst { subscribe, isSubscribed } = usePushNotifications()// Suscribir al usuarioawait subscribe() // Llama a /api/push/subscribe// Al crear un pedido, el servidor emite:// lib/webpush.tsawait sendPushNotification(restaurantId, {title: "🛎️ Nuevo pedido #1042",body: "Juan García · $45.000 · DELIVERY"})
Suscripciones SaaS
El modelo Subscription gestiona el estado de cada restaurante: TRIAL (gratuito por tiempo limitado), ACTIVE, SUSPENDED o EXPIRED. Un cron job (/api/cron/check-subscriptions) revisa diariamente las suscripciones próximas a vencer y envía emails de aviso.
Custom Hooks
useSSE(restaurantId)hooks/useSSE.tsConecta al stream SSE del restaurante. Dispara callbacks cuando llegan nuevos pedidos o mensajes de chat.
useCurrentRestaurant()hooks/useCurrentRestaurant.tsLee el restaurantId de la sesión y fetch la info del restaurante actual. Útil en componentes del dashboard.
useTemplate(template)hooks/useTemplate.tsRetorna la config de colores y fuente del template seleccionado. Usado en el menú público.
useRestaurantAuth()hooks/useRestaurantAuth.tsVerifica sesión y redirige a /login si no está autenticado. Wrapper de useSession.
usePushNotifications()hooks/usePushNotifications.tsGestiona la suscripción a Web Push: subscribe, unsubscribe e isSubscribed.
Variables de Entorno
.env en la raíz del proyecto. Nunca lo subas a git (está en .gitignore).DATABASE_URLRequeridaURL de conexión a la base de datos.
# file:./prisma/dev.db ← SQLite local# postgresql://user:pass@host:5432/db ← ProducciónNEXTAUTH_SECRETRequeridaClave secreta para firmar tokens JWT. Mínimo 32 caracteres.
# openssl rand -base64 32 ← Genera unaNEXTAUTH_URLRequeridaURL base de la app (sin slash final).
# http://localhost:3000 ← Dev# https://mi-app.railway.app ← ProdNEXT_PUBLIC_VAPID_PUBLIC_KEYOpcionalClave pública VAPID para Web Push Notifications.
# Genera con: npx web-push generate-vapid-keysVAPID_PRIVATE_KEYOpcionalClave privada VAPID (server-side only).
# Del mismo comando anteriorVAPID_EMAILOpcionalEmail de contacto para VAPID.
# mailto:admin@tudominio.comFlujo Completo de Pedidos
Desde que el cliente abre el menú hasta que el repartidor confirma la entrega:
Cliente accede a /[slug], navega el menú y agrega productos al carrito.
Completa el formulario (nombre, teléfono, dirección, método de pago). Aplica cupón si tiene.
POST /api/orders crea el pedido en BD y emite evento SSE al dashboard del restaurante.
El cocinero acepta y cambia el estado. Se emite nuevo SSE y el cliente ve el update en /track.
Plato listo. Si es DELIVERY, aparece el botón "Asignar domiciliario" en el dashboard.
Se selecciona un repartidor disponible. El pedido aparece en su app /delivery.
El repartidor confirma la entrega. El cliente recibe el enlace para dejar reseña.
Restaurant Menu SaaS · Documentación técnica completa