- saas_list_account_owners(): 1 linha/tenant com dono (master), nome/slug/email/
plano + selo "novo" (24h). Dev-only (is_saas_admin), cast text p/ varchar.
- notify_all_devs() insere em notifications_sistema p/ cada saas_admin
- trigger AFTER INSERT/UPDATE OF status em subscriptions avisa os devs com
deeplink /saas/usuarios
- testado em ROLLBACK: lista + notify ao inserir subscription
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Signup.vue reescrito: coleta tipo de conta + nome + nome do negocio + slug
(disponibilidade ao vivo via slug_disponivel) + email/senha; grava tudo no
raw_user_meta_data do signUp; PEGADINHA #2: signOut scope:local se nao veio
sessao + tela "confirme seu e-mail". Removido provisionamento/intent inline.
- OnboardingPage.vue (PEGADINHA #3): 1o login chama auto_provision_free_tenant
+ processar_pos_signup; resolve estados provisionando/slug-colidiu/erro;
redireciona pro painel conforme kind.
- guard: logado-sem-tenant (nao saas_admin) -> /onboarding em vez de /login
- rota /onboarding (meta.public; a pagina exige sessao)
NOTA: supabase/config.toml e gitignored — enable_confirmations=true foi setado
local (ativa no proximo restart do stack). No hosted, ligar em Auth>Email>Confirm.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Regressao do schema-per-tenant: o trigger de auditoria deriva tenant_id via
tenant_id_for_schema(TG_TABLE_SCHEMA), que retorna NULL para public.* (ex.:
tenant_members) -> violava audit_logs.tenant_id NOT NULL -> QUALQUER novo
membership (provisionamento, aceite de convite) falhava.
Fix: quando o schema nao resolve tenant, cai no tenant_id da propria linha;
se ainda NULL, nao audita mas nunca quebra a operacao.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- utils/planLimit.js: parsePlanLimitError + maybeShowPlanLimitToast (traduz
PLAN_LIMIT_REACHED em toast amigavel com CTA via grupo system-alerts)
- AppTopbar: botao "Upgrade PRO" quando plano ativo e gratuito (reusa
resolveActiveSubscriptionContext; plan_key nos selects)
- ligado nos 3 pontos de criacao de paciente: PatientsCadastroPage,
CadastrosRecebidos (intake), ComponentCadastroRapido (quick-create)
- build OK
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 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>
manual/f6_3_drop_public_tenant_tables.supabase_admin.sql — ponto de nao-retorno,
revisar antes de aplicar:
- pre-flight assert (tenants=schemas, 78 tabelas, 6 anon NAO no template)
- 2 FKs public->tenant viram coluna solta (document_share_links.documento_id,
whatsapp_credits_transactions.conversation_message_id)
- dropa 9 views public (6 recriadas por schema + 2 dead + v_twilio item#1)
- DROP CASCADE das 78 tabelas (derivado de _tenant_template; dados ja nos schemas)
- limpa financial_records_inject_tenant (obsoleto)
ITENS EM ABERTO verificados (16 reads FE remanescentes, TODOS na superficie
SaaS-admin ja flagada na F3): SaasFeriadosPage (feriados defaults),
SaasNotificationTemplatesPage (templates default), SaasWhatsappPage +
v_twilio_whatsapp_overview/getAllChannels (whatsapp admin cross-tenant).
Resolver (apontar pra _tenant_template ou fan-out por schema) ANTES do DROP.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- attach_agnostic_triggers reescrito SELF-CONTAINED (dirigido por colunas: set_
updated_at em toda tabela com updated_at + prevent_* em patient_groups). Nao
le mais de public -> sobrevive ao DROP da F6.3.
- trigger AFTER INSERT em tenant_schemas (trg_attach_business_triggers) dispara
os 3 attach (agnostic + schema_aware + notif) pro schema novo. clone_tenant_
template nao precisou ser tocado (ele insere em tenant_schemas). GRANT EXECUTE
dos attach pra postgres/service_role.
- provision_account_tenant: clone ANTES do seed (seed_determined_commitments e
no-op se schema nao existe; precisa do schema criado primeiro)
Smoke: tenant novo nasce com 84 triggers automaticamente (vs 81 nos existentes,
que sao mais que suficientes). Provision order corrigida.
App agora testavel: dados nos schemas (F6.1) + funcoes/triggers/RPCs roteiam
(F6.2). Falta so F6.3 DROP (com OK + app testado).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
DB (supabase_admin, manual/f6_2e_cron_rpcs.supabase_admin.sql):
- E2 (varrem TODOS os tenants via loop FROM tenant_schemas): cleanup/unstick_
notification_queue, sync_overdue_financial_records (EXECUTE format por schema),
populate_notification_queue (set_config search_path por tenant; profiles global)
- E1 (per-tenant via service_role): novo helper _tenant_schema_unchecked (sem
is_tenant_member — service_role nao e membro; REVOKE de anon/authenticated).
sla_open_breach, sla_mark_notified(+p_tenant_id), whatsapp_heartbeat_open_
incident/mark_notified/resolve(+p_tenant_id). convert_abandoned_intake_to_lead
resolve tenant internamente (intake public/F1b -> writes no schema).
first_response_stats/_runs: _tenant_route (user-facing, frontend ja passa
p_tenant_id); _first_response_runs computa thread_key (coluna nao existe).
- REVOKE das RPCs de servico de anon/authenticated; GRANT service_role
Edge: whatsapp-heartbeat-check (tdb.rpc->admin.rpc + p_tenant_id nos heartbeat
RPCs), conversation-sla-check (sla_mark_notified + p_tenant_id).
Gotchas: (1) service_role nao e tenant_member -> helper unchecked + REVOKE;
(2) conversation_messages nao tem coluna thread_key (computar); (3) DROP+CREATE
de nova assinatura precisa dropar ambas p/ idempotencia.
Smoke: E2 sync_overdue 13 across tenants; E1 sla_open_breach roteia; first_
response_stats user OK.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Aplicado como supabase_admin (trigger functions sao owned por supabase_admin).
14 funcoes reescritas pra operar no schema do TG_TABLE_SCHEMA (set_config
search_path dinamico + tenant_id_for_schema p/ tabelas globais audit_logs):
log_audit_change, trg_fn_patient_status_history/timeline/risco,
auto_create_financial_record_from_session, fn_sla_resolve_on_outbound,
fn_clinical_note_version, fn_document_signature_timeline,
fn_documents_timeline_insert, sync_legacy_email/phone_fields,
fn_agenda_regras_semanais_no_overlap, patients_validate_member_consistency.
sync_busy_mirror_agenda_eventos: cross-tenant via tenant_schema_for +
EXECUTE format (espelha "Ocupado" nos schemas das clinicas).
financial_records_inject_tenant: obsoleto, nao anexado nos schemas.
Detach dos 14 schema-aware das tabelas tenant em public (quebrariam la);
attach_schema_aware_triggers recria 22 triggers/schema (defs reais, tenant_id
removido de WHEN/UPDATE OF). agenda_cfg_sync e trg_fn_financial_records_auto_
overdue (agnosticos) ficam em public E nos schemas.
Smoke: sessao->realizado cria financial_record (R$250) no schema + marca billed;
audit roteia tenant_id correto; patient status muda -> timeline no schema.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
attach_agnostic_triggers(schema) recria nos schemas os triggers de public cuja
funcao e provadamente schema-agnostica (so mexe em NEW/OLD): familia updated_at
(8: set_updated_at, fn_clinical_notes_updated_at, set_insurance_plans/medicos/
services_updated_at, set_updated_at_recurrence, update_payment_settings/
professional_pricing_updated_at) + prevent_promoting_to_system +
prevent_system_group_changes. Backfill dos 9 (54 triggers/schema). Smoke:
set_updated_at dispara no schema. Schema-aware vem no Lote B; wiring no clone
no fim da F6.2
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- F6.0 (migration): clone_tenant_template pros 9 tenants existentes (schemas
vazios; dispara trigger F5 -> expostos no PostgREST)
- F6.1 (manual supabase_admin): copia dados public -> schemas com
session_replication_role=replica (desabilita FK check, so supabase_admin).
Tabelas com tenant_id por filtro; 3 filhas sem tenant_id (commitment_services,
insurance_plan_services, recurrence_rule_services) por JOIN no pai; exclui
colunas GENERATED (net_amount, margin_brl); reset de 4 sequences; ON CONFLICT
DO NOTHING (idempotente). Dados continuam em public (DROP so na F6.3)
- Verificado: contagens public vs schemas batem (35 patients, 37 eventos,
355 mensagens, 54 financeiro...); seeds default replicados por schema
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- manual/f5_pgrst_refresh_schemas.supabase_admin.sql: refresh_pgrst_schemas()
(owned supabase_admin, postgres nao e superuser neste stack) seta
pgrst.db_schemas in-database na role authenticator a partir de tenant_schemas
+ NOTIFY pgrst reload config/schema. Expoe/retira schema SEM restart; GUC
persiste entre stop/start
- migration 20260613000002: trigger em tenant_schemas dispara o refresh a cada
clone/drop (clone/drop nao precisam ser tocados)
- config.toml: baseline public,graphql_public + comentario explicando que a
config in-db supersede em runtime
- E2E testado via HTTP: clone -> pgrst.db_schemas inclui tenant_x -> REST
Accept-Profile retorna 200 (vs 406 schema inexistente); drop -> volta 406.
Sem restart de container
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fluxos anon identificam tenant por token/slug e nao resolvem o schema fisico.
Decisao (opcao C): manter em public com RLS por token. Volta a global:
patient_intake_requests, patient_invites, patient_invite_attempts,
document_share_links, agendador_configuracoes, agendador_solicitacoes.
- migration 20260613000001_f1b: remove as 6 do _tenant_template (template v2,
78 tabelas). Smoke: clone gera 78, zero tabelas anon no schema, drop limpo
- frontend: 38 cadeias em 14 arquivos revertidas tenantDb().from() ->
supabase.from() com tenant_id/owner_id restaurado (via comparacao com main)
- edge: convert-abandoned-intakes restaurada do main (SELECT global)
- save-intake-progress: ja usava public, sem mudanca
- doc F0 atualizado: 78 tenant + 59 global
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- _shared/tenant.ts: helper (adminClient, tenantDbForId, schemaForTenant,
listTenantSchemas, resolveTenantByChannel, tenantSchemaName)
- _shared/whatsapp-hooks.ts: hooks de tabela tenant recebem tdb; RPCs de
credito (deduct/add_whatsapp_credits) e tenant_members seguem em supa+p_tenant_id
- inbound (twilio/evolution): tenant_id da URL -> tdb pra conversation_messages
e notification_channels
- crons de fila (process-notification/email/sms/whatsapp-queue): varrem
listTenantSchemas e drenam a fila de cada schema (Q3: filas sao per-tenant);
modo single-tenant se body.tenant_id vier
- crons reminders/checks (send-session-reminders, conversation-sla-check,
whatsapp-heartbeat-check, convert-abandoned-intakes, sync-email-templates):
loop por tenant
- routing por tenant_id (send-whatsapp-message, send-session-reminder-manual,
twilio-provision, de/reactivate-channel, twilio-webhook): tenantDbForId;
channel-actions sem tenant_id varrem schemas por channel_id
- asaas-*: tenant_id do body -> tdb; asaas-webhook fica global (whatsapp_credit_purchases)
- notification-webhook (Meta): resolve tenant via channel_routing por phone_number_id,
fan-out por message_id quando nao resolve
- caller send-session-reminder-manual passa tenant_id (evento vive no schema)
Pendente: save-intake-progress e fluxos anon por token (decisao de roteamento)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sessao completa de ~14 commits. 2 grandes blocos:
BLOCO 1 — Melissa UI overhaul: tray bottom-right (substitui topbar
band), mobile collapse parcial em <md, busca global unificada
(MelissaBusca ganha "Ir para [data]", popover da agenda deletado),
dock com 4 builtins, hero resumo com cancelado/remarcado, settings+
ajuda click-outside, cronometro evento-aware (botao ⏱ na timeline +
sessionPlan + confirm fechar), documents edit in-place via
document_generated.documento_id, wire-up dos 5 botoes do preview.
BLOCO 2 — 5 docs saas novas (03-07 em development/saas-docs/) +
SQL imports + 60 FAQs total. Cobertura: aba Documentos paciente,
pagina Templates, Assinatura eletronica, Emissao de recibo
profissional, Relatorios + 3 formatos de export.
Memorias adicionadas:
- feedback_tailwind_utility_load_order (hidden perde pra CSS base
do componente por ordem de carga Vite)
- project_documents_reedit_in_place (linkage documento_id + editingDocId)
PROXIMA SESSAO (23/05): Fase 6 restante (C12 antecipar UX iter —
unico item de codigo da lista de ontem), Fase 7 restante (regressao
Agenda C7-C13, validacao manual). Antes/depois: panorama MVP no
ROADMAP canonico — ainda restam #12 papel timbrado, #15 NFS-e,
§1.5 Sentry, Asaas Fase B, M4 cutover, validacao centralizada
de forms.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Doc 07 cobrindo a pagina de Relatorios + 3 formatos de exportacao:
- Layout 2-col (sidebar stats+filtros, main grafico+tabela)
- 4 periodos (semana/mes/3m/6m), agrupamento auto (dia vs week ISO
vs month ISO)
- 5 KPIs clicaveis como filtros (total/realizadas/faltas/canc/remarc)
- Grafico Chart.js com cores por status
- DataTable paginada + status com tag colorida
- Export PDF (HTML->PDF A4, KPIs + tabela)
- Export Excel XLSX (exceljs dinamico, frozen header, alternating
rows, branded, formatos data+currency)
- Export CSV (vanilla, BOM UTF-8, separador ; pt-BR)
- Filtros aplicados na tela respeitados na exportacao
- Nome do arquivo com timestamp pra evitar overwrite
- Notas dev: reportExport.service.js, pdf.service, exceljs lazy load
12 FAQs: como ver, periodos disponiveis, exportar PDF/Excel/CSV,
quando usar qual formato, filtros respeitados, formulas, agrupamento
do grafico, filtrar por paciente, ver outro terapeuta, nome do
arquivo, exportacao agendada (pendente).
categoria='Relatórios', pagina_path='/melissa/relatorios', ordem=7.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Doc 06 cobrindo o quick path de emissao de recibo:
- Quando o botao aparece (AgendaEventoFinanceiroPanel com record
status=paid)
- O que vem auto-preenchido (paciente, sessao, valor, forma pgto,
terapeuta+registro formatado, clinica+CNPJ, data)
- Registro profissional generico — CRP/CRM/CRFa/CREFITO/CRESS/CRN/
Outro (variavel terapeuta_registro auto-formata)
- Valor por extenso (helper valorExtenso.js, ate 999 milhoes)
- Onde fica salvo (download + aba Documentos categoria 'Recibo')
- Quick path emitirReciboParaSessao() vs flow manual de Gerar
- Notas dev: service, helper, mapping, migration do template,
localizacao do botao
12 FAQs cobrindo casos comuns: emitir recibo de sessao paga, por
que botao nao aparece, valor por extenso correto, suporte multi-
conselho, onde salva, recibo avulso, CRP vazio, CNPJ formatado,
corrigir valor, enviar pra assinar, data sessao vs emissao,
reemitir.
categoria='Financeiro', pagina_path='/melissa/agenda', ordem=6.
SQL import em database-novo/tmp/import-doc-recibo-profissional.sql.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Doc 04 cobrindo a pagina de gestao de templates:
- Globais (sistema, read-only) vs Tenant (seus, editaveis)
- Lista em grid com cards + badge "padrao" pros globais
- Preview de template global (iframe sandbox A4) + botao Duplicar
- Criar novo template (nome/tipo/desc/cabecalho/corpo/rodape)
- Editor rich-text com menu de variaveis (insere {{nome_var}})
- Lista de variaveis disponiveis (paciente/terapeuta/clinica/sessao/geral)
- Mobile drawer pros templates
- Duplicar (cria copia em "Seus templates" com sufixo "(copia)")
- Desativar (soft-delete, docs antigos continuam acessiveis)
- Mapeamento tipo template -> categoria do doc gerado
12 FAQs: pra que serve, por que nao edita padroes, como usar variavel,
quais variaveis, recuperar desativado, duplicar pra personalizar,
global vs tenant, imagens (logo/assinatura), cabecalho/rodape em todas
as paginas, variaveis obrigatorias, limites, compartilhamento entre
terapeutas do mesmo tenant.
categoria='Documentos', pagina_path='/melissa/documentos-templates',
ordem=4. SQL import em database-novo/tmp/import-doc-documentos-
templates.sql.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DocumentPreviewDialog emitia @download/@edit/@share/@sign/@delete que
o MelissaPatientDocuments nao ouvia — os 5 botoes da sidebar do preview
caiam no vazio. Adicionado wire-up roteando pros mesmos handlers do
card (onDownload, onEdit, onShare, onSign, onDelete). Share/sign/delete
fecham o preview antes de abrir o proprio dialog pra UX limpa; download
mantem preview aberto (acao instantanea).
DocumentGenerateDialog ganha prop editing-doc-id. Quando setado:
- Busca template_id + dados_preenchidos via loadGeneratedFromDocId
- Pre-seleciona template, popula vars (sobrescreve auto-loaded vars
com dados_preenchidos pra preservar customizacao anterior)
- Pula direto pra step 'edit'
- Save vira UPDATE in-place (preserva documents.id e audit trail)
- Header muda pra "Editar documento" + icone pi-pencil amber
- Botao final vira "Substituir documento"
- Doc sem registro generated (legado): toast info + flow normal de
select template; ao salvar, cria o registro generated linkado.
MelissaPatientDocuments:
- onEdit substituido (era shortcut pra onPreview): abre generate dialog
com editing-doc-id setado.
- Novo ref editingDoc dedicado (separado do selectedDoc que serve
preview/share/sign/delete) pra evitar vazar "edit state" pro botao
"Gerar" do header quando user so abre preview e fecha.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
document_generated.documento_id (FK pra documents) estava sempre NULL
no INSERT — sem isso nao da pra rastrear qual generated belongs to
qual documents row, impossibilitando re-edicao.
DocumentGenerate.service saveGeneratedDocument:
- Modo create (default): INSERT em documents PRIMEIRO pra capturar
doc.id, depois INSERT em document_generated com documento_id setado.
- Modo edit (editingDocId param novo): UPDATE in-place — substitui
PDF no Storage (novo path), atualiza bucket_path/tamanho/nome em
documents (preserva id+audit), atualiza dados_preenchidos+pdf_path
em document_generated. Se nao houver registro generated (doc legado),
INSERT vinculando ao documents.id. Cleanup best-effort do PDF antigo.
- Nova fn loadGeneratedFromDocId(documentoId): busca template_id +
dados_preenchidos pra pre-popular o dialog de edicao.
useDocumentGenerate.generateAndSave: ganha 2o param editingDocId que
passa pro service.
Backfill SQL pra docs antigos: match dg.pdf_path = d.bucket_path +
tenant/patient guard. 3 docs linkados no DB local, 5 ficaram orfaos
(paths que nao existem mais em documents — cleanup antigo).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Script usado pra importar a doc 02-cronometro-melissa.json
diretamente no banco via psql (mesmo padrao da doc Busca global).
DO block com dollar quoting ($HTML$ e $FAQ$) pra evitar escape hell
no HTML conteudo + nos FAQs.
Importacao executada em 2026-05-22. Doc id=e87d4d33-7f5c-454e-a2ff-
0f92505b7c3c + 12 FAQ itens vinculados.
Path: database-novo/tmp/import-doc-cronometro.sql — pasta tmp pra
artefatos de operacao (nao parte do schema canonico).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>