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.

Rol

Desarrollador Principal

Contexto

Personal / Cliente

Año

2025

Enlaces

Tecnologías: Next.jsTailwindCSSReactDockermonorepoPostgreSQLDrizzle ORMmercadopago

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.

Web pública del restaurante

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

Demo completa: explorar productos, agregar al carrito, checkout con control de horarios y colocación del pedido

Checkout y alertas

Alertas de cambios no guardados, opciones de edición y checkout bloqueado fuera de horario

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

Panel de pedidos: recibir, gestionar y procesar pedidos entrantes

Gestión de productos

Creación de productos, acciones de tabla y gestión de categorías

Subida de imágenes

Flujo de subida de imágenes con almacenamiento en R2

Configuración del local

Actualización de dirección del local y configuración de delivery

Demo white-label

Mismo codebase, distinta marca: alternando entre temas de restaurantes

Modo mantenimiento

Activar modo mantenimiento para sacar el sitio de servicio al instante
Wide Events en acción: salida estructurada en terminal

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.