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
Agenda, prontuário e financeiro do seu consultório em um só lugar.
Translation pending
BusinessGitHub @splash-sitesOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Repository information has not been collected yet.
No updates detected yet. GitHub releases will appear here.
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗SaaS multi-tenant para gestão de consultório individual (agenda, prontuário, financeiro). Ver CLAUDE.md para a especificação completa do produto.
.env.example para .env.local e preencha com as credenciais do seu projeto Supabase (URL e anon key em Project Settings → API).https://www.googleapis.com/auth/calendar já solicitado no login para reuso na integração de Agenda/Meet).supabase/migrations/ no seu projeto Supabase (via SQL Editor do dashboard, ou npx supabase db push usando o Supabase CLI).npm install
npm run dev
npm run test # roda uma vez
npm run test:watch # modo watch
Os testes em tests/rls/ validam que uma psicóloga nunca acessa dados de outra conta. Exigem uma instância local do Supabase (precisa de Docker):
npx supabase start # sobe o stack local (primeira vez baixa imagens)
npx supabase db reset # aplica as migrations de supabase/migrations/
Depois, exporte as variáveis apontando para o local e rode a suíte. A anon key sai de npx supabase status:
# bash
export SUPABASE_TEST_URL="http://127.0.0.1:54321"
export SUPABASE_TEST_ANON_KEY="$(npx supabase status -o json | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>console.log(JSON.parse(s).ANON_KEY))')"
export SUPABASE_TEST_SERVICE_ROLE_KEY="$(npx supabase status -o json | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>console.log(JSON.parse(s).SERVICE_ROLE_KEY))')"
npm test
# PowerShell
$env:SUPABASE_TEST_URL = "http://127.0.0.1:54321"
$env:SUPABASE_TEST_ANON_KEY = (npx supabase status -o json | ConvertFrom-Json).ANON_KEY
$env:SUPABASE_TEST_SERVICE_ROLE_KEY = (npx supabase status -o json | ConvertFrom-Json).SERVICE_ROLE_KEY
npm test
Sem essas variáveis, os testes de RLS são pulados automaticamente (o resto da suíte roda normal). SUPABASE_TEST_SERVICE_ROLE_KEY só é usada pelo teste da trava de escrita por assinatura (liga/desliga app_config.assinatura_enforcement_ativo para testar os dois estados); sem ela, só esse teste específico pula.
Com o Supabase local de pé (npx supabase start):
npm run api-check # todas as páginas, rotas e server actions + isolamento entre contas
npm run e2e # Playwright no build de produção (porta 3200) com o Chrome instalado
O api-check precisa de um next dev -p 3100 rodando com as variáveis apontando para o Supabase local (URL e chaves de npx supabase status). Ele recria as contas [email protected] e [email protected] a cada execução. O e2e sobe o próprio servidor e recusa qualquer Supabase que não seja o local.
npx supabase start # precisa do Docker
npm run visual
Sobe o app em outra porta (3100) apontando para o Supabase local, cria um usuário e dados fictícios, abre 12 telas no Chrome em celular (390px), tablet (768px) e desktop (1366px), salva prints em .visual/ e falha se alguma tela tiver rolagem horizontal. Recusa qualquer Supabase que não seja o local. Feche o npm run dev antes de rodar.
Sem ASAAS_API_KEY, a integração roda em modo simulado — nenhuma chamada real, e /assinatura ganha um botão "Simular pagamento confirmado" pra testar o fluxo sem conta no Asaas.
Pra usar o sandbox de verdade:
ASAAS_API_KEY no .env.local (ASAAS_MODE fica sandbox automaticamente).https://<seu-domínio>/api/asaas/webhook, evento "Pagamento", e define um token — cola o mesmo valor em ASAAS_WEBHOOK_TOKEN.ngrok http 3000 ou similar) e cadastra a URL dele no passo 3. Sem isso, use o botão "Já paguei, verificar agora" em /assinatura, que consulta o Asaas direto (sem depender do webhook).A trava de escrita (somente leitura quando a assinatura vence) só entra em vigor com assinatura_enforcement_ativo = true em app_config — começa desligada de propósito. Pra ligar em produção, depois de validar a integração:
update public.app_config set assinatura_enforcement_ativo = true;