Sistema de gestão desenvolvido para clínicas de estética: agenda, prontuário, fotos de evolução, anamnese, termo de consentimento, CRM de leads, orçamentos, financeiro, comissões, estoque e relatórios no mesmo fluxo, do primeiro contato ao retorno da paciente.
Stack: Next.js 16 (App Router) + TypeScript + Tailwind + shadcn/ui, com Supabase (Postgres, Auth, RLS multi-clínica).
Texto público (landing, metadata, e-mails, anúncios): o posicionamento, os claims permitidos e os proibidos ficam em
.agents/product-marketing.md. Leia antes de escrever qualquer coisa que saia para fora.
Escopo implementado: cadastro de clínica com verificação de e-mail, login, recuperação de senha, estrutura multi-clínica, perfis e permissões granulares, layout base com menu principal.
- Crie um projeto em supabase.com.
- Em Project Settings > API, copie a URL, a
anon keye aservice_role key. - Copie
.env.local.examplepara.env.locale preencha os três valores. - Em Authentication > URL Configuration, adicione
http://localhost:3000/auth/callback?**ehttp://localhost:3000/auth/confirm?**às Redirect URLs. Em produção, veja a seção de domínio próprio mais abaixo. - Confirme que Authentication > Providers > Email > Confirm email está habilitado (obrigatório para o fluxo de verificação de e-mail).
- Rode, em ordem, o SQL de cada arquivo em
supabase/migrations/(0001 até a última) no SQL Editor do Supabase — ou via Supabase CLI, se o projeto estiver linkado. A migração0008_prontuario.sqlcria também o bucket de Storagepatient-media(privado); confirme em Storage que ele existe após rodar essa migração. - Instale as dependências e suba o servidor:
npm install
npm run devAbra http://localhost:3000. O cadastro em /cadastro cria a clínica e o usuário Dono/Admin; os demais perfis (Gerente, Recepção/Comercial, Profissional, Financeiro) são convidados pelo Dono em Configurações > Usuários e permissões.
npm run dev— ambiente de desenvolvimento (Turbopack)npm run build— build de produçãonpm run lint— ESLintnpm run typecheck— TypeScript, sem gerar arquivonpm test— testes de regressão de segurança (tests/)
Os testes rodam no executor nativo do Node, sem framework e sem dependência a mais, e por isso pedem Node 22.18 ou mais novo — é a versão a partir da qual o Node lê TypeScript direto. A aplicação em si roda a partir da 20.9.
Os quatro comandos rodam também na CI, a cada push e a cada pull request
(.github/workflows/ci.yml).
O que o sistema já protege, o que ainda não protege e o que depende de
configuração no painel do Supabase, da Vercel ou do DNS está em
docs/seguranca.md. Quem for mexer em
autenticação, permissão ou banco começa por ali.
Para relatar uma falha, veja SECURITY.md — não abra
issue pública.
- Em vercel.com, Add New > Project e importe o repositório
jmarinssousa-source/EsteticaOSdo GitHub. - Vercel detecta o Next.js automaticamente — não precisa mudar build command nem output directory.
- Em Environment Variables, adicione as mesmas quatro chaves de
.env.local(veja.env.local.example):NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_ANON_KEYSUPABASE_SERVICE_ROLE_KEY— nunca prefixe comNEXT_PUBLIC_; sem esse prefixo o Next.js não inclui a variável no bundle enviado ao navegador, mantendo-a só no servidor.NEXT_PUBLIC_SITE_URL—https://esteticaos.comem produção, usada para montar os links de confirmação de e-mail e redefinição de senha.
- Clique em Deploy.
O endereço oficial é https://esteticaos.com, sem www — o www só
redireciona para ele. O domínio está registrado na Hostinger e apontado
para a Vercel.
Nenhum domínio está escrito no código: tudo sai de NEXT_PUBLIC_SITE_URL
(veja src/lib/site-url.ts). Trocar de endereço é mudar essa variável e
refazer o deploy.
- Vercel > Settings > Domains: adicione
esteticaos.comewww.esteticaos.com, deixando owwwcomo redirecionamento para o domínio semwww. A Vercel mostra os registros de DNS a criar. - Hostinger > DNS: crie os registros que a Vercel indicou e remova
os registros
A/CNAMEque a Hostinger cria sozinha para a página de parking — eles conflitam e o domínio fica intermitente. - Vercel > Settings > Environment Variables: defina
NEXT_PUBLIC_SITE_URL=https://esteticaos.com, sem barra no fim. Variável nova só vale em build novo: faça um redeploy. - Supabase > Authentication > URL Configuration:
- Site URL:
https://esteticaos.com— este é o campo decisivo. Nos templates de e-mail ele vira o{{ .SiteURL }}de todo link, e é também para onde o Supabase joga quem tiver um destino recusado. - Redirect URLs:
https://esteticaos.com/auth/callback?**ehttps://esteticaos.com/auth/confirm?**, mais as delocalhost. O?**é obrigatório: entrada sem curinga casa com a URL inteira, query incluída.
- Site URL:
Sem o passo 4 o sintoma é traiçoeiro: cadastro e login seguem funcionando, mas todo e-mail de confirmação, convite e redefinição de senha leva o usuário para o endereço antigo.
Abra https://esteticaos.com/robots.txt. A última linha mostra o
endereço que o sistema está usando para montar tudo:
Sitemap: https://esteticaos.com/sitemap.xml ← certo
Sitemap: https://estetica-os-plum.vercel.app/... ← NEXT_PUBLIC_SITE_URL não está definida
Se aparecer o endereço da Vercel, a variável não chegou ao build de
produção — e aí nenhum link de e-mail vai sair certo, porque
src/lib/site-url.ts cai no endereço interno da Vercel quando a
variável falta. Defina a variável para o ambiente Production e faça
um redeploy.
Confira também o sentido do redirecionamento de domínio:
curl -sI https://esteticaos.com/ | grep -i locationNão deve devolver nada. Se devolver location: https://estetica-os-plum.vercel.app/,
o domínio oficial está configurado na Vercel como redirect para o
endereço interno — exatamente o contrário do que deveria. Em
Vercel > Settings > Domains, esteticaos.com precisa ser o domínio
de produção, e o www é que redireciona para ele.
Arrume o sentido do redirecionamento antes de definir
NEXT_PUBLIC_SITE_URL. Com os dois errados ao mesmo tempo, a Vercel empurra para o endereço interno e o proxy empurra de volta, em laço. O código corta o laço no segundo salto (verCANONICAL_GUARD_COOKIE), mas o sistema fica servindo no endereço errado até o painel ser corrigido.
estetica-os-plum.vercel.app não é o endereço público do sistema e
não deve gerar link novo nenhum. Ele pode ficar nas Redirect URLs por
algumas semanas, como compatibilidade para convites enviados antes da
troca de domínio — nunca na Site URL.
Como rede de segurança, o proxy devolve para esteticaos.com qualquer
requisição que chegue por outro endereço, preservando caminho e query
(src/lib/canonical-host.ts). Isso salva o link antigo, mas não
substitui o ajuste no painel: o e-mail que a pessoa recebe continua
mostrando o endereço errado. Deploys de preview são exceção e seguem
navegáveis no endereço próprio.
Configuração do envio e os textos dos três e-mails de autenticação estão
em docs/smtp.md, com os modelos prontos em
docs/emails/. Faça depois do domínio: o provedor só libera o envio com
o domínio já verificado.
Nenhuma chave sensível fica no código-fonte: o .env.local é ignorado pelo Git (.gitignore) e a service_role key só é lida em código que roda no servidor (src/lib/supabase/admin.ts, marcado com server-only — se algum dia for importada sem querer num componente de cliente, o build quebra em vez de vazar a chave).