f6470718b7
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
42 lines
5.6 KiB
Markdown
42 lines
5.6 KiB
Markdown
# Freemium / PLG
|
|
|
|
É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.
|
|
|
|
## Descoberta (Fase 0) — o que já existia
|
|
|
|
O sistema já estava ~70-85% pronto:
|
|
- **Planos free existem**: `clinic_free`, `therapist_free` (+ supervisor/patient) com `plan_features.limits` semeado (`clinic_free` → `clinic_calendar {max_patients:30, max_therapists:5}`, `online_scheduling {sessions_per_month:40}`, `reminders {reminders_per_month:50}`, `documents.upload {max_storage_mb:500}`; 14 features premium OFF).
|
|
- **Feature gating completo**: `entitlementsStore.js` (views `v_tenant_entitlements`/`v_user_entitlements`), `FeatureGate.vue`, guard `meta.feature` → `/upgrade` (`guards.js:814`), badge PRO no menu.
|
|
- **Provisionamento schema-per-tenant**: `ensure_personal_tenant`/`provision_account_tenant` → `clone_tenant_template`. Setup Wizard.
|
|
- **Signup self-service**: `/lp` (pricing dinâmico de `v_public_pricing`) → `/auth/signup` (`Signup.vue:219` `signUp` inline, cria intent só no pago).
|
|
- RPCs `activate_subscription_from_intent`, `change_subscription_plan`. `tenants.slug` 100% populado.
|
|
|
|
**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.
|
|
3. **Slug escolhido** no signup (sugestão sanitizada + `slug_disponivel(p_slug)→{ok,motivo}`), imutável, trava 3 camadas.
|
|
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.
|
|
|
|
## Fases
|
|
|
|
- **F1** ✅ DONE (2026-06-13) — therapist_free ganhou max_patients=20; trigger `enforce_patient_plan_limit` em patients (lê `plan_features.limits` em runtime, resolve plano via `tenant_active_plan_id`, conta vivos, RAISE `PLAN_LIMIT_REACHED|patients|n`); helpers globais + wiring + backfill 9 schemas. Front: `utils/planLimit.js` (toast com CTA via grupo system-alerts) nos 3 pontos de criação de paciente + botão **Upgrade PRO** no AppTopbar quando plano é free. Migrations: `20260613000005_*` + `manual/freemium_f1_plan_limits.supabase_admin.sql`. Testado em ROLLBACK (clinic_free bloqueia em 30, therapist_free em 20, PRO ilimitado).
|
|
- **F2** 🟡 NÚCLEO DONE (2026-06-13) — `enable_confirmations=true` (config.toml, gitignored, ativa no restart do stack); RPCs `slug_disponivel`/`auto_provision_free_tenant`/`processar_pos_signup` (manual/freemium_f2_provisioning.supabase_admin.sql, testados em ROLLBACK clínica+terapeuta); **fix de regressão** `log_audit_change` (migration 20260613000006) que quebrava INSERT em tenant_members; Signup.vue reescrito (kind+nome+slug ao vivo+metadata, signOut-local + tela confirme-email); OnboardingPage.vue (provision+estados slug-colidiu/erro); guard → /onboarding; rota registrada. Build OK. **Restam (polish):** welcome email best-effort (infra SMTP schema-per-tenant) + apresentação do free na vitrine (public_name/preço "Grátis"/bullets — os planos já são is_visible=true mas sem nome/preço).
|
|
- **F3** ✅ DONE (2026-06-13) — 4 extras. DB/edge: `blacklist` (tabela + trigger BEFORE INSERT em auth.users + integra slug_disponivel motivo 'bloqueado'); `saas_list_account_owners()` (donos por tenant, dev-only) + `notify_all_devs` + trigger em subscriptions; `saas_app_config`/`get_root_redirect()`; edge `recover-access` (esqueci-email por slug → magic link, dica mascarada). Front: SaasUsuariosPage (/saas/usuarios, selo Novo 24h) + SaasAppConfigPage (/saas/app-config, blacklist CRUD + toggle root_redirect); esqueci-email dialog no Login; root_redirect no guard ("/" não-logado→/lp|/login, cache TTL); pegadinha #4 (notificationStore.reset no logout). Arquivos: manual/freemium_f3a/b/c + functions/recover-access. Build OK, DB testado em ROLLBACK. ⚠️ edge recover-access precisa deploy (F4).
|
|
- **F4** — Deploy (hosted, dirigido pelo Leonardo).
|
|
|
|
Método: commits por assunto; cada migration testada em transação com ROLLBACK antes de aplicar; build a cada bloco front.
|