17 KiB
HANDOFF — 2026-04-23 (fim do dia)
Documento de continuidade. Quando voltar, comece lendo esta página.
Todo o estado vive no banco (/saas/desenvolvimento → Auditoria/Verificações/Testes).
📊 Estado atual
| 🔴 Críticos | 0 ✅ |
| 🟠 Altos | 0 ✅ |
| Vitest | 208/208 |
| SQL integration | 33/33 |
| E2E (Playwright) | 5/5 |
| Migrations totais | 47 (36 → 47) |
| Edge functions | 25 (20 → 25) |
| Cron jobs ativos | 2 (heartbeat 2min + SLA 5min) |
| Commits hoje | 18 (de f76a2e3 a f1c97ee) |
🎯 O que rolou hoje (2026-04-23)
✅ Admin SaaS: ajuste manual de créditos WhatsApp (f76a2e3)
- RPC
admin_adjust_whatsapp_credits(+/-) com|amount| ≤ 1000por operação - Remoção só afeta pool cortesia (topup/adjustment/refund) — compras
purchasesão intocáveis (FIFO cortesia primeiro) - Helper RPC
get_whatsapp_removable_balancepra UI mostrar breakdown - UI em
/saas/addons: SelectButton Adicionar/Remover, ConfirmDialog com impactados ao desativar pacote
✅ Heartbeat + Reconnect (6.1 + 6.3) — e1f756e / 4e4bac6
- Tabela
whatsapp_connection_incidentscom UNIQUE parcial (1 aberto por channel) - RPCs
whatsapp_heartbeat_open_incident/resolve/mark_notified - Edge
whatsapp-heartbeat-checkcom threshold configurável (padrão 5min) - Reconnect automático (6.3): antes de abrir incident tenta
POST /instance/restart, espera 3s, re-checa state. Se voltou → resolve. Cooldown 10min/channel. - UI em
/configuracoes/whatsapp-pessoalganhou card "Monitoramento de conexão" (toggles alerts/reconnect + threshold + histórico 7d) - Painel SaaS
/saas/whatsappmostra badge de incidents + "Verificar tudo agora" - Cron
*/2 * * * *ativo (job 5)
✅ SLA de conversas (3.4) — 771b636
conversation_sla_rules(config 1/tenant, threshold 1-1440 min, horário comercial opcional, escopo assigned_only|all)conversation_sla_breachescom UNIQUE parcial 1 aberto/thread- Trigger
trg_sla_resolve_on_outboundresolve breach automaticamente quando chega outbound - Edge
conversation-sla-checkcalculabusinessMinutesElapsedem TS - UI
/configuracoes/conversas-sla(config + histórico 7d) - Cron
*/5 * * * *ativo (job 6)
✅ Saldo baixo WhatsApp — e409ba6
- Trigger
fn_whatsapp_low_balance_notifyBEFORE UPDATE emwhatsapp_credits_balance - Dispara quando saldo cruza threshold + anti-spam via
low_balance_alerted_at - Reset automático quando
add_whatsapp_creditsrecredita
✅ Pipeline de alertas robusto — 881fa16 / 5c50db6 / 6db06ab / 4441661 / 36fbc02 / 5f51bc0 / f646efe / 4026415 / 64e7634
Múltiplos fixes e melhorias do pipeline system_alert:
- Toast vermelho sticky com botão de ação (deeplink ou "Abrir conversa")
- Polling a cada 60s + catch-up no
visibilitychangecomo fallback pro Realtime - Agrega múltiplas pendentes no catch-up (mostra só mais recente +
+N outros no sino) - Não redispara toast pra
system_alertjá existentes no mount (F5 limpo) - Sistema de aliases
/conversas→/therapist/conversasou/admin/conversaspor role - Browser notification do Chrome/Windows agora leva pro drawer da conversa ao clicar
- NotificationItem no sino ganhou botões inline "💬 Conversa" / "Abrir →" + fix NotFound
- Notifica owner_id do channel + admins (deduplicado)
✅ Analytics 7.1 — adf9208
- Helper interno
_first_response_runsidentifica "runs" de inbound (sequências do paciente) + delta até próxima outbound - RPCs:
first_response_stats,first_response_by_therapist,first_response_evolution - Card
FirstResponseCard.vueno Clinic e Therapist Dashboards com 3 KPIs + sparkline + ranking
✅ Fluxo de reativação de canal — 881fa16
- Edge
reactivate-notification-channel(espelho da deactivate) - SaasWhatsappPage detecta canal soft-deleted e reativa ao salvar
- ConfiguracoesWhatsappPage (tenant) mostra card "Reativar WhatsApp Pessoal"
- ChooserPage intercepta clique e reativa antes de ir pro setup
- Migration RLS
notification_channelspermite ler soft-deleted (donos/saas_admin/membros)
✅ Bot auto-triagem (3.7) — c2c42a1
- Tabelas
conversation_bots+conversation_bot_sessions - Helper
maybeProcessBotem_shared/whatsapp-hooks.ts - Integrado em
evolution-whatsapp-inboundEtwilio-whatsapp-inbound - Página
/configuracoes/conversas-botscom editor de steps, trigger, keywords, opt-out - Ao terminar: closing +
conversation_notescom resumo das respostas
✅ Grupo 8 completo — b8ea292
- 8.2 Botão "Lembrar paciente" no
AgendaEventDialog— edgesend-session-reminder-manual - 8.3 Trigger DB em
agenda_eventosdispara edgesend-session-status-notification(cancelado/remarcado/confirmado) - 8.4 Intake abandonado: coluna
last_progress_at, edgessave-intake-progress+convert-abandoned-intakes, RPCconvert_abandoned_intake_to_lead, autosave no form público
✅ Dashboard SaaS receita créditos — f1c97ee
- 4 RPCs (
saas_wa_credits_revenue_stats/top_packages/usage_summary/revenue_evolution) — saas_admin only - Card
SaasCreditsRevenueCard.vuecom 4 KPIs (receita, compras, créditos, consumo) + sparkline + top pacotes - Integrado em
/saas(SaasDashboard)
✅ Fix lateral — 0f64381
send-session-reminders comparava provider='evolution' mas DB tem 'evolution_api' — caía em unknown_provider. Corrigido e validado end-to-end (lembrete chegou pro paciente André Green no celular +55 16 98828 0038).
🧪 ROTEIRO DE TESTES PRA AMANHÃ
Ordem sugerida (3h estimado com tudo). Cada seção é independente — pode testar em qualquer ordem depois de 0.
0. Pré-requisitos (5 min)
# Reiniciar Supabase functions serve pra carregar 5 edges novas/alteradas
supabase functions serve --no-verify-jwt --env-file supabase/functions/.env
Confirma no output que aparecem:
reactivate-notification-channelwhatsapp-heartbeat-checkconversation-sla-checksend-session-reminder-manualsend-session-status-notificationsave-intake-progressconvert-abandoned-intakes
Também confirme crons:
docker exec -i supabase_db_agenciapsi-primesakai psql -U postgres -d postgres -c "SELECT jobid, schedule, jobname, active FROM cron.job WHERE active=true;"
Esperado: 2 jobs ativos (5 e 6).
1. Admin adjust créditos (~10 min)
Login como saas_admin → /saas/addons → aba Topup WhatsApp.
- Selecionar tenant Bruno Terapeuta
- Card "Breakdown do saldo" mostra removível/protegido
- Tentar adicionar 1500 → deve recusar (máx 1000)
- Adicionar 500 → ok
- Mudar pra Remover → deve respeitar limite removível
- Testar confirmação antes de remover
Aba Pacotes WhatsApp: desativar um pacote → confirmação com N compras / M tenants distintos.
2. Heartbeat + Reconnect (~10 min)
2.1. Baseline: /saas/whatsapp → "Verificar tudo agora" → 1 canal, status ok.
2.2. Simular queda com Evolution respondendo:
# Faz logout da instância no Evolution (Evolution API continua de pé)
curl -X POST http://localhost:8080/instance/logout/agenciapsi-teste -H "apikey: <APIKEY>"
Clica "Verificar tudo agora" 2 vezes. Na segunda, o heartbeat tenta restart automático e conecta de novo (precisa escanear QR, mas connection_status volta). Summary deve ter auto_reconnected: 1.
2.3. Simular queda total (Evolution offline):
docker stop evolution-api # ou o nome do container
Baixa threshold pra 1 min em /configuracoes/whatsapp-pessoal. Clica "Verificar tudo agora", espera 1 min, clica de novo. Agora abre incident + toast vermelho + notificação.
Resubir o container + clica verificar → breach fica "Resolvido".
3. SLA de conversas (~15 min)
/configuracoes/conversas-sla → ativa + threshold 1 min + escopo "Todas as conversas" + notify admin ON.
Apague breaches antigos e dispare manual:
docker exec -i supabase_db_agenciapsi-primesakai psql -U postgres -d postgres -c "DELETE FROM conversation_sla_breaches WHERE resolved_at IS NULL;"
SERVICE_KEY=$(supabase status -o env 2>/dev/null | grep SERVICE_ROLE | cut -d'"' -f2)
curl -s -X POST http://localhost:54321/functions/v1/conversation-sla-check -H "Authorization: Bearer $SERVICE_KEY" -d '{}'
Esperado: abre breaches pras threads inbound sem resposta. Notifica.
Responda uma das threads no CRM → trigger fecha o breach automaticamente (vê card "Resolvido" em /configuracoes/conversas-sla).
4. Bot de triagem (~15 min)
/configuracoes/conversas-bots → ligar + salvar com 4 perguntas default.
De outro celular que não seja paciente cadastrado, manda "Oi" pro WhatsApp conectado.
Esperado:
- Bot responde saudação + primeira pergunta (nome)
- Cada resposta avança 1 pergunta
- Ao final: closing + nota interna em
conversation_notescom resumo
Conferir:
docker exec -i supabase_db_agenciapsi-primesakai psql -U postgres -d postgres -c "
SELECT current_step, status, collected_data FROM conversation_bot_sessions ORDER BY started_at DESC LIMIT 3;"
Abra o CRM na thread desse número → no drawer, na aba notas, deve ter o resumo.
5. Botão "Lembrar paciente" na agenda (8.2) (~5 min)
Agenda → abrir evento existente (Edit) com paciente que tenha telefone. Footer tem botão verde de WhatsApp.
Clicar → confirmação → toast sucesso → mensagem chega no celular do paciente.
Teste erros: paciente sem telefone → mensagem clara.
6. Status sessão dispara mensagem (8.3) (~10 min)
Antes: criar templates custom (ou deixar sem — skip silencioso) pra cancelamento_sessao / remarcacao_sessao / confirmacao_sessao em /configuracoes/whatsapp → aba Templates.
No dialog de evento existente, mudar status pra "Cancelado" → Salvar.
Trigger DB chama edge, edge resolve template + envia. Conferir:
docker exec -i supabase_db_agenciapsi-primesakai psql -U postgres -d postgres -c "
SELECT direction, provider, provider_raw, substring(body from 1 for 80) FROM conversation_messages
WHERE provider_raw->>'status_change' = 'true' ORDER BY created_at DESC LIMIT 3;"
Se não tem template configurado, a edge retorna skipped: template_not_found (silencioso).
7. Intake abandonado → lead (8.4) (~15 min)
- Gere um link de convite em
/admin/agendamentos-recebidosou similar - Abra o link anônimo (incognito) → form público
- Preencha só nome + telefone (espera 1.5s pra autosave rodar)
- Feche a aba (não submete)
Conferir que o intake ficou in_progress:
docker exec -i supabase_db_agenciapsi-primesakai psql -U postgres -d postgres -c "
SELECT status, nome_completo, telefone, last_progress_at FROM patient_intake_requests ORDER BY updated_at DESC LIMIT 3;"
Forçar conversão (sem esperar 30 min):
SERVICE_KEY=$(supabase status -o env 2>/dev/null | grep SERVICE_ROLE | cut -d'"' -f2)
curl -s -X POST http://localhost:54321/functions/v1/convert-abandoned-intakes \
-H "Authorization: Bearer $SERVICE_KEY" -d '{"idle_minutes": 0}'
Esperado: converted: 1. Abre o CRM → thread nova com o telefone + nota interna com dados coletados.
Ativar cron (opcional, a cada 15 min):
SELECT cron.schedule('convert-abandoned-intakes-every-15min', '*/15 * * * *', $$
SELECT net.http_post(
url := current_setting('app.settings.supabase_url') || '/functions/v1/convert-abandoned-intakes',
headers := jsonb_build_object('Authorization', 'Bearer ' || current_setting('app.settings.service_role_key'), 'Content-Type', 'application/json'),
body := '{}'::jsonb
);
$$);
8. Saldo baixo WhatsApp (~5 min)
Login como terapeuta → /configuracoes/creditos-whatsapp → threshold em 20.
docker exec -i supabase_db_agenciapsi-primesakai psql -U postgres -d postgres -c "
UPDATE whatsapp_credits_balance SET balance=100, low_balance_alerted_at=NULL WHERE tenant_id='bbbbbbbb-0002-0002-0002-000000000002';
UPDATE whatsapp_credits_balance SET balance=10 WHERE tenant_id='bbbbbbbb-0002-0002-0002-000000000002';"
Toast vermelho "Saldo baixo" aparece + botão "Ir pra loja →".
9. Analytics 1ª resposta (~3 min)
/admin (ClinicDashboard) → card "Tempo de 1ª resposta" com sparkline + ranking terapeutas.
/therapist (TherapistDashboard) → card filtrado pelo user logado.
10. Dashboard SaaS receita (~3 min)
Login como saas_admin → /saas → seção nova "Receita de créditos WhatsApp":
- KPIs receita/compras/créditos/consumo
- Sparkline de evolução
- Ranking top 5 pacotes
Mudar período (30d/90d/6m/12m) → recarrega.
🎯 Próxima sessão (se tudo der ok no teste)
Items do backlog original que ficaram
- 5.4 Export LGPD de conversas — incluir conversas no export de paciente (que já existe)
- Tour guiado / onboarding wizard — refino UX
- Rotação de credenciais Twilio — se subconta vazar, precisa de flow pra regenerar
- Retention 5.1 — apagar/anonimizar conversas > X dias (você pulou — voltar quando for beta fechado)
Items novos nascidos desta sessão
- Suporte Twilio no status→msg e lembrete manual — hoje só Evolution (Twilio retorna
provider_not_supported_yet) - Persistir user "bot" sintético — hoje
conversation_notes.created_byusa primeiro admin como hack - Autosave do form de intake mais robusto — hoje só 4 campos (nome, telefone, email, onde_nos_conheceu); ideal seria todos os campos preenchidos até então
- Cron de
convert-abandoned-intakes— template pronto, ativar quando testar o fluxo - Bot v2: fallback quando paciente digita algo que não encaixa (ex: "sim" na pergunta de nome) — hoje aceita qualquer string
Pós-beta (deixar pra depois)
- (a) Smoke test infra — cloud Supabase + hospedagem. ~2-3h
- (b) Beta fechado com clínicas
- (c) Ampliar analytics (conversão por terapeuta, SLA por tag, etc)
🔧 Setup Evolution/WhatsApp / Asaas
Tudo em WHATSAPP_SETUP.md. Resumo crítico:
supabase functions serve --no-verify-jwt --env-file supabase/functions/.envem terminal separado.envdo functions tem:SUPABASE_URL,SUPABASE_ANON_KEY,SUPABASE_SERVICE_ROLE_KEY,ASAAS_API_KEY,ASAAS_API_URL=https://api-sandbox.asaas.com/v3- Evolution:
/saas/whatsappcadastra creds global →/configuracoes/whatsapp-pessoalconecta QR - Twilio:
/saas/twilio-whatsappprovisiona subconta → tenant ativa em/configuracoes/whatsapp-oficial(usa créditos)
⚠️ Após editar qualquer supabase/functions/** precisa reiniciar o supabase functions serve — sem hot reload.
📦 Commits de hoje (cronológico)
f76a2e3 Admin SaaS: ajuste manual de créditos WhatsApp (+/-) com proteção de compras
e1f756e Heartbeat WhatsApp Evolution (Grupo 6.1): detecção + incident + alerta admin
881fa16 Fluxo de reativação de canal WhatsApp + alerta toast sticky + notify owner
e409ba6 Saldo baixo WhatsApp: trigger dispara notificação ao cruzar threshold
5c50db6 Notifications: fallback de polling + catch-up ao focar a aba
6db06ab Toast system_alert ganha botão de ação com deeplink
4441661 Toast system_alert: agregar no catch-up pra não empilhar enxurrada
771b636 SLA de conversas WhatsApp (Grupo 3.4): config + detecção + alerta
4026415 Notifications: não redispara toast pra system_alert antigas após F5
5f51bc0 Fix deeplink /crm/conversas não existe; alias dinâmico por role
f646efe Toast SLA: botão "Abrir conversa" abre drawer direto da thread
64e7634 NotificationItem: resolve alias + botões inline "Conversa"/"Abrir"
36fbc02 Browser notification: click leva pro destino real (drawer ou rota)
adf9208 Analytics 7.1: tempo médio de 1ª resposta WhatsApp no dashboard
0f64381 Fix send-session-reminders comparava provider='evolution' mas DB guarda 'evolution_api'
4e4bac6 6.3 Reconnect automático Evolution antes de abrir incident
c2c42a1 3.7 Bot auto-triagem WhatsApp: config por tenant + hook nos inbound
b8ea292 Grupo 8: agenda ↔ WhatsApp completo (8.2 lembrar manual, 8.3 status→msg, 8.4 lead)
f1c97ee Dashboard SaaS ganha seção de receita de créditos WhatsApp (Asaas)
🛠️ Stack lembretes
- DB local:
docker exec -i supabase_db_agenciapsi-primesakai psql -U postgres -d postgres - DB como supabase_admin (ALTER POLICY em tabelas owned):
docker exec -i -e PGPASSWORD=postgres -e PGCLIENTENCODING=UTF8 \ supabase_db_agenciapsi-primesakai \ psql -U supabase_admin -d postgres -h localhost -f migration.sql - Vitest:
npx vitest run - SQL integration:
node database-novo/tests/run.cjs - Edge functions serve:
supabase functions serve --no-verify-jwt --env-file supabase/functions/.env - Evolution Manager:
http://localhost:8080/manager/ - Supabase Studio:
http://localhost:54323 - Asaas sandbox:
https://sandbox.asaas.com
📚 Memória persistente (carregada automaticamente)
Já saved em MEMORY.md:
- Project overview · MVP Assessment · Deploy options
- Sanitização sempre · Priorização por severidade · Self-hosted > provider externo
- Gotcha supabase_admin · Tracking dev_*_items