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)
- BK-136
- BK-137
- BK-138
- BK-147
- BK-148
- BK-149
- BK-150
- BK-151
- BK-156
- BK-158
- BK-159
- BK-160
- BK-161
- BK-162
- BK-163
- BK-164
- BK-165
- BK-166
- BK-170
- BK-171
- BK-172
- BK-177
- BK-183
- BK-186
- BK-187
- BK-189
- BK-190
- BK-191
- BK-192
- BK-193
- BK-195
- BK-196
- BK-197
- BK-198
- BK-199
- BK-201
- BK-205
- BK-207
- BK-208
- BK-209
- BK-210
- BK-211
- BK-212
- BK-213
- BK-214
- BK-215
- BK-216
- BK-217
- BK-218
- BK-219
- BK-220
- BK-221
- BK-229
- BK-230
- BK-231
- BK-232
- BK-233
- BK-234
- BK-235
- BK-236
- BK-239
- BK-240
- BK-241
- BK-242
- BK-243
- BK-244
- BK-245
- BK-246
- BK-248
- BK-249
- BK-250
- BK-251
- BK-252
- BK-253
- BK-254
- BK-255
- BK-256
- BK-257
- BK-258
- BK-259
- BK-260
- BK-261
- BK-262
- BK-263
- BK-264
- BK-265
- BK-266
- BK-267
- BK-268
- BK-269
- BK-270
- BK-271
- BK-272
- BK-275
- BK-276
- BK-277
- BK-278
- BK-279
- BK-280
- BK-295
- BK-313
Detalhe do BK Selecionado
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,--executee--dispatchabortam 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-csve 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
\nliteral 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.phppassa. - [x] Rodar o script sem flags de envio gera CSV/JSON em
admin/runtime/campaigns/. - [x] Rodar com
--sendaborta 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 = 1925clientes_com_celular = 1622ja_compraram_alvo = 3- Destinatarios apos allowlist:
0- Motivo operacional:
- o numero allowlisted
552199915551nao existe emclientes.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:
1864WhatsApps unicos- CSV extra Zona Norte + Baixada sem os 2 locais:
2503WhatsApps unicos0duplicados internos0sobreposicao com o CSV baseskipped_base_theater_phones = 1866linhas 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 = 1864skipped_duplicate_phones = 1- variantes:
v1 = 590v2 = 621v3 = 653- arquivo runtime gerado:
admin/runtime/campaigns/loucas_meier_zona_norte_20260429-dryrun-20260429-103307.csv
Proxima fase
So depois de aprovacao humana:
- validar copy e link;
- validar status do WhatsApp oficial;
- testar envio manual para o numero allowlisted;
- ampliar allowlist em pequenos lotes;
- 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.phpadmin/cron/whatsapp_campaign_queue.phpSQL/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 = 1dry_run_default = 1- campanhas importadas com
dry_run_only = 1 - campanhas sem
approved_at process --executebloqueado 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, basemiguel_falabella_imperator:1864linhas pendentes,
0 enviadas.
- campanha
#2, basezona_norte_baixada_extra:2503linhas pendentes,
0 enviadas.
Validacoes executadas:
php -l includes/whatsapp_campaign_queue.phpphp -l admin/cron/whatsapp_campaign_queue.phpphp83 -l includes/whatsapp_campaign_queue.phpphp83 -l admin/cron/whatsapp_campaign_queue.phpinstallem dry-run e depois com--execute --confirm=INSTALL_SCHEMAimportem dry-run para os dois CSVsimport --execute --confirm=IMPORT_CSVpara os dois CSVsstatusconfirmandosent_count = 0nas duas campanhasprocess --limit=5em dry-runprocess --limit=5 --execute --confirm=PROCESS_QUEUEretornando
processed = 0 por global_kill_switch_on
- teste unico autorizado para
5521999915554com retorno HTTP200e
message_id = true_5521999915554@c.us_3EB07455DF7569B9F8AB55
- fila JSON legada
bot/whatsapp/queuepermaneceu com0arquivos.
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=1edefault_cycle_max_messages=4; processcalcularandom_limitpor execucao e usa o menor valor entre
--limit, random_limit e o teto tecnico 50;
- dry-run mostra
cycle_limitcomrequested_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.phpchamarnt_wa_campaign_process_inbound_optout()
para mensagens recebidas;
- palavras como
SAIR,PARAR,CANCELAR,REMOVER,DESCADASTRARe
equivalentes gravam whatsapp_campaign_blacklist;
- pendencias do telefone em campanhas sao marcadas como
blockedcom
last_error='inbound_opt_out';
- evento
inbound_opt_outfica registrado emwhatsapp_campaign_events.
Regras mantidas:
- nenhum envio real sem
global_kill_switch=0; - nenhuma campanha real sem
approved_atedry_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 -lephp83 -lemincludes/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=3mostroucycle_limitrandomico e sem envio;process --limit=10 --campaign=3 --execute --confirm=PROCESS_QUEUEretornou
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:
mainlocal alinhada comorigin/mainem8d70565f5;git status -sblimpo antes da atualizacao documental;php -lephp83 -lpassaram em:includes/whatsapp_campaign_queue.php;admin/cron/whatsapp_campaign_queue.php;bot/whatsapp/webhook.php;php83 admin/cron/whatsapp_campaign_queue.php statusconfirmou:global_kill_switch=true;dry_run_default=true;- allowlist admin restrita a
5521999915554e5521999605790; cycle_min_messages=1;cycle_max_messages=4;- campanhas v2
#3e#4comdry_run_only=1,approved_at=NULL,
sent_count=0, blocked_count=0 e todos os contatos ainda pendentes;
process --limit=10 --campaign=3em 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
SAIRinserido quando ausente; - footer oficial nao duplicado quando ja presente;
- smoke local de
rnt_wa_campaign_is_optout_text()reconheceuSAIR,
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:
- revisar copy final da campanha com o footer de opt-out visivel;
- confirmar que o bot WhatsApp esta conectado e saudavel;
- manter
global_kill_switch=1ate a aprovacao humana expressa; - aprovar uma campanha por vez com
dry_run_only=0eapproved_atpreenchido; - iniciar com
--limitbaixo, mantendo os ciclos randomicos de1..4; - 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
#3e#4foram 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
/sendfoi sondada com numero invalido e retornou erro, sem envio
para cliente.
- Conferencia anti-disparo automatico:
- nao foi encontrado cron chamando
whatsapp_campaign_queue.phpou
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
#3foi aprovada com--allow-real; - campanha
#4permaneceudry_run_only=1,approved_at=NULL,
status=imported.
- Item 4, kill switch:
global_kill_switchfoi alterado paraoff.
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_QUEUEsem ordem expressa do
admin;
- quando autorizado, iniciar com limite baixo na campanha
#3e 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
#3estavaapproved,dry_run_only=0,sent_count=0; - campanha
#4permanecia bloqueada (dry_run_only=1,approved_at=NULL); send_window_endda campanha#3foi limitado de20:00para18: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 randomico1..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_switchreligado comKILL_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 de1864contatos levaria pelo
menos cerca de 23h20 de envio efetivo, antes de considerar bloqueios,
pausas e janela diaria;
- portanto, a janela de
17:14a18:40nao 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:
- desligar novamente o kill switch;
resume --campaign=3;- rodar novo
process --execute --confirm=PROCESS_QUEUEcom limite baixo; - 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-csve--expanded-csvagora so geram destinatarios no formato
55219XXXXXXXX;
- a escolha do numero tenta
clientes.celularprimeiro; - se
celularnao for valido nesse padrao, tentaclientes.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-numberestiver indisponivel, a fila devolve a mensagem para
pending e para o ciclo sem enviar;
- se o numero nao existir no WhatsApp, marca como
blockedcom
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;
251registros fora do padrao foram movidos paracancelledcom
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 -lephp83 -lnos dois arquivos alterados;- precheck
/check-numbertestado em um pendente real, sem envio, retornando
ok=true, exists=true, http=200;
process --limit=3 --campaign=3rodou em dry-run com kill switch ligado e
mostrou apenas telefones mascarados 5521...;
- regeneracao dry-run pos-correcao:
- base Miguel Falabella + Imperator:
1632contatos validos
55219XXXXXXXX;
- Zona Norte + Baixada extra:
2003contatos validos55219XXXXXXXX; - os arquivos gerados continuam em
admin/runtime/campaigns/, sem envio.