# Handoff — Onde paramos, Riscos e Passo a passo de teste > Estado consolidado dos dois épicos grandes (schema-per-tenant + freemium/PLG). > Última atualização: 2026-06-13. Branch de trabalho: **`feat/schema-per-tenant`** (base) > e **`feat/freemium-plg`** (empilhada — contém tudo). `main` segue no modelo RLS antigo. --- ## 1. Onde paramos (estado atual) ### Branches - `main` — modelo RLS-only (produção atual). Recebeu só F0/F1/F2 aditivos da schema-per-tenant. - `feat/schema-per-tenant` — migração completa F0→F6.2 + wiring + F6.4. **F6.3 DROP NÃO aplicado.** - `feat/freemium-plg` — **ramificada da schema-per-tenant**, contém TODO o freemium (F1/F2/F3) + os dois runbooks de deploy + este handoff. **É a branch a deployar** (tem os dois épicos). ### Banco LOCAL (Docker `supabase_db_agenciapsi-primesakai`) Está no estado **schema-per-tenant + freemium aplicado**: - Schemas `tenant_` existem (9 tenants clonados) + dados COPIADOS (espelho ainda em `public`). - Todas as migrations + todos os `manual/*.supabase_admin.sql` aplicados, **EXCETO o F6.3 DROP**. - `enable_confirmations` está `true` no `config.toml` mas **só ativa após reiniciar o stack**. ### Schema-per-tenant — ✅ feito / ⏳ pendente - ✅ Estrutura, helpers, template, clone/drop, provisionamento, 66 funções migradas, dados dos 9 tenants copiados+verificados, PostgREST dinâmico (local), frontend/edge roteando por schema. - ⏳ **F6.3 DROP** (remove o espelho em `public`) — preparado, NÃO aplicado. Aguarda teste no browser + OK + backup fresco. (task #7) - 📄 Deploy: `docs/DEPLOY_SCHEMA_PER_TENANT.md`. ### Freemium/PLG — ✅ feito / ⏳ pendente - ✅ **F1** limite de pacientes (trigger runtime + toast + Upgrade PRO). - ✅ **F2** self-service (confirmação de e-mail, RPCs idempotentes, signup reescrito, /onboarding, welcome email, vitrine "Grátis") + **fix de regressão** do audit em `tenant_members`. - ✅ **F3** 4 extras (blacklist, /saas/usuarios + notify devs, esqueci-email, root_redirect). - ⏳ **F4 deploy** (hosted) — runbook em `docs/DEPLOY_FREEMIUM_F4.md`. Não deployado. - ⏳ **Teste local ponta-a-ponta** — exige reiniciar o stack (seção 3). ### Tudo commitado e pushado em `feat/freemium-plg`. Nada pendente no working tree (só `.env`/dashboard/`.claude` locais, intencionalmente fora). --- ## 2. Riscos (todos) ### 🔴 Críticos 1. **PostgREST dinâmico no hosted** — a exposição de schemas tenant usa `ALTER ROLE authenticator SET pgrst.db_schemas`. Pode ser restrito no hosted. Se falhar, o app novo recebe 404 nas tabelas tenant. **Testar cedo** (Fase C do runbook); fallback = Exposed schemas no dashboard (estático → problema com signup self-service). **Decidir antes de abrir signup.** 2. **F6.3 DROP é irreversível** — remove as tabelas tenant de `public`. Só após dias de soak no modelo novo + backup fresco. Rollback = restore (`f6_3_ROLLBACK.md`). 3. **Confirmação de e-mail + SMTP do GoTrue (hosted)** — com `Confirm email = ON`, se o SMTP do GoTrue não estiver configurado com provedor real, **ninguém consegue logar** (o link de confirmação não chega). Configurar SMTP no dashboard ANTES de ligar a confirmação. ### 🟠 Importantes 4. **Manual files fora do fluxo do `db.cjs`** — os `manual/*.supabase_admin.sql` NÃO são aplicados pelo `db.cjs migrate`. São aplicados à mão (psql como `supabase_admin`). Fácil esquecer um → função/trigger ausente. Os runbooks listam a ordem. 5. **`postgres` não é superuser no stack local** — por isso vários objetos são `supabase_admin`. No hosted o `postgres` é mais privilegiado, mas o schema `auth` é de `supabase_admin`: o trigger da blacklist em `auth.users` e os `OWNER TO supabase_admin` podem precisar de SQL Editor ou troca pra `OWNER TO postgres`. 6. **`config.toml` é gitignored** — `enable_confirmations=true` está só no arquivo local (não versionado). No hosted a confirmação vai pelo **dashboard** (Auth → Confirm email). 7. **Migração de dados (cutover)** — `f6_1` COPIA; conferir **paridade de contagens** por tenant/tabela antes de confiar (e antes do DROP). 8. **Edge functions novas precisam deploy** — `recover-access` e `send-welcome-email` (freemium) + as edges de roteamento por schema (schema-per-tenant). Esquecer = esqueci-email/welcome/ webhooks quebram. 9. **Slug é IMUTÁVEL** — = nome do schema físico. Uma vez escolhido, não muda (trava em 3 camadas). UX do signup deixa claro, mas é definitivo. ### 🟡 Menores / a observar 10. **Enforcement de limite é por-linha** (BEFORE INSERT) — um bulk insert de pacientes numa única statement pode passar marginalmente do limite (cada linha não vê as anteriores da mesma statement). Na prática o cadastro é 1 a 1; ok. 11. **notify_all_devs dispara a cada subscription** (inclui a free do auto_provision) — em escala, muitos avisos no sino do dev. Intencional; reavaliar se incomodar. 12. **send-welcome-email usa SMTP de sistema** (separado do canal do tenant) — precisa secrets no hosted; é best-effort (falha não bloqueia login). 13. **auto_provision idempotente** retorna o 1º tenant ativo se o user já tem algum — usuário multi-tenant que se cadastra de novo não ganha tenant novo (esperado). 14. **Local vs main inconsistente** — o banco local está no modelo novo; o código da `main` é RLS. Se fizer `git checkout main`, o app antigo ainda funciona porque `public` tem as tabelas (até o DROP). Não rodar `main` esperando o modelo novo (e vice-versa). --- ## 3. Passo a passo — como testar TUDO (local) > Pré: Docker do Supabase rodando (portas 643xx). Frontend via `npm run dev`. ### Passo 0 — Ativar a confirmação de e-mail A confirmação só vale após reiniciar o stack (o volume do banco **persiste** — nada se perde): ```bash supabase stop && supabase start # ou o tooling equivalente do projeto ``` Conferir no Studio/Mailpit que está de pé. (Se preferir NÃO testar confirmação agora, pule — o front trata os dois casos; mas o fluxo "confirme e-mail" só aparece com isto ligado.) ### Passo 1 — Schema-per-tenant: tenants EXISTENTES ainda funcionam 1. `npm run dev`, logar num tenant existente (ex.: clínica Bem-Estar / um terapeuta). 2. Abrir **Agenda, Pacientes, Financeiro, Conversas** → tudo carrega (lendo de `tenant_`). 3. Criar/editar um registro (ex.: um bloqueio na agenda, editar um paciente) → salva sem erro. 4. Sino de notificações abre (dual-source tenant + sistema). > Se algo não carregar, é sinal de roteamento de schema — anotar a tela/erro. ### Passo 2 — Freemium: signup self-service NOVO (o fluxo principal) 1. Deslogar. Ir em **`/lp`** → conferir o cartão **"Grátis"** na vitrine. 2. **Criar conta grátis** → escolher tipo (terapeuta/clínica) + seu nome + nome do negócio + **slug** (ver a checagem de disponibilidade ao vivo) + e-mail + senha. 3. Submeter → cai na tela **"Confirme seu e-mail"** (NÃO loga ainda). 4. Abrir o **Mailpit** (caixa de e-mail local) → achar o e-mail de confirmação → clicar no link. 5. Voltar/entrar em **`/auth/login`** → logar → cai em **`/onboarding`** → "Preparando seu ambiente…" → provisiona → entra no painel do tenant novo. 6. Conferir no Mailpit o **e-mail de boas-vindas** (welcome — best-effort). 7. Conferir que o schema `tenant_` foi criado (Studio) e que você é master. ### Passo 3 — Limite do plano gratuito 1. No tenant gratuito recém-criado (ou num clinic_free existente), cadastrar pacientes. 2. Ao passar do limite (clínica=30, terapeuta=20) → aparece o **toast "Limite do plano gratuito"** com botão **"Fazer upgrade"** (não o erro cru). 3. Conferir o botão **"Upgrade PRO"** dourado no topbar (visível porque o plano é free). ### Passo 4 — SaaS / dev (logar como saas_admin) 1. **`/saas/usuarios`** → o cliente novo aparece com selo **"Novo"** (verde, 24h), com slug/e-mail/plano. 2. **Sino do dev** → recebeu **"Nova assinatura"** (do provisionamento). 3. **`/saas/app-config`**: - Adicionar um **e-mail na blacklist** (ex.: `bloqueado@x.com`). Depois, deslogar e tentar **criar conta** com ele → bloqueado de verdade. - Testar **`@dominio.com`** (domínio inteiro). - Trocar **root_redirect** (landing↔login) e abrir **`/`** deslogado → confere o destino. ### Passo 5 — Esqueci meu e-mail 1. **`/auth/login`** → **"Esqueci meu e-mail"** → digitar o **slug** do tenant criado no Passo 2. 2. Recebe a confirmação com a **dica mascarada** (jo****@gm****.com) e um **magic link** no Mailpit. 3. Clicar no magic link → entra. (O e-mail real nunca aparece na tela.) 4. ⚠️ Edge functions locais: precisam estar servidas (`supabase functions serve` ou o runtime do stack). Se o esqueci-email/welcome não responder, é a edge não estar de pé localmente. ### Passo 6 — Pegadinha #4 (sino ao trocar de usuário) 1. Logado como user A, com notificações no sino → **logout**. 2. Logar como user B → o sino **não** mostra notificações do A (foi resetado no logout). ### Passo 7 (opcional, destrutivo, só quando confiante) — preparar o DROP NÃO aplicar agora. Quando tudo acima estiver validado por dias: seguir a **Fase G** do `docs/DEPLOY_SCHEMA_PER_TENANT.md` (backup fresco → `f6_3_drop_public_tenant_tables`). --- ## 4. Atalhos / referências - Runbooks: `docs/DEPLOY_SCHEMA_PER_TENANT.md`, `docs/DEPLOY_FREEMIUM_F4.md`. - Rollback do DROP: `database-novo/manual/f6_3_ROLLBACK.md`. - Migrations: `database-novo/migrations/` (aplicar via `node database-novo/db.cjs migrate`). - Manual privilegiados: `database-novo/manual/*.supabase_admin.sql` (aplicar como `supabase_admin`). - Wiki: `Obsidian/Brain/wiki/Migracao Schema-per-Tenant.md` e `Obsidian/Brain/wiki/Freemium PLG.md`. - Portas locais: API 64321 · DB 64322 · Studio 64323 (stack shiftada +10000).