- therapist_free ganha max_patients=20 (clinic_free ja tinha 30)
- trigger BEFORE INSERT em patients le plan_features.limits em runtime,
resolve tenant por TG_TABLE_SCHEMA, plano ativo (clinica via tenant_id +
pessoal via owner user_id), conta vivos (status<>Arquivado) e da RAISE
PLAN_LIMIT_REACHED|patients|<n>
- helpers tenant_active_plan_id / plan_feature_limit (globais, sobrevivem F6.3)
- wiring: tenants novos ganham via trg_attach_business_triggers; 9 existentes backfill
- testado: clinic_free bloqueia em 30, therapist_free em 20, PRO ilimitado (rollback)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Épico iniciado em 2026-06-13, branch `feat/freemium-plg` (sobre [[Migracao Schema-per-Tenant]]). Objetivo: qualquer visitante cria conta gratuita sozinho, confirma e-mail, e o ambiente do tenant é provisionado automaticamente. Plano gratuito limitado + botão "Upgrade PRO". Blueprint-diretor: `novo-rumo.txt` (raiz), vindo do sistema-irmão (sindicato) e adaptado a clínica.
**Gap confirmado:** limites semeados mas **ninguém lê/enforça**. Sem confirmação de e-mail (`enable_confirmations=false`), sem /onboarding, signup só coleta email+senha, sem welcome email, sem os extras.
## Decisões (Fase 0.5)
1. **Modelo do blueprint** — confirmação de e-mail ON; signup grava escolha em `raw_user_meta_data` + signOut-local + tela "confirme e-mail"; provisionamento+intent viram RPCs idempotentes no 1º login (`auto_provision_free_tenant(p_slug_override)`, `processar_pos_signup`); guard manda logado-sem-tenant → `/onboarding`. Reescreve o signup inline.
2. **Pacientes** = recurso limitado. Trigger BEFORE INSERT em `patients` lê limits em runtime, resolve tenant por `TG_TABLE_SCHEMA`, conta linhas vivas, `RAISE 'PLAN_LIMIT_REACHED|patients|<n>'`. clinic_free=30, therapist_free=20. No template + backfill 9 schemas.
4. **Todos os 4 extras**: /saas/usuarios + `notify_all_devs`; esqueci-email (magic link por slug, dica mascarada); blacklist (email|slug); root_redirect.
## Pegadinhas (do blueprint, ⚠️ caras no irmão)
- **#1** Signup sem sessão (confirmação ON) → tudo com `auth.uid()` quebra em silêncio. Gravar escolha em metadata, processar pós-confirmação.
- **#2** signOut `scope:'local'` se não veio sessão — senão vaza sessão anterior e joga no painel errado.
- **#3** Logado-sem-tenant nunca cai em painel quebrado → `/onboarding` resolve estados (provisionando, slug-colidiu, pago-aguardando, sem-acesso, erro).
- **#4** Sino de notificação singleton precisa re-buscar ao trocar de user (logout+login).
## Divergência de infra
Blueprint pede welcome email via **Resend**; aqui é **SMTP/Mailpit** (`process-email-queue`). Reusar o pipeline SMTP existente (best-effort), não Resend.
master, cria subscription gratuita ativa) — chamada em carregarPerfil quando
o usuário não tem tenant. Gratuito não gera intenção.
- processar_pos_signup() (aceite legal + cria a intenção SÓ pro caminho pago).
3. ⚠️ PEGADINHA CRÍTICA #2 (segurança): após o signUp, se NÃO veio sessão
(confirmação pendente), ENCERRE qualquer sessão local (signOut scope:'local')
e mostre uma tela "confirme seu e-mail". Senão, uma sessão anterior (ex: dev
testando) vaza e o push pra /login joga o usuário pro painel da sessão antiga.
A pessoa só pode logar APÓS clicar no link do e-mail.
4. ⚠️ PEGADINHA CRÍTICA #3 (blindagem): um usuário logado SEM tenant nunca pode
cair num painel quebrado. No guard, redirecione todo logado-sem-tenant (não-dev)
pra uma tela /onboarding que resolve os estados: provisionando, slug colidiu
(deixa escolher outro slug e finalizar — faça o auto_provision aceitar um
p_slug_override), conta paga aguardando ativação, sem acesso, erro (retry).
5. Signup coleta nome do sindicato + slug (sugestão + sanitização + disponibilidade
ao vivo via RPC slug_disponivel que retorna {ok, motivo}) + "seu nome".
Torne o plano gratuito visível na vitrine agora.
6. E-mail de boas-vindas: edge function (Resend) que renderiza o template, disparada
no provisionamento. Best-effort (não bloqueia o login). Destinatário derivado
do JWT, não do body.
## SAAS / EXTRAS (faça os que fizerem sentido)
- Página /saas/usuarios: 1 linha por tenant com o DONO (master) — nome, slug,
e-mail principal — via uma RPC dev-only que cruza tenants+profiles+subscriptions
(SECURITY DEFINER). Realce em verde + selo "Novo" pra cliente criado nas últimas
24h (rowClass baseado em created_at). Reaproveite essa RPC pra mostrar o e-mail
principal também nas listagens de assinaturas e tenants.
- Notificação aos devs quando nasce/muda uma assinatura (incl. trial): trigger em
subscriptions chamando a função notify_all_devs com deeplink. ⚠️ PEGADINHA #4:
se o sino de notificações é um singleton com flag "initialized", garanta que ele
RE-BUSCA ao trocar de usuário (logout+login), senão fica stale e ainda vaza
notificações entre usuários. A notificação só aparece pós-provisionamento e no
sino do DEV (não do novo usuário).
- "Esqueci meu e-mail": tela onde a pessoa informa o IDENTIFICADOR do sindicato
(slug, que ela escolheu e foi avisada ser definitivo) → o servidor acha o e-mail
do dono → mostra só uma DICA MASCARADA (jo****@gm****.com) → envia magic link
(signInWithOtp, que usa o mesmo pipeline de e-mail do GoTrue, sem depender de
Resend) → a pessoa clica e entra. O e-mail real NUNCA volta pro cliente.
- root_redirect: coluna em config + RPC pública + guard, pra escolher pra onde o
visitante não logado vai na raiz "/" (landing ou login).
- Lista de bloqueio (blacklist) de e-mails e slugs, gerida em Configurações:
tabela blacklist (kind email|slug). E-mail bloqueia o cadastro DE VERDADE via
trigger BEFORE INSERT em auth.users (não só no front); suporte a domínio inteiro
com entrada '@dominio.com'. Slug integra no slug_disponivel (motivo 'bloqueado').
## MÉTODO DE TRABALHO
- Tudo numa branch nova. Commits pequenos por assunto, mensagem clara.
- Cada migration: aplique no banco local e TESTE em transação com ROLLBACK (crie
auth.users fake + impersone via set_config('request.jwt.claims',...)) antes de
seguir. RPCs idempotentes.
- Rode o build do frontend a cada bloco pra pegar erro cedo.
- NUMERE as migrations com cuidado pra não colidir versão (quebra o db push).
- Me mostre o mapa da Fase 0 e as decisões da Fase 0.5 ANTES de codar.
## DEPLOY (no fim)
Migrations no hosted (db push) → dashboard Auth "Confirm email" ON + Site/Redirect
URLs corretas → deploy das edge functions + secret do provedor de e-mail → rebuild
do frontend → smoke test do fluxo: /lp → grátis → confirma e-mail → entra
provisionado → limite bloqueia → sino do dev → esqueci-email.
---
Esse prompt é "diretor": ele força a IA a mapear o teu outro sistema primeiro (porque as tabelas/nomes vão diferir) e
te apresentar decisões antes de codar — do jeito que fizemos aqui. As 4 pegadinhas marcadas com ⚠️ são as que mais
custaram tempo; com elas escritas, a IA evita de cara.
Quer que eu gere também uma versão curta (1 parágrafo) pra um primeiro disparo, ou uma variante específica caso o
outro sistema seja RLS puro (sem schema-per-tenant)? Aí eu ajusto os trechos de provisionamento/enforcement.
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.