Files
agenciapsilmno/HANDOFF.md
T
Leonardo 2644e60bb6 CRM WhatsApp Grupo 3 completo + Marco A/B (Asaas) + admin SaaS + refactors polimórficos
Sessão 11+: fechamento do CRM de WhatsApp com dois providers (Evolution/Twilio),
sistema de créditos com Asaas/PIX, polimorfismo de telefones/emails, e integração
admin SaaS no /saas/addons existente.

═══════════════════════════════════════════════════════════════════════════
GRUPO 3 — WORKFLOW / CRM (completo)
═══════════════════════════════════════════════════════════════════════════

3.1 Tags · migration conversation_tags + seed de 5 system tags · composable
useConversationTags.js · popover + pills no drawer e nos cards do Kanban.

3.2 Atribuição de conversa a terapeuta · migration 20260421000012 com PK
(tenant_id, thread_key), UPSERT, RLS que valida assignee como membro ativo
do mesmo tenant · view conversation_threads expandida com assigned_to +
assigned_at · composable useConversationAssignment.js · drawer com Select
filtrável + botão "Assumir" · inbox com filtro aside (Todas/Minhas/Não
atribuídas) e chip do responsável em cada card (destaca "Eu" em azul).

3.3 Notas internas · migration conversation_notes · composable + seção
colapsável no drawer · apenas o criador pode editar/apagar (RLS).

3.5 Converter desconhecido em paciente · botão + dialog quick-cadastro ·
"Vincular existente" com Select filter de até 500 pacientes · cria
telefone WhatsApp (vinculado) via upsertWhatsappForExisting.

3.6 Histórico de conversa no prontuário · nova aba "Conversas" em
PatientProntuario.vue · PatientConversationsTab.vue com stats (total /
recebidas / enviadas / primeira / última), SelectButton de filtro, timeline
com bolhas por direção, mídia inline (imagem/áudio/vídeo/doc via signed
URL), indicadores ✓ ✓✓ de delivery, botão "Abrir no CRM".

═══════════════════════════════════════════════════════════════════════════
MARCO A — UNIFICAÇÃO WHATSAPP (dois providers mutuamente exclusivos)
═══════════════════════════════════════════════════════════════════════════

- Página chooser ConfiguracoesWhatsappChooserPage.vue com 2 cards (Pessoal/
  Oficial), deactivate via edge function deactivate-notification-channel
- send-whatsapp-message refatorada com roteamento por provider; Twilio deduz
  1 crédito antes do envio e refunda em falha
- Paridade Twilio (novo): módulo compartilhado supabase/functions/_shared/
  whatsapp-hooks.ts com lógica provider-agnóstica (opt-in, opt-out, auto-
  reply, schedule helpers em TZ São Paulo, makeTwilioCreditedSendFn que
  envolve envio em dedução atômica + rollback). Consumido por Evolution E
  Twilio inbound. Evolution refatorado (~290 linhas duplicadas removidas).
- Bucket privado whatsapp-media · decrypt via Evolution getBase64From
  MediaMessage · upload com path tenant/yyyy/mm · signed URLs on-demand

═══════════════════════════════════════════════════════════════════════════
MARCO B — SISTEMA DE CRÉDITOS WHATSAPP + ASAAS
═══════════════════════════════════════════════════════════════════════════

Banco:
- Migration 20260421000007_whatsapp_credits (4 tabelas: balance,
  transactions, packages, purchases) + RPCs add_whatsapp_credits e
  deduct_whatsapp_credits (atômicas com SELECT FOR UPDATE)
- Migration 20260421000013_tenant_cpf_cnpj (coluna em tenants com CHECK
  de 11 ou 14 dígitos)

Edge functions:
- create-whatsapp-credit-charge · Asaas v3 (sandbox + prod) · PIX com
  QR code · getOrCreateAsaasCustomer patcha customer existente com CPF
  quando está faltando
- asaas-webhook · recebe PAYMENT_RECEIVED/CONFIRMED e credita balance

Frontend (tenant):
- Página /configuracoes/creditos-whatsapp com saldo + loja + histórico
- Dialog de confirmação com CPF/CNPJ (validação via isValidCPF/CNPJ de
  utils/validators, formatação on-blur, pré-fill de tenants.cpf_cnpj,
  persiste no primeiro uso) · fallback sandbox 24971563792 REMOVIDO
- Composable useWhatsappCredits extrai erros amigáveis via
  error.context.json()

