Cerebro Studio · Backlog · Changelog
RioNoTeatro • /www/wwwroot/rionoteatro.com.br/docs/BACKLOG.md
Abrir Studio Projeto externo em modo read-only; encaminhamento permitido, escrita bloqueada.

Backlog Unificado

Projeto: RioNoTeatro. Fonte principal: /www/wwwroot/rionoteatro.com.br/docs/BACKLOG.md.

Modo read-only: ações de escrita ficam disponíveis apenas para o Cérebro.

Sem itens pendentes em /www/wwwroot/rionoteatro.com.br/docs/BACKLOG.md.

Especificações Disponíveis (fora da fila pendente)

Detalhe do BK Selecionado

/www/wwwroot/rionoteatro.com.br/docs/backlog/BK-277-whatsapp-zona-norte-dry-run.md • 2026-04-29T20:32:02.437Z

BK-277 - Dry-run campanha WhatsApp Zona Norte

Status: validado em dry-run

Data: 2026-04-29

Contexto

Pedido operacional: preparar campanha emergencial para divulgar a ultima

apresentacao de As Loucas do Meier em 29/04/2026, inicialmente para

compradores antigos de eventos nos 2 teatros informados da Zona Norte:

  • Teatro Miguel Falabella (teatros.id = 8)
  • Imperator (teatros.id = 44)

Observacao: este BK nao seleciona todos os teatros da Zona Norte. A consulta

ficou restrita aos dois locais acima.

Decisao de seguranca

Nenhum disparo pode ocorrer sem autorizacao humana expressa.

A primeira entrega e apenas um dry-run com guardrails:

  • script CLI-only;
  • sem chamada a enviar_whatsapp, fila, webhook, Manus task ou helper de envio;
  • flags de envio como --send, --execute e --dispatch abortam o script;
  • query de destinatarios exige allowlist fixa do admin:
  • 552199915551
  • excecao posterior autorizada pelo admin:
  • para geracao de CSV apenas, o script aceita --full-csv e remove o

filtro de allowlist da query de destinatarios;

  • mesmo em --full-csv, o script segue sem qualquer rota de envio.
  • relatorios com destinatarios ficam em admin/runtime/campaigns/, ignorado pelo Git.
  • a lista final e deduplicada por WhatsApp normalizado para evitar mais de um

envio futuro ao mesmo numero.

  • a mensagem no CSV usa \n literal para manter uma linha por destinatario.

Escopo entregue

  • Criar script:
  • admin/cron/whatsapp_campaign_dry_run_zona_norte.php
  • Gerar relatorio CSV de dry-run apenas para o numero allowlisted.
  • Gerar resumo JSON sem acionar envio.
  • Manter estatistica agregada do segmento completo sem gerar lista completa de

destinatarios nesta fase.

Criterios de aceite

  • [x] php -l admin/cron/whatsapp_campaign_dry_run_zona_norte.php passa.
  • [x] Rodar o script sem flags de envio gera CSV/JSON em admin/runtime/campaigns/.
  • [x] Rodar com --send aborta antes de qualquer operacao ativa.
  • [x] O CSV de destinatarios contem no maximo numeros da allowlist.

Evidencia do primeiro dry-run com allowlist

  • Segmento agregado, sem lista completa de destinatarios:
  • clientes_segmento = 1925
  • clientes_com_celular = 1622
  • ja_compraram_alvo = 3
  • Destinatarios apos allowlist:
  • 0
  • Motivo operacional:
  • o numero allowlisted 552199915551 nao existe em clientes.celular

nem clientes.telefone nas variacoes verificadas (552199915551 e

2199915551).

  • Resultado:
  • nenhum disparo;
  • CSV gerado apenas com cabecalho.

Modo full CSV autorizado

Comando:

```bash

php admin/cron/whatsapp_campaign_dry_run_zona_norte.php --full-csv

```

Esse modo gera a lista completa elegivel em CSV para auditoria operacional,

mas continua bloqueando qualquer flag de envio e nao chama helper/fila/webhook

de WhatsApp.

Hardening adicional:

  • dedupe por WhatsApp normalizado no PHP, depois da query, preservando o

cadastro com compra historica mais recente.

Modo Zona Norte + Baixada extra

Comando:

```bash

php admin/cron/whatsapp_campaign_dry_run_zona_norte.php --expanded-csv

```

Esse modo gera um segundo CSV para a etapa posterior, incluindo locais

identificados no cadastro como Zona Norte/Baixada e excluindo por WhatsApp

normalizado quem ja entrou na base Miguel Falabella + Imperator.

