CL-2026-04-29 - BK-277 dry-run WhatsApp Zona Norte
Entregue
- Criado script CLI-only de dry-run:
admin/cron/whatsapp_campaign_dry_run_zona_norte.php- A campanha alvo ficou configurada para:
As Loucas do Meier(pecas.id = 1509)- Teatro Miguel Falabella e Imperator como origem historica de compradores.
- A selecao de origem ficou restrita aos dois locais informados, nao a todos os
teatros da Zona Norte.
- Adicionados guardrails contra disparo acidental:
- bloqueio de flags
--send,--execute,--dispatch,--enviar; - nenhum helper de WhatsApp e chamado;
- nenhum webhook/fila/task Manus e criado;
- por padrao, query de destinatarios exige allowlist fixa
552199915551. - Apos autorizacao do admin, adicionado modo
--full-csvpara gerar CSV
completo sem allowlist, mantendo o script sem qualquer rota de envio.
- Adicionado dedupe por WhatsApp normalizado para evitar contatos repetidos no
CSV final.
- O CSV passou a gravar quebras de linha da mensagem como
\nliteral para
manter uma linha por destinatario.
- Adicionado modo
--expanded-csvpara gerar CSV posterior de Zona Norte +
Baixada, excluindo por WhatsApp normalizado os compradores ja presentes no
CSV Miguel Falabella + Imperator.
--expanded-csvvalidado:2503WhatsApps unicos;0duplicados internos;0sobreposicao com a base Miguel Falabella + Imperator;- CSV em
admin/runtime/campaigns/loucas_meier_zona_norte_20260429-expanded-zn-baixada-extra-20260429-103602.csv. - Relatorios de runtime ficam em
admin/runtime/campaigns/, fora do Git.
Validacao
php -l admin/cron/whatsapp_campaign_dry_run_zona_norte.php.git diff --check.php admin/cron/whatsapp_campaign_dry_run_zona_norte.php.php admin/cron/whatsapp_campaign_dry_run_zona_norte.php --sendretornou
bloqueio com exit 64.
- O CSV gerado no primeiro dry-run ficou apenas com cabecalho, porque a
allowlist 552199915551 nao encontrou cliente correspondente no banco.
--full-csvdeve ser validado no fechamento para confirmar geracao de CSV
completo sem envio.
--full-csvvalidado:recipients_generated = 1864skipped_duplicate_phones = 1- CSV em
admin/runtime/campaigns/loucas_meier_zona_norte_20260429-dryrun-20260429-103307.csv
Observacao
Esta entrega nao dispara mensagem. A ampliacao para clientes reais depende de
autorizacao humana explicita por lote.
Complemento - fila profissional WhatsApp
- Adicionado modulo CLI seguro:
includes/whatsapp_campaign_queue.phpadmin/cron/whatsapp_campaign_queue.phpSQL/whatsapp_campaign_queue_BK277.sql- Criadas tabelas MySQL para:
- settings globais com kill switch;
- campanhas;
- fila persistente;
- eventos/logs por mensagem;
- blacklist/opt-out manual.
- Estados suportados na fila:
pending,sending,sent,failed,blocked,paused,cancelled.- Guardrails ativos:
- dry-run por padrao;
global_kill_switch=1;- campanhas importadas com
dry_run_only=1; - processamento real exige
--execute, confirmacao nominal, campanha
aprovada, dry_run_only=0 e kill switch desligado.
- Importados os CSVs ja gerados:
- base Miguel Falabella + Imperator:
1864pendentes,0enviados; - base Zona Norte + Baixada extra:
2503pendentes,0enviados. - Teste unico autorizado para
5521999915554enviado pela rota direta do novo
modulo:
- HTTP
200; message_id=true_5521999915554@c.us_3EB07455DF7569B9F8AB55.- Prova de nao disparo para clientes:
sent_count=0nas duas campanhas;process --execute --confirm=PROCESS_QUEUEretornouprocessed=0por
global_kill_switch_on;
- fila JSON legada em
bot/whatsapp/queuepermaneceu vazia.
Complemento - opt-out e ciclo randomico
- Adicionada migracao complementar:
SQL/whatsapp_campaign_queue_BK277_optout_random.sqlprocesspassou a escolher quantidade randomica por ciclo:- settings
default_cycle_min_messagesedefault_cycle_max_messages; - resultado do dry-run e da execucao mostra
cycle_limit. - Mensagens de campanha recebem footer obrigatorio no momento do envio:
Se nao quiser receber novas mensagens, responda SAIR.- a deteccao tambem reconhece avisos equivalentes ja presentes na copy, como
Para parar de receber, responda SAIR., evitando duplicacao semantica.
- Webhook inbound agora interpreta opt-out:
SAIR,PARAR,CANCELAR,REMOVER,DESCADASTRAR, entre outros;- grava/atualiza
whatsapp_campaign_blacklist; - marca pendencias daquele telefone como
blocked; - registra evento
inbound_opt_out. - Estado de seguranca preservado:
- kill switch global continua ligado;
- campanhas seguem
dry_run_only=1e semapproved_at; - nenhum cliente real foi enviado nesta validacao.
- Validacao e revisao:
processem dry-run exposcycle_limitrandomico;process --execute --confirm=PROCESS_QUEUEpermaneceu bloqueado por
global_kill_switch_on;
- smoke transacional de opt-out confirmou insercao em blacklist e rollback
sem sujeira;
- revisao Gemini fatiada do footer retornou GO, com ressalva de duplicacao
semantica tratada no mesmo ciclo.
Auditoria final - guardrails WhatsApp
- Revalidado em
8d70565f5que omainlocal esta alinhado com
origin/main.
php -lephp83 -lpassaram emincludes/whatsapp_campaign_queue.php,
admin/cron/whatsapp_campaign_queue.php e bot/whatsapp/webhook.php.
statusconfirmouglobal_kill_switch=true, campanhas v2#3e#4
ainda em dry_run_only=1, sem approved_at, sent_count=0 e nenhum contato
bloqueado por engano.
process --execute --confirm=PROCESS_QUEUEcontinuou retornando
processed=0 por global_kill_switch_on.
- Smoke sem envio confirmou que o footer de opt-out e inserido quando ausente e
nao duplicado quando ja existe.
- Smoke transacional de opt-out inbound confirmou blacklist e bloqueio de
pendencia dentro da transacao, com ROLLBACK e contadores reais intactos.
- Revisores externos finais nao completaram a rodada:
- Gemini CLI iniciou leitura, mas encerrou por timeout de
120s; - OpenCode falhou por
tools[*].eager_input_streaming. - Sem achado P0/P1 local apos a auditoria do Codex; qualquer disparo real
futuro ainda depende de aprovacao humana expressa, kill switch desligado e
campanha aprovada individualmente.
Preparacao ate item 4
- Por autorizacao humana, foi executada a preparacao ate o item 4 da checklist:
copy final, saude do bot, aprovacao de uma campanha e kill switch.
- Copy final conferida em amostras das campanhas
#3e#4, com footer
obrigatorio Se nao quiser receber novas mensagens, responda SAIR.
- Bot WhatsApp conferido como
reachable=true,connected=true,
status=connected, service=rionoteatro-whatsapp-service.
- Conferido que nao ha cron chamando
whatsapp_campaign_queue.phpou
PROCESS_QUEUE.
- Criado snapshot runtime antes da liberacao:
admin/runtime/campaigns/whatsapp_campaign_state_pre_item4_20260429-170118.json.
- Aprovada somente a campanha
#3com--allow-real. - Campanha
#4permaneceu sem aprovacao real (dry_run_only=1,
approved_at=NULL).
global_kill_switchfoi desligado.- Validacao posterior confirmou
sent_count=0nas campanhas#3e#4;
nenhum process --execute foi rodado.
- Proximo passo segue bloqueado por autorizacao humana expressa: executar a
fila real da campanha #3 com limite baixo e monitoramento imediato.
Disparo real monitorado e pausa automatica
- Com autorizacao humana, iniciado disparo real da campanha
#3em
2026-04-29 17:14 -03, limitado ate 18:40.
- Antes do disparo,
send_window_endda campanha#3foi ajustado para
18:40 e snapshot runtime foi salvo em:
admin/runtime/campaigns/whatsapp_campaign_state_pre_real_send_20260429-171357.json.
- Loop monitorado registrado em:
admin/runtime/campaigns/whatsapp_campaign_real_send_20260429-171431.log.
- Resultado antes da pausa:
sent_count=15;blocked_count=4;failed_count=0;pending_count=1845.- A campanha
#3pausou automaticamente com
last_pause_reason=auto_failure_rate_21.
- Bloqueios analisados indicavam numeros sem conta WhatsApp ativa (
No LID),
nao falha tecnica do bot.
- O loop foi interrompido e o
global_kill_switchfoi religado. - Estado final seguro:
global_kill_switch=true;- campanha
#3emstatus=paused; - campanha
#4sem aprovacao real; - nenhum processo de envio ficou ativo.
Correcao de telefones elegiveis
- Corrigida a geracao de CSV para aceitar somente telefones de campanha no
formato 5521 + 9 digitos locais (55219XXXXXXXX).
- A selecao passou a tentar
clientes.celularprimeiro e so usar
clientes.telefone se o celular nao for valido nesse padrao.
- A fila profissional passou a aplicar o mesmo guardrail antes do envio:
pendencia fora do padrao e cancelada antes de chamar WhatsApp.
- A fila tambem passou a chamar
/check-numberantes de/send; numero sem
WhatsApp ativo vira blocked sem tentativa de envio.
- Campanha
#3importada foi limpa: 251registros fora do padrao foram movidos paracancelled;- estado apos limpeza:
pending_count=1596,sent_count=15,
blocked_count=2, cancelled_count=251.
- Regeneracao dry-run pos-correcao:
- base Miguel Falabella + Imperator:
1632contatos validos; - Zona Norte + Baixada extra:
2003contatos validos. - Validacoes:
php -l,php83 -l,/check-numberem um pendente real sem
envio e process em dry-run com kill switch ligado.