Synaplan
기업을 위한 오픈소스 AI 플랫폼
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Sistema de gestión para una clínica dental. Incluye:
Stack: Next.js 16 (App Router), TypeScript, Tailwind CSS 4, Supabase (Postgres, Auth, Storage y RLS), Motion, Recharts, Zod, @react-pdf/renderer y ExcelJS.
El prototipo original y las instrucciones del bot están en referencia/, solo como referencia de reglas de negocio.
Requisito: Node 22 o superior.
npm install
npm run dev # http://localhost:3000
Mientras .env.local no tenga las claves de Supabase, la app arranca en vista previa:
/login eliges un rol (Administrador, Odontólogo o Recepción).npm run dev vuelven los datos originales. Ajustes → Datos de ejemplo → Reiniciar hace lo mismo.| Comando | Qué hace |
|---|---|
npm run dev | Servidor de desarrollo |
npm run build && npm start | Compilación y servidor de producción |
npm test | Pruebas de las reglas críticas (saldo, recordatorios, citas repetidas, choque de horario, odontograma, horarios libres) |
npm run typecheck | Revisión de tipos |
npm run seed:generar | Regenera supabase/seed.sql desde src/datos/ejemplo.ts |
npm run superadmin -- correo "Nombre" | Crea la cuenta de superadministrador de Rido |
npm run probar:rls | Prueba las políticas RLS contra tu proyecto de Supabase |
Crear el proyecto en supabase.com → New project.
Copiar las claves en .env.local. Están en Project Settings → API Keys:
| Variable | Clave |
|---|---|
NEXT_PUBLIC_SUPABASE_URL | URL del proyecto |
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY | publishable (o anon public) |
SUPABASE_SECRET_KEY | secret (o service_role). Solo servidor, nunca en git |
Crear las tablas. En SQL Editor ejecuta en orden:
supabase/migrations/20261021000001_base.sql (clínica, equipo, roles, auditoría)supabase/migrations/20261021000002_operacion.sql (pacientes, citas, recordatorios, caja, cotizaciones)supabase/migrations/20261021000003_clinico.sql (panorámicas, odontograma, IA y buckets privados)supabase/migrations/20261022000004_inventario.sql (inventario y sus movimientos)supabase/seed.sql (opcional: datos de ejemplo con es_ejemplo = true)Con la CLI también se puede: npx supabase login, npx supabase link --project-ref <ref> y npx supabase db push.
Configurar Auth:
Sign In / Providers → Email: desactiva "Allow new users to sign up". Solo entra quien fue invitado.
URL Configuration:
http://localhost:3000 para probar.<tu dominio>/** a Redirect URLs.Emails → Templates: en Invite user y Reset password, cambia el enlace por:
{{ .SiteURL }}/auth/confirmar?token_hash={{ .TokenHash }}&type=invite&siguiente=/cuenta/contrasena
En Reset password usa .
| Rol | Puede |
|---|---|
| Administrador | Todo, incluidos el equipo, el catálogo, los ajustes y la actividad (auditoría) |
| Odontólogo | Agenda, todos los pacientes, cotizaciones, odontograma y panorámicas. Sin caja |
| Recepción | Agenda, pacientes, recordatorios, caja, cotizaciones e inventario. No ve radiografías, solo sabe si el paciente tiene |
En el inventario, todo el equipo puede poner y sacar existencias (queda en el historial quién lo hizo). Solo administración y recepción crean, editan o borran artículos.
/api/panoramicas/[id]: revisa el rol y redirige a una URL firmada que vence en 2 minutos. Nunca hay URLs públicas.Flujo existente: "Clínica Dental · Bot de recordatorios WhatsApp" (ID tA98h2EQOxFvT49K).
Todos piden Authorization: Bearer <N8N_TOKEN>. Pon un valor largo y aleatorio en N8N_TOKEN. En la vista previa el token es vista-previa.
| Método y ruta | Para qué |
|---|---|
GET /api/n8n/recordatorios | Recordatorios que deben salir hoy: avisos del mismo día, avisos de 3 días antes y fallidos para reintentar |
POST /api/n8n/recordatorios/resultado | Marca cada uno como enviado o fallido |
POST /api/n8n/respuestas | Preparado y apagado. Recibe «Confirmo» o «Aplazar» (N8N_RESPUESTAS_ACTIVAS=true) |
Respuesta de GET /api/n8n/recordatorios:
{ "fecha": "2026-10-21", "total": 2, "recordatorios": [
{ "recordatorioId": "…", "citaId": "…", "tipo": "previo", "telefono": "18095550111", "nombre": "Ana",
"fecha": "sábado, 24 de octubre", "hora": "9:00 a. m.", "doctor": "Dra. Laura Peña",
"clinica": "Clínica Dental Sonrisa", "direccion": "Calle del Sol #45, Santiago", "mensaje": "…" } ],
"sinTelefono": [] }
Cuerpo de POST /api/n8n/recordatorios/resultado:
{ "resultados": [ { "recordatorioId": "…", "estado": "enviado" },
{ "recordatorioId": "…", "estado": "fallido", "error": "Número sin WhatsApp" } ] }
45 7 * * *.America/Santo_Domingo (Settings del flujo → Timezone).https://<tu-dominio>/api/n8n/recordatorios.Authorization: Bearer <N8N_TOKEN>.recordatorios.Plantilla: recordatorio_cita_3dias si tipo es previo, recordatorio_cita_hoy si es hoy. Usa un nodo If o una expresión.
Variables:
| Variable | Campo |
|---|---|
{{1}} | nombre |
{{2}} | fecha |
{{3}} | hora |
{{4}} | doctor |
{{5}} | clinica |
{{6}} | direccion |
Destinatario: telefono.
En Settings del nodo, activa Continue On Fail para que un error no detenga a los demás.
{ recordatorioId, estado: $json.error ? "fallido" : "enviado", error: $json.error?.message }.resultados.https://<tu-dominio>/api/n8n/recordatorios/resultado, con la misma credencial y el cuerpo { "resultados": … }.Lo que el bot no pueda enviar aparece en Recordatorios → Fallidos para enviarlo a mano.
POST /api/n8n/respuestas:
{ "citaId", "respuesta": "confirmo" } → la cita pasa a confirmada.{ "citaId", "respuesta": "aplazar" } → devuelve 3 horarios libres del mismo odontólogo, dentro del horario de la clínica.{ "citaId", "respuesta": "aplazar", "eleccion": { "fecha", "hora" } } → mueve la cita y reprograma los recordatorios.N8N_RESPUESTAS_ACTIVAS=true.| Función | En la vista previa |
|---|---|
| Cotizaciones | El asistente responde con preguntas y respuestas fijas, como un FAQ. Arma la cotización solo con servicios del catálogo y sus precios exactos |
| Panorámicas | Apagadas por defecto (Ajustes → IA). Al activarlas, «Pedir sugerencia» marca zonas fijas para revisar |
Las sugerencias de panorámicas siempre llevan la etiqueta «Sugerencia, no es diagnóstico. Confirmar por el odontólogo». Nunca cambian el odontograma solas: el odontólogo acepta o descarta cada una.
Para usar un modelo real, solo se cambia src/lib/ia/asistente.ts y la llamada se hace desde el servidor. La clave va en .env.local y nunca en el navegador.
.env.local en Settings → Environment Variables. NEXT_PUBLIC_SITE_URL debe ser el dominio real.https://<tu-dominio>/demo.La vista previa en memoria es solo para demostrar en local. En Vercel, conecta Supabase.
src/
app/
(auth)/ login, recuperar, crear contraseña
(app)/ pantallas con barra lateral
resumen/ agenda/ recordatorios/ caja/ cotizaciones/ ajustes/
pacientes/[id]/clinico/ visor, comparador, odontograma, panel de pieza
demo/ demostración pública del bot
api/ panorámicas, comprobantes, Excel, PDF y endpoints de n8n
design/tokens.css ÚNICO archivo del sistema de diseño (oscuro y claro)
components/bona/ tarjetas, calendario, anillos, chips, mini gráficas…
components/ui/ formularios, diálogos, pestañas, menús y avisos
datos/ repo.ts (Supabase o memoria) y datos de ejemplo
lib/reglas/ reglas de negocio puras y probadas
lib/ia/ asistente (modo de prueba)
proxy.ts protege rutas y refresca la sesión
supabase/migrations/ SQL versionado con RLS
scripts/ superadmin, prueba de RLS y generador del seed
tests/ Vitest
npm run probar:rls (al cerrar la venta).type=recoveryCrear el superadministrador:
npm run superadmin -- [email protected] "Tu Nombre"
Imprime una contraseña temporal. Cámbiala desde «¿Olvidaste tu contraseña?».
Invitar al equipo desde Ajustes → Equipo.
Verificar la seguridad con npm run probar:rls. Crea usuarios temporales por rol, comprueba qué ve cada uno y los borra.