diff --git a/.gitignore b/.gitignore index 1dd5a6c..6901089 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,9 @@ coverage .nitro .cache .output -# .env +.env +.env.local +.env.*.local dist/ dist-*/ .DS_Store @@ -16,6 +18,10 @@ api-generator/typedoc.json Dev-documentacao/ supabase/* !supabase/functions/ +# Mas os .env dentro de functions NÃO vão pro git (sobrescreve a negação acima) +supabase/functions/.env +supabase/functions/.env.local +supabase/functions/.env.* evolution-api/ # Backups locais do banco — não comitar (regeneráveis via db.cjs backup) @@ -27,3 +33,8 @@ playwright-report/ # Config local do Claude Code (cada dev tem o seu) .claude/settings.local.json + +# Notas locais do dev e rascunhos de commit — não subir +informacoes Gerais.txt +pasteds.txt +commit.txt diff --git a/Atestado_Psicológico_1774873197838.pdf b/Atestado_Psicológico_1774873197838.pdf deleted file mode 100644 index 35e27fe..0000000 Binary files a/Atestado_Psicológico_1774873197838.pdf and /dev/null differ diff --git a/Atestado_Psicológico_1774873520538.pdf b/Atestado_Psicológico_1774873520538.pdf deleted file mode 100644 index 5f3d12d..0000000 Binary files a/Atestado_Psicológico_1774873520538.pdf and /dev/null differ diff --git a/HANDOFF.md b/HANDOFF.md index 4565daa..8736ac3 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -1,4 +1,4 @@ -# HANDOFF — 2026-04-19 (após Sessões 1-10) +# 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). @@ -9,107 +9,165 @@ Todo o estado vive no banco (`/saas/desenvolvimento` → Auditoria/Verificaçõe | | | |---|---| -| **A# auditoria** abertos | **1** (A#31 — reformular pra "Preparação pra deploy") | -| **V# verificações** abertos | 14 (todos médios/baixos adiados, plano completo no DB) | | **🔴 Críticos** | **0** ✅ | | **🟠 Altos** | **0** ✅ | -| **Áreas auditadas** | **15** (todas as principais do SaaS) | +| 🟡 Médios adiados | 8 | +| 🟢 Baixos adiados | 7 | | Vitest | 208/208 | | SQL integration | 33/33 | | E2E (Playwright) | 5/5 | -| Migrations totais | 18 | -| Último commit | `d6eb992` (pushed ao Gitea) | +| 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** | --- -## 🎯 Próxima sessão — A#31-rev (Preparação pra deploy) +## 🎯 O que rolou hoje (2026-04-21) -**Contexto:** A#31 era "Deploy real" mas você não tem cloud Supabase nem -secrets reais ainda (MVP). Precisa virar **preparação** — deixar tudo -pronto pra quando criar a cloud você executar sozinho com mínimo atrito. +### ✅ Grupo 3 completo — Workflow / CRM -**Tarefas (~2-3h, zero risco):** +- **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 internas** — `conversation_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") -1. **`DEPLOY.md`** na raiz — checklist de 8 passos com comandos exatos + - diagnóstico de erros comuns + ordem de execução -2. **Validar migrations num container limpo** — recriar banco do zero, - aplicar as 18 migrations + seeds em ordem, garantir zero erro -3. **`.env.example`** completo — todas VITE_ vars + cada secret de edge - function listado com instrução -4. **Auditoria das edge functions** — CORS, fallback de env var ausente, - error handling. Documentar quais env cada uma precisa -5. **Script `db.cjs deploy-check`** — comando que valida pré-condições - antes de deploy (ordena migrations, verifica diffs, lista secrets) -6. **Atualizar HANDOFF.md** com seção "Pra deployar" +### ✅ Marco A — Unificação WhatsApp (dois providers) -**Quando voltar, é só dizer "começa A#31-rev" e eu sigo o plano.** +- **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-reply** — `conversation_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ão** — `conversation_session_reminders_*`; edge `send-session-reminders` (cron); já trata Twilio com dedução + rollback + +### ✅ Grupo 5 Compliance (LGPD Art. 18 §2) + +- **5.2 Opt-out** — `conversation_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 --- -## 📚 Memória persistente (carregada automaticamente) +## 🎯 Próxima sessão (começar por aqui) -Já saved no memory system (`MEMORY.md` — não precisa lembrar): -- **Sanitização sempre** — trim, length, regex em toda entrada/saída -- **Priorização por severidade** — críticos+altos imediatos, médios/baixos adiam com plano -- **Self-hosted > provider externo** — LGPD/clínico -- **Gotcha supabase_admin** — `psql -U supabase_admin -h localhost` direto pra ALTER POLICY em tabelas owned -- **Tracking dev_*_items** — A#/V#/T# vivem no DB, UI `/saas/desenvolvimento` -- **Project Overview** + **MVP Assessment** +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 --- -## 📦 Commits relevantes +## 🔧 Setup Evolution/WhatsApp / Asaas -``` -d6eb992 Sessoes 6cont-10: hardening em 6 areas + scan completo do SaaS ← último -7c20b51 Sessoes 1-6 acumuladas: hardening B2, defesa em camadas, +192 testes -d088a89 (commit anterior do projeto) -``` +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. --- -## 🗂️ Áreas auditadas (15) +## 🌲 Deploy options (guardadas pra depois) -| Área | Estado | -|---|---| -| auth, router, stores, agenda, seguranca, saas | 100% fechado/ok | -| **pacientes** ✨ | **100% fechado** (V#9 — script extraído; template breakdown adiado pra quando houver E2E) | -| **documentos** ✨ | 100% fechado | -| **calendario** ✨ | 100% fechado | -| **servicos** ✨ | 100% fechado | -| financeiro | 5 fechados, 6 médios/baixos adiados | -| comunicacao | 5 fechados, 5 médios/baixos adiados | -| tenants | 6 fechados, 2 baixos adiados | -| addons | 3 resolvidos, 1 médio adiado | -| central_saas | 1 alto fechado, 2 médios adiados | - -✨ = áreas 100% fechadas (zero pendência). +- **(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. --- -## ⚠️ Pendências documentadas no DB (14 V# adiados) +## 📦 Commits -Todos médios/baixos com plano completo em `dev_verificacoes_items.acao_sugerida`. -**Não esquecer.** Sprint dedicado de polimento depois do deploy. +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. -- **financeiro** (6): parcelamento CHECK, payouts flow, recurrence DELETE, - composables, máscara PIX, dashboard inadimplência -- **comunicacao** (5): notifications/schedules silos, email_templates_global - filtros, retention notification_logs, dashboard health, audit dismissals -- **tenants** (2): owner_users policies, company_profiles + dev_user_credentials -- **central_saas** (2): rate limit voto, valores tipo_acesso -- **addons** (1): UI de extrato - -Plus (não V#): -- **PatientsCadastroPage template breakdown** — 1951 linhas. Esperar E2E -- **Sprint de polimento** dos 14 médios/baixos juntos +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) --- -## 🛠️ Stack lembretes (caso precise) +## ⚠️ 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 local como supabase_admin (pra ALTER POLICY em tabelas owned):** +- **DB como supabase_admin (ALTER POLICY em tabelas owned):** ```bash docker exec -i -e PGPASSWORD=postgres -e PGCLIENTENCODING=UTF8 \ supabase_db_agenciapsi-primesakai \ @@ -117,7 +175,19 @@ Plus (não V#): ``` - **Vitest:** `npx vitest run` - **SQL integration:** `node database-novo/tests/run.cjs` -- **E2E:** `npx playwright test` (precisa dev server: `npm run dev`) +- **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 --- diff --git a/WHATSAPP_SETUP.md b/WHATSAPP_SETUP.md new file mode 100644 index 0000000..f5d62a8 --- /dev/null +++ b/WHATSAPP_SETUP.md @@ -0,0 +1,518 @@ +# WhatsApp Setup — CRM de Conversas + Créditos + Automações + +Guia end-to-end do subsistema de WhatsApp do AgenciaPSI. Cobre **WhatsApp Pessoal** (Evolution, gratuito), **WhatsApp Oficial AgenciaPSI** (Twilio com créditos), **Asaas** (gateway de pagamento), e todas as automações (auto-reply, lembretes, opt-out, tags, notas). + +--- + +## 🎯 Arquitetura + +### Dois provedores, escolha exclusiva por tenant + +``` +┌─────────────────────────────────────────────────┐ +│ Tenant escolhe 1 canal em /configuracoes/whatsapp │ +└─────────────────────────────────────────────────┘ + │ │ + ▼ ▼ +┌────────────────────┐ ┌──────────────────────┐ +│ WhatsApp Pessoal │ │ WhatsApp Oficial │ +│ (Evolution) │ │ AgenciaPSI (Twilio) │ +│ │ │ │ +│ • Gratuito │ │ • Consome créditos │ +│ • QR code │ │ • API oficial Meta │ +│ • Celular real │ │ • Zero ban risk │ +│ • Docker self-host │ │ • Cloud gerenciado │ +│ • Tier free do SaaS │ │ • Tier pago do SaaS │ +└────────────────────┘ └──────────────────────┘ + │ │ + └───────────┬────────────────┘ + ▼ + ┌─────────────────────────┐ + │ Edge Functions │ + │ │ + │ • send-whatsapp-message │ ← rota por provider + │ • send-session-reminders │ ← idem + │ • evolution-whatsapp-inbound (auto-reply, opt-out) + │ • twilio-whatsapp-inbound (⚠ sem auto-reply ainda) + │ • create-whatsapp-credit-charge (Asaas PIX) + │ • asaas-webhook (credita saldo) + └─────────────────────────┘ + │ + ▼ + ┌─────────────────────────┐ + │ PostgreSQL │ + │ │ + │ conversation_messages │ + │ conversation_notes │ + │ conversation_tags │ + │ conversation_optouts │ + │ conversation_autoreply_* │ + │ session_reminder_* │ + │ whatsapp_credits_* │ + │ whatsapp_credit_packages │ + └─────────────────────────┘ +``` + +### Dedução de créditos + +``` +Usuário envia pelo drawer / lembrete dispara / auto-reply + ↓ +Edge function detecta provider do canal ativo + ↓ + Evolution? Twilio? + ↓ ↓ + Envia direto deduct_whatsapp_credits(1) ← atômico, lock, valida saldo + ↓ ↓ + Registra msg ┌──── OK ────┐ ┌── insufficient ──┐ + ↓ ↓ + send Twilio return 402 + ↓ + ┌── ok ──┐ ┌── fail ──┐ + ↓ ↓ + Registra add_whatsapp_credits(1, 'refund') + msg +``` + +--- + +## 🔧 Setup completo (dev local) + +### 1. Supabase local + edge functions + +```bash +# Subir stack Supabase local (Postgres + Auth + Storage + etc) +npx supabase start + +# Em outro terminal: rodar edge functions +supabase functions serve --no-verify-jwt --env-file supabase/functions/.env +``` + +**Funções carregadas:** + +| Função | URL | Uso | +|---|---|---| +| `evolution-whatsapp-inbound` | `?tenant_id=` | Webhook Evolution: inbound msgs + auto-reply + opt-out | +| `evolution-webhook-provision` | — | Configura webhook na Evolution | +| `twilio-whatsapp-inbound` | `?tenant_id=` | Webhook Twilio (inbound only; sem auto-reply ainda) | +| `send-whatsapp-message` | — | Envio unificado: detecta provider, deduz crédito se Twilio | +| `send-session-reminders` | — | Cron/manual: dispara lembretes 24h e 2h antes | +| `create-whatsapp-credit-charge` | — | Cria PIX Asaas pra compra de créditos | +| `asaas-webhook` | — | Recebe eventos Asaas e credita saldo | +| `deactivate-notification-channel` | — | Desativa canal (usado ao trocar provider) | + +**Flag `--no-verify-jwt`:** necessária porque webhooks externos (Twilio, Evolution, Asaas) não mandam JWT. + +### 2. Evolution API (WhatsApp Pessoal — tier gratuito) + +```bash +# Subir Evolution + Postgres + Redis +docker compose -f evolution-api/docker-compose.yml up -d + +# Verificar status +docker ps --filter name=evolution_api +``` + +Evolution roda em `http://localhost:8080` com API key `minha_chave_123` (ver `evolution-api/docker-compose.yml`). + +### 3. Asaas (pagamentos — tier pago) + +Ativa só em prod ou quando quiser testar compra de créditos end-to-end. + +**Passo 1 — Criar conta sandbox:** +1. https://sandbox.asaas.com (gratuito, CPF qualquer) +2. Menu → Integrações → Integrações Avançadas → API → copia a API key (começa com `$aact_...`) + +**Passo 2 — Configurar env:** + +Edita `supabase/functions/.env`: +```env +ASAAS_API_KEY=$aact_sua_chave_aqui +ASAAS_API_URL=https://sandbox.asaas.com/api/v3 +ASAAS_WEBHOOK_TOKEN= # opcional, pra autenticar webhook +``` + +**Passo 3 — Reiniciar functions serve:** +```bash +# Ctrl+C no terminal do serve +supabase functions serve --no-verify-jwt --env-file supabase/functions/.env +``` + +**Passo 4 — (Opcional) Expor webhook via ngrok pro Asaas alcançar:** +```bash +# Outro terminal +ngrok http 54321 +# Copia a URL (ex: https://abc123.ngrok.app) +``` + +Configura no Asaas: +- Dashboard → Integrações → Webhooks → **Adicionar** +- URL: `https://abc123.ngrok.app/functions/v1/asaas-webhook` +- Eventos: marca **Cobranças** (PAYMENT_RECEIVED, PAYMENT_CONFIRMED, PAYMENT_OVERDUE, PAYMENT_DELETED, PAYMENT_REFUNDED) +- Token (opcional): cadastra o mesmo valor de `ASAAS_WEBHOOK_TOKEN` + +**Em produção:** +```bash +supabase secrets set ASAAS_API_KEY="$aact_prod_key" +supabase secrets set ASAAS_API_URL="https://api.asaas.com/v3" +supabase secrets set ASAAS_WEBHOOK_TOKEN="token_seguro" +``` + +Webhook da prod aponta pro URL real do Supabase cloud (sem ngrok). + +--- + +## 📋 Features & como testar + +### A. Envio manual via drawer + +**Onde:** drawer de qualquer conversa (clica no card do Kanban em `/therapist/conversas`) + +**Fluxo:** +1. Compose no drawer → `store.sendMessage()` → chama `send-whatsapp-message` edge function +2. Function detecta provider ativo em `notification_channels` +3. Evolution: envia direto via `/message/sendText/{instance}` +4. Twilio: `deduct_whatsapp_credits(1)` → se OK envia via Twilio API → se falhar, refunda +5. Registra em `conversation_messages` (direction=outbound, delivery_status=sent/queued) + +**Testar sem Twilio real** (valida dedução + rollback): +- Topup 100 créditos via SQL (ver seção 🧪 mais abaixo) +- Criar canal Twilio fake via SQL +- Enviar msg → deduz, tenta enviar, falha com 401, refunda → saldo volta ao original + +### B. Lembretes automáticos de sessão (2.4) + +**Onde:** `/configuracoes/lembretes-sessao` + +**Config:** +- Toggle on/off +- Ativa lembretes 24h e/ou 2h antes da sessão +- Templates com variáveis: `{{nome_paciente}}`, `{{data_sessao}}`, `{{hora_sessao}}`, `{{modalidade}}`, `{{nome_clinica}}` +- Quiet hours (default 22h-8h SP) +- Respeitar opt-out (LGPD — recomendado ON) + +**Como dispara:** +- Cron hit `send-session-reminders` a cada 15min (pg_cron comentado na migration; em prod configure via Supabase Dashboard → Database → Cron Jobs) +- Em dev: botão **"Testar agora"** na página dispara manualmente + +**Query do worker:** busca `agenda_eventos` com `status='agendado'` dentro de: +- Janela 24h: `inicio_em` entre `now+23h45min` e `now+24h15min` +- Janela 2h: `inicio_em` entre `now+1h45min` e `now+2h15min` + +**Anti-dup:** UNIQUE `(event_id, reminder_type)` no `session_reminder_logs`. + +**Testar:** +```sql +-- Cria evento daqui a ~2h (no horário de SP) +-- Pelo UI da agenda é mais fácil; via SQL: +INSERT INTO agenda_eventos (tenant_id, owner_id, patient_id, inicio_em, fim_em, status, modalidade, tipo, titulo) +VALUES ( + '', + '', + (SELECT id FROM patients WHERE tenant_id='' LIMIT 1), + now() + interval '2 hours', + now() + interval '3 hours', + 'agendado', + 'presencial', + 'session', + 'Teste lembrete' +); +``` +Depois clica **"Testar agora"** em `/configuracoes/lembretes-sessao`. + +### C. Auto-reply fora do horário (2.3) + +**Onde:** `/configuracoes/conversas-autoreply` + +**Config:** +- Toggle on/off + mensagem + cooldown (minutos entre auto-replies pra mesma thread) +- 3 modos: + - **Seguir agenda** — usa `agenda_regras_semanais` dos membros ativos do tenant + - **Horário de funcionamento** — janela semanal editável (armazena em JSONB `business_hours`) + - **Custom** — janela específica pro auto-reply (`custom_window`) + +**Como dispara:** +- Webhook Evolution `evolution-whatsapp-inbound` recebe msg +- Depois de inserir msg, chama `maybeSendAutoReply()` +- Checa: enabled, não está em horário útil, não está em cooldown +- Se OK → envia via Evolution (futuro: rotear pra Twilio se provider='twilio') + +**⚠ Limitação:** atualmente **só funciona com Evolution**. Pra Twilio precisa implementar a mesma lógica em `twilio-whatsapp-inbound` (dívida técnica). + +**Testar:** +- Ativa feature + define janela custom (ex: seg-sex 9h-18h) +- Fora dessa janela, paciente manda msg → chega no inbox + auto-reply é enviado de volta em ~1s + +### D. Opt-out LGPD (5.2) + +**Onde:** `/configuracoes/conversas-optouts` + +**Como funciona:** +- Paciente envia "PARAR", "SAIR", "CANCELAR", "STOP", etc (keyword match case-insensitive sem acentos) +- Edge function `evolution-whatsapp-inbound` detecta → registra em `conversation_optouts` → envia msg de confirmação +- Paciente envia "VOLTAR" / "RETORNAR" → reativa (opted_back_in_at preenchido) +- Auto-reply e lembretes **respeitam opt-out** automaticamente (skip + log `opted_out`) +- Envio manual do terapeuta NÃO é bloqueado (relação terapêutica existe) + +**Keywords padrão:** 10 palavras (configuráveis na página — pode adicionar custom do tenant). + +**Testar:** +- Manda mensagem com "parar" pelo WhatsApp conectado +- Volta em `/configuracoes/conversas-optouts` → número aparece na lista +- Nova mensagem que dispararia auto-reply → não dispara mais + +### E. Notas internas (3.3) + +**Onde:** dentro do drawer de conversa, seção "Notas internas" collapsible + +**Como funciona:** +- CRUD simples por thread +- Visível apenas pra membros ativos do tenant +- Edição/remoção só pelo criador (ou SaaS admin) +- Soft delete (`deleted_at`) +- **NÃO vai pro paciente** — apenas anotação interna da equipe + +### F. Tags na conversa (3.1) + +**Onde:** +- **Gestão:** `/configuracoes/conversas-tags` (CRUD de tags custom; system tags são read-only) +- **Aplicação:** dentro do drawer de conversa + pills visíveis nos cards do Kanban + +**Tags system (seedadas):** Urgente (🔴), Primeira consulta (🔵), Remarcação (🟡), Confirmada (🟢), Follow-up (🟣) + +**Custom:** tenant cria suas próprias com nome + slug + cor + ícone (primeicons). + +### G. Mídia (áudio / imagem / vídeo / documento) + +**Arquitetura:** +- Evolution manda URLs encriptadas do Meta CDN (não tocam direto) +- Edge function `evolution-whatsapp-inbound` chama `/chat/getBase64FromMediaMessage/{instance}` do Evolution → decripta +- Decoda base64 → faz upload no bucket **privado `whatsapp-media`** +- Path: `///_.` +- Salva apenas o PATH em `media_url`, NÃO URL pública +- Frontend (`ConversationDrawer`) gera **signed URL on-demand** (1h TTL) ao renderizar + +**LGPD:** bucket privado, RLS só permite membros ativos do tenant; path tenant-scoped; signed URLs expiram. + +**Player de áudio:** `