Personal / Cliente · 2025
Single Resto
Web de pedidos y dashboard para un restaurante
Plataforma para restaurantes con web de pedidos y dashboard admin, self-hosted con Docker en un VPS y tomando pedidos reales con MercadoPago.
Resumen
Single Resto es un sistema para restaurantes en dos partes: una web pública de pedidos para los comensales y un dashboard de administración para el que maneja el local. Empezó como un trabajo para un cliente y terminó siendo un template white-label que sirve varias marcas desde un solo codebase, desplegado en un VPS que administro yo.
El problema
El cliente tomaba pedidos por WhatsApp y los anotaba a mano. Dos cosas se rompían todo el tiempo: pedidos que entraban con el local cerrado y pedidos que se perdían entre el mensaje y la cocina. Tampoco había catálogo en ningún lado, así que los precios vivían en una foto de una carta impresa.
Y cuando el primer restaurante funcionó, el siguiente pidió lo mismo con su marca. Reescribir el proyecto por cliente no era opción.
Decisiones
- Server Sent Events en vez de WebSockets para el panel de pedidos: el flujo es unidireccional, el servidor empuja los pedidos nuevos al dashboard, y SSE da eso con una fracción de las piezas móviles y la reconexión resuelta por el navegador.
- Horarios aplicados en el checkout, no solo mostrados: la web bloquea el pedido fuera de horario y explica por qué, porque el costo real era el pedido que entra con la cocina cerrada.
- MercadoPago como pasarela: es lo que el cliente final ya tiene en Argentina, y su confirmación cierra el pedido automáticamente en vez de exigir una revisión manual.
- Drizzle ORM con PostgreSQL y un monorepo compartido entre la web y el dashboard: productos, categorías y configuración del local son un solo schema y un solo juego de tipos, así la carta pública nunca se desfasa de lo que editó el dashboard.
- White-label desde el principio: temas, logos, catálogos y configuración por tenant sobre el mismo codebase, cada uno con su URL. Un cliente nuevo es una configuración, no un fork.
- Self-hosted en un VPS de Oracle Cloud con Docker Compose: container de la app, container de PostgreSQL, un container con una API de WhatsApp self-hosted y Nginx como reverse proxy para SSL y routing. Estabilizar el servicio de WhatsApp fue lo más difícil, y es lo que permite que el dashboard avise al cliente por su cuenta.
- Cloudflare R2 para las imágenes de productos: object storage detrás de una CDN en vez de archivos en el disco del VPS, así las imágenes sobreviven a los redeploys y cargan rápido.
- Un flag de mantenimiento en la configuración: el dueño puede sacar la web de servicio al instante sin llamarme.
- Logging estructurado con Wide Events (canonical log lines) en vez de console.log dispersos: un evento por acción con todo el contexto, que es lo que después permite preguntar la tasa de error por acción.
Flujo completo de pedido
Checkout y alertas
Dashboard de administración
El dashboard le da al dueño control sobre toda la operación:
- Panel de pedidos: los pedidos entrantes aparecen casi en tiempo real vía SSE y pasan por aceptado, en preparación y completado, con soporte de impresión de tickets.
- CRUD de productos: crear, editar y eliminar productos con subida de imágenes a R2, categorías, precios y toggles de visibilidad.
- Configuración del local: horarios, zonas de delivery, dirección y modo mantenimiento.
- Acciones de WhatsApp: botones rápidos que abren mensajes pre-formateados al cliente con novedades del pedido.
Gestión de pedidos
Gestión de productos
Subida de imágenes
Configuración del local
Demo white-label
Modo mantenimiento
Ejemplo: Wide Event estructurado
{
"timestamp": "2026-02-19T16:40:29.786Z",
"request_id": "mltot5y2-3pf6a",
"service": "single-resto",
"environment": "development",
"action_name": "updateProduct",
"duration_ms": 23,
"outcome": "error",
"user": {
"id": "LE58YKCC",
"role": "admin"
},
"error": {
"type": "ValidationError",
"code": "VALIDATION_ERROR",
"message": "Error de validación. Revise los campos."
}
}Qué compró el logging
- Un solo evento por acción con contexto completo (user, duration, outcome).
- Detección automática de errores y resultados usando byethrow.
- Queries analíticas: tasa de error por acción, tiempo promedio de respuesta por tenant.
- Trazabilidad completa con request_id único por solicitud.
- Mucho menos ruido en los logs que con console.log dispersos.
Resultado
La plataforma corre en producción en el VPS, tomando pedidos con pago, imprimiendo tickets y avisando a los clientes por WhatsApp, con el mismo codebase sirviendo a más de una marca.
Fue el proyecto que más me alejó del frontend típico: Docker multi-container, networking entre containers, SSL, uptime, una pasarela de pagos y storage de producción. También fue donde apliqué por primera vez, sobre un sistema vivo con clientes reales, la planificación y la organización que hasta ahí solo había estudiado en las materias de ingeniería de software.