Frontend (SaaS admin):
- Em /saas/addons (reuso do pattern existente, não criou página paralela):
  - Aba 4 "Pacotes WhatsApp" — CRUD whatsapp_credit_packages com DataTable,
    toggle is_active inline, dialog de edição com validação
  - Aba 5 "Topup WhatsApp" — tenant Select com saldo ao vivo · RPC
    add_whatsapp_credits com p_admin_id = auth.uid() (auditoria) · histórico
    das últimas 20 transações topup/adjustment/refund

═══════════════════════════════════════════════════════════════════════════
GRUPO 2 — AUTOMAÇÃO
═══════════════════════════════════════════════════════════════════════════

2.3 Auto-reply · conversation_autoreply_settings + conversation_autoreply_
log · 3 modos de schedule (agenda das regras semanais, business_hours
custom, custom_window) · cooldown por thread · respeita opt-out · agora
funciona em Evolution E Twilio (hooks compartilhados)

2.4 Lembretes de sessão · conversation_session_reminders_settings +
_logs · edge send-session-reminders (cron) · janelas 24h e 2h antes ·
Twilio deduz crédito com rollback em falha

═══════════════════════════════════════════════════════════════════════════
GRUPO 5 — COMPLIANCE (LGPD Art. 18 §2)
═══════════════════════════════════════════════════════════════════════════

5.2 Opt-out · conversation_optouts + conversation_optout_keywords (10 system
seed + custom por tenant) · detecção por regex word-boundary e normalização
(lowercase + strip acentos + pontuação) · ack automático (deduz crédito em
Twilio) · opt-in via "voltar", "retornar", "reativar", "restart" ·
página /configuracoes/conversas-optouts com CRUD de keywords

═══════════════════════════════════════════════════════════════════════════
REFACTOR POLIMÓRFICO — TELEFONES + EMAILS
═══════════════════════════════════════════════════════════════════════════

- contact_types + contact_phones (entity_type + entity_id) — migration
  20260421000008 · contact_email_types + contact_emails — 20260421000011
- Componentes ContactPhonesEditor.vue e ContactEmailsEditor.vue (add/edit/
  remove com confirm, primary selector, WhatsApp linked badge)
- Composables useContactPhones.js + useContactEmails.js com
  unsetOtherPrimaries() e validação
- Trocado em PatientsCadastroPage.vue e MedicosPage.vue (removidos campos
  legados telefone/telefone_alternativo e email_principal/email_alternativo)
- Migration retroativa v2 (20260421000010) detecta conversation_messages
  e cria/atualiza phone como WhatsApp vinculado

═══════════════════════════════════════════════════════════════════════════
POLIMENTO VISUAL + INFRA
═══════════════════════════════════════════════════════════════════════════

- Skeletons simplificados no dashboard do terapeuta
- Animações fade-up com stagger via [--delay:Xms] (fix specificity sobre
  .dash-card box-shadow transition)
- ConfirmDialog com group="conversation-drawer" (evita montagem duplicada)
- Image preview PrimeVue com botão de download injetado via MutationObserver
  (fetch + blob para funcionar cross-origin)
- Áudio/vídeo com preload="metadata" e controles de velocidade do browser
- friendlySendError() mapeia códigos do edge pra mensagens pt-BR via
  error.context.json()
- Teleport #cfg-page-actions para ações globais de Configurações
- Brotli/Gzip + auto-import Vue/PrimeVue + bundle analyzer
- AppLayout consolidado (removidas duplicatas por área) + RouterPassthrough
- Removido console.trace debug que estava em watch de router e queries
  Supabase (degradava perf pra todos)
- Realtime em conversation_messages via publication supabase_realtime
- Notifier global flutuante com beep + toggle mute (4 camadas: badge +
  sino + popup + browser notification)

═══════════════════════════════════════════════════════════════════════════
MIGRATIONS NOVAS (13)
═══════════════════════════════════════════════════════════════════════════