Locais incluidos na query expandida:

  • Teatro Miguel Falabella (8) - base excluida no cruzamento
  • Imperator (44) - base excluida no cruzamento
  • Centro da Musica Carioca - Artur da Tavola (45)
  • Teatro Henriqueta Brieba (34)
  • Teatro Odylo Costa Filho - UERJ Maracana (93)
  • Teatro Armando Gonzaga (47)
  • Lona Cultural Municipal Carlos Zefiro (52)
  • Teatro Nova Iguacu Petrobras (89)
  • Teatro Sylvio Monteiro (86)

Evidencia do CSV extra:

  • CSV base Miguel Falabella + Imperator:
  • 1864 WhatsApps unicos
  • CSV extra Zona Norte + Baixada sem os 2 locais:
  • 2503 WhatsApps unicos
  • 0 duplicados internos
  • 0 sobreposicao com o CSV base
  • skipped_base_theater_phones = 1866 linhas candidatas removidas por

cruzamento com a base dos 2 locais

  • arquivo runtime gerado:
  • admin/runtime/campaigns/loucas_meier_zona_norte_20260429-expanded-zn-baixada-extra-20260429-103602.csv

Evidencia do full CSV:

  • recipients_generated = 1864
  • skipped_duplicate_phones = 1
  • variantes:
  • v1 = 590
  • v2 = 621
  • v3 = 653
  • arquivo runtime gerado:
  • admin/runtime/campaigns/loucas_meier_zona_norte_20260429-dryrun-20260429-103307.csv

Proxima fase

So depois de aprovacao humana:

  1. validar copy e link;
  2. validar status do WhatsApp oficial;
  3. testar envio manual para o numero allowlisted;
  4. ampliar allowlist em pequenos lotes;
  5. manter dedupe, opt-out, rate limit e log por lote.

Fila profissional instalada

Status: implementado e bloqueado para envio real em 2026-04-29.

Arquivos adicionados:

  • includes/whatsapp_campaign_queue.php
  • admin/cron/whatsapp_campaign_queue.php
  • SQL/whatsapp_campaign_queue_BK277.sql

Comandos seguros:

```bash

php83 admin/cron/whatsapp_campaign_queue.php install

php83 admin/cron/whatsapp_campaign_queue.php import --csv=CAMINHO --campaign=CHAVE --base=BASE --name=NOME

php83 admin/cron/whatsapp_campaign_queue.php status

php83 admin/cron/whatsapp_campaign_queue.php process --limit=5

php83 admin/cron/whatsapp_campaign_queue.php pause --campaign=ID

php83 admin/cron/whatsapp_campaign_queue.php resume --campaign=ID

php83 admin/cron/whatsapp_campaign_queue.php cancel --campaign=ID

php83 admin/cron/whatsapp_campaign_queue.php test-send --to=5521999915554

```

Todo comando que grava ou envia nasce em dry-run. Execucao real exige

--execute e uma confirmacao nominal, por exemplo:

  • --confirm=IMPORT_CSV
  • --confirm=PROCESS_QUEUE
  • --confirm=SEND_ADMIN_TEST
  • --confirm=APPROVE_REAL_SEND
  • --confirm=KILL_SWITCH_OFF

Guardrails ativos apos a instalacao:

  • global_kill_switch = 1
  • dry_run_default = 1
  • campanhas importadas com dry_run_only = 1
  • campanhas sem approved_at
  • process --execute bloqueado enquanto o kill switch estiver ligado
  • envio de teste bloqueado para qualquer numero fora da allowlist admin
  • envio tecnico usa chamada direta ao helper e nao chama enviar_whatsapp(),

evitando drenar a fila JSON legada apos um teste isolado.

CSVs importados para a fila:

  • campanha #1, base miguel_falabella_imperator: 1864 linhas pendentes,

0 enviadas.

  • campanha #2, base zona_norte_baixada_extra: 2503 linhas pendentes,

0 enviadas.

Validacoes executadas:

  • php -l includes/whatsapp_campaign_queue.php
  • php -l admin/cron/whatsapp_campaign_queue.php
  • php83 -l includes/whatsapp_campaign_queue.php
  • php83 -l admin/cron/whatsapp_campaign_queue.php
  • install em dry-run e depois com --execute --confirm=INSTALL_SCHEMA
  • import em dry-run para os dois CSVs
  • import --execute --confirm=IMPORT_CSV para os dois CSVs
  • status confirmando sent_count = 0 nas duas campanhas
  • process --limit=5 em dry-run
  • process --limit=5 --execute --confirm=PROCESS_QUEUE retornando

