Files
agenciapsilmno/HANDOFF.md
T

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| ≤ 1000 por operação
  • Remoção só afeta pool cortesia (topup/adjustment/refund) — compras purchase são intocáveis (FIFO cortesia primeiro)
  • Helper RPC get_whatsapp_removable_balance pra 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_incidents com UNIQUE parcial (1 aberto por channel)
  • RPCs whatsapp_heartbeat_open_incident/resolve/mark_notified
  • Edge whatsapp-heartbeat-check com 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-pessoal ganhou card "Monitoramento de conexão" (toggles alerts/reconnect + threshold + histórico 7d)
  • Painel SaaS /saas/whatsapp mostra 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_breaches com UNIQUE parcial 1 aberto/thread
  • Trigger trg_sla_resolve_on_outbound resolve breach automaticamente quando chega outbound
  • Edge conversation-sla-check calcula businessMinutesElapsed em TS
  • UI /configuracoes/conversas-sla (config + histórico 7d)
  • Cron */5 * * * * ativo (job 6)

Saldo baixo WhatsApp — e409ba6

  • Trigger fn_whatsapp_low_balance_notify BEFORE UPDATE em whatsapp_credits_balance
  • Dispara quando saldo cruza threshold + anti-spam via low_balance_alerted_at
  • Reset automático quando add_whatsapp_credits recredita

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 visibilitychange como fallback pro Realtime
  • Agrega múltiplas pendentes no catch-up (mostra só mais recente + +N outros no sino)
  • Não redispara toast pra system_alert já existentes no mount (F5 limpo)
  • Sistema de aliases /conversas/therapist/conversas ou /admin/conversas por 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_runs identifica "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.vue no 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_channels permite ler soft-deleted (donos/saas_admin/membros)

Bot auto-triagem (3.7) — c2c42a1

  • Tabelas conversation_bots + conversation_bot_sessions
  • Helper maybeProcessBot em _shared/whatsapp-hooks.ts
  • Integrado em evolution-whatsapp-inbound E twilio-whatsapp-inbound
  • Página /configuracoes/conversas-bots com editor de steps, trigger, keywords, opt-out
  • Ao terminar: closing + conversation_notes com resumo das respostas

Grupo 8 completo — b8ea292

  • 8.2 Botão "Lembrar paciente" no AgendaEventDialog — edge send-session-reminder-manual
  • 8.3 Trigger DB em agenda_eventos dispara edge send-session-status-notification (cancelado/remarcado/confirmado)
  • 8.4 Intake abandonado: coluna last_progress_at, edges save-intake-progress + convert-abandoned-intakes, RPC convert_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.vue com 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-channel
  • whatsapp-heartbeat-check
  • conversation-sla-check
  • send-session-reminder-manual
  • send-session-status-notification
  • save-intake-progress
  • convert-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.

  1. Selecionar tenant Bruno Terapeuta
  2. Card "Breakdown do saldo" mostra removível/protegido
  3. Tentar adicionar 1500 → deve recusar (máx 1000)
  4. Adicionar 500 → ok
  5. Mudar pra Remover → deve respeitar limite removível
  6. 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_notes com 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)

  1. Gere um link de convite em /admin/agendamentos-recebidos ou similar
  2. Abra o link anônimo (incognito) → form público
  3. Preencha só nome + telefone (espera 1.5s pra autosave rodar)
  4. 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_by usa 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:

  1. supabase functions serve --no-verify-jwt --env-file supabase/functions/.env em terminal separado
  2. .env do functions tem: SUPABASE_URL, SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY, ASAAS_API_KEY, ASAAS_API_URL=https://api-sandbox.asaas.com/v3
  3. Evolution: /saas/whatsapp cadastra creds global → /configuracoes/whatsapp-pessoal conecta QR
  4. Twilio: /saas/twilio-whatsapp provisiona 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

📌 Bom descanso. Amanhã, testes.