Correcao Sidebar Classico e Rail, Correcao Layout, Ajuste de Breakpoint para Tailwind, Ajuste AppTopbar, Ajuste Menu PopOver, Recriado Paleta de Cores, Inserido algumas animações leves, Reajuste Cor items NOVOS da tabela, Drawer Ajuda Corrigido no Logout, Whatsapp, sms, email, recursos extras

This commit is contained in:
Leonardo
2026-03-24 21:26:58 -03:00
parent a89d1f5560
commit 53a4980396
453 changed files with 121427 additions and 174407 deletions

View File

@@ -0,0 +1,556 @@
# Sistema de Lembretes Automáticos — WhatsApp, E-mail, SMS
> Agência PSI — Arquitetura completa
> Data: 2026-03-21
> Autor: Leonardo Nohama
---
## Sumário
1. [Decisão de Provedor WhatsApp](#1-decisão-de-provedor-whatsapp)
2. [Modelagem do Banco de Dados](#2-modelagem-do-banco-de-dados)
3. [Lógica de Agendamento (Cron + Edge Functions)](#3-lógica-de-agendamento)
4. [Integração Evolution API (MVP)](#4-integração-evolution-api-mvp)
5. [Integração API Oficial Meta (Escala)](#5-integração-api-oficial-meta)
6. [Frontend: Telas de Configuração](#6-frontend-telas-de-configuração)
7. [LGPD e Boas Práticas](#7-lgpd-e-boas-práticas)
8. [Diagrama do Fluxo Completo](#8-diagrama-do-fluxo-completo)
9. [Checklist de Produção](#9-checklist-de-produção)
---
## 1. Decisão de Provedor WhatsApp
### Comparativo de Provedores
| Critério | Evolution API | WPPConnect | Z-API (grátis) | Z-API Pro | Twilio (Meta oficial) | 360dialog (Meta oficial) | Zenvia (Meta oficial) |
|----------|--------------|------------|----------------|-----------|----------------------|-------------------------|----------------------|
| **Tipo** | Não-oficial (baileys) | Não-oficial | Não-oficial | Não-oficial (infra deles) | API Oficial Meta | API Oficial Meta | API Oficial Meta |
| **Custo mensal** | R$ 0 (self-hosted) | R$ 0 (self-hosted) | R$ 0 (limite de msgs) | R$ 99299/mês | ~R$ 0.30/msg (conversa) | €49/mês + msg | R$ 0.150.40/msg |
| **Setup** | 24h (Docker) | 48h | 30min (SaaS) | 1h (SaaS) | 12 semanas (aprovação) | 35 dias | 35 dias |
| **Risco de ban** | **MÉDIO-ALTO** | ALTO | MÉDIO | MÉDIO | **ZERO** | **ZERO** | **ZERO** |
| **Templates Meta** | ❌ Não suporta | ❌ | ❌ | ❌ | ✅ Obrigatório | ✅ Obrigatório | ✅ Obrigatório |
| **Webhooks status** | ✅ Completo | ✅ Parcial | ✅ | ✅ | ✅ Completo | ✅ Completo | ✅ Completo |
| **Multi-instância** | ✅ Nativo | ❌ Manual | ❌ | ✅ | ✅ Via WABA | ✅ | ✅ |
| **Uptime SLA** | Depende de você | Depende de você | 99.5% | 99.5% | 99.95% | 99.9% | 99.9% |
| **Escalabilidade** | ~500 msgs/dia seguro | ~300 msgs/dia | ~200 msgs/dia | ~2000 msgs/dia | Ilimitado | Ilimitado | Ilimitado |
### Recomendação
**MVP (0100 clínicas):** Evolution API self-hosted
- Custo zero, setup rápido, suficiente para validar o produto
- Cada terapeuta conecta seu próprio número via QR Code
- Limite prático: ~500 mensagens/dia por número
- Mitigação de ban: mensagens personalizadas (não genéricas), intervalos entre envios,
máximo 2 lembretes por sessão
**Escala (100+ clínicas):** Migrar para API Oficial da Meta via 360dialog ou Twilio
- Zero risco de banimento
- Templates aprovados pela Meta = entrega garantida
- Custo previsível por conversa (~R$ 0.250.40 por conversa de 24h)
- Suporte a botões interativos (confirmar/cancelar)
**Estratégia de migração:** O sistema será projetado com abstração de provedor desde o início.
A tabela `notification_channels` registra qual provedor cada tenant usa. Trocar de Evolution
para Meta oficial = mudar o `provider` e credenciais, sem alterar a fila ou templates.
---
## 2. Modelagem do Banco de Dados
> **Nota de integração:** O sistema existente já possui:
> - `email_templates_global` / `email_templates_tenant` → serão estendidos (não duplicados)
> - `notifications` → continuam para notificações in-app (realtime)
> - `profiles.notify_reminders` → será respeitado como opt-out global
> - `TEMPLATE_CHANNELS` em emailTemplateConstants.js → já prevê whatsapp/sms
### Relação entre tabelas novas e existentes
```
┌─────────────────────────────────────────────────────────────────┐
│ EXISTENTES (não alterar) │
│ │
│ email_templates_global ──→ templates de email (11 seeds) │
│ email_templates_tenant ──→ overrides por tenant/owner │
│ notifications ──→ notificações in-app (realtime) │
│ profiles ──→ notify_reminders, notify_system_email│
│ agenda_eventos ──→ sessões com patient_id, inicio_em │
│ patients ──→ nome_completo, telefone, email │
├─────────────────────────────────────────────────────────────────┤
│ NOVAS TABELAS │
│ │
│ notification_channels ──→ config WhatsApp/SMS por tenant │
│ notification_templates ──→ templates multi-canal (wpp/sms) │
│ notification_queue ──→ fila de envio │
│ notification_logs ──→ histórico completo │
│ notification_preferences ──→ opt-in/opt-out por paciente │
│ notification_schedules ──→ regras de quando disparar │
└─────────────────────────────────────────────────────────────────┘
```
---
## 3. Lógica de Agendamento
### Fluxo Completo
```
pg_cron (a cada 5 min)
├──→ populate_notification_queue() ← PL/pgSQL function
│ Busca agenda_eventos com inicio_em futuro,
│ cruza com notification_schedules ativas,
│ verifica notification_preferences do paciente,
│ insere na notification_queue com idempotency_key
└──→ HTTP call → Edge Function: process-notification-queue
├── Busca itens pendentes (status = 'pendente', scheduled_at <= now())
├── Marca como 'processando' (lock otimista via updated_at)
├── Resolve variáveis do template
├── Despacha para o provedor correto:
│ ├── WhatsApp → Evolution API ou Meta API
│ ├── Email → Resend / SendGrid / SMTP
│ └── SMS → Zenvia / Twilio
├── Atualiza status → 'enviado' ou 'falhou'
├── Insere em notification_logs
└── Em caso de falha: agenda retry exponencial
```
### Retry Exponencial
```
Tentativa 1: imediato
Tentativa 2: +5 minutos
Tentativa 3: +15 minutos
Tentativa 4: +60 minutos
Tentativa 5: +4 horas
Máximo: 5 tentativas → marca como 'falhou' definitivamente
```
### Prevenção de Duplicatas
1. **Idempotency key** = `{agenda_evento_id}:{schedule_key}:{canal}:{data_sessao}`
2. **UNIQUE constraint** na notification_queue sobre idempotency_key
3. **Lock otimista** no processamento: `UPDATE ... WHERE status = 'pendente' AND updated_at = ?`
4. **pg_cron não overlap**: usa `pg_try_advisory_lock()` no populate
---
## 4. Integração Evolution API (MVP)
### Setup Docker
```yaml
# docker-compose.evolution.yml
version: '3.8'
services:
evolution-api:
image: atendai/evolution-api:latest
ports:
- "8080:8080"
environment:
- AUTHENTICATION_API_KEY=sua_chave_global_aqui
- DATABASE_PROVIDER=postgresql
- DATABASE_CONNECTION_URI=postgresql://user:pass@host:5432/evolution
- WEBHOOK_GLOBAL_URL=https://seu-dominio.com/api/webhooks/evolution
- WEBHOOK_GLOBAL_ENABLED=true
- WEBHOOK_EVENTS_STATUS_INSTANCE=true
- WEBHOOK_EVENTS_MESSAGES_UPSERT=true
- WEBHOOK_EVENTS_SEND_MESSAGE=true
volumes:
- evolution_data:/evolution/store
restart: unless-stopped
volumes:
evolution_data:
```
### Endpoints Necessários
```
Base URL: https://evolution.seudominio.com
POST /instance/create → criar instância para o tenant
GET /instance/connect/{name} → obter QR Code para conectar número
GET /instance/connectionState/{name} → verificar status da conexão
POST /message/sendText/{name} → enviar mensagem de texto
POST /message/sendMedia/{name} → enviar com mídia (opcional)
DELETE /instance/delete/{name} → remover instância
```
### Payload de Envio
```json
// POST /message/sendText/{instance_name}
{
"number": "5516999887766",
"text": "Olá Ana Clara! 👋\n\nLembrete: você tem sessão amanhã, 21/03, às 14:00 com Dra. Beatriz Costa.\n\n📍 Online via Google Meet\n🔗 https://meet.google.com/abc-defg-hij\n\nPara confirmar, responda OK.\nPara cancelar, responda CANCELAR.\n\nAgência PSI"
}
```
### Webhook de Status
```json
// POST /api/webhooks/evolution (recebido do Evolution)
{
"event": "messages.update",
"instance": "clinica_abc",
"data": {
"key": {
"remoteJid": "5516999887766@s.whatsapp.net",
"id": "3EB0A0B6F..."
},
"update": {
"status": 3 // 1=pendente, 2=enviado ao servidor, 3=entregue, 4=lido
}
}
}
```
### Credenciais no Supabase
Armazenadas na tabela `notification_channels.credentials` como JSONB criptografado:
```json
{
"api_url": "https://evolution.seudominio.com",
"api_key": "chave_global_evolution",
"instance_name": "clinica_dr_beatriz",
"connected_number": "5516999887766",
"connection_status": "open"
}
```
> A criptografia das credenciais usa `pgcrypto` com chave armazenada como
> variável de ambiente do Supabase (Vault). Veja a função SQL `encrypt_credentials()`.
---
## 5. Integração API Oficial Meta
### Template de Lembrete para Aprovação
Nome: `session_reminder_v1`
Categoria: `UTILITY`
Idioma: `pt_BR`
```
HEADER: 📋 Lembrete de Sessão
BODY: Olá {{1}}! Sua sessão com {{2}} está agendada para {{3}} às {{4}}.
Modalidade: {{5}}
BUTTONS:
[quick_reply] ✅ Confirmar presença
[quick_reply] ❌ Preciso cancelar
FOOTER: Agência PSI — Tecnologia aplicada à escuta
```
### Envio via Graph API
```
POST https://graph.facebook.com/v19.0/{phone_number_id}/messages
Authorization: Bearer {access_token}
Content-Type: application/json
{
"messaging_product": "whatsapp",
"to": "5516999887766",
"type": "template",
"template": {
"name": "session_reminder_v1",
"language": { "code": "pt_BR" },
"components": [
{
"type": "body",
"parameters": [
{ "type": "text", "text": "Ana Clara" },
{ "type": "text", "text": "Dra. Beatriz Costa" },
{ "type": "text", "text": "21/03/2026" },
{ "type": "text", "text": "14:00" },
{ "type": "text", "text": "Online" }
]
}
]
}
}
```
### Webhook de Status (Meta)
```json
// POST /api/webhooks/meta-whatsapp
{
"entry": [{
"changes": [{
"value": {
"statuses": [{
"id": "wamid.HBgN...",
"status": "delivered", // sent, delivered, read, failed
"timestamp": "1711036800",
"recipient_id": "5516999887766",
"errors": []
}]
}
}]
}]
}
```
---
## 6. Frontend: Telas de Configuração
### 6.1 Configuração de Canal (`ConfiguracoesCanaisPage.vue`)
```
┌─────────────────────────────────────────────────┐
│ 📡 Canais de Notificação │
├─────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ 💬 WhatsApp [Ativo ✅] │ │
│ │ Provedor: Evolution API │ │
│ │ Número: +55 16 99988-7766 │ │
│ │ Status: 🟢 Conectado │ │
│ │ │ │
│ │ [Reconectar] [Ver QR Code] [Testar] │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ 📧 E-mail [Ativo ✅] │ │
│ │ Provedor: Resend │ │
│ │ Remetente: clinica@drbeat... │ │
│ │ Status: 🟢 Verificado │ │
│ │ │ │
│ │ [Configurar SMTP] [Testar] │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ 📱 SMS [Inativo ⬜] │ │
│ │ Não configurado │ │
│ │ │ │
│ │ [Ativar] │ │
│ └──────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────┘
```
### 6.2 Templates (`ConfiguracoesNotifTemplatesPage.vue`)
- Lista de templates por domínio (Sessão, Triagem, Sistema)
- Preview ao vivo com variáveis mock (já existe no sistema)
- Cada template pode ser personalizado por canal (email / whatsapp / sms)
- Herança: se o tenant não customizou, mostra o template global com badge "Padrão"
### 6.3 Regras de Envio (`ConfiguracoesNotifRegrasPage.vue`)
```
┌──────────────────────────────────────────────┐
│ ⏰ Regras de Envio de Lembretes │
├──────────────────────────────────────────────┤
│ │
│ Lembrete de Sessão │
│ ├── 24 horas antes [WhatsApp ✅] [Email ✅] │
│ ├── 2 horas antes [WhatsApp ✅] [Email ⬜] │
│ └── 30 min antes [WhatsApp ⬜] [Email ⬜] │
│ │
│ Confirmação de Sessão │
│ ├── Imediata (ao criar) [Email ✅] │
│ └── Imediata [WhatsApp ✅] │
│ │
│ Cancelamento │
│ └── Imediata [WhatsApp ✅] [Email ✅]│
│ │
│ Boas-vindas (novo paciente) │
│ └── Imediata [WhatsApp ✅] [Email ✅]│
│ │
│ ⚙️ Horário permitido: 08:00 20:00 │
│ 📅 Não enviar: Domingos e feriados │
│ │
│ [Salvar configurações] │
└──────────────────────────────────────────────┘
```
### 6.4 Logs de Envio (`ConfiguracoesNotifLogsPage.vue`)
- Tabela paginada com filtros por canal, status, período
- Detalhes por envio: template usado, variáveis resolvidas, resposta do provedor
- Estatísticas: total enviado, entregue, lido, falhou (últimos 30 dias)
- Export CSV
### 6.5 Opt-out pelo Paciente
- **WhatsApp:** Paciente responde "SAIR" → webhook captura → atualiza `notification_preferences`
- **Email:** Link "Cancelar inscrição" no rodapé → página pública de opt-out
- **Portal do paciente (futuro):** Toggle na área logada do paciente
---
## 7. LGPD e Boas Práticas
### Coleta de Consentimento
1. **Cadastro externo** (CadastroPacienteExterno.vue): já tem checkbox LGPD
2. **Cadastro pelo terapeuta**: adicionar checkbox "Paciente autoriza receber lembretes por WhatsApp/E-mail"
3. **Primeiro lembrete**: incluir mensagem "Responda SAIR a qualquer momento para parar de receber mensagens"
### Armazenamento Seguro
- Telefone do paciente: armazenado na tabela `patients.telefone` (já existe)
- Credenciais do provedor: `notification_channels.credentials` criptografado com `pgcrypto`
- Chave de criptografia: Supabase Vault (variável de ambiente, nunca no código)
### Retenção de Logs
- `notification_logs`: reter por **2 anos** (exigência legal para comprovação de comunicação)
- `notification_queue`: limpar itens processados após **90 dias** (via pg_cron)
- `notification_preferences`: manter enquanto o paciente estiver ativo
### Opt-out Imediato
- Resposta "SAIR" no WhatsApp → webhook → `notification_preferences.whatsapp_opt_in = false`
- Efeito imediato: todas as mensagens pendentes na fila para aquele paciente são canceladas
- Trigger SQL: ao atualizar opt-out, cancela itens pendentes na queue
---
## 8. Diagrama do Fluxo Completo
```
┌──────────────┐
│ agenda_eventos│ ← terapeuta cria/edita sessão
└──────┬───────┘
┌──────────────────────────────────────┐
│ pg_cron: populate_notification_queue │ ← roda a cada 5 min
│ │
│ 1. Busca sessões com inicio_em │
│ entre agora e +48h │
│ 2. Cruza com notification_schedules │
│ (ex: 24h antes, 2h antes) │
│ 3. Verifica: │
│ - notification_preferences (opt-in)│
│ - notification_channels (canal ativo)│
│ - profiles.notify_reminders │
│ - agenda_eventos.status ≠ cancelado│
│ 4. Gera idempotency_key │
│ 5. INSERT INTO notification_queue │
│ ON CONFLICT DO NOTHING │
└──────────────┬───────────────────────┘
┌──────────────────────────────────────┐
│ pg_cron: chama Edge Function │ ← roda a cada 5 min (offset 2min)
│ POST /functions/v1/process-notif-queue│
└──────────────┬───────────────────────┘
┌──────────────────────────────────────┐
│ Edge Function: process-notif-queue │
│ │
│ 1. SELECT ... FROM notification_queue│
│ WHERE status = 'pendente' │
│ AND scheduled_at <= now() │
│ LIMIT 50 │
│ FOR UPDATE SKIP LOCKED │
│ │
│ 2. Para cada item: │
│ a. Marca 'processando' │
│ b. Resolve template + variáveis │
│ c. Despacha para provedor: │
│ ┌──────────────────────┐ │
│ │ channel = 'whatsapp' │ │
│ │ → Evolution API │ │
│ │ ou Meta Graph API │ │
│ ├──────────────────────┤ │
│ │ channel = 'email' │ │
│ │ → Resend / SMTP │ │
│ ├──────────────────────┤ │
│ │ channel = 'sms' │ │
│ │ → Zenvia / Twilio │ │
│ └──────────────────────┘ │
│ d. Atualiza status │
│ e. INSERT notification_logs │
│ │
│ 3. Itens com falha: │
│ attempts += 1 │
│ next_retry_at = exponential │
│ status = attempts >= 5 │
│ ? 'falhou' : 'pendente' │
└──────────────────────────────────────┘
┌──────────────────────────────────────┐
│ Webhook Handler │
│ POST /functions/v1/notif-webhook │
│ │
│ Recebe status do provedor: │
│ - Evolution: messages.update │
│ - Meta: webhook de status │
│ - Email: bounce/delivery events │
│ │
│ Atualiza notification_logs: │
│ - delivered_at, read_at, failed_at │
│ - provider_status, provider_response │
│ │
│ Se resposta = "SAIR": │
│ → UPDATE notification_preferences │
│ SET whatsapp_opt_in = false │
│ → CANCEL pendentes na queue │
└──────────────────────────────────────┘
```
---
## 9. Checklist de Produção
### Infraestrutura
- [ ] Subir Evolution API (Docker) em VPS dedicada
- [ ] Configurar domínio + SSL para Evolution API
- [ ] Configurar Supabase Vault com chave de criptografia
- [ ] Habilitar extensão `pgcrypto` no Supabase
- [ ] Habilitar extensão `pg_cron` no Supabase
- [ ] Configurar DNS para webhook (ex: `webhooks.agenciapsi.com.br`)
### Banco de Dados
- [ ] Rodar migration: tabelas de notificação
- [ ] Rodar migration: functions PL/pgSQL (populate queue, encrypt/decrypt)
- [ ] Configurar pg_cron jobs (populate + process)
- [ ] Verificar RLS policies em todas as tabelas
- [ ] Seed: notification_schedules padrão (24h, 2h)
- [ ] Seed: notification_templates padrão (whatsapp + sms)
### Edge Functions
- [ ] Deploy: `process-notif-queue`
- [ ] Deploy: `notif-webhook`
- [ ] Configurar secrets: `EVOLUTION_API_KEY`, `ENCRYPTION_KEY`
- [ ] Testar com payload simulado
### Frontend
- [ ] Tela de configuração de canais
- [ ] Tela de templates (estender ConfiguracoesEmailTemplatesPage existente)
- [ ] Tela de regras de envio
- [ ] Tela de logs
- [ ] Adicionar consentimento no cadastro de paciente
- [ ] Adicionar opt-out no rodapé de emails
### Testes
- [ ] Teste E2E: criar sessão → verificar queue populada → verificar envio
- [ ] Teste: opt-out WhatsApp → verificar cancelamento na queue
- [ ] Teste: retry após falha → verificar exponential backoff
- [ ] Teste: idempotency → rodar populate 2x → verificar sem duplicatas
- [ ] Teste: tenant isolation → verificar RLS
- [ ] Teste de carga: 1000 mensagens na queue → medir throughput
### Monitoramento
- [ ] Alerta se queue > 500 itens pendentes
- [ ] Alerta se taxa de falha > 10% em 1h
- [ ] Dashboard de métricas (envios/dia, taxa de entrega, tempo médio de processamento)
- [ ] Log de erros no Supabase Logs
---
*Documento gerado como parte da arquitetura do sistema Agência PSI.*
*As implementações SQL e JavaScript estão nos arquivos separados referenciados abaixo.*