20260420000001_patient_intake_invite_info_rpc
20260420000002_audit_logs_lgpd
20260420000003_audit_logs_unified_view
20260420000004_lgpd_export_patient_rpc
20260420000005_conversation_messages
20260420000005_search_global_rpc
20260420000006_conv_messages_notifications
20260420000007_notif_channels_saas_admin_insert
20260420000008_conv_messages_realtime
20260420000009_conv_messages_delivery_status
20260421000001_whatsapp_media_bucket
20260421000002_conversation_notes
20260421000003_conversation_tags
20260421000004_conversation_autoreply
20260421000005_conversation_optouts
20260421000006_session_reminders
20260421000007_whatsapp_credits
20260421000008_contact_phones
20260421000009_retroactive_whatsapp_link
20260421000010_retroactive_whatsapp_link_v2
20260421000011_contact_emails
20260421000012_conversation_assignments
20260421000013_tenant_cpf_cnpj

═══════════════════════════════════════════════════════════════════════════
EDGE FUNCTIONS NOVAS / MODIFICADAS
═══════════════════════════════════════════════════════════════════════════

Novas:
- _shared/whatsapp-hooks.ts (módulo compartilhado)
- asaas-webhook
- create-whatsapp-credit-charge
- deactivate-notification-channel
- evolution-webhook-provision
- evolution-whatsapp-inbound
- get-intake-invite-info
- notification-webhook
- send-session-reminders
- send-whatsapp-message
- submit-patient-intake
- twilio-whatsapp-inbound

═══════════════════════════════════════════════════════════════════════════
FRONTEND — RESUMO
═══════════════════════════════════════════════════════════════════════════

Composables novos: useAddonExtrato, useAuditoria, useAutoReplySettings,
useClinicKPIs, useContactEmails, useContactPhones, useConversationAssignment,
useConversationNotes, useConversationOptouts, useConversationTags,
useConversations, useLgpdExport, useSessionReminders, useWhatsappCredits

Stores: conversationDrawerStore

Componentes novos: ConversationDrawer, GlobalInboundNotifier, GlobalSearch,
ContactEmailsEditor, ContactPhonesEditor

Páginas novas: CRMConversasPage, PatientConversationsTab, AddonsExtratoPage,
AuditoriaPage, NotificationsHistoryPage, ConfiguracoesWhatsappChooserPage,
ConfiguracoesConversasAutoreplyPage, ConfiguracoesConversasOptoutsPage,
ConfiguracoesConversasTagsPage, ConfiguracoesCreditosWhatsappPage,
ConfiguracoesLembretesSessaoPage

Utils novos: addonExtratoExport, auditoriaExport, excelExport,
lgpdExportFormats

Páginas existentes alteradas: ClinicDashboard, PatientsCadastroPage,
PatientCadastroDialog, PatientsListPage, MedicosPage, PatientProntuario,
ConfiguracoesWhatsappPage, SaasWhatsappPage, ConfiguracoesRecursosExtrasPage,
ConfiguracoesPage, AgendaTerapeutaPage, AgendaClinicaPage, NotificationItem,
NotificationDrawer, AppLayout, AppTopbar, useMenuBadges,
patientsRepository, SaasAddonsPage (aba 4 + 5 WhatsApp)

Routes: routes.clinic, routes.configs, routes.therapist atualizados
Menus: clinic.menu, therapist.menu, saas.menu atualizados

═══════════════════════════════════════════════════════════════════════════
NOTAS

- Após subir, rodar supabase functions serve --no-verify-jwt
  --env-file supabase/functions/.env pra carregar o módulo _shared
- WHATSAPP_SETUP.md reescrito (~400 linhas) com setup completo dos 3
  providers + troubleshooting + LGPD
- HANDOFF.md atualizado com estado atual e próximos passos

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 07:05:24 -03:00

11 KiB

HANDOFF — 2026-04-21 (CRM WhatsApp Grupo 3 + Marco B/credits + Asaas + polimento)

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
🟡 Médios adiados 8
🟢 Baixos adiados 7
Vitest 208/208
SQL integration 33/33
E2E (Playwright) 5/5
Migrations totais 36 (23 → 36)
Edge functions 20
Fases Opção C concluídas 5 + 5b + Grupo 3 inteiro + Marco A + Marco B (Asaas) + admin SaaS

🎯 O que rolou hoje (2026-04-21)

