Cerebro Studio · Backlog · Changelog
RioNoTeatro • /www/wwwroot/rionoteatro.com.br/docs/changelog/2026/CL-2026-04-29-BK-277-whatsapp-zona-norte-dry-run.md • 2026-04-29T20:32:12.479Z

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-csv para 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 \n literal para

manter uma linha por destinatario.

  • Adicionado modo --expanded-csv para gerar CSV posterior de Zona Norte +

Baixada, excluindo por WhatsApp normalizado os compradores ja presentes no

CSV Miguel Falabella + Imperator.

  • --expanded-csv validado:
  • 2503 WhatsApps unicos;
  • 0 duplicados internos;
  • 0 sobreposicao 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 --send retornou

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-csv deve ser validado no fechamento para confirmar geracao de CSV

completo sem envio.

  • --full-csv validado:
  • recipients_generated = 1864
  • skipped_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.php
  • admin/cron/whatsapp_campaign_queue.php
  • SQL/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: 1864 pendentes, 0 enviados;
  • base Zona Norte + Baixada extra: 2503 pendentes, 0 enviados.
  • Teste unico autorizado para 5521999915554 enviado pela rota direta do novo

modulo:

  • HTTP 200;
  • message_id=true_5521999915554@c.us_3EB07455DF7569B9F8AB55.
  • Prova de nao disparo para clientes:
  • sent_count=0 nas duas campanhas;
  • process --execute --confirm=PROCESS_QUEUE retornou processed=0 por

global_kill_switch_on;

  • fila JSON legada em bot/whatsapp/queue permaneceu vazia.

Complemento - opt-out e ciclo randomico

  • Adicionada migracao complementar:
  • SQL/whatsapp_campaign_queue_BK277_optout_random.sql
  • process passou a escolher quantidade randomica por ciclo:
  • settings default_cycle_min_messages e default_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=1 e sem approved_at;
  • nenhum cliente real foi enviado nesta validacao.
  • Validacao e revisao:
  • process em dry-run expos cycle_limit randomico;
  • process --execute --confirm=PROCESS_QUEUE permaneceu 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 8d70565f5 que o main local esta alinhado com

origin/main.

  • php -l e php83 -l passaram em includes/whatsapp_campaign_queue.php,

admin/cron/whatsapp_campaign_queue.php e bot/whatsapp/webhook.php.

  • status confirmou global_kill_switch=true, campanhas v2 #3 e #4

ainda em dry_run_only=1, sem approved_at, sent_count=0 e nenhum contato

bloqueado por engano.

  • process --execute --confirm=PROCESS_QUEUE continuou 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 #3 e #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.php ou

PROCESS_QUEUE.

  • Criado snapshot runtime antes da liberacao:

admin/runtime/campaigns/whatsapp_campaign_state_pre_item4_20260429-170118.json.

  • Aprovada somente a campanha #3 com --allow-real.
  • Campanha #4 permaneceu sem aprovacao real (dry_run_only=1,

approved_at=NULL).

  • global_kill_switch foi desligado.
  • Validacao posterior confirmou sent_count=0 nas campanhas #3 e #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 #3 em

2026-04-29 17:14 -03, limitado ate 18:40.

  • Antes do disparo, send_window_end da campanha #3 foi 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 #3 pausou 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_switch foi religado.
  • Estado final seguro:
  • global_kill_switch=true;
  • campanha #3 em status=paused;
  • campanha #4 sem 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.celular primeiro 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-number antes de /send; numero sem

WhatsApp ativo vira blocked sem tentativa de envio.

  • Campanha #3 importada foi limpa:
  • 251 registros fora do padrao foram movidos para cancelled;
  • estado apos limpeza: pending_count=1596, sent_count=15,

blocked_count=2, cancelled_count=251.

  • Regeneracao dry-run pos-correcao:
  • base Miguel Falabella + Imperator: 1632 contatos validos;
  • Zona Norte + Baixada extra: 2003 contatos validos.
  • Validacoes: php -l, php83 -l, /check-number em um pendente real sem

envio e process em dry-run com kill switch ligado.