3 Commits

Author SHA1 Message Date
Leonardo 4e4bac622c 6.3 Reconnect automático Evolution antes de abrir incident
Fluxo novo no heartbeat-check quando threshold vence:
1. Verifica se reconnect está habilitado (metadata.heartbeat_reconnect_
   enabled, default true) E se API respondeu (sem fetchError) E se
   passou do cooldown de 10min desde a última tentativa.
2. POST /instance/restart/{instance} na Evolution.
3. Aguarda 3s pra estabilizar + rechecka connectionState.
4. Se state voltou pra 'open': restaura connected + limpa
   first_unhealthy_at + incrementa heartbeat_reconnect_count + resolve
   qualquer incident aberto. Retorna action='auto_reconnected'.
5. Senão: atualiza heartbeat_reconnect_last_at (respeita cooldown) e
   abre incident normalmente com details.reconnect_attempted=true.

Anti-loop: 1 tentativa por ciclo (não retry), cooldown de 10min/channel
pra não martelar Evolution nem gerar restart infinito. Tentativas são
contadas em metadata.heartbeat_reconnect_count (auditoria futura).

UI em /configuracoes/whatsapp-pessoal ganha novo toggle no card de
Monitoramento: "Tentar reconectar automaticamente" (default ligado)
com explicação clara. Tenant pode desligar se preferir ser alertado
imediato sem tentativa.

Summary do endpoint agora inclui auto_reconnected count — útil pra
métricas de confiabilidade da Evolution.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 12:19:52 -03:00
Leonardo 881fa16c27 Fluxo de reativação de canal WhatsApp + alerta toast sticky + notify owner
Cadeia de fixes descoberta ao testar o heartbeat 6.1 num tenant que migrou
de Evolution → Twilio e precisava voltar pro Evolution.

1. RLS notification_channels (migration 20260423000003)
   - Policy antiga tinha `deleted_at IS NULL` como primeira condição AND,
     bloqueando leitura de soft-deleted até pro próprio owner/saas_admin.
   - Isso fazia o chooser nunca detectar "canal antigo pra reativar".
   - Relaxada: owner/membro/saas_admin leem inclusive soft-deleted.
   - Filtro de deleted_at fica no código aplicativo (todos os queries já
     filtram explicitamente quando querem apenas ativos).

2. Edge function reactivate-notification-channel (nova)
   - Espelho da deactivate existente; service_role bypass RLS.
   - Aceita {channel_id} OU {tenant_id + provider}.
   - Autoriza saas_admin OU membro ativo do tenant.
   - Garante exclusividade: soft-deleta qualquer OUTRO canal ativo do
     mesmo tenant+channel.
   - Reseta metadata.first_unhealthy_at + connection_status=disconnected
     (heartbeat começa do zero).

3. SaasWhatsappPage (/saas/whatsapp)
   - loadChannel busca soft-deleted como fallback quando não tem ativo.
   - saveCredentials detecta soft-deleted e chama reactivate edge,
     depois atualiza credentials+display_name.
   - Banner âmbar "Canal configurado anteriormente" + botão vira
     "Reativar e salvar".

4. ConfiguracoesWhatsappPage tenant (/configuracoes/whatsapp-pessoal)
   - loadCredentials busca soft-deleted como fallback.
   - Card âmbar "WhatsApp Pessoal foi usado anteriormente" com botão
     "Reativar WhatsApp Pessoal" em vez de mostrar apenas "chame o suporte".

5. ChooserPage (/configuracoes/whatsapp)
   - Fix bug lateral: comparava activeProvider === 'evolution' (template)
     com 'evolution_api' (DB) — card nunca mostrava estado ativo. Agora
     normaliza via computed activeProviderKey.
   - softDeletedByProvider map carregado no mount; cards que têm row
     soft-deleted mostram "Reativar" em vez de "Ativar".
   - handleChoose chama reactivate edge antes de goSetup se detecta
     soft-deleted do provider escolhido.

6. whatsapp-heartbeat-check: notifica owner do channel + admins
   - notifyChannelStakeholders substitui notifyTenantAdmins.
   - Set dedupa o owner_id do channel + clinic_admin + tenant_admin.
   - Em tenant solo: 1 notificação; em clínica com canal de terapeuta
     específico: terapeuta (owner) + admin recebem; em clínica com canal
     do próprio admin: 1 (owner=admin).

7. Toast frontend para system_alert
   - notificationStore.subscribeRealtime aceita callback onInsert.
   - useNotifications registra callback que dispara toast PrimeVue
     (severity error, life 24h, closable) para type='system_alert'.
   - Usuário precisa fechar manualmente — alerta crítico de infra
     não pode sumir sozinho.

Cron heartbeat ativado em runtime local via cron.schedule()
(não vai neste commit — é config de ambiente, não migration).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 09:21:29 -03:00
Leonardo e1f756ea82 Heartbeat WhatsApp Evolution (Grupo 6.1): detecção + incident + alerta admin
Detecta celular desconectado antes de falhar envios silenciosamente.

Banco (migration 20260423000002):
- Tabela whatsapp_connection_incidents (tenant_id, channel_id, kind,
  started_at, resolved_at, duration_seconds, notified_at, details).
  UNIQUE parcial garante no máximo 1 incident aberto por channel.
- RPCs whatsapp_heartbeat_open_incident (idempotente), _resolve_open_incidents
  e _mark_notified. Service_role only.
- RLS: membros do tenant leem, saas_admin tudo.
- ALTER notifications.type pra aceitar 'system_alert' (usado pelo alerta).

Edge function whatsapp-heartbeat-check:
- Varre notification_channels provider=evolution_api e ativos.
- GET {api_url}/instance/connectionState/{instance} (timeout 8s, rewrite
  localhost → host.docker.internal pra containers).
- Mapeia state pra connection_status (open/connecting/qr_pending/
  disconnected/error), persiste + last_health_check.
- Lógica de threshold: marca first_unhealthy_at em metadata na primeira
  falha; só abre incident após heartbeat_threshold_minutes (default 5).
- Notifica admins ativos (clinic_admin/tenant_admin) do tenant via
  insert em notifications. Anti-spam: só notifica 1x por incident.
- Aceita ?channel_id=X pra check on-demand de um tenant específico.

UI tenant (/configuracoes/whatsapp-pessoal):
- Novo card "Monitoramento de conexão" com toggle alerts_enabled +
  InputNumber threshold (1-60 min). Persiste em
  notification_channels.metadata.
- Histórico últimos 7 dias: kind (tag colorida), aberto/resolvido,
  início → fim, duração formatada (Ns/Xmin Ys/Nh Xmin).

UI SaaS (/saas/whatsapp):
- Badge "N incidents abertos" no header quando há algum.
- Botão "Verificar tudo agora" invoca a edge function e atualiza a lista.
- Tabela enriquecida: coluna Status ganha pill "Incident aberto",
  colunas novas Última check e Incidents 7d (em laranja se > 0).

Cron template no final da migration (comentado — descomentar
cron.schedule pra ativar 2min periódico).

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