Grupo 3 completo — Workflow / CRM

  • 3.1 Tags — migration conversation_tags + 5 system tags seed · composable useConversationTags.js · popover + pills no drawer · pills nos cards do Kanban
  • 3.2 Atribuição de conversa a terapeuta (HOJE de tarde) — migration conversation_assignments (PK (tenant_id, thread_key), UPSERT, RLS membro-ativo + valida assignee como membro do mesmo tenant) + view conversation_threads expandida com assigned_to · composable useConversationAssignment.js · drawer com Select filtrável + "Assumir" · inbox com filtro aside "Todas/Minhas/Não atribuídas" + chip no card
  • 3.3 Notas internasconversation_notes + composable + seção colapsável no drawer
  • 3.5 Converter número desconhecido em paciente — botão + dialog quick-cadastro e "Vincular existente" com Select filter + 500 pacientes
  • 3.6 Histórico de conversa no prontuário (HOJE) — nova aba "Conversas" no PatientProntuario.vue com PatientConversationsTab.vue (stats + filter + timeline + mídia + "Abrir no CRM")

Marco A — Unificação WhatsApp (dois providers)

  • Evolution (pessoal, free) + Twilio (AgenciaPSI Oficial, créditos) — mutuamente exclusivos por tenant
  • Página chooser ConfiguracoesWhatsappChooserPage.vue com 2 cards + deactivate via edge deactivate-notification-channel
  • send-whatsapp-message refatorada — roteamento por provider; Twilio deduz crédito ANTES e refunda em falha
  • Paridade Twilio (HOJE) — módulo compartilhado supabase/functions/_shared/whatsapp-hooks.ts (provider-agnóstico) consumido por Evolution e Twilio inbound. Hooks: opt-in/opt-out/auto-reply + schedule helpers + makeTwilioCreditedSendFn (dedução + rollback). Evolution refatorado (~290 linhas duplicadas removidas). Twilio agora roda mesmo pipeline de hooks (antes só inseria a mensagem e saía)

Marco B — Sistema de créditos WhatsApp + Asaas

  • Migration whatsapp_credits (4 tabelas: balance, transactions, packages, purchases) + 2 RPCs atômicas (add_whatsapp_credits, deduct_whatsapp_credits)
  • Edge create-whatsapp-credit-charge — integração Asaas v3 (PIX sandbox + prod); getOrCreateAsaasCustomer patcha customer existente com CPF quando falta
  • Edge asaas-webhook — recebe PAYMENT_RECEIVED/CONFIRMED e credita balance
  • Página tenant /configuracoes/creditos-whatsapp — saldo + loja + histórico + dialog PIX com QR code
  • CPF/CNPJ no dialog de compra (HOJE) — migration 20260421000013_tenant_cpf_cnpj.sql (coluna + CHECK 11/14 dígitos), dialog de confirmação com validação (isValidCPF/isValidCNPJ de utils/validators), formatação on-blur, pré-fill de tenants.cpf_cnpj, persiste automaticamente no primeiro uso. Fallback sandbox removido

Admin SaaS — gestão de créditos (HOJE)

Integrado em /saas/addons (reuso do pattern existente, não criou página paralela):

  • Aba 4 "Pacotes WhatsApp" — CRUD whatsapp_credit_packages com DataTable (destaque, posição, preço + BRL/msg), toggle is_active inline, dialog de edição com validação
  • Aba 5 "Topup WhatsApp" — tenant Select filtrável com saldo ao vivo; RPC add_whatsapp_credits com p_admin_id = auth.uid() (auditoria); histórico das últimas 20 transações topup/adjustment/refund

Grupo 2 Automação

  • 2.3 Auto-replyconversation_autoreply_settings + conversation_autoreply_log; schedule por modo (agenda / business_hours / custom); cooldown por thread; respeita opt-out; agora funciona em ambos providers
  • 2.4 Lembretes de sessãoconversation_session_reminders_*; edge send-session-reminders (cron); já trata Twilio com dedução + rollback

Grupo 5 Compliance (LGPD Art. 18 §2)

  • 5.2 Opt-outconversation_optouts + conversation_optout_keywords (10 keywords system + custom); detecção por regex word-boundary; ack automático; opt-in via "voltar/retornar/reativar/restart"
  • Página /configuracoes/conversas-optouts

Refactor polimórfico — telefones + emails

  • contact_types + contact_phones (polimórfico: entity_type + entity_id) — migration 20260421000008
  • contact_email_types + contact_emails — migration 20260421000011
  • Componentes ContactPhonesEditor.vue + ContactEmailsEditor.vue
  • Trocado em PatientsCadastroPage.vue, MedicosPage.vue
  • Migration retroativa v2: detecta conversas e cria/atualiza phone como WhatsApp (vinculado)

