Marqués — Sistema de Reservas
liveSistema de reservas full-stack para un restaurante: API REST (Node, Express, PostgreSQL) y frontend Next.js desplegados por separado y comunicándose en producción.
por qué existe
Contexto
Sistema de reservas real para un restaurante: comprobar disponibilidad de mesa sin dobles reservas, gestionar el ciclo de vida de una reserva (de pendiente a confirmada o cancelada) y permitir reseñas solo a quien realmente tuvo una reserva confirmada — con un backend y un frontend que se despliegan y evolucionan por separado.
lo más difícil
Reto técnico
El manejo de errores está centralizado en un único middleware (errorHandler) que traduce excepciones de negocio (AppError) y códigos nativos de Postgres (23505 duplicado, 23503 FK inválida) a respuestas HTTP consistentes, evitando try/catch repetido en cada controlador (patrón asyncHandler). En producción, el backend en Fly.io "duerme" tras un rato de inactividad: la primera petición tras la inactividad puede tardar varios segundos en responder (cold start) mientras la máquina arranca. La mitigación es un workflow de GitHub Actions (keepalive.yml) que hace ping periódico a /health — el mismo endpoint pensado originalmente para probes de un orquestador, reutilizado para mantener la máquina despierta.
el porqué, no solo el qué
Decisiones técnicas
Disponibilidad garantizada a nivel de base de datos, no solo de aplicación
La restricción UNIQUE(table_id, date, time) en PostgreSQL evita reservas duplicadas de la misma mesa en el mismo turno a nivel de base de datos, además de la comprobación de disponibilidad en la capa de aplicación — la garantía real vive en la BD, no solo en el código.
Autorización por rol a nivel de middleware, no de controlador
Las reglas de acceso (customer / admin) quedan explícitas en la definición de las rutas en vez de esparcidas dentro de cada handler, así son visibles de un vistazo.
El frontend no tiene lógica de negocio propia
restaurant-web no tiene base de datos ni reglas propias: toda la comunicación con el backend pasa por una única variable de entorno (NEXT_PUBLIC_API_URL) y un cliente fetch propio, sin proxy ni API routes intermedias — separación limpia entre dos despliegues independientes.
dónde está hoy
Resultado
Ambos servicios están desplegados y comunicándose en producción de forma independiente: la web en Vercel, la API en Fly.io, vía HTTPS y JWT. La API tiene su propia suite de tests (Jest + Supertest, mocks de Postgres, sin dependencias externas) centrada en lógica de negocio — disponibilidad, ownership, reglas de reseñas — no en CRUDs triviales, y CI ejecuta lint, typecheck y tests en cada push y pull request.
con qué está construido
Stack
Next.js 14 (App Router) + React 18
frontendEncaja con un sitio mayormente de contenido (home, carta) más unas pocas rutas autenticadas (reservas, panel de administración).
TypeScript
toolingTipos compartidos (User, Reservation, Table, Review) entre las llamadas a la API y la UI.
Tailwind CSS
frontendEstilado rápido para un sitio con bastante superficie (home editorial, carta, panel admin) sin mantener una hoja de estilos propia.
Node.js + Express 5
backendAPI REST clásica; no hace falta un framework full-stack cuando el cliente ya es un proyecto Next.js separado.
PostgreSQL
backendRelaciones reales entre reservas, mesas y reseñas (FKs, UNIQUE constraints) que encajan mejor en un modelo relacional que en un documento.
JWT + bcryptjs
backendAutenticación stateless entre dos servicios desplegados por separado (Vercel / Fly.io), sin sesión de servidor compartida.
Fly.io
infraDespliegue del backend con máquinas que se apagan en inactividad: barato para un proyecto de portfolio, a cambio de cold starts.
Vercel
infraDespliegue del frontend Next.js con CI/CD nativo desde main.
GitHub Actions
toolingCI (lint, typecheck, tests) en la API, y un workflow de keepalive que hace ping a /health para mitigar el cold start de Fly.io.
pruébalo tú
Demo en vivo
La web es instantánea, pero la API vive en Fly.io y duerme por inactividad: la primera petición tras un rato puede tardar unos segundos mientras arranca la máquina.