processed = 0 por global_kill_switch_on

  • teste unico autorizado para 5521999915554 com retorno HTTP 200 e

message_id = true_5521999915554@c.us_3EB07455DF7569B9F8AB55

  • fila JSON legada bot/whatsapp/queue permaneceu com 0 arquivos.

Complemento obrigatorio pos-revisao

Status: implementado em 2026-04-29 apos comparacao com os guardrails do

modulo de disparo do Edmundo10.

Adicionado:

  • numero randomico de disparos por ciclo:
  • settings default_cycle_min_messages=1 e default_cycle_max_messages=4;
  • process calcula random_limit por execucao e usa o menor valor entre

--limit, random_limit e o teto tecnico 50;

  • dry-run mostra cycle_limit com requested_limit, random_min,

random_max, random_limit e effective_limit.

  • footer obrigatorio de opt-out:
  • setting default_opt_out_text;
  • envio real por campanha passa por rnt_wa_campaign_message_with_optout();
  • texto padrao: Se nao quiser receber novas mensagens, responda SAIR.
  • a deteccao evita duplicar o footer oficial e tambem reconhece avisos

equivalentes como Para parar de receber, responda SAIR.

  • opt-out inbound automatico:
  • bot/whatsapp/webhook.php chama rnt_wa_campaign_process_inbound_optout()

para mensagens recebidas;

  • palavras como SAIR, PARAR, CANCELAR, REMOVER, DESCADASTRAR e

equivalentes gravam whatsapp_campaign_blacklist;

  • pendencias do telefone em campanhas sao marcadas como blocked com

last_error='inbound_opt_out';

  • evento inbound_opt_out fica registrado em whatsapp_campaign_events.

Regras mantidas:

  • nenhum envio real sem global_kill_switch=0;
  • nenhuma campanha real sem approved_at e dry_run_only=0;
  • envio de teste segue restrito a allowlist admin;
  • respostas de opt-out nao disparam mensagem de confirmacao automatica.
  • revisoes externas devem ser fatiadas por tema quando o escopo for sensivel

(ciclo, footer, webhook/opt-out) para reduzir ruido e acelerar

retorno tecnico.

Validacoes adicionais:

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

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

  • migracao complementar aplicada com install --execute --confirm=INSTALL_SCHEMA;
  • process --limit=10 --campaign=3 mostrou cycle_limit randomico e sem envio;
  • process --limit=10 --campaign=3 --execute --confirm=PROCESS_QUEUE retornou

processed=0 por global_kill_switch_on;

  • smoke do footer confirmou injecao de SAIR e nao duplicacao de texto

equivalente;

  • smoke de opt-out inbound em transacao confirmou blacklist durante a transacao

e ROLLBACK limpo.

Auditoria final Codex - 2026-04-29 12:33 -03

Status: sem achado P0/P1 local apos o commit 8d70565f5.

Evidencias revalidadas:

  • main local alinhada com origin/main em 8d70565f5;
  • git status -sb limpo antes da atualizacao documental;
  • php -l e php83 -l passaram em:
  • includes/whatsapp_campaign_queue.php;
  • admin/cron/whatsapp_campaign_queue.php;
  • bot/whatsapp/webhook.php;
  • php83 admin/cron/whatsapp_campaign_queue.php status confirmou:
  • global_kill_switch=true;
  • dry_run_default=true;
  • allowlist admin restrita a 5521999915554 e 5521999605790;
  • cycle_min_messages=1;
  • cycle_max_messages=4;
  • campanhas v2 #3 e #4 com dry_run_only=1, approved_at=NULL,

sent_count=0, blocked_count=0 e todos os contatos ainda pendentes;

  • process --limit=10 --campaign=3 em dry-run mostrou ciclo randomico e

mascarou telefones no sample;

  • process --limit=10 --campaign=3 --execute --confirm=PROCESS_QUEUE

retornou processed=0 e reason=global_kill_switch_on;

  • smoke local de rnt_wa_campaign_message_with_optout() confirmou:
  • footer SAIR inserido quando ausente;
  • footer oficial nao duplicado quando ja presente;
  • smoke local de rnt_wa_campaign_is_optout_text() reconheceu SAIR,

PARAR, cancelar, remover, descadastrar e nao quero receber;

  • smoke transacional de rnt_wa_campaign_process_inbound_optout() usou um

telefone pendente da campanha #3, confirmou blocked e blacklist dentro

