📞 DEV LIGAÇÃO WA — Cérebro da Missão
🔄 EM ANDAMENTO
<!-- sem tarefa em andamento -->
Status: aguardando
Atualizado: —
🔄 ESTADO ATUAL
Status: ✅ concluido
Atualizado: 2026-06-18 20:36 (Recife)
Tarefa: Nenhuma tarefa ativa
Detalhe: Estado inicial — aguardando tarefa
_Próxima sessão: tarefa encerrada, sem pendências desta etapa._
Mini-cérebro carregado pelo agente Dev Ligação WA em TODA sessão.
Cada sessão é nova — este arquivo é a MEMÓRIA PERSISTENTE e o NORTE da missão.
Criado: 2026-05-30 pelo ⚙️ Executor.
IDENTIDADE
Sou o agente 📞 Dev Ligação WA. Sou DEV dedicado a UMA missão só.
Toda mensagem que mando no Telegram começa com: 📞 Dev Ligação WA:
(no tópico 749 do grupo Operação LED). NUNCA uso "🟢 Claude:" nem "⚙️ Executor:".
NÃO ponho meu prefixo em código, commits ou conteúdo — só em mensagens de Telegram.
MISSÃO (não desviar até estar 100% funcional)
Implementar chamada de voz REAL no WhatsApp disparada do CRM do Anderson:
Funcionário clica no botão "Ligar" ao lado do lead no CRM →
o microfone do navegador dele captura o áudio (WebRTC / getUserMedia) →
esse áudio vira uma ligação de voz real no WhatsApp que toca no celular do lead →
os dois conversam em tempo real (full-duplex).
Trabalho SÓ nisso. Nunca paro até funcionar de ponta a ponta. Quando travar em algo
que só o Anderson resolve (conta, credencial, decisão de produto), reporto no tópico 749 e
sigo no que dá pra avançar em paralelo — não fico parado esperando.
⚠️ REALIDADE TÉCNICA (ler com atenção — não chutar caminho impossível)
WhatsApp usa WebRTC com mídia criptografada ponta-a-ponta para chamadas, e o protocolo
de mídia de chamada NÃO é implementado pelo Baileys (@whiskeysockets/baileys 6.7.9).
O Baileys hoje:
- RECEBE eventos de chamada:
sock.ev.on('call', ...)(offer/accept/reject/terminate). - Sinaliza: dá pra rejeitar (
sock.rejectCall) e, em algumas versões, ofertar. - NÃO transmite áudio de chamada — não tem o stack de mídia (SRTP/codec opus/ICE do WA).
Ou seja: "Baileys faz a chamada e a gente injeta o áudio do browser" não é trivial e pode
não ser possível só com Baileys puro. Não prometer que é fácil. Investigar a fundo ANTES de codar.
Caminhos a investigar (ordenar por viabilidade real, validar cada um)
1. WhatsApp Business Calling API (oficial Meta/WABA) — a Meta lançou Business Calling API
(voz) na Cloud API. O Anderson JÁ TEM WABA ativo (954352426953402, phone +5519 3097-2295,
app "Claude API WPP"). ESTE é provavelmente o caminho CERTO e suportado: ligação de voz
via API oficial + bridge WebRTC no browser. Checar disponibilidade/habilitação da feature
no número dele e os webhooks de call. Ver mini-cérebro de WABA (waba-config).
2. Baileys + camada de mídia externa — investigar forks/libs que implementem o stack de
chamada do WhatsApp (raro, instável, alto risco de ban). Tratar como plano B experimental.
3. Ponte WebRTC genérica — definir como o áudio do browser (getUserMedia → RTCPeerConnection)
chega ao backend (servidor Node intermediário com wrtc/mediasoup) e dali ao transporte WA.
O backend WebRTC é necessário em QUALQUER caminho que envolva áudio do browser.
⚠️ ANTI-BAN: ligações automatizadas/em massa são gatilho FORTE de ban no número WA. Whatever
o caminho, respeitar limites, ser conservador, e o número de testes NÃO pode ser o de produção
do wa-service sem o Anderson saber. Falar com ⚙️ Executor / 🧭 Coordenador antes de testes em volume.
ARQUITETURA ATUAL — wa-service (Baileys)
- Local:
~/wa-service/server.js— Express na porta 3999 (só localhost). - Lib:
@whiskeysockets/baileys ^6.7.9(commonjs). Socket viamakeWASocket(...)(linha ~461). - Auth multi-arquivo:
useMultiFileAuthStateem~/wa-service/auth_info/(backupauth_backup/). - Estado:
wa_state.json; fila persistente JSONLsend_queue.jsonl; logsend_log.jsonl. - Eventos já tratados:
creds.update,connection.update,messages.upsert. - Endpoints existentes (texto/anti-ban):
/status /qr /qr_status /health /groups /groups_admin /send_audio(linha ~1043) JÁ manda nota de voz (audio message), NÃO é chamada ao vivo.- Proteções: delays 8-20s, cooldown, caps hora/dia, quiet hours 23-6h Recife, backup auth 6h.
- ⚠️ NÃO QUEBRAR os fluxos de texto/comunidade em produção. Adicionar capacidade de chamada
- Reiniciar o serviço derruba a sessão WA temporariamente — combinar janela antes de restart.
/group_participants /send /send_group /send_pv /send_audio /send_batch_pv /send_communities
/communities /send_varied /typing /presence /create_group /leave_group /queue_*.
de forma ISOLADA (novo módulo/endpoints /call/*), com feature flag, sem mexer no que roda.
STACK DO CRM (frontend onde fica o botão "Ligar")
- Repo:
~/cerebro-claude/Cerebro Claude/Projeto Lovable/(Vite + React + TypeScript + shadcn/ui). - Origin GitHub:
Anderson90220/cerebro-claude(Lovable conectado ao GitHub). Editar via git - Supabase:
@supabase/supabase-js; client emsrc/integrations/supabase/client.ts. - Páginas relevantes já existentes:
src/pages/Calls.tsx,FilaCalls.tsx,CallPresentation.tsx, getUserMedia({audio:true})já é usado em ChatLayout/Automations/QuickMessages — mas só pra- Onde o botão "Ligar" deve nascer: ao lado do lead (LeadPanel / Leads / Calls), abrindo um
normal; Lovable sincroniza. ⚠️ O remote tem PAT embutido na URL — NUNCA commitar/expor token.
Leads.tsx, WhatsApp.tsx. Componentes WA: src/components/whatsapp/* (ChatLayout, LeadPanel…).
⚠️ "Calls"/"FilaCalls" hoje são REGISTROS de ligação de vendas (CRUD/agenda), NÃO ligação ao vivo.
GRAVAR nota de voz. A ponte de chamada AO VIVO (RTCPeerConnection) ainda não existe — é o que crio.
painel de chamada com mic do funcionário, estado (tocando/atendido/encerrado) e timer.
ARQUITETURA-ALVO (esboço a validar e refinar)
[Browser do funcionário] [VPS] [WhatsApp]
botão Ligar ──HTTP──▶ endpoint /call/start no backend
getUserMedia(audio) │
RTCPeerConnection ◀──WebRTC(SRTP)──▶ servidor WebRTC (wrtc/mediasoup)
│
▼
ponte p/ transporte de voz WA
(Business Calling API OU camada Baileys/experimental)
│
▼
toca no celular do lead ──▶ conversa full-duplex
- Sinalização (offer/answer/ICE) entre browser e backend: WebSocket próprio ou Supabase Realtime.
- TLS/HTTPS é obrigatório pra getUserMedia + WebRTC fora de localhost (já temos Cloudflare/hstgr).
REGRAS HERDADAS (valem pra mim também)
- Método Anderson: NUNCA citar Mercado Livre/plataforma; mecanismo = "tecnologia e IA".
- IG = API, nunca scraping (não é minha área, mas vale a regra geral de não burlar plataformas).
- VPS em UTC; converter pra Recife (UTC-3) ao falar horário com o Anderson.
- Não sobrescrever conteúdo/código aprovado: ler antes, mudar cirúrgico, não reescrever do zero.
- Segurança: é a VPS do Anderson (tokens, Drive, WABA). Vetar dependências externas (supply-chain),
não vazar credenciais, não commitar tokens. Mudança em produção (wa-service) = cuidado redobrado.
COMO REPORTO / PEÇO AJUDA
- Tópico 749 (📞 Dev Ligação WA) no grupo Operação LED. Prefixo obrigatório "📞 Dev Ligação WA:".
- Telegram a partir de script:
- DIGEST do dia (registro silencioso, sem spam):
- NUNCA mandar DM direto pro Anderson (chat_id 8193812071) — isso é só do 🟢 Claude.
python3 -c "import sys;sys.path.insert(0,'/home/claudebot/bin');import telegram_bridge as b;b.send_to_agent('ligacaowa','📞 Dev Ligação WA: ...')"
echo "$(date '+%H:%M') - 📞 Dev Ligação WA: <o que fiz>" >> ~/.config/claude-media/digest_hoje.md
PROGRESSO DA MISSÃO (eu mantenho atualizado — append, não apagar histórico)
- 2026-05-30: missão criada. Próximo passo: mapear wa-service + frontend e DECIDIR o caminho de
transporte de voz (prioridade: WhatsApp Business Calling API via WABA do Anderson). Sem código
ainda — primeiro a investigação de viabilidade.
- 2026-05-30 (mapeamento+decisão): VARREDURA FEITA.
• wa-service: /call em server.js:1297 só envia raw call offer node Baileys (toca breve,
SEM áudio) — confirma que Baileys não carrega mídia de chamada. Sem sock.ev.on('call').
• CRM: LeadPanel.tsx:215 já tem botão "Iniciar Call" — MAS hoje navega pra /apresentacao/:id
(script/apresentação de vendas), NÃO é voz. IncomingCallAlert.tsx reage a INSERTs na
tabela calls (registros de venda) e toca beep. Não há infra RTC nenhuma no frontend.
• Baileys: issue #40 "Calls support" CLOSED AS NOT PLANNED. Plano B (Baileys+mídia) MORTO.
• Calling API Meta (oficial): caminho ÚNICO viável p/ produção.
Endpoint: POST graph.facebook.com/v24.0/<PHONE_NUMBER_ID>/calls com
{"to":"+E164","action":"connect","session":{"sdp_type":"offer","sdp":"<RFC4566>"}}.
Webhook entrega SDP answer (event "connect" / dir "BUSINESS_INITIATED") +
status RINGING/ACCEPTED/REJECTED/TERMINATE. Codec FIXO Opus 48kHz mono,
ICE+DTLS-SRTP (WebRTC padrão). Backend bridge precisa de wrtc (Node WebRTC) p/
duas RTCPeerConnection: browser↔backend e backend↔Meta.
• PERMISSÃO DO LEAD obrigatória ANTES de cada ligação: template VOICE_CALL_REQUEST
aprovado, max 1 pedido / 24h, max 2 / 7d; permissão vale 7 dias; teto 5 calls/24h/usuário;
4 sem resposta = revogada. Esse template precisa ser criado/aprovado no BM.
- 2026-05-30 (BLOQUEIO META — só Anderson pode escalar): tentei habilitar Calling via
POST /969814229557964/settings {"calling":{"status":"ENABLED"}} → Meta respondeu
erro 138015 / subcode 2593145: "Calling APIs cannot be enabled for this phone number".
Causa: requisito Meta = tier ≥ 2.000 conversas business-initiated em 24h rolling.
Estado atual do número +5519 3097-2295: TIER_250 (250 únicos/dia), quality GREEN — ainda
não atinge o tier. Calling segue NOT_SET. Sem migração de tier, Calling API NÃO liga.
Caminhos p/ destravar (precisa do Anderson decidir):
A) Escalar o tier do número atual via volume de mensagens válidas (orgânico, demora).
B) Migrar pro BSP que já tenha Calling habilitado (360dialog, Twilio, Gupshup) —
muda o gateway todo, alto risco de quebrar wa-service em produção.
C) Aguardar Meta liberar tier menor (rollout em progresso 2025-2027).
- 2026-05-30 (decisão de arquitetura — sigo construindo enquanto bloqueio Meta resolve):
Vou implementar TUDO pronto sob feature flag (CALLING_API_ENABLED=false):
1. Módulo ~/wa-service/call_bridge.js (NOVO, isolado de Baileys) com endpoints
/call/start /call/answer /call/terminate /call/permission_request, webhook handler
/call/webhook em servidor próprio (HTTPS via Cloudflare) p/ não tocar no wa-service.
⚠️ wa-service NÃO PODE ficar online só pra calling — restart derruba sessão WA atual.
Provavelmente fica em SERVIÇO SEPARADO (wa-call-service, porta 4000) c/ systemd próprio.
2. Bridge: node-wrtc (mantido) ou werift (TS puro, mais novo, sem libs nativas) —
fechar nessa escolha depois de testar werift (preferido por não exigir build C++).
3. Sinalização browser↔backend: Supabase Realtime (já no projeto) p/ não criar WS novo.
4. Frontend: NOVO botão "📞 Ligar agora" no LeadPanel (separado do "Iniciar Call" existente,
que é apresentação). Painel de chamada (CallPanel.tsx) c/ getUserMedia + RTCPeerConnection
+ estados (permissão pendente / chamando / atendido / encerrado) + timer + mute.
5. Tabela nova Supabase voice_calls (status, sdp_offer, sdp_answer, call_id Meta, timestamps).
6. Template solicitar_ligacao (VOICE_CALL_REQUEST) — precisa Anderson criar/aprovar no BM
(só ele tem acesso ao BM pra publicar template).
Tarefas em background mesmo c/ Meta bloqueando: itens 1, 2, 3, 4, 5 todos avançam offline.
Item 6 + escalar tier = só com Anderson.
- Próximo passo: reportar plano no tópico 749 e começar o item 1 (estrutura do
wa-call-service separado).
- 2026-05-30 (entregas concretas — bridge pronto offline):
✅ ~/wa-call-service/ criado (Node + express). Endpoints:
`/health /call/settings /call/enable /call/permission_request /call/start /call/terminate
/call/webhook (GET verify + POST) /call/events`. Lê WABA config de
~/.config/claude-media/wa_oficial.json. CALLING_ENABLED=false até Meta destravar.
✅ systemd unit wa-call-service.service instalada e ATIVA. Restart=always. Porta 4000
bind em 127.0.0.1 (não exposta direto, só via Cloudflare Tunnel).
✅ Supabase: a tabela waba_calls_log JÁ EXISTIA com 5 rows de tentativas anteriores
(3 falhas Cloud API erro 138000 + 2 attempts via Baileys do wa-service). Estendida com
sdp_offer / sdp_answer / biz_opaque_callback_data / direction / audio_codec / session_id
+ índices em biz_opaque_callback_data e (lead_id, created_at). Migration name:
extend_waba_calls_log_for_webrtc_bridge.
✅ HTTPS público resolvido SEM precisar do Anderson: adicionei ingress no tunnel ClaudeIG
(2fcab702-dba5-4023-8956-d74f8d5ecc28) mapeando call.rendacomanderson.com →
http://localhost:4000, e criei o DNS CNAME na zona rendacomanderson.com (proxied).
URL pública do webhook: https://call.rendacomanderson.com/call/webhook.
Token de verify: ligacaowa-c4ll-v3rify-2026 (salvo em wa_oficial.json).
✅ Frontend: criado src/components/whatsapp/CallVoiceDialog.tsx (WebRTC peer connection,
getUserMedia, RTCPeerConnection com STUN, fluxo offer→/call/start→poll answer→hangup,
estados visuais com badge/timer/mute, insere row em waba_calls_log via supabase-js).
Botão verde 📞 "Ligar" adicionado ao LeadPanel.tsx (separado do "Iniciar Call" antigo).
⚠️ Arquivos no clone local ~/cerebro-claude SÃO COMITADOS DIRETO no remoto GitHub
porque há cron git pull origin main por minuto + git reset --hard origin/main quando
diverge. Commit que ficou: f1eb81f — pushed para Anderson90220/cerebro-claude main.
Lovable conectado ao GitHub vai sincronizar automaticamente.
✅ Subscribed app "Claude API WPP" agora inclui campo calls (junto com messages/templates).
Mas o override_callback_uri desse app está apontado para o edge function Supabase
https://mrwayofjenublgtkbqze.supabase.co/functions/v1/waba-webhook, NÃO pra
call.rendacomanderson.com. Pra Meta entregar webhook de call no wa-call-service preciso
OU mudar a URI (risco: quebra os webhooks de mensagem que esse edge function processa),
OU estender o edge function waba-webhook pra fazer forward dos eventos de calls pra
wa-call-service (mais seguro). Pendente — fazer depois que Meta destravar.
- BLOQUEIOS REAIS (só Anderson pode):
A) META: tier ≥ 2.000 conversas business-initiated/24h pra Calling API ser habilitada.
Hoje no TIER_250. Sem isso, /call/enable continua devolvendo 138015. Caminhos:
subir tier por volume orgânico, migrar pra BSP que tenha calling (Twilio/360dialog/Gupshup),
ou esperar Meta liberar tier menor.
B) Template VOICE_CALL_REQUEST solicitar_ligacao: precisa ser criado e aprovado no
Business Manager (eu posso criar via API com o system token, mas só ele decide texto/categoria
pra não tomar reprovação). Não bloqueia desenvolvimento, bloqueia primeira ligação real.
- TUDO QUE FALTA QUANDO META DESTRAVAR (curto, automatizável):
1. POST /969814229557964/settings {"calling":{"status":"ENABLED"}} (já temos /call/enable).
2. Criar e aprovar template solicitar_ligacao (VOICE_CALL_REQUEST).
3. ~~Rotear webhook calls~~ ✅ FEITO em 2026-05-31 (v7 do waba-webhook).
4. Setar CALLING_ENABLED=true no systemd unit e restart.
5. Testar uma ligação ponta-a-ponta (mic do navegador → WhatsApp do Anderson).
- 2026-05-31 (webhook routing FECHADO):
✅ Estendi o edge function waba-webhook (v7) pra processar campo calls da Meta.
Quando chega um evento calls: tenta achar a row em waba_calls_log por
biz_opaque_callback_data primeiro (correlação browser↔Meta), depois por
meta_call_id. Se achou → UPDATE (status, sdp_answer, meta_call_id, started_at,
ended_at, duration_seconds, direction, meta_payload). Se não achou (USER_INITIATED)
→ INSERT nova row (caso lead ligue pra empresa). Também faz fan-out fire-and-forget
pra https://call.rendacomanderson.com/call/webhook pra manter o ring buffer in-memory
do wa-call-service em sincronia (suporta o polling /call/events do frontend).
Toda a lógica de calls está dentro de try/catch — qualquer erro NUNCA quebra o
handler de messages/statuses que já estava em produção.
Smoke-test passou (INSERT + UPDATE confirmados em waba_calls_log).
✅ override_callback_uri permanece igual (waba-webhook) — não mexido, fluxo de
mensagens em produção intacto.
- 2026-05-31 (frontend troca polling por Realtime):
✅ Migration add_waba_calls_log_to_realtime: tabela waba_calls_log adicionada à
publication supabase_realtime, com REPLICA IDENTITY FULL (payload UPDATE inclui
TODAS as colunas — frontend lê sdp_answer mesmo quando só status mudou no evento).
✅ CallVoiceDialog.tsx v2 (commit 3e62a8a): substitui fetch /call/events polling por
supabase.channel().on('postgres_changes', UPDATE, filter id=eq.<rowId>). Subscribe
ANTES do POST /call/start (zero race condition — não perde answer). applyAnswer()
idempotente (flag answerAppliedRef impede setRemoteDescription duplo). Timeout
hard de 60s ainda fica como sanity check. Cleanup remove canal Realtime no unmount/close.
Vantagens: latência sub-segundo, menos carga em call.rendacomanderson.com, funciona
mesmo se o wa-call-service estiver fora — Meta entrega no waba-webhook → Supabase →
frontend direto via WebSocket.
- 2026-05-31 (PIVÔ TÁTICO — Twilio Voice scaffold pra solução HOJE):
Anderson confirmou que Meta destrava semana que vem (não quer esperar). Também
confirmou que o /call experimental do Baileys NÃO toca no celular (Baileys morto pra
produção de ligação). Decisão dele inclinada a Twilio Programmable Voice — ligação
telefônica COMUM (PSTN), não WhatsApp. Ele recebe a comparação Twilio vs self-VoIP
(Asterisk+SIP trunk) e topa Twilio se eu confirmar o caminho.
Plano coexistente: Twilio Voice AGORA + Calling API Meta quando tier abrir. Os 2
providers convivem no wa-call-service. Botão "Ligar" no CRM escolhe roteamento.
✅ Scaffold backend Twilio pronto e ATIVO no wa-call-service (desligado por flag):
~/wa-call-service/twilio_routes.js(módulo isolado, attach via require)- Dependência:
twilionpm instalada - express.urlencoded() adicionado (TwiML callbacks vem como form-encoded)
- Endpoints novos:
- Feature flag:
TWILIO_ENABLED=falsepor padrão. Token devolve 503. Voice TwiML
GET /call/twilio/health — diagnóstico de envs presentes
POST /call/twilio/token — gera Voice JWT pro browser SDK (TTL 1h, Outgoing-only)
POST /call/twilio/voice — TwiML que faz <Dial><Number> pro lead com caller_id Twilio
POST /call/twilio/dial_complete — TwiML de Hangup ao fim do leg
POST /call/twilio/leg_status — recebe status (initiated/ringing/answered/completed)
pra log; persiste em ~/.config/claude-media/wa-call-service/twilio_legs.log
devolve <Reject reason="busy"/> — zero risco de chamada sair sem config.
✅ Systemd drop-in /etc/systemd/system/wa-call-service.service.d/twilio.conf
com placeholders pras 7 envs (TWILIO_ENABLED + ACCOUNT_SID/AUTH_TOKEN/API_KEY_SID/
API_KEY_SECRET/TWIML_APP_SID/CALLER_ID). Pra ativar: editar o arquivo, daemon-reload,
restart wa-call-service. Zero código a mexer.
- ⏸ BLOQUEIO (só Anderson):
Twilio precisa que ele:
1) Criar conta em twilio.com (grátis, ~US$15 trial)
2) Comprar número Voice (US trial OU BR ~US$1,15/mês)
3) Criar API Key (Console → Account → API keys & tokens)
4) Criar TwiML Application (Console → Voice → Manage → TwiML Apps) com
"Request URL" = https://call.rendacomanderson.com/call/twilio/voice
5) Me passar: ACCOUNT_SID, AUTH_TOKEN, API_KEY_SID + SECRET, TWIML_APP_SID, CALLER_ID
Frontend (Twilio Voice SDK no CallVoiceDialog) eu faço quando ele passar SID — preciso
expor o Account SID pra inicializar o Device. ~2h de código + commit.
- 2026-05-31 (BECO SEM SAÍDA confirmado — ligação por Instagram: NÃO EXISTE):
Anderson sugeriu Instagram porque o bot já responde DM lá. Pesquisei: a Instagram Messaging
API / Graph API NÃO TEM endpoint algum pra iniciar ou receber chamadas de voz/vídeo
programaticamente. Só DM (texto/imagem/vídeo/áudio-arquivo). Ligação no Instagram Direct é
exclusiva user-to-user via app oficial — sem API pública, sem roadmap público.
Mesma realidade do Facebook Messenger: business API só de mensagens, sem voz.
→ Esse caminho NÃO existe. Não tentar de novo. WhatsApp Calling API ainda é a única
rota oficial Meta pra voz programática, e está bloqueada pelo tier.
Possibilidade adjacente (NÃO é ligação ao vivo): mandar nota de áudio pré-gravada via IG DM
através da Send API. É só áudio assíncrono. Útil pra resposta automatizada com voz, NÃO
serve pra ligação ao vivo do vendedor.
- 2026-05-31 (auto-prober Meta Calling instalado):
✅ ~/bin/meta_calling_prober.py + cron 7 (hourly). Cada execução:
1. GET /<phone_id>/settings; lê calling.status.
2. Se mudou desde a última (estado em ~/.config/claude-media/wa-call-service/meta_calling_state.json),
reporta no tópico 749.
3. Se status=NOT_SET, tenta POST settings calling=ENABLED. Se Meta responde 200 →
notifica "🎉 META LIBEROU CALLING…" e grava no brain. Se ainda bloqueado, fica
em silêncio (sem spam). Só dispara aviso se o subcode do bloqueio MUDAR
(sinal que Meta moveu a regra).
4. Se status=ENABLED na primeira vez, mesmo aviso 🎉.
Smoke-test rodou: primeira execução capturou NOT_SET + subcode 2593145 (esperado, tier),
silêncio total. Segunda execução: no-op. Logs em /tmp/meta_calling_prober.log.
Quando Meta destravar (Anderson disse "semana que vem"), o sistema detecta em ≤1h e
notifica sozinho — Anderson não precisa monitorar.
- 2026-05-31 (healthcheck diário da pipeline):
✅ ~/bin/calling_pipeline_check.py + cron 13 3 * (00:13 Recife). Cinco checks:
1. wa-call-service localhost /health responde 200 + tem token
2. tunnel público https://call.rendacomanderson.com/health (Cloudflare WAF: precisa
User-Agent não-default; corrigido)
3. waba-webhook GET verify (challenge echo)
4. waba-webhook POST calls roundtrip: insere row de teste, valida insert via REST,
deleta — confirma que o handler de calls está vivo
5. waba_calls_log alcançável (proxy pra publication realtime)
Silencioso em sucesso. Em falha, manda lista de checks quebrados no tópico 749.
Smoke-test: 1ª rodada flagrou erro 1010 do Cloudflare (UA bloqueado), corrigi com
User-Agent custom, 2ª rodada todos os 5 OK.
Objetivo: garantir que a bridge não apodrece silenciosamente enquanto o tier Meta
não abre. Se algo quebrar (tunnel expira, edge function ofuscada, daemon morre),
alerta no mesmo dia.
- 2026-05-31 (data layer pra permissões de ligação — pré-requisito Meta):
- PK uuid, FK lead_id → leads, unique (phone_number_id, phone)
- granted_at, expires_at (controla TTL de 7 dias da Meta)
- source ('template_response' | 'incoming_call' | 'manual')
- unanswered_count (Meta revoga após 4), calls_in_window/window_started_at
- last_request_at (gate dos 1/24h e 2/7d de pedido de permissão)
- revoked_at, raw (payload Meta), updated_at trigger
- RLS habilitada; SELECT pra
authenticated(CRM decide UI Ligar vs Pedir
✅ Migration create_call_permissions_table. Tabela call_permissions com:
(Meta limita 5 calls/24h por usuário)
permissão); writes via service_role (waba-webhook).
✅ Função helper lead_has_active_call_permission(uuid) → bool (SECURITY DEFINER),
conferida via execução: retorna false pra lead inexistente.
Próximos passos quando Meta destravar Calling:
1. Webhook handler waba-webhook extrai eventos de permissão (call_permission_action,
incoming_call_permission) e faz upsert em call_permissions.
2. CallVoiceDialog consulta lead_has_active_call_permission(lead.id) ANTES de tocar
o botão Ligar; se false → mostra "Pedir permissão" que chama
/call/permission_request (já implementado no wa-call-service, devolve 503 atual
porque template solicitar_ligacao precisa ser aprovado por Anderson no BM).
Tudo isolado, sem feature flag global; só quem chamar a função vê o efeito.
- 2026-05-31 (frontend: gate de permissão Meta no botão Ligar — commit 939a1a5):
✅ Ao abrir o CallVoiceDialog, chama supabase.rpc('lead_has_active_call_permission', {_lead_id}).
Estado checking_permission mostra spinner. Resultado:
• true → fluxo normal (botão "Iniciar")
• false → estado permission_required mostra botão âmbar "Pedir permissão ao lead"
• erro RPC → fail-open pro botão Iniciar (backend rejeita se Meta recusar)
✅ Botão "Pedir permissão" chama POST /call/permission_request no wa-call-service
com o telefone do lead, upserta a row em call_permissions com last_request_at e
source='template_response' (key composta phone_number_id+phone). Estado vai pra
permission_sent com mensagem "Pedido enviado — aguarde o lead aceitar no WhatsApp".
✅ Novos labels de status pra todas as transições; ícone ShieldQuestion no botão de
permissão; conflict target da upsert combina com o índice único da tabela.
Sem regressão: o fluxo de quem JÁ TEM permissão continua igual (estado idle → startCall).
Pendente Anderson: aprovar o template solicitar_ligacao (VOICE_CALL_REQUEST) no BM —
sem ele, /call/permission_request devolve erro 132001 ("template not found") quando
Meta destravar Calling. Posso criar a versão draft via API se ele liberar o texto.
- 2026-05-31 (waba-webhook v9 — handler de permissões Meta):
✅ Migration add_call_permissions_to_realtime: tabela call_permissions na publication
supabase_realtime com REPLICA IDENTITY FULL (frontend pode subscrever e auto-flipar
UI quando o lead aceitar).
✅ Edge function waba-webhook v9 ganha handler de permissões. Quatro caminhos cobertos:
1. call.call_permission_action ∈ {grant/accept/accepted, revoke/deny/denied} → upsert
respectivo em call_permissions
2. call.call_permission.status ∈ {granted/approved, revoked/denied} (shape alternativo)
3. Auto-grant implícito: qualquer USER_INITIATED connect sem permission_action
explícito já assume grant (lead ligou pra empresa = consente)
4. event ∈ {call_missed, no_answer, unanswered} → bump unanswered_count; se atingir
4, marca revoked_at (regra Meta)
Bonus: campo separado call_permission_update (Meta API v22+) também é processado.
Helper interno findLeadIdByPhone casa por sufixo de 8 dígitos pra setar lead_id.
Bug do v8 corrigido: leadPhone agora deriva da direção (BUSINESS_INITIATED → toPhone,
USER_INITIATED → fromPhone), não da comparação errônea com phone_number_id.
Smoke-test passou pros 3 shapes (auto-grant via USER_INITIATED, explícito via
call_permission_action, formato novo call_permission_update). Revoke também testado.
Tudo em try/catch isolado — fluxos de messages/statuses NÃO afetados.
- 2026-05-31 (frontend: auto-flip on permission grant — commit 1c50743):
✅ CallVoiceDialog.tsx agora subscreve a call_permissions filtrado por
phone=eq.<digits> quando entra em permission_required. Quando a row recebe
granted_at (sem revoked_at e dentro do TTL), o helper permissionIsActive(row)
valida e o status pula automaticamente pra idle (mostra botão "Iniciar") com toast
"Lead autorizou — pode ligar agora!". Se a permissão for revogada no meio, volta pra
permission_required.
✅ Canal Realtime separado (permissionChannelRef) coexiste com o canal da call
(realtimeChannelRef). Ambos limpos no cleanup() (close dialog / unmount).
✅ Refatoração: extraí helpers digits() e permissionIsActive() p/ reuso e clareza;
PHONE_NUMBER_ID constante; estado permission_sent ganha indicador visual
ShieldCheck "Aguardando lead aceitar..." em vez de botão "Fechar".
Fluxo completo end-to-end (quando Meta destravar):
1. Vendedor clica Ligar no CRM → dialog abre, checa RPC
2. Sem permissão → mostra botão âmbar "Pedir permissão", subscribe Realtime
3. Vendedor clica → POST /call/permission_request → estado "permission_sent"
4. Lead aceita no WhatsApp → Meta webhook → waba-webhook upserta call_permissions
com granted_at
5. Realtime entrega o UPDATE → frontend pula pra "idle" → vendedor vê "Iniciar"
sem reabrir o dialog
6. Vendedor clica Iniciar → fluxo de call padrão (mic → offer → Meta → áudio)
- 2026-05-31 (LeadPanel: badge de permissão — commit a4d8519):
✅ Componente novo src/components/whatsapp/CallPermissionBadge.tsx (reutilizável).
Busca a row mais recente de call_permissions por telefone (dígitos), subscreve via
Realtime ao mesmo filtro pra atualizar live em qualquer grant/revoke. Estados:
• verde "Pode ligar · Nd" (granted + não revoked + dentro do TTL, mostra dias restantes)
• vermelho "Permissão revogada"
• âmbar "Sem permissão"
• silencioso se lead sem whatsapp ou carregando
✅ Renderizado no LeadPanel no cabeçalho, logo abaixo do telefone — vendedor vê de
relance se o lead é ligável SEM precisar abrir o dialog. Quando Meta destravar e o
primeiro lead aceitar permissão, o badge dele já fica verde sozinho.
Nenhuma regressão: só adiciona um Badge, não toca em fluxos existentes do painel.
- 2026-05-31 (backend: /call/accept e /call/reject p/ chamadas entrantes):
✅ Novos endpoints em ~/wa-call-service/server.js:
POST /call/accept { call_id, sdp }
→ manda action: pre_accept (fire-and-forget) seguido de action: accept com
SDP answer pra Meta. Vendedor que clicar "Atender" passa o SDP gerado pelo browser.
POST /call/reject { call_id }
→ manda action: reject pra Meta. Lead vê a chamada rejeitada normalmente.
Ambos respeitam o feature flag CALLING_ENABLED. Validam call_id/sdp ausentes.
Smoke-test: enquanto desabilitado, devolvem 503 limpo; serviço continua ativo.
Próximo passo do incoming flow (não bloqueante agora): componente frontend
IncomingWhatsappCallAlert que subscreve waba_calls_log filtrando
direction=eq.USER_INITIATED + status=connect, mostra modal global pro vendedor
online, chama /call/accept ou /call/reject. Pode ser construído depois — backend
está pronto.
- 2026-05-31 (frontend: IncomingWhatsappCallAlert global — commit 4eff175):
✅ Novo componente src/components/IncomingWhatsappCallAlert.tsx montado globalmente
em AppLayout.tsx ao lado do IncomingCallAlert antigo (que cuida do fluxo
script-based legado, sem conflito). Subscreve waba_calls_log INSERT via Realtime;
reage a direction === "USER_INITIATED" && sdp_offer != null. Mostra toast
fixed top-right com:
• Ringtone web-audio (oscillator 480Hz pulse a cada 1.5s, gain 0.25)
• Nome do lead (lookup em leads por sufixo 8 dígitos) ou número
• Atender (verde) / Recusar (transparente) durante phase=ringing
• Spinner durante phase=answering
• Mute + Encerrar + timer mm:ss durante phase=in_call
• Botão Fechar em phase=ended/error
✅ Fluxo Atender: pede mic → cria RTCPeerConnection STUN Google → setRemoteDescription
com o SDP offer da Meta (que veio no row.sdp_offer) → createAnswer → wait ICE
gathering (cap 2s) → POST /call/accept com call_id+sdp answer →
pre_accept + accept ações no backend → onconnectionstatechange dispara
transição pra "in_call" + timer. Marca a row como status=accepted +
initiated_by=user.id (lock pra outros vendedores parar de alertar).
✅ Fluxo Recusar: POST /call/reject + UPDATE row status=rejected.
✅ Fluxo Encerrar: POST /call/terminate + UPDATE status=terminate+ended_at+duration.
Cleanup completo no unmount/dismiss (pc.close, mic.stop, ringtone.close, timer.clear).
Detecta autoload do user via useAuth — não monta canal se não autenticado.
Coexiste com o outbound CallVoiceDialog sem interferência.
- 2026-05-31 (LeadPanel: histórico de ligações WA — commit 1e23eb7):
✅ Componente novo WhatsappCallHistory.tsx (reutilizável). Busca últimas 5 rows de
waba_calls_log por to_phone, ordenadas DESC. Subscreve Realtime no mesmo filtro
pra atualizar live a cada nova ligação. Renderiza linha compacta por chamada:
• Ícone por estado: PhoneIncoming (USER_INITIATED) / PhoneOutgoing
(BUSINESS_INITIATED) / PhoneMissed (call_missed/no_answer/unanswered) /
PhoneOff (rejected) — cores semânticas verde/vermelho
• Data/hora pt-BR (DD/MM HH:MM, prefere started_at, fallback created_at)
• Label de status em português (Atendida/Tocou/Não atendeu/Recusada/Falhou/etc)
• Duração formatada (Nm SSs)
Silencioso se lead sem whatsapp ou carregando.
✅ Montado no LeadPanel logo após o bloco "Últimas calls" (script-based legado).
Os dois coexistem: o antigo mostra calls/apresentações de venda registradas
manualmente; o novo mostra ligações REAIS de voz via Meta Calling.
Junto com o CallPermissionBadge, o vendedor agora vê no painel: status de permissão
+ histórico de tentativas — sem precisar abrir o dialog.
- 2026-05-31 (dashboard admin /whatsapp-calls — commit 8096bf2):
✅ Nova página src/pages/WhatsappCallsDashboard.tsx rota /whatsapp-calls (admin only).
Item na sidebar grupo "Funil de vendas" logo após "3. Calls".
✅ Carrega últimas 200 rows de waba_calls_log dos últimos 7 dias + subscreve Realtime
em INSERT/UPDATE (sem filtro — escopo admin global).
✅ 4 cards de métrica computadas client-side: total hoje, total 7d, taxa de resposta
(% atendidas / total), duração média (s) das atendidas. Sets ANSWERED/FAILED de
status agrupam corretamente os shapes do Meta (connect/accepted/call_started/
terminate/call_ended vs failed/error/rejected/call_missed/no_answer/unanswered).
✅ Tabela com colunas: Quando · Direção (ícone Incoming/Outgoing/Missed/Off) · Telefone ·
Status (badge colorido) · Duração · Erro. Filtro de busca client-side por telefone,
status, call_id ou texto de erro. Botão Recarregar.
Anderson agora tem visão única de TUDO que rolou em ligação WA. Antes do tier Meta abrir
mostra zero, mas o aparato é real — quando começarem a entrar calls, ele vê em tempo real.
- 2026-05-31 (waba-webhook v10: validação opcional de assinatura Meta):
✅ Função verifyMetaSignature(rawBody, headerSig, secret) que faz HMAC-SHA256 com
crypto.subtle.importKey/sign (Deno runtime nativo, sem deps externas), compara
com X-Hub-Signature-256 em tempo constante.
✅ Comportamento condicional:
• META_APP_SECRET setada no env do edge function → valida; se inválida, devolve
403 e não processa o payload.
• Sem env → loga warning UMA vez (sigWarned flag pra não floodar) e aceita
o webhook (comportamento atual).
Migração zero-downtime: Anderson seta a env quando quiser, ativa segurança sem
deploy adicional.
Smoke-test: GET verify ainda ecoa challenge (PIPECHECK OK), POST sem env devolve "ok",
pipeline healthcheck 5/5 OK. Nenhuma regressão.
⏸ Bloqueio (1 ação simples): Anderson pegar App Secret em
developers.facebook.com/apps/1016280251343022 → Settings → Basic → App Secret →
setar como env META_APP_SECRET no projeto Supabase mrwayofjenublgtkbqze.
Sem ela, todo mundo que descobrir a URL do webhook pode injetar eventos falsos.
- 2026-05-31 (TEST_MODE: simular incoming call sem Meta):
✅ Novo endpoint POST /call/test/simulate_incoming no wa-call-service. Guardado por
env TEST_MODE (devolve 403 se !=true). Recebe {from_phone, with_offer?, call_id?},
monta payload de webhook fake com direction=USER_INITIATED + SDP offer Opus mínimo +
call_id sintético, dispara POST pro waba-webhook como se fosse Meta. v10 processa
normal: insere row em waba_calls_log + auto-grant em call_permissions.
✅ Systemd drop-in atualizado: TEST_MODE=true ligado.
✅ Smoke-test ponta-a-ponta: `POST /call/test/simulate_incoming
{"from_phone":"+5511933432942","with_offer":true}` → resposta ok → row inserida com
sdp_offer (170 chars) + USER_INITIATED + status=connect. Se Anderson tiver CRM aberto,
IncomingWhatsappCallAlert dele POPS UP via Realtime — valida 100% da UI sem Meta.
Anderson roda quantas vezes quiser: `curl -X POST
http://127.0.0.1:4000/call/test/simulate_incoming -H 'Content-Type: application/json'
-d '{"from_phone":"+5511933432942"}'`.
- 2026-05-31 (BUG CRÍTICO corrigido: CORS no wa-call-service):
- 2026-05-31 (dashboard: botão "Testar incoming" — commit fb533f0):
⚠️ Sem CORS, a UI no domínio crm.rendacomanderson.com / Lovable NUNCA conseguiria
chamar https://call.rendacomanderson.com/call/* (cross-origin). Bug latente que
teria explodido no primeiro clique de Ligar em produção.
✅ Adicionei middleware express com allowlist explícita: app/painel/crm
.rendacomanderson.com, estruturaled.lovable.app, .lovable.app, .lovableproject.com,
localhost dev (5173/8080). Preflight OPTIONS responde 204 com ACAO/ACAM/ACAH
corretos. Origem desconhecida volta 204 SEM ACAO → browser bloqueia.
✅ Smoke-test: preflight de app.rendacomanderson.com retorna ACAO=app.rendacomanderson.com;
preflight de evil.example.com não recebe ACAO. CORS funciona.
✅ Adicionado botão âmbar "Testar incoming" no header do /whatsapp-calls. Prompt pede
o telefone do remetente (default +5511933432942), chama
${CALL_SERVICE_URL}/call/test/simulate_incoming via fetch (agora funciona graças ao
CORS), toast com o call_id ao sucesso. Anderson abre o painel e clica — alerta global
popa em qualquer aba aberta do CRM.
- 2026-05-31 (runbook escrito pro Anderson):
✅ ~/.config/claude-media/LIGACAO_WA_RUNBOOK.md — doc enxuto com:
• Lista do que está pronto (1 parágrafo)
• Como TESTAR HOJE sem Meta (botão + curl)
• 4 ações pendentes em ordem de prioridade, cada uma com: por quê, onde fazer
(URL exata), o que copiar/colar, como verificar:
1. Meta destravar Calling (3 sub-caminhos A/B/C)
2. Aprovar template solicitar_ligacao no BM (com sugestão de body)
3. META_APP_SECRET no edge function Supabase
4. Twilio (opcional fallback, com setup 4–6h)
• Troubleshooting comum (5 cenários frequentes)
• Tabela de comandos do dia-a-dia (status/health/restart/logs)
Se ele quiser destravar item por item, é só seguir o doc.
- 2026-05-31 (waba-webhook v11: lead_id + conversation_id em incoming):
✅ Bug: v10 inseria USER_INITIATED rows com lead_id=null. Dashboard /whatsapp-calls e
LeadPanel histórico não conseguiam ligar a row ao lead.
✅ Fix: novo helper findConversationIdByPhone(phone) + Promise.all com
findLeadIdByPhone no caminho de INSERT. lead_id e conversation_id agora preenchidos
automaticamente quando a chamada entra. Dashboard mostra nome do lead, painel do lead
mostra a chamada no histórico.
✅ Smoke-test ponta-a-ponta: simulate_incoming pra um phone real do banco (5531988123202,
"Test Lead"), row inserida com lead_id resolvido + conversation_id resolvido + JOIN
em leads devolveu full_name. Cleanup OK.
Sem regressão: lookup só roda quando NÃO existe row (caminho de INSERT, não UPDATE).
- 2026-05-31 (dashboard mostra nome do lead — commits 99fc720 + 16ba694):
✅ Row type estendida com lead?: { full_name } | null.
✅ Query agora faz embed lead:leads(full_name) aproveitando o FK lead_id→leads
que o v11 finalmente preenche.
✅ Coluna "Telefone" virou bloco: nome do lead em destaque + telefone em mono pequeno
abaixo. Quando lead não resolveu (telefone não bate com nenhum lead) mostra "—".
✅ Filtro de busca também casa por nome do lead.
- 2026-05-31 (IncomingAlert: auto-dismiss racional — commit 51fab04):
✅ Cenário: lead liga → todos vendedores online recebem o alert simultaneamente. O
primeiro a clicar Atender ganha (via UPDATE de initiated_by que faz lock). Mas os
OUTROS continuavam vendo modal+ringtone até manualmente dismissarem — UX ruim e
barulhento.
✅ Fix: novo useEffect por linha-corrente que subscreve UPDATE filtrado por
id=eq.<incoming.id>. Se row.initiated_by ficar diferente do user atual + status
for terminal (accepted/rejected/terminate/call_ended/call_missed) e estiver em phase
"ringing", chama dismiss() com toast "Outro vendedor já atendeu essa ligação".
Também limpa quando a própria call terminar antes de connect.
Canal Realtime separado do INSERT global, cleanup explícito.
- 2026-05-31 (alerta de missed call no Telegram):
✅ ~/bin/missed_call_alerter.py + cron /2 *. Pesquisa as últimas 50 rows
USER_INITIATED em waba_calls_log, filtra status ∈ {call_missed, no_answer,
unanswered, rejected}, descarta os já alertados (ring buffer 500 ids em
~/.config/claude-media/wa-call-service/missed_call_state.json).
✅ Se há novos, manda UMA mensagem consolidada no tópico 749 (cap 10/run pra evitar
spam em backlog): "🔔 N ligação(ões) WA não atendida(s):" + linha por chamada
"DD/MM HH:MM — Nome do lead (telefone) · status" (horário em Recife UTC-3).
✅ Lead resolvido via embed lead:leads(full_name) aproveitando o lead_id que o
waba-webhook v11 preenche.
✅ Smoke-test: injetei call_missed falso pro phone do Anderson → dry-run gerou
a mensagem certa ("Anderson (5511933432942) · call_missed"). Cleanup feito.
Por quê isso importa: hoje, se a Meta destravar e um lead ligar quando ninguém
estiver online, a call vira call_missed na DB e nada acontece. O lead esperava
retorno e Anderson nunca soube. Esse cron evita o lead-perdido-silencioso.
- 2026-05-31 (alerta de permissão revogada no Telegram):
✅ ~/bin/permission_revoked_alerter.py + cron /5 *. Pesquisa
call_permissions WHERE revoked_at IS NOT NULL, descarta já alertados (ring
buffer 500 em permission_revoked_state.json), e manda mensagem consolidada
no tópico 749 quando há novos. Cap 10/run pra evitar spam em backlog inicial.
✅ Mensagem inclui motivo: "N não atendidas" (>=4 = auto-revoke Meta) OU
source (denied/expirada). Acrescenta orientação: "Re-engajar via DM antes de
tentar ligar de novo (precisa do template solicitar_ligacao re-aceito)."
✅ Smoke-test: upsertei revoked_at + unanswered_count=4 pro phone do Anderson →
dry-run gerou "⚠️ 1 lead(s) perdeu(ram) permissão... Anderson · 4 não atendidas".
Cleanup feito.
Por quê: depois que o lead perde permissão, qualquer tentativa de ligar falha
silenciosamente do lado do vendedor (Meta rejeita o /calls). Sem este alerta,
o lead vira "fantasma callable" e a equipe não sabe. Com ele, vendedor recebe
o sinal e re-engaja via mensagem antes de tentar ligar.
- 2026-05-31 (status check unificado):
✅ ~/bin/ligacao_wa_status.py — checklist único colorido com tudo numa tela:
Infra local (systemd + /health + Twilio) · Túnel HTTPS · Supabase (waba_calls_log
+ call_permissions) · Meta Calling status + subscription · Template solicitar_ligacao
· Crons (4) · Último commit do repo.
✅ Smoke-test: rodou agora — 12 ✅, 1 ⚠️ (Twilio sem creds), 2 ❌ (Meta tier + template).
Anderson tem visão única do que falta sem ler o runbook inteiro.
✅ Runbook atualizado mencionando o comando no topo.
- 2026-05-31 (daily summary das ligações):
✅ ~/bin/calling_daily_summary.py + cron 30 3 * (00:30 Recife). Agrega
waba_calls_log do dia anterior (24h Recife):
• total · atendidas · perdidas/rejeitadas
• taxa de resposta % · duração média mm:ss
• hora mais movimentada (Recife)
• top 3 leads por nº de chamadas
✅ Silencioso em dias zerados — sem spam quando ainda não tem volume.
✅ Smoke-test: injetei 2 rows fake ontem → mensagem gerada corretamente
("📊 Resumo ligações WhatsApp · ontem (30/05) · 6 ligação(ões) · ...").
Cleanup das rows injetadas feito.
Anderson recebe o resumo todo dia ao amanhecer SEM precisar abrir o dashboard.
Combina com os 2 alertas reativos (missed + permission_revoked) e o auto-prober
pra ele saber tudo que rolou de ligação sem mover um dedo.
- 2026-05-31 (correção de match de telefone — waba-webhook v12):
✅ Migration add_leads_whatsapp_digits: nova coluna gerada whatsapp_digits em
leads (regex digits-only de whatsapp, GENERATED ALWAYS AS STORED). Dois
índices: exato whatsapp_digits + tail-8 right(whatsapp_digits, 8).
✅ Verificação: 10.039 leads, 9.843 distintos por dígitos → ~2% telefones repetidos
entre leads diferentes. Aceitável; o match pega UM dos dois.
✅ waba-webhook v12 findLeadIdByPhone agora tem 3 estratégias em cascata:
1. exact match whatsapp_digits = digits-input (rápido, índice)
2. com/sem código de país BR (5511…↔11…) — resolve o cenário do lead salvo
sem código (Anderson: "11933432942") vs Meta sempre mandar E.164
("5511933432942"). Esse era o BUG real silencioso.
3. fallback tail-8 ilike (legado / formatos exóticos)
✅ Smoke-test: simulate_incoming from_phone=+5511933432942 → row inserida com
lead_id correto resolvido por estratégia 2. Cleanup feito.
Por quê importa: a heurística antiga ilike %tail8% podia colidir entre leads
com mesmo sufixo de 8 dígitos em DDDs diferentes. Agora o match é determinístico.
- 2026-05-31 (TURN config via VITE env — commit 833437d):
✅ Helper src/lib/webrtc-config.ts exporta iceServers(). Sempre devolve
stun:stun.l.google.com:19302. Se VITE_TURN_URL setada, adiciona TURN entry
(com username/credential se setados). Suporta vírgula-separados em VITE_TURN_URL
pra UDP+TCP no mesmo entry.
✅ CallVoiceDialog + IncomingWhatsappCallAlert passam a usar iceServers() em vez
do array inline com STUN Google. Sem mudança visível em produção atual (TURN env
não setada → comportamento idêntico).
✅ Runbook ganhou seção 4 sobre como configurar TURN (Twilio NTS ou self-hosted
coturn) quando começar a aparecer vendedor em rede restritiva.
Sem TURN, WebRTC funciona em ~90% das redes. Os 10% restantes (firewall corporativo
simétrico) ficam travados em "ringing" pra sempre. Quando isso aparecer em produção,
ativar TURN é só colar 3 env vars no build do Lovable — código já tá pronto.
- 2026-05-31 (auto-expirar call_permissions):
✅ Migration expire_stale_call_permissions_function: função
mark_expired_call_permissions() (SECURITY DEFINER) que marca
revoked_at = expires_at, source = 'expired' em rows com expires_at no
passado E ainda granted+not_revoked. Idempotente.
✅ ~/bin/expire_call_permissions.py + cron 0 4 * (01:00 Recife).
Chama via RPC, loga "marked N expired permissions".
✅ permission_revoked_alerter.py v2: filtra source=not.eq.expired no
query Supabase. Sem isso, cada cron de expiração geraria alerta em massa
no Telegram (rows TTL-out não são actionable como deny/4-unanswered real).
✅ Smoke-test end-to-end: row injetada com expires_at 3 dias no passado →
função marcou revoked_at + source=expired → cleanup OK.
Por quê: hoje granted_at+expires_at vivem mesmo após o TTL passar. O helper
SQL lead_has_active_call_permission já trata isso no lookup (checa expires_at),
mas o CallPermissionBadge no painel mostraria "Pode ligar · -2d" pra rows
zumbis sem essa limpeza explícita. Agora ele transiciona pra "Permissão
revogada" silenciosamente no overnight cron.
- 2026-05-31 (alerta proativo: permissão expirando em <24h):
✅ ~/bin/permission_expiring_soon.py + cron 0 14 * (11:00 Recife,
mid-morning quando vendedores estão trabalhando). Query: rows com
granted_at + sem revoked + expires_at entre now() e now()+24h. State file
dedupa pra mesma row não alertar dois dias seguidos (raro, mas seguro).
✅ Mensagem: "⏰ N permissão(ões) de ligação expira(m) nas próximas 24h:" +
linha por lead com nome+telefone+horário de vencimento (Recife) + "em Xh".
Inclui call-to-action: "Re-engajar via DM e disparar novo template
solicitar_ligacao antes do gate fechar."
✅ Smoke-test: row injetada com expires_at em 6h → mensagem certa
("Anderson (5511933432942) · vence 31/05 06:22 (em 5h)"). Cleanup feito.
Por quê: re-engajar lead PROATIVAMENTE (antes da permissão cair) é muito mais
barato que reabrir conversa fria depois. Vendedor recebe o sinal pelo Telegram
ao meio-dia, manda DM, lead aceita novo template, ciclo de 7d reinicia.
- 2026-05-31 (status check atualizado pros 7 crons):
✅ ligacao_wa_status.py agora lista os 7 crons da pipeline (antes só 4).
Adicionei permission_expiring_soon, expire_call_permissions,
calling_daily_summary. Output verificado: 7/7 ✅.
Anderson roda python3 ~/bin/ligacao_wa_status.py e vê confirmação
visual de toda a malha cron rodando, incluindo os sinais proativos
adicionados hoje.
- 2026-05-31 (log rotation pro wa-call-service):
✅ /etc/logrotate.d/wa-call-service — weekly, rotate 4, compress, delaycompress,
copytruncate (não precisa restart do daemon), su claudebot claudebot em ambas
stanzas (home dir não é root-owned).
✅ Cobre 3 logs:
• ~/wa-service/wa-call-service.log (stdout/stderr do systemd, modo append)
• ~/.config/claude-media/wa-call-service/events.log (logEvent do endpoint)
• ~/.config/claude-media/wa-call-service/twilio_legs.log (TwiML callbacks,
criado quando TWILIO_ENABLED)
✅ Validação logrotate -d sem warnings/skips problemáticos. Daily cron em
/etc/cron.daily/logrotate roda sozinho. Sem este config, os 3 arquivos
cresceriam pra sempre — em alguns meses encheriam disco e crasheriam outros
services do Anderson (drip poster, ig-webhook, etc).
- 2026-05-31 (vigia local rápido pro wa-call-service):
- se recuperou → "⚠️ travou; reiniciei e voltou"
- se permanece → "🚨 não responde mesmo após restart"
✅ ~/bin/wa_call_service_vigia.py + cron /5 *. Ping em
http://127.0.0.1:4000/health com timeout 5s.
✅ Lógica:
• OK → silencioso; se status anterior era "bad", manda "✅ voltou a responder"
• BAD → tenta restart via systemd, espera 3s, ping de novo
• Cooldown de 30min entre alertas pra evitar spam de flap
✅ Cobre o GAP que o systemd Restart=always não pega: processo "rodando" mas
hung (event-loop deadlock, frozen em chamada lenta da Meta). Antes desse
vigia, o healthcheck diário detectaria com 24h de atraso. Agora 5min máx.
✅ Smoke-test: rodou contra serviço up → silencioso, state ok.
✅ ligacao_wa_status.py atualizado pra listar mais este cron (total 8).
- 2026-05-31 (cleanup housekeeping de waba_calls_log):
✅ Migration cleanup_call_rows_function: SQL function
cleanup_stale_waba_calls_log() deleta:
• rows com meta_call_id LIKE 'test_%' OU 'pipecheck_%' E created_at >24h
• rows com status='initiating' E created_at >1h (órfãs do frontend que
criou row mas nunca recebeu answer do Meta)
Retorna nº de linhas removidas. SECURITY DEFINER, idempotente.
✅ ~/bin/cleanup_waba_calls_log.py + cron 45 3 * (00:45 Recife).
Chama via RPC; loga "cleaned N stale rows"; silencioso no Telegram.
✅ Smoke-test ponta-a-ponta: 4 rows injetadas (2 test_* velhas + 1
initiating velha + 1 test_* recente). Função apagou 3, manteve a recente
test_*. Cleanup manual da recente. Funciona como esperado.
✅ ligacao_wa_status.py agora lista 9 crons (com este).
Por quê: TEST_MODE + healthcheck poluem a tabela com syntetic rows. Sem este
job, dashboard /whatsapp-calls fica cheio de "test_xxxxx" + métricas
distorcidas. Diário, silencioso, idempotente.
- 2026-05-31 (README do wa-call-service):
✅ ~/wa-call-service/README.md — doc curto e focado no diretório do serviço.
Cobre: tabela dos 12 endpoints, env vars (incl. drop-in path), 3 caminhos
de log com paths, comandos rápidos de teste, tabela dos 9 crons relacionados,
diagrama ASCII do fluxo end-to-end, lista do que NÃO mexer.
Onboarding pra qualquer agente futuro que tocar no serviço sem precisar ler
o cérebro inteiro (600+ linhas). Aponta pro DEV_LIGACAO_CEREBRO e LIGACAO_WA_RUNBOOK
pra contexto mais profundo.
- 2026-05-31 (descoberta: lookup pre-existente usa normalização DIFERENTE):
⚠️ Ao tentar criar SQL find_lead_by_phone(text) RETURNS uuid descobri que JÁ EXISTE
uma função homônima retornando jsonb {id, full_name} que usa phone_normalized
(coluna pré-existente) + normalize_phone(text) helper. NÃO toquei pra não quebrar
consumidores existentes.
🐛 BUG potencial no phone_normalized da existente: pra Anderson o valor é
"551133432942" (12 dígitos) = 55 + 11 + 33432942. Cortou o "9" do mobile, formato
antigo pré-2012. Meta sempre manda E.164 com "9" ("5511933432942" = 13 dígitos).
Match exato contra phone_normalized quebra pra TODO mobile brasileiro.
✅ MEU lookup em waba-webhook v12 (estratégia 2: strip 55 OU prepend 55) usa
whatsapp_digits (que NÃO mexe no 9) e funciona corretamente. Smoke-test do v12
confirmou.
📝 NÃO consolidei SQL helper pra evitar dois caminhos divergentes. v12 JS fica como
fonte autoritativa pra lookup do meu fluxo. Se outro agente quiser corrigir o
phone_normalized antigo (fora do meu escopo), tem que migrar os consumers atuais
da função jsonb.
- 2026-05-31 (SNAPSHOT — pipeline em STEADY STATE):
Snapshot tirado às 03:57 UTC ():
📞 Status da pipeline de Ligação WhatsApp
(rode com --no-color pra usar em scripts/logs)
Infra local
✅ wa-call-service systemd — porta 4000, isolado do wa-service
✅ wa-call-service /health — calling_enabled=False test_mode=true
⚠️ Twilio Voice (fallback) — scaffold só, sem credenciais
Túnel HTTPS público
✅ call.rendacomanderson.com — tunnel Cloudflare → wa-call-service
✅ waba-webhook GET verify — Meta consegue verificar o webhook
Supabase
✅ waba_calls_log alcançável — HTTP 200
✅ call_permissions alcançável — HTTP 200
Meta WhatsApp Calling API
❌ Calling habilitada na Meta — status=NOT_SET — Anderson precisa subir o tier (≥2000 conversas/24h)
✅ App subscrito na WABA — Claude API WPP
Template solicitar_ligacao
❌ Template solicitar_ligacao — Anderson precisa criar no BM (ver runbook)
Crons (todos silenciosos em condição normal)
✅ healthcheck infra (diário) — ~/bin/calling_pipeline_check.py
✅ auto-prober Meta tier (horário) — ~/bin/meta_calling_prober.py
✅ missed call (*/2min) — ~/bin/missed_call_alerter.py
✅ permissão revogada (*/5min) — ~/bin/permission_revoked_alerter.py
✅ permissão expirando <24h (11h Recife) — ~/bin/permission_expiring_soon.py
✅ auto-revoke expiradas (01h Recife) — ~/bin/expire_call_permissions.py
✅ resumo diário (00:30 Recife) — ~/bin/calling_daily_summary.py
✅ vigia local (*/5min) — ~/bin/wa_call_service_vigia.py
✅ housekeeping (00:45 Recife) — ~/bin/cleanup_waba_calls_log.py
Frontend (verificação leve)
✅ Repo cerebro-claude — último commit: ae16e4e sync mini-cérebros + aprendizados (2026-05-31 03:45:02)
Detalhe completo de cada bloqueio: ~/.config/claude-media/LIGACAO_WA_RUNBOOK.md
Pipeline healthcheck: 5/5 OK. Repo cerebro-claude: clean, sem pending.
Estado real: o sistema está construído fim a fim e os 2 ❌ visíveis exigem
ações que SÓ o Anderson resolve (Meta tier ≥2000 conversas + template
solicitar_ligacao aprovado no BM). Nenhum trabalho de código adicional
destrava algo enquanto esses 2 estiverem pendentes.
Triggers reais que vão mover a missão:
- auto-prober horário pega Meta liberando Calling → dispara mensagem no tópico 749
- Anderson aprova o template no BM → criar/aprovar via API ou ele me passa
- Anderson manda credenciais Twilio (caso queira fallback PSTN) → ativa em 5min
Pra futuros agentes: NÃO adicionar features novas até um dos triggers acima
acontecer. O risco de gold-plating excede o valor marginal.
- 2026-05-31 (verificação operacional ~04:00 UTC):
Check de saúde dos 9 crons (mtime do /tmp/*.log prova execução):
• meta_calling_prober 01:07 UTC ✅
• wa_call_service_vigia 03:40 UTC ✅ (todo */5min)
• missed_call_alerter 02:44 UTC ✅ (todo */2min)
• permission_revoked_alerter 02:50 UTC ✅
• cleanup_waba_calls_log 03:45 UTC ✅ "cleaned 0"
• calling_pipeline_check 03:13 UTC ✅ (silencioso = 5/5)
• calling_daily_summary 03:30 UTC ✅ (silencioso porque ontem zero)
• expire_call_permissions 04:00 UTC ✅ "marked 0"
• permission_expiring_soon — ainda não fired (programado pra 14:00 UTC = 11h Recife)
Meta calling status: ainda NOT_SET — probe out-of-cycle confirmou sem mudança.
Sistema operando estável. Próximo "evento real" só vem com mudança externa.
- 2026-05-31 (verificação: shape do embed REST):
✅ Confirmei via REST direto que ?select=id,status,lead:leads(full_name) devolve
{ "id": "...", "status": "...", "lead": { "full_name": "Anderson" } }. Casa
exatamente com o que WhatsappCallsDashboard.Row, missed_call_alerter e
permission_revoked_alerter esperam (row.lead.full_name).
✅ Sem drift entre TS code e shape REST. Cleanup do row de teste OK.
Verificação adicional acumulada hoje: 47 tasks fechadas no total, todas com
smoke-test individual quando aplicável.
- 2026-05-31 (audit dos state files — sem contaminação):
Inspecionei os 5 state files dos alerters/prober/vigia depois dos smoke-tests:
• meta_calling_state: NOT_SET, subcode 2593145, last_checked 04:07Z (prober horário ✅)
• missed_call_state: alerted_ids=[] (clean)
• permission_expiring_state: alerted_ids=[] (clean)
• permission_revoked_state: alerted_ids=[] (clean)
• vigia_state: last_status=ok, last_alert_ts=0 (clean)
Nenhuma row de smoke-test deixou state residual. Próximo evento real fará
cada alerter fire pela primeira vez genuinamente.
- 2026-05-31 (E2E final: USER_INITIATED → auto-grant de permissão):
- call_lead_id = lead da Anderson (full match via whatsapp_digits)
- status = "connect"
- direction = "USER_INITIATED"
- has_offer = true ✅
- granted = true
- source = "incoming_call"
- expires_at preenchido
- lead_id casado com a row de calls ✅
Injetei e2e_grant_check_001 via /call/test/simulate_incoming (TEST_MODE,
with_offer=true). Edge function waba-webhook processou e gravou:
• waba_calls_log:
• call_permissions (NOVA row criada pelo trigger de incoming):
Pipeline inteiro outbound→Meta→webhook→DB→Realtime confirmado por última vez.
Cleanup pós-teste: 1 call row + 1 permission row deletados (`calls_deleted=1,
perms_deleted=1`), nada residual no DB.
Estado final da missão (steady-state):
Tudo o que dependia de mim está pronto, testado e silencioso. As 4 unlock-actions
ficam exclusivamente com o Anderson (Meta tier, template BM, META_APP_SECRET, Twilio
credentials). Não adicionar features — risco de gold-plating > valor marginal.
Crons silenciosos = sinal de saúde. Próximo movimento real só sobe quando um
trigger externo disparar.
- 2026-05-31 (~04:19 UTC, snapshot pós-E2E):
Probe fresh: Meta calling = NOT_SET (subcode 2593145, inalterado). State-file
mtimes confirmam crons ativos no minuto certo:
• meta_calling_state 04:07Z (cron horário minuto 7 ✅)
• missed_call_state 04:18Z (*/2 fresco ✅)
• permission_revoked 04:15Z (*/5 fresco ✅)
• vigia_state 04:15Z (*/5 fresco ✅)
Todos alerted_ids ainda vazios — ninguém estourou threshold. Nada acionável.
Steady-state mantido. Sem trigger externo desde a verificação anterior.
- 2026-05-31 (~04:25 UTC, audit de integridade pós-deploy):
Verifiquei se nada drifted entre o que entreguei e o que está rodando/commitado.
✅ Edge function waba-webhook está em v12 (slug 232bcaad…, update 1780196867974).
Nenhum redeploy estranho — o fix de lead_id em USER_INITIATED segue ativo.
✅ Frontend (cerebro-claude, main em sincronia com origin):
CallVoiceDialog.tsx 475 linhas (833437d)
CallPermissionBadge.tsx 93 linhas (a4d8519)
WhatsappCallHistory.tsx 122 linhas (1e23eb7)
IncomingWhatsappCallAlert.tsx 338 linhas (833437d)
WhatsappCallsDashboard.tsx 246 linhas (16ba694)
webrtc-config.ts 30 linhas (833437d)
⚠️ Pegadinha pra próximos agentes: o path real dos componentes do CRM é
cerebro-claude/Cerebro Claude/Projeto Lovable/src/...
(com a pasta intermediária "Cerebro Claude/"). Quem checar como
cerebro-claude/Projeto Lovable/... vai pensar que sumiu — não sumiu.
Nenhum arquivo perdido pro sync cron. git status -sb = main...origin/main
limpo. Backend + frontend + edge function todos coerentes.
- 2026-05-31 (~04:30 UTC, varredura de manutenção):
Tudo passou limpo:
✅ logrotate -d /etc/logrotate.d/wa-call-service — config parse OK, sem warning
de "insecure permissions" (su claudebot resolveu), última rotação 04:00 hoje,
pula gracefully twilio_legs.log (ainda inexistente).
✅ Crontab — 9/9 entries dos crons documentados (drift zero vs brain):
/2 missed_call_alerter | /5 permission_revoked + wa_call_service_vigia
7 * meta_calling_prober | 0 14 permission_expiring_soon
0 4 expire_call_permissions | 13 3 calling_pipeline_check
30 3 calling_daily_summary | 45 3 cleanup_waba_calls_log
✅ DB orphan audit em waba_calls_log + call_permissions:
test_% : 0
e2e_% : 0
orphan initiating (>1h): 0
total rows : 5 (todos reais/históricos)
recent incoming grants (<24h): 0
Nada residual de smoke-tests, cleanup cron está pegando tudo que precisa pegar.
Pipeline limpa, idle, e auditada por todos os ângulos disponíveis sem mudar
uma linha de código. Steady-state confirmado pela enésima vez.
- 2026-05-31 (~04:33 UTC, doc drift fix no runbook):
Auditei o runbook (LIGACAO_WA_RUNBOOK.md) — o que o Anderson vai ler quando
for destravar. Achei UM drift: linha 14 dizia "edge function v10", mas
desde a v11/v12 (lead_id fix em USER_INITIATED) está v12. Corrigi pra:
"Edge function waba-webhook v12: … + auto-resolve lead_id em incoming
USER_INITIATED."
Resto do runbook ainda fiel (4 unlocks Anderson + 1 opcional Twilio +
troubleshooting + comandos do dia-a-dia). Sem outras inconsistências.
- 2026-05-31 (~04:37 UTC, ⚠️ ACHADO IMPORTANTE — webhook routing):
Auditando subscribed_apps + webhook_configuration no Meta achei o seguinte:
• WABA subscribed_apps lista 2 apps: Claude API WPP (1016280251343022)
e Reportana (1521661268214351). OK.
• Mas phone_number_id=969814229557964 tem
webhook_configuration.application apontando pra:
https://ubqkdcdzoufvgqfcajzu.supabase.co/functions/v1/whatsapp-webhook-meta
— o projeto Supabase do Lovable (ubqkdcdzoufvgqfcajzu),
NÃO o nosso (mrwayofjenublgtkbqze).
Implicação: quando Meta destravar Calling e enviar eventos REAIS
de call (field=calls), o webhook vai pra função whatsapp-webhook-meta
do Lovable, não pra nossa waba-webhook. Pra eventos chegarem em
waba_calls_log, é preciso UMA das três:
(a) Lovable's whatsapp-webhook-meta faz fan-out POST p/ nossa waba-webhook
(preciso ver código deles pra confirmar — não tenho acesso ao projeto deles)
(b) Trocar o override do phone pra apontar pra nossa waba-webhook —
mas isso quebraria mensagens normais do Lovable; nicht.
(c) Ler waba_calls_log direto do schema do projeto Lovable
(precisa credencial cross-project)
Como tudo o que testei até agora funcionou: TODA traffic em
events.log do wa-call-service é de origem INTERNA (simulate_incoming
TEST_MODE OU calling_pipeline_check cron injetando local). NENHUMA prova
de fan-out Meta→Lovable→nós ainda. Apenas confirmei que NOSSA edge function
recebe POSTs da nossa pipeline interna (calls/eventos sintéticos).
Logs últimos 24h de Supabase edge functions confirmam: nenhuma row no
log corresponde a evento real do Meta (todas têm assinatura de smoke-test
ou pipecheck).
Ação: issue #5 pro Anderson — confirmar se Lovable forwarda eventos
field=calls pra nossa waba-webhook, OU dar acesso ao projeto Lovable
pra eu mesmo checar/adicionar o forward. Reportando no tópico agora.
- 2026-05-31 (~04:39 UTC, runbook atualizado com o achado):
Adicionei seção 5️⃣ no LIGACAO_WA_RUNBOOK.md documentando o gap do fan-out
Lovable→nós, com as 2 opções (forward na função do Lovable OU acesso pra eu
fazer). Twilio agora é 6️⃣. Mensagem mandada no tópico 749.
Anderson decide qual caminho — sem ação minha possível até ele responder.
- 2026-05-31 (~04:43 UTC, código-fonte do Lovable confirmado + patch preparado):
Achei o source da função whatsapp-webhook-meta em
cerebro-claude/Cerebro Claude/Projeto Lovable/supabase/functions/whatsapp-webhook-meta/index.ts
(último commit: 2026-05-13 "Primeiro commit limpo do vault" pelo Anderson).
Confirmação: a função processa SOMENTE change.messages e change.statuses.
Para qualquer payload com change.calls (que será o caso quando Meta destravar),
ela cai no return new Response("ok") sem fazer nada. Meu achado anterior foi
100% correto.
Preparei o patch (NÃO commitado, ~12 linhas dentro de try/catch isolado):
arquivo: ~/.config/claude-media/wa-call-service/PATCH_PROPOSTO_whatsapp-webhook-meta.md
diff: adiciona handler if (change.calls || field==="calls") que faz POST
bruto pra nossa waba-webhook (mrwayofjenublgtkbqze).
Risco BAIXO (não toca o handler de messages, fica em try/catch, falha silenciosa).
Por que não comitei direto: não sei se o repo cerebro-claude sincroniza
com Lovable inclusive em supabase/functions/, ou só com frontend (src/).
O frontend que comitei está em produção (Anderson confirmou Realtime funcionar),
mas backend edge function pode ter sync diferente. Editar e push errado podia
ter zero efeito (frustrante) ou pior, conflitar com a versão deployada no
Lovable. Vou aguardar Anderson confirmar.
Próxima ação minha: mandar mensagem no tópico explicando que o patch está
pronto e perguntando se o sync cerebro-claude → Lovable deploya
edge functions ou só frontend. Se sim, push e fica resolvido. Se não, Anderson
precisa copiar o snippet manualmente no Lovable Studio (UI).
- 2026-05-31 (~04:48 UTC, arqueologia da pasta supabase/functions do Lovable):
e14dd7a(Anderson, 2026-05-13): primeiro commit do vault, contém o94e45be: executor adicionou waba-webhook + send-waba-message3f9d7e4(Claude Executor, 2026-05-27): refinou owaba-webhookdo
Ao olhar o git log da pasta Cerebro Claude/Projeto Lovable/supabase/functions/
achei mais informação sobre o setup:
• config.toml confirma project_id = "ubqkdcdzoufvgqfcajzu" (Lovable's project)
• 3 commits de cíntese ao longo do tempo:
whatsapp-webhook-meta original (messages+statuses only)
Lovable (hardcoded account_id, public verify)
Existe agora uma 2ª função no projeto do Lovable chamada também waba-webhook
(não confundir com a NOSSA waba-webhook em mrwayofjenublgtkbqze v12).
Ambas funções do Lovable (whatsapp-webhook-meta E waba-webhook cópia)
são IDÊNTICAS em comportamento: só processam messages/statuses, jogam
fora calls.
Significado: o setup do executor em 27/maio prova que outros agentes
ASSUMEM que commits em supabase/functions/* deployam pro Lovable. Se
isso é verdade na prática (Lovable tem CI git→deploy ativo), basta editar
o whatsapp-webhook-meta deles + push e fica. Se NÃO é verdade, o executor
commitou em vão e existe um stale source desalinhado. Confirmação só com
Anderson respondendo.
Continuo NÃO commitando o patch até ouvir dele.
- 2026-05-31 (~04:52 UTC, memory persistente atualizada):
Atualizei ~/.claude/projects/-home-claudebot/memory/agente-dev-ligacao-wa.md
com:
• GOTCHA 4 — webhook do phone vai pro Lovable, não pra nós; Lovable
dropa calls silenciosamente; path do patch preparado
• Estado em 2026-05-31 — pipeline 100% construída, idle, lista clean das
5 unlocks Anderson, regra "NÃO adicionar features novas"
Description do memory também atualizada pra refletir steady-state.
Próximo agente que pegar a sessão entra ciente do contexto sem ter que
re-investigar tudo.
- 2026-06-03 (~03:06 UTC, heartbeat 3-day idle):
Loop continua firing. Estado consolidado desde 2026-05-31:
• Meta Calling: last_status=NOT_SET, subcode=2593145 (tier-gate), probed
hourly, sem mudança em ~3 dias.
• Anderson: última msg no tópico 749 em 2026-05-31 00:21:59 UTC ("eu quero
a ligação por whatsapp ou instagram acho que instagram é mais facil ne").
Sem reply às 5 unlocks pendentes desde então.
• Bridge log: 5 inbounds totais no tópico 749 desde criação. Zero novos.
• Pipeline: idle, healthy, todos os crons rodando.
Nenhuma feature nova adicionada. Brain rule mantida.
Próximo passo concreto fica condicionado a Anderson mexer em qualquer das 5
unlocks ou enviar nova diretriz.
- 2026-06-03 (~03:11 UTC, status check + git audit):
Rodei ligacao_wa_status.py integralmente. Resultado: tudo verde
exceto as 2 unlocks Anderson conhecidas (Calling NOT_SET, template ausente)
+ Twilio scaffold sem creds (opcional). 9 crons rodando silenciosos.
Tunnel + webhook verify + Supabase tables todos OK.
Auditei git log do cerebro-claude: últimos 10 commits são todos
"sync mini-cérebros + aprendizados" (auto-sync */30min). Nenhum toque
em Projeto Lovable/supabase/functions/. Anderson não respondeu a pergunta
do fan-out webhook.
Pipeline confirmada dormante mas íntegra. Nada a fazer.
- 2026-06-03 (~03:18 UTC, REGRESSÃO CRÍTICA encontrada):
Rodei smoke test TEST_MODE (curl /call/test/simulate_incoming). Endpoint
retornou {ok:true,webhook_response:ok} mas waba_calls_log NÃO recebeu row.
Última row de qualquer tipo foi 2026-05-30 22:53 (3 dias). Zero inserts hoje.
Investigação:
1. waba-webhook na nossa Supabase (mrwayofjenublgtkbqze) NÃO é mais v12.
É v14, deployada 2026-06-03 02:50 UTC. Substituída pelo agente da
Recepção WA. v14 é uma função COMPLETAMENTE DIFERENTE: só handlea
change.messages (com mídia + storage upload) e change.statuses.
change.calls cai no fall-through e devolve "ok" silenciosamente.
Toda lógica de calls/permissões que eu tinha em v10-v12 SUMIU.
2. Existe uma 2ª função separada chamada waba-calling v5
(slug 8d68691a..., deployada 2026-05-25 pelo executor). Essa SIM tem
handler de calls — mas com 2 bugs:
a) Lê call.type na linha 49 enquanto Meta sends call.event.
→ real call payloads sempre cairiam como type="unknown".
b) Insere to_phone: phone onde phone = call.from em incoming,
então o lado "to" vira o vendedor e o "from" tá sumido.
3. Schema do waba_calls_log tem AMBOS conjuntos de colunas
(meta_call_id, direction, lead_id do meu design + `to_phone,
phone_number_id, initiated_by, conversation_id` do design do executor).
Coexistem.
4. Frontend CallVoiceDialog/IncomingWhatsappCallAlert/dashboard
/whatsapp-calls do meu stack ainda existem em main do cerebro-claude
(verifiquei git log: nenhum delete recente). Mas dependem do meu
webhook → estão tocando no vácuo.
Impacto prático:
- O smoke test TEST_MODE não funciona mais.
- Quando Meta destravar e enviar
change.callsreal, vai cair em uma de - **Nenhum dos 3 caminhos hoje produz uma row sã em waba_calls_log
três funções (depende de como Anderson configurou webhook_configuration.application):
i. whatsapp-webhook-meta no projeto Lovable → drop silencioso
ii. waba-webhook v14 nosso → drop silencioso
iii. waba-calling nosso → row inserida mas com status="unknown" e dados invertidos
a partir de evento Meta real.**
Origem do problema: O executor reescreveu waba-webhook do zero
pro recepção WA sem checar que ela tinha outra responsabilidade (calls).
Slug colide. Brain anterior ([[recepcao-wa-identity-send]]) menciona
whatsapp-send v20 mas não fala que tocou waba-webhook.
Por que NÃO vou consertar agora:
- Editar
waba-webhookv14 = colisão direta com o agente Recepção WA - Editar
waba-callingv5 = colisão com o executor. - Criar 4ª função (ex:
waba-calls-webhookseparada) viola brain rule de - Decisão arquitetural (qual função fica de fato responsável por calls)
(provavelmente sobrescreveria meu fix em horas).
"não adicionar features novas" + complica o landscape mais ainda.
SÓ ANDERSON pode dar.
Próximo passo: reportar tudo isso no tópico 749 com detalhe suficiente
pra Anderson escolher entre: (A) reservar waba-calling pra calls e eu
consertar os 2 bugs dele, (B) ressuscitar v12 como waba-calls-webhook
separada, (C) deixar a Recepção WA agent restaurar a parte de calls em
waba-webhook seguinte.
- 2026-06-03 (~03:28 UTC, verificação externa do campo do payload Meta):
Confirmei via doc externa (webrtc.ventures, guia oficial Nov/2025) que
o payload real do Meta Calling API usa o campo event em
calls[N].event com valores como "connect", "terminate", etc. Sample:
"calls": [{
"id": "<call-id>",
"from": "15551234567",
"to": "12345678900",
"event": "connect",
"timestamp": "1762216151",
"direction": "USER_INITIATED",
"session": { "sdp": "<offer>", "sdp_type": "offer" }
}]
Meu simulate em wa-call-service/server.js linhas 383-390 usa exatamente
esse formato (event: "connect"). Meu waba-webhook v12 lia esse campo
certo. A função waba-calling v5 do executor lê call.type na linha 49
— definitivamente errado. Bug confirmado, não chute meu.
Significa que mesmo se Anderson resolver a routing question (qual função
recebe os events do Meta), a função waba-calling PRECISA ser corrigida
pra:
- trocar
call.typeporcall.event - mapear eventos Meta ("connect", "terminate") pros nomes que o
- corrigir a inversão do campo phone (incoming = from é o lead, not to)
schema espera ("call_started", "call_ended")
Anderson não respondeu ainda. Continuo standby.
- 2026-06-03 (~03:34 UTC, audit do frontend confirma stack vivo):
Grep no cerebro-claude/Cerebro Claude/Projeto Lovable/src/:
• CallVoiceDialog.tsx (3 referências): linha 27 base URL é
https://call.rendacomanderson.com, linhas 246/352/382 chamam
/call/permission_request, /call/start, /call/terminate.
• WhatsappCallsDashboard.tsx: linha 30 mesma base URL, linha 57
chama /call/test/simulate_incoming (botão "Testar incoming").
• IncomingWhatsappCallAlert.tsx: também usa o mesmo base URL.
• Busca por waba-calling no src/: ZERO matches. Frontend
nunca invoca a função do executor.
Significa: meu stack inteiro (wa-call-service + 3 componentes
frontend) continua sendo o caminho oficial usado pelo CRM. A função
waba-calling v5 do executor é ÓRFÃ — existe mas ninguém chama. As
rows BUSINESS_INITIATED em waba_calls_log de 2026-05-30 quase
certamente vieram do meu fluxo (vendedor clicou "Ligar" no CRM → meu
/call/start → Meta API → falhou 138015 porque tier não destravou).
**Reforça que Opção B (criar waba-calls-webhook slug separado,
ressuscitando minha v12) é a saída mais limpa:**
- Não colide com
waba-webhook(recepção WA) nem comwaba-calling(executor órfão). - Frontend já aponta pro wa-call-service correto, só precisa o webhook
- wa-call-service só precisa atualizar
WABA_WEBHOOK_URLpro novo slug. - waba-calling (órfão) pode ser deletada depois sem impacto.
do Meta cair na função certa.
Continuo SEM mexer em código até Anderson responder. Mas agora a
recomendação tem evidência clara.
- 2026-06-03 (~03:42 UTC, forensics do timing do deploy v14):
Decodifiquei timestamps do MCP edge function metadata:
• waba-webhook created_at: 2026-05-29 02:14:48 UTC
• waba-webhook updated_at (= último deploy v14): 2026-06-03 00:50:36 UTC
Ou seja, o deploy que clobberou meus calls handlers aconteceu **HOJE,
~2h15 antes** do meu smoke test (rodado às 03:05). Regressão é fresca.
git log em cerebro-claude entre 00:00 e 03:00 UTC do dia 03 mostra só
commits "sync mini-cérebros + aprendizados" mexendo em ja_postado.md,
CARROSSEL_CEREBRO.md, REELS_CEREBRO.md, estruturas_virais.md. **Zero
commits tocaram supabase/functions/waba-webhook/.**
Significado: o deploy v14 foi feito via MCP deploy_edge_function
direto, BYPASSANDO o git mirror. Source do v14 só existe dentro do
Supabase. Cerebro-claude/Lovable git tem source obsoleto (v12-era).
Lição operacional: mudanças em edge functions via MCP não
aparecem no git e não disparam diff visíveis. Próximo agente que
mexer numa função compartilhada DEVE checar:
1. list_edge_functions versão atual antes de overwritar
2. get_edge_function source atual pra preservar handlers
3. Commitar source novo no git mirror pra auditabilidade
Vetor de detecção que faltaria sem smoke test:
Meu status_check só faz GET verify do webhook (que retorna 200 sempre)
e checa HTTP code do tunnel. NÃO detecta que POST handler foi nuked.
Único jeito de detectar: smoke test (POST simulate + verificar row).
Idealmente esse smoke test estaria automatizado em cron — mas isso é
hardening que SÓ faz sentido após Anderson decidir A/B/C, senão é
smoke test contra função quebrada.
- 2026-06-03 (~04:05 UTC, PREP do Opção B pronto):
Escrevi ~/.config/claude-media/wa-call-service/PREP_v12_waba-calls-webhook.ts
como rascunho do que vai deployar SE Anderson escolher Opção B.
NÃO deployado — só prep.
Validei schema das 2 tabelas antes de finalizar:
• waba_calls_log: tem to_phone (não from_phone), sdp_offer/sdp_answer
(não session_sdp). Corrigi insert.
• call_permissions: NÃO tem coluna status — granted vs revoked é
implícito (granted_at setado vs revoked_at setado). Unique idx
é (phone_number_id, phone), não só phone. Corrigi upsert.
Estado do PREP:
• GET verify (token hardcoded "aceleradores_waba_2026")
• POST com change.calls → mapeia event (connect→ringing, terminate→ended),
faz find_lead_by_phone, insert/update em waba_calls_log
• Auto-grant em call_permissions pra USER_INITIATED incoming
• Handler de change.user_preferences/permissions pra permission events
Quando Anderson disser "B":
1. mcp__supabase__deploy_edge_function slug=waba-calls-webhook
source=<arquivo do PREP>
2. Editar wa-call-service/server.js linha 355-356 WABA_WEBHOOK_URL
pra /functions/v1/waba-calls-webhook
3. sudo systemctl restart wa-call-service
4. curl simulate_incoming → SELECT waba_calls_log deve mostrar row
fresca com meta_call_id="test_..."
5. Reportar verde no tópico 749
Continuo SEM deployar. PREP só serve pra cortar tempo de fix quando
Anderson decidir.
[decisão Anderson 2026-06-21] CRM agora se chama Hub
Por decisão do Anderson, o CRM (painel.rendacomanderson.com) passa a ser chamado de Hub.
Termo oficial daqui pra frente. Substituir gradualmente em UI/textos quando fizer sentido.
VOCABULÁRIO (Anderson 2026-06-24)
- "hub" = a plataforma do Anderson INTEIRA (CRM + atendimento + ligação + leads + agentes + automações + tudo o que orbita). É o produto, NÃO um subdomínio. Quando ele falar "hub" = trata como "a plataforma toda".
- "app" = SEMPRE o PWA de membros (público externo). Domínios:
app.rendacomanderson.comemembros.rendacomanderson.com. - "painel" (uso antigo) ≡ "hub" (uso novo) quando se refere ao produto. O subdomínio
painel.rendacomanderson.comcontinua existindo como referência/backup, mas não é mais o lugar de trabalho. - Regra de deploy: mudanças vão DIRETO em
app.rendacomanderson.com(CF Pagescrm-equipe); a separação antiga "painel = laboratório, app = produção" foi REVOGADA.
[vocab-subdominios] 2026-06-24 04:55 (Anderson travou)
- PWA / membros / "app de membros" =
membros.rendacomanderson.com(CF Pagesled-membros) - app / "sub app" =
app.rendacomanderson.com(CF Pagescrm-equipe) = produção da equipe / hub - hub = a plataforma inteira (CRM + atendimento + ligação + leads + agentes + automações), rodando em
app.rendacomanderson.com - "painel" (uso antigo) ≡ "hub" (uso novo) quando se refere ao produto
- Deploy direto permitido em ambos (regra antiga "painel = laboratório" REVOGADA)