Polimento visual — sessão manhã

  • Skeletons simplificados no dashboard terapeuta · animações fade-up com stagger por [--delay:Xms] (fix specificity sobre .dash-card)
  • ConfirmDialog com group="conversation-drawer" (evita duplicata)
  • Image preview PrimeVue com botão download injetado via MutationObserver (fetch + blob pra cross-origin)
  • Audio/video com preload="metadata", controles de velocidade herdados do browser
  • friendlySendError() no drawer store — mapeia códigos pt-BR via error.context.json()
  • Teleport #cfg-page-actions pra ações globais da Configurações
  • Brotli/Gzip + auto-import Vue/PrimeVue + bundle analyzer

Infra geral

  • Bucket privado whatsapp-media + decrypt via Evolution getBase64FromMediaMessage + upload + signed URLs on-demand
  • Realtime em conversation_messages via publication supabase_realtime
  • AppLayout consolidado (removido duplicatas por área) + RouterPassthrough
  • console.trace debug removido (watch router/Supabase) que degradava perf

🎯 Próxima sessão (começar por aqui)

Do CRM WhatsApp ainda restam:

Grupo 3 (resto)

  • 3.4 SLA / alerta — conversa sem resposta > X min. Trigger conversation_sla_rules + worker cron
  • 3.7 Bot auto-triagem — pergunta nome/horário antes de sair pro humano

Grupo 6 — Conexão resiliente

  • 6.1 Heartbeat — cron verifica Evolution; dispara incident se desconectado > N min
  • 6.2 Alerta quando celular desconecta (notification + e-mail admin tenant)
  • 6.3 Reconnect automático (tentar re-init da instância)

Grupo 7 — Analytics

  • 7.1 Tempo médio de primeira resposta (card no ClinicDashboard + filtro por terapeuta)

Grupo 8 — Integrações

  • 8.2 Botão na agenda "Lembrar paciente da sessão" — dispara send-whatsapp-message com template lembrete_sessao
  • 8.3 Status sessão dispara mensagem (ex: cancelada → aviso auto)
  • 8.4 Link agendador cria lead — quando paciente preenche intake mas não finaliza, aparece no CRM como thread

Outros blocos

  • Notificação de saldo baixo WhatsApp — trigger em whatsapp_credits_balance quando balance < low_balance_threshold; e-mail + toast
  • Dashboard saas de receita créditos — total arrecadado Asaas por mês, pacotes mais vendidos
  • Retention policy 5.1 — apagar/anonimizar conversas > X dias (configurável por tenant)
  • 5.4 — seção de conversas no LGPD export do paciente

🔧 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 — ele não tem hot reload.


🌲 Deploy options (guardadas pra depois)

  • (a) Smoke test de infra — subir pra Supabase cloud + hospedagem só pra testar sozinho. ~2-3h.
  • (b) Beta fechado com clínicas — precisa: 3.4 SLA, 6.1 heartbeat, 7.1 analytics, retention policy, tour/onboarding refinamento.
  • (c) [em andamento] Fechar gaps funcionais.

📦 Commits

Tem um commit.txt pronto na raiz com mensagem consolidada pra um commit único de tudo que está pendente. git status mostra ~160 arquivos modificados/criados. Conferir commit.txt antes de usar.

Se preferir quebrar em commits menores, os grupos lógicos são:

  1. Migrations CRM + créditos + polimorfismo (pasta database-novo/migrations/20260420* + 20260421*)
  2. Edge functions (pasta supabase/functions/)
  3. Frontend CRM (src/components/conversations/, src/features/conversations/, src/features/patients/prontuario/PatientConversationsTab.vue)
  4. Composables novos (src/composables/useConversation*.js, useWhatsappCredits.js, useContact*.js, useAutoReplySettings.js, useSessionReminders.js)
  5. Páginas config novas (src/layout/configuracoes/*)
  6. Admin SaaS (SaasAddonsPage.vue com 2 tabs novas)
  7. Refactors (PatientsCadastro/Medicos trocando pra ContactEditors; AppLayout; router)

⚠️ Pendências conhecidas

  • 15 V# adiados (8 médios + 7 baixos) — sprint de polimento depois do beta
  • Tour guiado / onboarding wizard — refino deixado pro fim
  • Dashboard SaaS de receita Asaas — falta página
  • Rotação de credenciais Twilio (segurança) — se subconta vazar, precisa de flow pra regenerar

🛠️ 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 (login separado do prod)

📚 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, até amanhã!