da transacao, e fez ROLLBACK; contadores reais continuaram:

  • campanha #3: pending_count=1864, sent_count=0, blocked_count=0;
  • campanha #4: pending_count=2503, sent_count=0, blocked_count=0.

Revisao externa read-only da auditoria final:

  • Gemini CLI (gemini-3-flash-preview, approval-mode plan) iniciou a leitura

dos arquivos e nao reportou achado antes do timeout de 120s;

  • OpenCode (opencode/minimax-m2.5-free) falhou por erro conhecido do

provider: tools[*].eager_input_streaming;

  • por falha operacional dos revisores externos, a aprovacao desta etapa ficou

baseada na auditoria local do Codex + smokes reais sem envio.

Condicoes obrigatorias antes de qualquer disparo real futuro:

  1. revisar copy final da campanha com o footer de opt-out visivel;
  2. confirmar que o bot WhatsApp esta conectado e saudavel;
  3. manter global_kill_switch=1 ate a aprovacao humana expressa;
  4. aprovar uma campanha por vez com dry_run_only=0 e approved_at preenchido;
  5. iniciar com --limit baixo, mantendo os ciclos randomicos de 1..4;
  6. monitorar sent_count, failed_count, blocked_count,

whatsapp_campaign_events e whatsapp_campaign_blacklist durante o envio.

Preparacao autorizada ate item 4 - 2026-04-29 17:02 -03

Autorizacao humana na sessao: seguir ate o item 4 da checklist acima.

Executado:

  • Item 1, copy final:
  • amostras das campanhas #3 e #4 foram renderizadas com

rnt_wa_campaign_message_with_optout();

  • os links curtos corretos apareceram:
  • campanha #3: https://rionoteatro.com.br/1a2b;
  • campanha #4: https://rionoteatro.com.br/9t66;
  • footer final visivel:

Se nao quiser receber novas mensagens, responda SAIR.

  • Item 2, bot WhatsApp:
  • helper retornou reachable=true, connected=true, status=connected,

service=rionoteatro-whatsapp-service, version=2.0.0;

  • rota /send foi sondada com numero invalido e retornou erro, sem envio

para cliente.

  • Conferencia anti-disparo automatico:
  • nao foi encontrado cron chamando whatsapp_campaign_queue.php ou

PROCESS_QUEUE;

  • nao havia processo ativo de processamento da fila fora dos comandos de

validacao desta rodada.

  • Snapshot operacional antes da liberacao:
  • admin/runtime/campaigns/whatsapp_campaign_state_pre_item4_20260429-170118.json
  • Item 3, aprovacao por campanha:
  • apenas a campanha #3 foi aprovada com --allow-real;
  • campanha #4 permaneceu dry_run_only=1, approved_at=NULL,

status=imported.

  • Item 4, kill switch:
  • global_kill_switch foi alterado para off.

Estado apos item 4:

  • global_kill_switch=false;
  • campanha #3:
  • status=approved;
  • dry_run_only=0;
  • approved_at=2026-04-29 17:01:24;
  • pending_count=1864;
  • sent_count=0;
  • blocked_count=0;
  • campanha #4:
  • status=imported;
  • dry_run_only=1;
  • approved_at=NULL;
  • pending_count=2503;
  • sent_count=0;
  • blocked_count=0.

Validacao sem envio apos a liberacao:

  • php83 admin/cron/whatsapp_campaign_queue.php process --limit=5 --campaign=3

rodou em dry-run, sem --execute, retornando:

  • global_kill_switch=false;
  • campaign_status=approved;
  • dry_run_only=0;
  • would_process=1;
  • nenhum envio executado.

Proximo passo bloqueado por autorizacao humana:

  • nao rodar process --execute --confirm=PROCESS_QUEUE sem ordem expressa do

admin;

  • quando autorizado, iniciar com limite baixo na campanha #3 e monitorar

imediatamente sent_count, failed_count, blocked_count, eventos e

blacklist.

Disparo real monitorado - 2026-04-29 17:14 a 17:26 -03

Autorizacao humana na sessao: disparar a campanha, apenas ate 18:40.

Antes de iniciar:

  • horario do servidor confirmado: 2026-04-29 17:13:46 -0300;
  • campanha #3 estava approved, dry_run_only=0, sent_count=0;
  • campanha #4 permanecia bloqueada (dry_run_only=1, approved_at=NULL);
  • send_window_end da campanha #3 foi limitado de 20:00 para 18:40;
  • snapshot pre-disparo:

admin/runtime/campaigns/whatsapp_campaign_state_pre_real_send_20260429-171357.json.

