Proyecto demo

Turnia: SaaS de turnos para barberías, en tres partes

Landing, app de gestión y bot de Telegram sobre la misma base de datos

Producto ficticio de portafolio con código y despliegue reales: landing en Next.js, agenda con drag & drop y ⌘K sobre Postgres, y un bot de Telegram que reserva y recuerda turnos. Todo en vivo; los datos de la demo se reinician cada noche.

turnia-saas.vercel.app
Captura de Turnia

Contexto

Una barbería vive de su agenda: turnos que se pisan, clientes que no confirman, cambios de último momento y la caja del día. Turnia es el ejercicio de resolver eso como producto completo —vender, operar y atender por chat— sin duplicar reglas de negocio en tres lugares, y con un bot que cualquiera puede escribir sin tirar abajo el servidor.

Solución

Una sola app Next.js 16 con Drizzle sobre Postgres (Neon) hace todo el trabajo: agenda por profesional (crear en hueco, arrastrar, estirar, ⌘K que crea turnos por texto, deshacer), clientes, membresías y caja del día; link público de reserva con disponibilidad real.

El bot es una máquina de estados pura dentro de la app; n8n solo es el canal entre Telegram y la API.

turnia-saas.vercel.app
Landing de Turnia
Landing de Turnia
turnia-saas.vercel.app
Agenda de la app, en escritorio
Agenda de la app, en el celular
Agenda de la app, en escritorio · Agenda de la app, en el celular
turnia-saas.vercel.app
Panel del bot de Telegram
El bot en el celular
Panel del bot de Telegram · El bot en el celular

Arquitectura

Flujo en 6 pasosUna reserva por chat
  1. El cliente escribe al bot en Telegram

  2. Caddy solo deja pasar las subredes de Telegram y cuerpos de hasta 64 KB

  3. n8n normaliza el mensaje (tope global de 60 por minuto) y lo manda a la API con clave

  4. La app corre la máquina de estados y responde con texto y botones (30 por chat y hora)

  5. El turno se guarda en Postgres y aparece en la agenda

  6. Un cron de n8n pide los recordatorios de 24 h y 2 h; el de 24 h trae «Confirmo» / «Cambiar»

Piezas

  • Next.js 16 en Vercel: landing, app /app/*, reserva pública y API /api/bot/*
  • Postgres en Neon con Drizzle ORM (driver serverless en producción)
  • n8n: bot, recordatorios, reserva pública y lista de espera
  • Caddy delante de n8n, con filtro de subredes de Telegram
  • Cron de Vercel para reiniciar la demo

Decisiones técnicas

  1. El cerebro vive en la app; n8n es el canal

    Por qué: las reglas de disponibilidad, choques y caja existen una sola vez y las usan la agenda, la reserva pública y el bot; pasar de Telegram a WhatsApp es cambiar dos nodos.

    Trade-off la app tiene que exponer una API propia, protegida por clave, y mantenerla.

  2. Defensa en tres capas para un bot público

    Borde: Caddy responde 403 fuera de las subredes de Telegram. n8n: tope global de 60 updates por minuto. App: 30 mensajes por chat y hora y 200 por local y día, con respuesta 429.

    Trade-off si Telegram agrega rangos de IP, el bot deja de responder hasta actualizar el filtro.

  3. Recordatorios idempotentes

    Al listar, la API crea el mensaje en estado «pendiente» y no lo repite si ya existe para ese turno. Por qué: el cron puede correr dos veces sin mandar duplicados.

    Trade-off una tabla más de estado en Postgres.

  4. Demo pública que se reinicia sola

    Un cron de Vercel diario (07:00 UTC) llama a un endpoint con Bearer CRON_SECRET y devuelve al seed solo el local demo. Por qué: cualquiera puede entrar y editar sin dejarla rota.

    Trade-off lo que haga un visitante se borra esa noche.

  5. Reglas de choque en el servidor, interfaz optimista

    El arrastre mueve el turno al instante; si el servidor detecta un choque, la tarjeta vuelve a su lugar con un aviso.

    Trade-off hay que mantener la lógica de revertir en el cliente.

Resultados

  • Landing, app y bot en vivo (HTTP 200 el 2-oct-2026)
  • Sin cortes de layout en 7 anchos, de 320 a 2560 px
  • 0 errores y 0 avisos de consola; 0 peticiones externas
  • Build en verde, tsc y eslint limpios
  • Prueba automatizada del bot: 401, 400, reserva completa, recordatorio sin duplicar, 409 por chat ajeno y los dos límites (31 llamadas reales)

Stack

  • Next.js 16
  • React 19
  • TypeScript
  • Tailwind 4
  • Drizzle ORM
  • Postgres (Neon)
  • Server Actions
  • n8n
  • Telegram Bot API
  • Caddy
  • Vercel