Synaplan
Open-source AI platform for companies: self-hosted AI agents, GDPR-compliant chatbot widget, RAG knowledge base, WhatsApp and Outlook AI, MCP, multi-model routing. Docker, Kubernetes or hosted in Germ
Reserva tu turno de peluqueria online, sin llamadas y sin sena previa.
Translation pending
BusinessTypeScript · PLpgSQLGitHub @radawskiOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Loading the full README. The saved excerpt is shown below.
Sistema de turnos y gestión comercial para una peluquería de operación unipersonal: un profesional, una estación de trabajo. • Portal público (/, /reservar, /turno/[token]): el cliente elige servicio, día y horario, deja sus datos y recibe un link propio para seguir su turno. Sin pago ni seña previa. • Panel privado (/admin): agenda, bandeja de solicitudes, catálogo de servicios y configuración de disponibilidad.Read the full README ↗
Sistema de turnos y gestión comercial para una peluquería de operación unipersonal: un profesional, una estación de trabajo.
/, /reservar, /turno/[token], /baja/[token]): el
cliente elige servicio, día y horario, deja sus datos y recibe un link propio
para seguir su turno. Sin pago ni seña previa. Un botón flotante abre
WhatsApp con el local./admin): Hoy, agenda, bandeja de solicitudes, caja por
categoría y, desde Más, servicios, productos, disponibilidad y alta de
clientes.South America (São Paulo)).anon y la clave service_role.cp .env.local.example .env.local
Completar NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY y
SUPABASE_SERVICE_ROLE_KEY.
Generar también MANAGE_TOKEN_SECRET, con el que se derivan los links de
autogestión (/turno/[token]) a partir del id de cada turno:
node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))"
MANAGE_TOKEN_SECRETtiene que tener el mismo valor en.env.localy en Vercel: los dos entornos usan la misma base, y un turno reservado en uno puede recibir emails armados en el otro. Si cambia, los links de los turnos ya reservados dejan de coincidir.
Para los emails y la tarea diaria completar también:
NEXT_PUBLIC_SITE_URL: la URL pública (https://houssestudio.com.ar en
producción), base de los links de los mails.RESEND_API_KEY, EMAIL_FROM (remitente verificado en Resend) y
ADMIN_EMAIL (la casilla del peluquero, que recibe los avisos).CRON_SECRET: lo exige /api/cron/reminders, que Vercel Cron llama todos
los días a las 10 de Buenos Aires (vercel.json). Se genera con el mismo
comando que MANAGE_TOKEN_SECRET y se carga igual en Vercel.
SUPABASE_SERVICE_ROLE_KEYsaltea todas las políticas de seguridad de la base. Nunca se commitea ni se expone al navegador: los módulos que la usan importanserver-only, así que el build falla si alguna vez terminan alcanzados por código de cliente.
En el SQL Editor de Supabase, ejecutar en orden los archivos de
supabase/migrations/:
0001_schema.sql — tablas, tipos y la restricción anti-solapamiento.0002_rls.sql — Row Level Security.0003_seed.sql — servicios y horario comercial iniciales.0004_dni.sql — DNI como identidad del cliente.0005_ventana_de_reserva.sql — ventana de reserva configurable.0006_horario_partido.sql — segundo tramo horario (reemplazado por la 0009).0007_ventas_sueltas.sql — ventas sueltas y cobro atómico de turnos.0008_ventas_productos.sql — venta de productos además de servicios.0009_horario_bloques.sql — horario con N bloques por día.0010_retira_columnas_horario.sql — retira las columnas de horario
anteriores a la 0009.0011_email_aceptacion.sql — tipo de email para el turno confirmado.0012_productos_y_categorias.sql — categorías y productos, ventas
sueltas con varios ítems y la función que las registra.0013_email_vuelta.sql — tipo de email del recordatorio para volver.
Va sola: Postgres no deja usar un valor de enum recién agregado en la
misma ejecución.0014_recordatorio_vuelta.sql — baja de ese recordatorio
(customers.comeback_opt_out_at) y el índice que impide repetirlo.Cada archivo depende de los anteriores: la 0009 copia a bloques el horario que dejan cargado la 0003 y la 0006, y la 0010 recién después borra esas columnas.
No hay registro público: la cuenta se crea a mano.
User UID y habilitarlo como administrador desde el SQL Editor:insert into public.admins (user_id) values ('<pegar-el-user-uid>');
npm run dev
El portal público queda en http://localhost:3000 y el panel en
http://localhost:3000/admin.
npm run dev
npm test
npm run typecheck
npm run build
| Capa | Ubicación |
|---|---|
| Motor de disponibilidad | lib/availability.ts — función pura, cubierta por tests |
| Lecturas del portal público | lib/data/availability.ts, lib/data/public.ts |
| Lecturas del panel | lib/data/appointments.ts |
| Escrituras | lib/actions/ — una Server Action por dominio |
| Emails | lib/email/ — plantillas puras (con tests) y envío por Resend |
| Tarea diaria | app/api/cron/reminders/route.ts — los dos recordatorios, aislados |
| Navegación por fecha (Agenda y Caja) | components/admin/period-navigation.tsx, lib/period-nav.ts |
| Esquema | supabase/migrations/ |
Tres decisiones que conviene conocer antes de tocar el código:
Los horarios se encadenan según la duración del servicio. Dentro de cada hueco libre los slots arrancan en el borde del hueco y avanzan de a la duración del servicio elegido. Aprovecha mejor la agenda de una estación única, y trae como consecuencia que los horarios ofrecidos cambien según el servicio — por eso el flujo de reserva pide el servicio antes que la hora.
El portal público nunca habla directo con Postgres. RLS está activo en todas
las tablas y anon no tiene ninguna política. Todo el tráfico público pasa por
Server Actions que usan la service_role del lado del servidor, así que hay una
sola superficie de entrada que auditar.
Contra el doble booking manda la base, no el frontend. Una restricción de exclusión GiST sobre el rango de cada turno impide dos turnos activos solapados. La validación previa mejora el mensaje de error, pero entre leer la disponibilidad y escribir el turno siempre hay una ventana: la que cierra la base.
Todo se guarda en UTC (timestamptz) y se muestra en
America/Argentina/Buenos_Aires. Las fechas de agenda se identifican por su
clave local yyyy-MM-dd y no por un Date, porque un Date arrastra una hora
que al cruzar zonas corre los turnos de día.
Fase 1 completa: reservas públicas, agenda, solicitudes, servicios y disponibilidad.
Fase 2 completa (ver SPEC.md): emails transaccionales con el diseño de
la web, autogestión de cancelaciones, registro de cobros, venta suelta con
carrito de catálogo, productos con categorías y cierre de caja por categoría.
Fase 3 completa: recordatorio automático el día anterior al turno, con
dominio propio para los emails ([email protected]).
Mejoras de octubre completas (ver SPEC.md §9 y
tasks/plan-mejoras-octubre.md): botón de WhatsApp, alta de clientes de otra
zona, estados de carga del panel y recordatorio "Ya pasó un mes" con baja.
Pendiente (ver tasks/todo.md): pasar ADMIN_EMAIL a la casilla del
peluquero, Vercel Pro y copias de seguridad de Supabase antes de usarla con
clientes reales. CRM y reportes quedan para más adelante.