Execucao:

  • primeiro ciclo manual:
  • processed=2;
  • sent=2;
  • failed=0;
  • blocked=0.
  • loop monitorado:
  • log: admin/runtime/campaigns/whatsapp_campaign_real_send_20260429-171431.log;
  • rodava somente --campaign=3;
  • cada ciclo usava --limit=4, mas a fila aplicava ciclo randomico 1..4,

limite por minuto e limite por hora.

Pausa automatica:

  • as mensagens foram interrompidas pela propria fila em 2026-04-29 17:25:35;
  • motivo: last_pause_reason=auto_failure_rate_21;
  • contadores no momento da pausa:
  • sent_count=15;
  • blocked_count=4;
  • failed_count=0;
  • pending_count=1845;
  • last_send_at=2026-04-29 17:24:29;
  • last_error_at=2026-04-29 17:25:35.
  • bloqueios conferidos:
  • Numero sem conta WhatsApp ativa (No LID);
  • nao houve evidencia de falha tecnica do bot nos bloqueios analisados.

Acao de seguranca executada pelo Codex:

  • loop interrompido apos a pausa automatica;
  • global_kill_switch religado com KILL_SWITCH_ON;
  • status final:
  • global_kill_switch=true;
  • campanha #3: status=paused, sent_count=15, blocked_count=4,

failed_count=0, send_window_end=18:40;

  • nenhum processo de envio ficou rodando.

Calculo operacional:

  • com per_hour_limit=80, a campanha completa de 1864 contatos levaria pelo

menos cerca de 23h20 de envio efetivo, antes de considerar bloqueios,

pausas e janela diaria;

  • portanto, a janela de 17:14 a 18:40 nao seria suficiente para zerar a

base inteira.

Proximo passo bloqueado por autorizacao humana:

  • decidir se a taxa de bloqueio inicial e aceitavel para retomar;
  • se retomar, sera necessario:
  1. desligar novamente o kill switch;
  2. resume --campaign=3;
  3. rodar novo process --execute --confirm=PROCESS_QUEUE com limite baixo;
  4. monitorar taxa de bloqueio imediatamente.

Correcao de elegibilidade de telefone - 2026-04-29 17:32 -03

Achado durante o monitoramento:

  • os bloqueios iniciais incluiram telefones fora do DDD 21 (5561...,

5541...);

  • isso mostrou que a base importada aceitava telefone/celular normalizado como

Brasil, mas nao exigia o padrao da campanha de Zona Norte:

5521 + 9 digitos locais.

Correcao aplicada no codigo:

  • admin/cron/whatsapp_campaign_dry_run_zona_norte.php:
  • --full-csv e --expanded-csv agora so geram destinatarios no formato

55219XXXXXXXX;

  • a escolha do numero tenta clientes.celular primeiro;
  • se celular nao for valido nesse padrao, tenta clientes.telefone;
  • telefone fixo, DDD fora de 21, numero 21 sem nono digito e formatos fora do

padrao ficam fora do CSV.

  • includes/whatsapp_campaign_queue.php:
  • antes de enviar, a fila cancela qualquer pendencia fora do padrao

55219XXXXXXXX;

  • antes de chamar /send, a fila chama /check-number;
  • se /check-number estiver indisponivel, a fila devolve a mensagem para

pending e para o ciclo sem enviar;

  • se o numero nao existir no WhatsApp, marca como blocked com

whatsapp_not_registered_precheck, sem chamar /send.

Limpeza da campanha #3 ja importada:

  • snapshot pre-limpeza:

admin/runtime/campaigns/whatsapp_campaign_pre_ddd21_cleanup_20260429-173058.json;

  • 251 registros fora do padrao foram movidos para cancelled com

last_error=phone_not_5521_9digits_preflight;

  • estado apos a limpeza:
  • global_kill_switch=true;
  • status=paused;
  • pending_count=1596;
  • sent_count=15;
  • blocked_count=2;
  • cancelled_count=251.

Validacoes:

  • php -l e php83 -l nos dois arquivos alterados;
  • precheck /check-number testado em um pendente real, sem envio, retornando

ok=true, exists=true, http=200;

  • process --limit=3 --campaign=3 rodou em dry-run com kill switch ligado e

mostrou apenas telefones mascarados 5521...;

  • regeneracao dry-run pos-correcao:
  • base Miguel Falabella + Imperator: 1632 contatos validos

55219XXXXXXXX;

  • Zona Norte + Baixada extra: 2003 contatos validos 55219XXXXXXXX;
  • os arquivos gerados continuam em admin/runtime/campaigns/, sem envio.