Cerebro Studio · Backlog · Changelog
/www/wwwroot/rionoteatro.com.br/docs/CHANGELOG.md • 2026-10-18T22:07:45.197Z

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

Changelog - Rio no Teatro

> Changelog = historico do que ja foi entregue e validado.

> Backlog vivo de cada BK fica em docs/backlog/BK-XX-*.md.

> Antes de alterar regra compartilhada de governanca/workflow, comparar este repositorio com /root/cerebro.

2026

[2026-10-18] Página /parceria-vendas; "Sem repasse. Vendas abaixo dos R$50"; relatório com divulgação e vendas da temporada

Pedidos do ADMIN (18/10): (1) no relatório de fim de temporada (só admin), custo com divulgação, pessoas que viram os anúncios, que acessaram a página, que compraram e o total arrecadado — só da última temporada; (2) mês com vendas abaixo de R$ 50 não tem repasse: mostrar "Sem repasse. Vendas abaixo dos R$50" (admin e produtor), com o trecho da regra no topo da tela do produtor e mantendo o comprovante no admin; (3) página /parceria-vendas com as regras e botão para o produtor aceitar pelo WhatsApp (peça, link oficial, valor promocional, celulares da lista, 20 assentos, "Li e aceito as regras da parceria com vendas e comissão").

Mudança: admin/modulos/pecas/relatorios.php — bloco "Divulgação e vendas" da última temporada (detectada pelas sessões: pausa > 30 dias separa temporadas): custo/exibições/cliques Meta+Google pelos dias da janela (30 dias antes da 1ª sessão até a última; de /www/rnt-deploy/status/divulgacao_pecas.json), alcance total da campanha, visitas à página (cliente_tracking view_item), compradores e arrecadado (pagos 3/4, sessão na temporada); fechamento < R$ 50 = "Sem repasse" (Marcar como pago continua) e botão de WhatsApp sem pedir dados bancários. produtor/fechamento.php — mês < R$ 50: quadro com itens 5.2 e 13 do termo, "Sem repasse. Vendas abaixo dos R$50" e sem o botão de aprovar/PIX. parceria-vendas.php (novo) + includes/termo_parceria_regras.php (texto do termo extraído; termo-parceria.php passa a incluir, saída idêntica). Regras seguem 30 ingressos (20 em sala pequena); o aceite pelo WhatsApp é sempre 20 (o ADMIN pede mais com boa venda). Fora do Git: rewrite nginx /parceria-vendas (e /parceira-vendas → 301); rnt-tools/motor_divulgacao_resumo.php (cron :43, por dia, não sobrescreve com erro).

Validado: Sintaxe PHP ok (php83); JS inline validado (simulação do formulário e da mensagem); nginx -t, curl 200/301 e páginas de peça intactas; termo idêntico (diff só de ids aleatórios); relatório e tela do produtor renderizados (mês < R$ 50 e mês normal). gpt-6-astra: P2 corrigidos (celular sem DDD, número enganoso, anúncios por temporada), revisão final sem P1/P2.

[2026-10-18] Fim de temporada: WhatsApp à produção + push ao ADMIN; Google para 1h antes da última sessão; aviso "pausar" sem ruído

Pedidos do ADMIN (18/10): (1) Google parar sozinho 1h antes da última sessão; (2) calar o aviso "pausar" da rotina simulada quando a campanha já terminou sozinha; (3) "quando a temporada termina, push com link do relatório de vendas, e mensagem para a produção perguntando se a peça irá prorrogar (tem no e-mail, deveria ter no WhatsApp)".

Mudança: admin/cron/fim_temporada_whatsapp.php (novo; /etc/cron.d/rnt-fim-temporada-whatsapp, 10h) — no dia seguinte à ÚLTIMA sessão (tab_sessoes, não fim_temporada), mesmo filtro do e-mail do z_dispara_diario.php (ativa, venda própria ou parceria_online=1; fora origem='bot'): WhatsApp à produção (rnt_fin_whatsapp_produtor, sem celular = não envia, nunca SAC) com o texto aprovado pelo ADMIN ("Boa tarde! Tudo bem? / A temporada de X terminou em dd/mm. Vamos ter prorrogação ou nova temporada? / Agradecemos desde já a parceria e seguimos a disposição! / Jorge | Rio no Teatro 15 anos"); mesmo número com 2+ peças = 1 mensagem (anti-spam dos últimos 100 bytes); push ao ADMIN por peça (push_eu.php) com admin/modulos/pecas/relatorios.php?id=X. Trava, reserva na tabela rnt_fim_temporada_aviso antes de enviar (uma vez por peça+última sessão), falha de banco para tudo, --data só com ensaio/teste. Fora do Git: rnt-tools/motor_google_fim.php (cron 15/15 min) pausa a campanha do Google 1h antes da última sessão (a API não aceita término com hora) e religa só o que pausou se a peça for prorrogada (respeita motor_anuncios.PARAR); Cinderela Negra pausada no Google. motor_anuncios_rotina.php não avisa "pausar" quando o end_time do conjunto já passou.

Validado: Sintaxe PHP ok (php83); ensaio do Google (só Cinderela) e aplicação real; ensaio do fim de temporada (Cinderela Negra e Discutindo a Relação, celulares da produção achados) e 3 testes idênticos só para o gestor (WhatsApp + push), texto aprovado pelo ADMIN. gpt-6-astra: P2 (PARAR no religar) e 4 P2 do fim de temporada corrigidos.

[2026-10-18] BK-566 fechado: motor não religa anúncio pausado à mão + campanha da Meta termina 1h antes da última sessão

Pedidos do ADMIN (18/10): "manda o BK-566 pra frente agora"; "a campanha tinha que ter sido programada para acabar 1 hora antes da peça, sem necessidade de pausar ao fim da temporada".

Mudança BK-566: admin/modulos/campanhas/api/fb_dashboard.php — ao pausar anúncio, o ID entra em /www/rnt-deploy/status/anuncios_pausa_manual.json ANTES do pedido à Meta; ao religar, sai só depois da Meta confirmar; tudo sob a trava fixa anuncios_pausa_manual.lock (temporário exclusivo + rename no JSON); lista sumida/inválida recusa a operação; marca "pausa manual" na lista de anúncios. Fora do Git: motor_anuncios_conf.php (motor_pausas_manuais(), motor_pausas_travar()), motor_anuncios_auto.php — ativação inicial, --ligar e "anúncio novo" pulam os IDs pausados à mão; todos pausados = campanha não liga; lista ilegível/sumida = não liga nada; aviso uma vez.

Mudança término: MOTOR_HORAS_ANTES_FIM = 1 (era 3h) em motor_anuncios_meta.php e motor_anuncios_auto.php; as 4 campanhas da Meta ajustadas (ex.: Discutindo a Relação 16:00 → 18:00 de hoje). Google: a API recusa término que não seja fim do dia (DATE_RANGE_ERROR_END_TIME_MUST_BE_THE_END_OF_A_DAY), continua 23:59:59 do último dia.

Validado: Sintaxe PHP ok (php83); ad_pause real como www em anúncio já pausado (lista, marca, motor lê com trava); lista corrompida/sumida = não liga; ensaios Meta e Google; aplicação real na Meta ok. gpt-6-astra: 1 P1 (trava do motor sobrescrita) + 1 P2 (lista sumida recriada) corrigidos, nova passada sem P1/P2.

[2026-10-18] Deploy automático desligado: push só guarda no GitHub; deploy manual só publica o .env

Pedido do ADMIN (18/10): "ninguem mexe em outro lugar mais, so VPS"; "so seria preciso o deploy qdo alterada alguma senha"; permissões e afins por "um script nosso mais rapido q o deploy".

Mudança: .github/workflows/deploy.yml virou "Publicar senhas (.env) - manual" — só workflow_dispatch, só monta e publica o .env (mesmas 43 linhas de Secret) + smoke; sem rsync, composer ou build. AGENTS.md: seção "Deploy: so manual, so senhas". Fora do Git: /root/rnt-tools/rnt_pos_commit.sh (post-commit em /usr/local/libexec/rnt/git-hooks/, o core.hooksPath): dono/permissão dos arquivos do commit e pastas (segredos de fora, executável preservado), api/eventos, composer install se mudou, aviso se o scraper mudou, smoke com Host certo; log backups/rnt_pos_commit.log. pre-push (BK-489) só avisa edição sem commit. /root/rnt-tools/publicar_senhas.sh dispara o workflow, acompanha a execução certa e falha se token sumir ou o monitor do MP não der OK.

Validado: YAML ok; bash -n ok; rnt_pos_commit.sh em 2 commits reais (inclusive arquivo novo); gpt-6-astra: 2 P2 do publicar_senhas.sh corrigidos.

[2026-10-18] Motor de anúncios: rodada diária às 05:00 (era 9h), aviso ao gestor às 9h

Pedido do ADMIN (18/10): "vamos mudar para as 05:00 e nao mais as 9h"; o aviso de campanha ligada/término ajustado fica segurado até as 9h.

Mudança: admin/modulos/campanhas/api/fb_dashboard.php — texto "Roda sozinho todo dia às 5h (o aviso no WhatsApp chega às 9h)". Fora do Git: /etc/cron.d/rnt-motor-anuncios (rodada 0 5, novo 0 9 com motor_avisos_soltar.php); motor_anuncios_auto_rodar.sh exporta MOTOR_SEGURAR_AVISO_ATE=9 só na rodada diária (o botão "rodar agora" avisa na hora); motor_anuncios_conf.php — motor_avisar_gestor() guarda o aviso em backups/motor_avisos_segurados.json antes das 9h (Rio).

Validado: Sintaxe PHP ok (php83), bash -n ok; fila testada com aviso falso sem envio (guardado, listado no --ensaio, apagado).

Pedido do ADMIN (18/10): "sempre que eu precisar clicar em um link aqui você me envia o link pelo push, e quando for um texto para eu copiar também, com o botão de copiar sempre no push". Depois: só no Android; botão Copiar também no push de link; não fechar a aba sozinho; texto editável antes de copiar.

Mudança: copiar.php (raiz, novo) — mostra o texto gravado em /www/rnt-deploy/copiar/<token>.json (pasta do root, www só lê, vale 24h), copia sozinha quando aberta pelo botão Copiar (auto=1 ou pclick=copiar do sw antigo), texto editável, no-store/noindex/no-referrer/CSP, texto escapado. sw.js — requireInteraction opcional (fixo), endereço próprio por botão (data.acoes), pclick só em link do próprio site (origem exata; link externo abre intacto), clique em qualquer botão conta como "botao" no BK-481. js/rnt-push.js — a cada página pede registration.update() (o sw.js tem cache de 30 dias e o Android ficou preso na versão antiga; o log mostrou que ele nunca buscou o novo) + ?ver=1.5 em admin/inc-footer.php e includes/inc_peca.php. Fora do Git: /www/rnt-push-sender/send.php ganhou --action-id, --tag, --fixo, --inscricao e segundo botão (--button2/--action-id2/--url2); /root/rnt-tools/push_eu.php (--link= com Abrir+Copiar, --copiar=/--copiar-arquivo= com Copiar), só para a inscrição #223 (Android do ADMIN), recusa texto com formato de senha/token/chave. Regra em /root/AGENTS.md e na memória: link/texto acionável sempre também por push.

Validado: Sintaxe PHP ok (php83); Sintaxe JS ok (sw.js, rnt-push.js); JS inline validado; copiar.php pelo site (XSS escapado, cabeçalhos, token inválido/inexistente); ensaio só com #223; 5 testes reais no Android (Abrir, Copiar após atualizar o sw, permissão de cópia só na 1ª vez, editar e copiar). gpt-6-astra sem P1/P2 (2 passadas).

[2026-10-18] Deploy não apaga mais o GOOGLE_ADS_REFRESH_TOKEN do .env

Pedido do ADMIN (18/10): "pq isso sempre acontece? vamos corrigir p nao acontecer mais".

Causa: /usr/local/libexec/rnt/publicar_env.sh substitui o .env inteiro pelo candidato montado dos GitHub Secrets; o refresh token é gravado no servidor pelo google_ads_oauth_callback.php e não existe como Secret, então sumia em todo push (e o .env mudado obrigava restaurar + --seal do monitor do MP).

Mudança (fora do Git): publicar_env.sh ganhou MANTER_DO_SERVIDOR=GOOGLE_ADS_REFRESH_TOKEN: se o candidato não traz valor (ausente, vazio, só espaços ou CR), a linha do .env atual é copiada literalmente para um arquivo de trabalho; candidato com valor vence. Validações (obrigatórias, contagem, duplicadas) rodam no arquivo final; nenhum valor é impresso. deploy.yml: comentário atualizado (não criar Secret nem linha vazia para o token).

Validado: cópia isolada do script em 5 casos (sem token → .env final idêntico ao real; vazio; só espaços; vazio com CR; token no candidato vence) + --dry-run; bash -n ok. gpt-6-astra: 1 P2 (espaços/CR) corrigido. Pendente: prova no próximo deploy real (--check OK sem restaurar nada). Resíduo: callback e deploy ao mesmo tempo podem perder um token recém-gerado (raro: consentimento manual).

[2026-10-18] Mapa de assentos sem "Cadeirante" + aba "Textos de divulgação" (BK-564) — commit do trabalho da madrugada

Pedido do ADMIN (18/10): commitar junto com o painel de anúncios o que estava no ar sem commit (o push travava pelo BK-489).

Mudança: peca.php — "Cadeirante" saiu da legenda do mapa (não vendemos online, só na bilheteria), legenda no verde do assento livre, datas no celular ajustadas. admin/modulos/campanhas/redes_sociais.php — aba "Textos de divulgação" (BK-564: textos de Reels/Feed escritos por IA), com redes_sociais_textos.php, textos_divulgacao_action.php, textos_divulgacao_lib.php, admin/js/textos-divulgacao.js e admin/cron/textos_divulgacao.php. BACKLOG: BK-565 aberto.

Validado no commit: Sintaxe PHP ok (php83) nos 6 arquivos; Sintaxe JS ok; sem segredo nos arquivos novos. A validação de tela é a da sessão que fez o trabalho.

[2026-10-18] Painel Meta: anúncios de cada campanha com pausar/ligar; fim do PHP 5.6; legenda do carrossel sem log do dotenv

Pedidos do ADMIN (18/10): (1) o carrossel aprovado saiu com [dotenv@17.3.1] injecting env... no início da legenda; (2) impulsionar o Reels de "Uma Velha Canção" com link marcado para a página da peça e ver os anúncios no painel fb_dashboard.php (só listava campanhas); (3) acabar com a compatibilidade com o 5.6 sempre que mexermos num arquivo.

Mudança: admin/modulos/campanhas/api/facebook_ads.php ganhou getAccountAds()/getAccountAdInsights() (conta inteira, seguindo a paginação da Meta, com incompleto), getAd(), setAdStatus() (só ACTIVE/PAUSED) e getAdAccountId(). fb_dashboard.php: "Anúncios (N)" embaixo de cada campanha (situação real em português, gasto/impressões/cliques/CTR do período, prévia, pausar/ligar); POST ad_pause/ad_activate exige CSRF, ID numérico e reconsulta na Meta que o anúncio é da conta e da campanha da linha; recusa arquivado; error_log com quem mudou; aviso quando a lista vier incompleta. AGENTS.md: 5.6 encerrado (lint só php83 -l; modernizar o arquivo que for mexido; v83.sh u deixa de ser rollback garantido).

Fora do Git: anúncio MOTOR p1652 ... | reels (ID 120253405404680418) criado no conjunto do motor com o Reels Dent_EDtTgr, botão Comprar agora e utm_content=reels, ligado e aprovado. Legenda: quiet:true no dotenv de cerebro/tools/groq_cli.js, kimi_cli.js e kernel/social_creative_service.js (cerebro 591e01e) + filtro dotenv@ em rnt-tools/carrossel_semanal.php; legenda do Facebook do post 20261018-ultimodia corrigida pela API (Instagram corrigido à mão pelo ADMIN).

Validado: Sintaxe PHP ok (php83); render GET logado em CLI com 16 anúncios em 9 campanhas (igual à Meta); POST recusa campanha errada, ID inválido e CSRF ruim sem escrever na Meta; ligar anúncio pela mesma chamada da API ok. gpt-6-astra sem P1/P2. Pausa manual de anúncio do motor só é desfeita se a peça for religada à força pelo motor.

[2026-10-17] BK-563: aba "Vídeo promo" no Gestor de peças (editor de vídeo básico)

Pedido do ADMIN (17/10): subir os vídeos que o produtor manda, cortar (em geral tirar o flyer de informações do final), juntar vídeos, pôr a nossa arte no final e exportar MP4 em formato storie ou feed. Ao longo do dia: listar os vídeos da aba Storie, linha do tempo com arrastar e soltar, edições salvas (várias por peça), botões Cortar/Dividir, play/pausar/continuar a partir de qualquer ponto, 3 tipos de transição com tempo, camadas por cima do vídeo (template da Arte promo, imagem/logo, texto) e limite de 5 min (Reels aceita 3 min).

Mudança: admin/modulos/pecas/video_promo_lib.php (acts video_promo_* em action.php; upload em pedaços de 4 MB porque o nginx corta POST > 50 MB; fontes em arquivos/pecas/<id>/video_promo/fontes/, apagadas em 30 dias; prontos em arquivos/pecas/<id>/video_promo/; edições salvas em admin/runtime/video_promo/rascunhos/<peca>/). admin/cron/video_promo.php + /etc/cron.d/rnt-video-promo (minuto, root, flock): ffprobe, converte HEVC/HDR para H.264, monta tudo num único ffmpeg (concat/xfade/acrossfade + overlays das camadas; selos com enable periódico, uma entrada por selo). Tela: admin/inc-gestor-video-promo.php + admin/js/gestor-video-promo.js (aba incluída por admin/inc-gestor.php). Exportar NÃO vira mídia dos stories automáticos sozinho: só pelo botão "Usar nos stories" (copia para video/).

Complemento (mesmo dia, pedido do ADMIN): as camadas viraram FAIXAS acima da linha do tempo, no mesmo eixo de tempo do vídeo final (arrastar a barra move a janela, bordas mudam início/fim, clique no fundo marca o ponto de reprodução); geometria respeita a largura mínima dos blocos curtos e o desconto das transições.

Fluxo: opencode (deepseek-v4.1-flash-free) escreveu o grosso em pedidos pequenos com diário + Monitor; Claude revisou/corrigiu; gpt-6-astra revisou em 7 rodadas (todos os P2 corrigidos, sem P1).

Validado: lint php83 + node --check; ensaios do cron (storie 1080x1920 e feed 1080x1350, transições com quadros conferidos, camadas template/selos/imagem/texto, 180 s renderizado em 87 s); importação da aba Storie (cópia md5 idêntica, original intacto, ../ recusado); capturas da tela em 1400/500 px com o JS real. Não validado: tela no navegador logado (teste do ADMIN).

[2026-10-17] Carrossel "Programa infantil do fim de semana" (sexta 10h) e regra de prioridade

Pedido do ADMIN (17/10): fazer a sugestao 3 (infantil de sexta). Regra permanente: SEMPRE nossas pecas a venda primeiro, com informacao completa; depois eventos do eventos.php so com nome, local e datas com horarios.

Mudanca (fora do Git, rnt-tools/carrossel_semanal.php): --edicao=infantil = nossas infantis com sessao ate domingo + infantis do eventos.php (mais acessados), ate 10 slides; sem nossa, so com 2+ eventos. Eventos passam a listar TODAS as sessoes da janela (ate 4). Edicao com 1 item vira post unico (Instagram nao aceita carrossel de 1). Correcoes do gpt-6-astra: "— ESTREIA" so na estreia de verdade; ultimos ingressos exige MAIS de 80%, inclusive com sessao indicada direto, e so marca a sessao se gerou; "ultimas apresentacoes" 1 por DIA. Pagina: rotulo de validade por tipo e previa do post unico com o video vertical do Reels. BK-562 aberto (captador traz pouco infantil).

Validado: lint php83/56; simulacoes (16/10 infantil = Cinderela; 23/10 = 1 evento so, nao gera); 18/10 ultimodia em carrossel com titulo no plural.

[2026-10-17] Posts "estreia", "ultimos ingressos", "ultimas apresentacoes" (qua-sex) e ultimo dia em carrossel; alerta com nome do status

Pedidos do ADMIN (17/10): fazer as sugestoes 1 (estreia) e 2 (ultimos ingressos); o post do ultimo dia vai num CARROSSEL com todas as pecas que encerram no dia (nao um post por peca), e durante a semana posts separados de "ultimas apresentacoes" para variar e nao amontoar no fim de semana. Alerta "Pedido pago mudou de status" mostrava so "status 11".

Mudanca (/root/rnt-tools/carrossel_semanal.php, fora do Git): --edicao=ultimodia agora agrupa todas as nossas com ultima sessao hoje (1 peca = post unico; titulo "Hoje sao as ultimas apresentacoes"); ultimasemana (qua-sex 9h) = 1 post por dia da peca que encerra primeiro ate domingo (nunca no ultimo dia; historico em backups/carrossel_ultimasemana.json); estreia (todo dia 9h) = post unico no dia da PRIMEIRA sessao de todas da peca, a partir das 13h; ultimos (a cada 2h, 9-19h) = post unico quando a sessao passa de 80% da cota (sec_total) em pedidos pagos - sec_quantidade NAO baixa com a venda -, uma vez por sessao. Carrosseis marcam "— ESTREIA" na peca que estreia na janela. Publicador e admin/modulos/campanhas/carrossel_semanal.php aceitam as edicoes novas.

Alerta: includes/compra_aprovada_avisos.php - rnt_ca_status_nome ganhou o 11 ("Virou credito na carteira do cliente", gravado por painel/modulos/pedidos/action.php) e mostra nome + codigo.

Validado: lint php83/56; simulacoes: 18/10 ultimodia = carrossel Cinderela + Discutindo; 31/10 estreia = Uma Velha Cancao; 28/10 ultimasemana = Labios Que Beijei (31/10 nao gera, e o ultimo dia); 19/10 segunda marca a estreia; ultimos hoje = nenhuma sessao > 80%.

[2026-10-17] Posts sazonais: "ultima semana em cartaz" (terca) e "hoje e a ultima apresentacao" (dia da ultima sessao)

Pedido do ADMIN (17/10): post no dia da ultima sessao (so venda online) e carrossel das pecas que encerram na semana; nunca na mesma hora: 3h de intervalo, intercalando; seg = carrossel da semana (eventos ate qui), ter = ultimas semanas.

Mudanca: /root/rnt-tools/carrossel_semanal.php ganhou --edicao=terca (nossas com a ultima sessao ate domingo) e --edicao=ultimodia (um post unico por peca, publica a partir das 13h, vence 1h antes da sessao; Reels com o video vertical no IG, foto no FB). publicar_carrossel_semana.php agora le uma FILA (/www/rnt-deploy/pedidos/<edicao>.json): 3h desde o ultimo post, intercala carrossel/unico, prioriza o que vence primeiro, avisa quando um post vence sem sair. admin/modulos/campanhas/carrossel_semanal.php aceita as edicoes novas, grava na fila e mostra horario/validade. Cron: ter 10h e todo dia 9h.

Validado: lint php83/56; ensaio de terca (Discutindo a Relacao + Cinderela Negra); simulacao de 18/10 09:00 gera os 2 posts do ultimo dia; teste da fila sem publicar: pedido aprovado ficou aguardando o intervalo de 3h e foi preservado. Nao validado: publicacao real de Reels (primeira em 18/10).

[2026-10-16] Registro das entregas do dia feitas pela sessao do opencode (conferido pelo gpt-6-astra)

Commits na main em 16/10 que nao tinham entrada propria neste changelog:

  • f93325db0 fix(BK-538): oculta pecas expiradas da conciliacao e do alerta;
  • 250e0ede4 scrapers Divertix e Ticketmaster (nasceram desligados); b1ea06fd2 deploy propaga TICKETMASTER_API_KEY dos Secrets; c82a49d3a liga os dois scrapers (prova em dry-run);
  • b15647681 whitelist Algolia do Sympla aceita o tema TEATRO (o volume do Sympla subiu para ~465 eventos/rodada - motivo da cota por fonte);
  • 70e8c8502, a21120fa8, 47da3be55: painel do bot com filtros, vigencia, deep-link e resumo por fonte; links oficial/site na conciliacao;
  • dedff7b4d telas de conciliacao Divertix e Ticketmaster + limpeza exige fonte (nunca mais limpeza global);
  • 3bb03d143 Squad Social usa sempre o video da peca quando existir (sem alternancia semanal).

Revisao do gpt-6-astra (17/10): codigo confere com as mensagens dos commits.

[2026-10-16] Carrossel semanal novo (segunda e quinta) substitui o do Squad

Origem: o ADMIN aprovou o carrossel "O que ver no teatro esta semana" publicado a mao em 16/10 ("muito melhor que o nosso squad tem feito semanalmente") e pediu para programar, incluindo "outros espetaculos" do eventos.php e duas edicoes.

Mudanca:

  • admin/cron/sync_facebook_events.php: interruptor RNT_SQUAD_CARROSSEL_SEMANAL_ANTIGO (false) desliga o carrossel semanal antigo; 79 itens antigos parados em pending_approval desde abril foram cancelados (last_error = substituido_carrossel_jornalistico_20261016, backup em /root/backups-versionados/squad-carrossel-cancelado-*).
  • admin/modulos/campanhas/carrossel_semanal.php (novo): previa (videos/fotos, legendas IG e FB, descricao por foto) e Aprovar/Descartar; link assinado (HMAC, mesmo segredo do ir_gerenciador.php) ou login do admin; grava pedido em /www/rnt-deploy/carrossel_semanal.pedido.
  • Fora do Git (/root/rnt-tools): carrossel_semanal.php (gera seg/qui 10h; nossas a venda + ate 5 outros mais acessados em 7 dias pelo log do nginx - o site nao grava view_item nas paginas do evento.php -, senao sessao mais proxima; texto pelo opencode deepseek -> groq -> modelo fixo, conferido), publicar_carrossel_semana.php (le o manifest; IG carrossel video/foto, FB fotos com descricao; grava "publicando" ANTES de cada rede e, se uma tentativa nao confirmar, NAO republica sozinho - avisa para conferir; --forcar libera. Nao e garantia absoluta contra duplicata: e protecao contra repeticao automatica). Cron /etc/cron.d/rnt-carrossel-semanal.

Validado: lint php83/56; edicao de teste 20261016-quinta gerada sem aviso (texto do opencode, 1690 caracteres, 10 slides); pagina 200 com assinatura e 302 para o login sem ela; publicador em ensaio. Nao validado: o clique em Aprovar e a publicacao automatica (primeira edicao real: segunda 19/10 10h).

[2026-10-16] Monitor de integridade do Mercado Pago: o mesmo desvio so reavisa em 24h

Origem: o ADMIN recebeu 6 alertas no WhatsApp (2 numeros) entre 16:19 e 20:40 pelo mesmo desvio (config/bot_config.php editado de forma legitima e sem re-seal). Pedido: parar de "sujar a reputacao do WhatsApp a toa".

Mudanca: mp_guard_should_send_alert - mesmo hash de desvio volta a avisar depois de 86400 s (era 1800). Desvio novo continua avisando na hora, WhatsApp + push. O "PUSH - falhou" do log nao era falha: o push do gestor deduplica 24h por conteudo (push_dispatch.log: "SUPRIMIDO (dedup 86400s)"). Copias admin/cron/ e /usr/local/libexec/rnt/ mantidas iguais.

Validado: lint php83; --seal + --status OK. Licao (3a vez): editar arquivo monitorado (incl. config/bot_config.php) exige --seal logo depois, e todo push no RNT tambem (o deploy recria o .env).

[2026-10-16] [BK-460] Motor de anuncios automatico (Meta e Google) + botao no fb_dashboard

Decisao do ADMIN (16/10): peca com venda online (pecas.xml) ganha campanha SOZINHA e JA LIGADA na Meta e no Google, sem aprovacao; o gestor recebe o link e pausa se precisar. Termino acompanha a ultima sessao (prorrogou no admin -> prorroga nas campanhas). Verba R$ 10 de 24h antes ate 1h antes de cada sessao; regra por vendas so como AUMENTO (4 -> R$ 10, mais de 8 -> R$ 20); freios so avisam (atribuicao fraca, BK-560).

No repositorio: admin/modulos/campanhas/api/fb_dashboard.php ganhou o botao "Rodar motor de anuncios agora" (CSRF da tela; grava pedido em /www/rnt-deploy/motor_anuncios.pedido, o cron de root atende em ate 5 min) e a linha "ultima rodada" (/www/rnt-deploy/motor_anuncios.ultimo.json).

Fora do repositorio (/root/rnt-tools, sem Git): motor_anuncios_auto.php (Meta: cria pelo criador de sempre e liga so o que nasceu na rodada; rollback tudo-ou-nada; pendente com retentativa; termino so prorroga), google_ads_auto.php (Google v26: Search R$ 5/dia, endDateTime, nao recria removida, mapeia por link, limite 5 novas/rodada, titulo sem "OFF" - politica CAPITALIZATION), motor_verba_janela.php (cron */15), motor_conversao_pronta.php (segunda 10h, so avisa), motor_anuncios_auto_rodar.sh (9h + botao). Crons em /etc/cron.d/rnt-motor-anuncios. Desligar: /root/rnt-tools/backups/motor_anuncios.PARAR.

Corrigido no caminho: sync do Google nao rodava ha dias (cron como www sem poder criar o log; token apagado do .env a cada deploy) -> credencial so em /www/rnt-secrets/google_ads.env (fora do open_basedir do site); API do Google v22 em brownout -> v26.

Validado: lint php83/56 no fb_dashboard; ensaios das duas plataformas; validateOnly no Google (criar campanha, endDateTime, status, anuncio, palavra-chave); rodada real da verba as 20h (Lábios Meta+Google e Discutindo Meta para R$ 10, conferido por releitura); 1652 ligada pelo ADMIN (--ligar). Revisao: opencode NO-GO -> pontos corrigidos; gpt-6-astra sem cota (revisao agendada 21:50/21:56). Nao validado: criacao real (primeira rodada 9h de 17/10) e clique no botao.

[2026-10-16] Bot captador: cota por fonte no botao, cron sem limite, Sympla por ultimo

Origem: o ADMIN sentiu falta de eventos (Divertix tinha 14 na home e 0 na fila). O teto global BOT_MAX_EVENTOS_POR_EXECUCAO=50 (trava provisoria de 08/08) era consumido pelo Sympla, primeiro da fila (465 eventos apos o tema TEATRO), e Eventim/Divertix/Ticketmaster nem rodavam (log: "Limite de eventos atingido (50). Parando execucao.").

Medido: rodada de 16/10 16:28-16:52 com o site no ar: 571 requisicoes, 0 erros 5xx; ~28 s por evento.

Mudanca:

  • config/bot_config.php: teto global 0 (cron das 4h sem limite); BOT_LIMITE_POR_FONTE_BOTAO=40; BOT_TEMPO_MAX_EXECUCAO=10800 (3h).
  • bot/cron_captador.php: aceita --limite-fonte=N.
  • bot/captador.php: cota por eventos PROCESSADOS por fonte (o custo vem antes de virar sugestao); teto de tempo entre eventos; Sympla roda por ultimo.
  • admin/modulos/bot/action.php: o botao passa --limite-fonte=40 (o painel nao carrega o bot_config; valor duplicado e documentado nos dois lados).

Validado: lint php83 e 5.6 nos 4 arquivos; dry-run --limite-fonte=2/3 processou as 4 fontes na ordem eventim, divertix, ticketmaster, sympla. gpt-6-astra: 2 NO-GO corrigidos, GO na 3a rodada. Residuo (timeout de chamada externa e cursor entre rodadas) virou BK-558.

[2026-10-16] Conciliacao: "reativar" abre como evento do bot e sempre pega a foto nova

Origem: o ADMIN reativou 3 pecas na aba Reativar (#94 A Bela e a Fera, #166 Allan Kardec, #845 Paulinho Serra 40+) e recebeu 6 pedidos de post do Squad Social no WhatsApp: as pecas antigas eram origem='admin' e voltaram no layout de venda propria do peca.php (o reativar preservava origem de proposito). Ordem do ADMIN: reativar do painel do bot NUNCA vira venda propria, e sempre pega a foto nova. Os 6 pedidos foram so para o numero do gestor; nada publicado.

Mudanca (admin/modulos/eventos/action.php, conciliar_reativar):

  • peca admin/bot reabre como origem='bot' + id_produtor do bot + parceria_online=0 -> evento.php e fora do Squad Social (que so pega id_produtor IS NULL OR parceria_online=1); evento de produtor continua do produtor; sem o produtor do bot, recusa;
  • baixa a foto da sugestao pela mesma baixarFotoExternaEvento do cadastro novo, via baixarFotoExternaEventoComCopia (copia o cartaz atual antes, aborta se a copia falhar, devolve se a gravacao falhar, preserva a copia se a devolucao falhar); mensagem avisa quando a foto nao vem.

Dados (com OK do ADMIN): #94, #166 e #845 passaram a origem='bot'/produtor 50929 e receberam a foto do Sympla (estavam sem cartaz: o cleanup das 04:17 apaga imagem de peca inativa). Backup das linhas e script em /root/backups-versionados/rnt-reativar-evento-20261016-1805/. Status mantido pausado (ADMIN).

Validado: lint php83 e 5.6; testes reais na #94: link invalido (cartaz intacto, http_403), link certo (troca), falha de gravacao simulada com chattr +i (cartaz devolvido com md5 igual, sem temporarios); imagens servidas com 200. gpt-6-astra: 2 NO-GO corrigidos, GO na 3a rodada. Nao validado: o clique do reativar na tela (proximo uso prova). Mesmo problema no "vincular" virou BK-559.

[2026-10-15] [BK-557] Atribuição de pedido quase nunca fechava — 2 bugs reais corrigidos

Origem: investigando por que a tabela final de atribuição por pedido (rnt_pedido_atribuicao) tinha só 27-28 linhas no total e zero com fbclid/gclid, apesar da captura de visita/clique funcionar bem (achado do BK-556). Investigado pelo Codex (gpt-6-astra); os 2 fixes revisados e validados com execução real por mim antes de comitar, porque a rodada do Codex foi interrompida (timeout) antes do próprio relato final.

Causa raiz 1: rnt_atrib_capturar_interno() (includes/atribuicao_helper.php) checava $_SESSION['cliente']['id']/$_SESSION['CLIENTE_X']['id'] pra saber se tinha cliente logado — nenhuma das duas é a chave real. O login de cliente de verdade (painel/data/Login.php, área do CLIENTE) grava em $_SESSION['CMS_X_Cliente']['id']. Resultado: cliente_id ficava sempre null, mesmo com o cliente logado no momento do clique. Confirmado com a campanha do Meta Ads do BK-556: 459 contatos capturados, 0 com cliente_id.

Causa raiz 2: action.php, ao reaproveitar um pedido "rascunho" (criado por pedido.php, até 60min antes), sobrescrevia metadados com um $meta montado do zero — descartando a chave rnt_atrib que o rascunho original já carregava (gravada mais perto do clique, mais confiável), quebrando o elo que a reconciliação horária precisa.

Mudança:

  • includes/atribuicao_helper.php: nova condição checando $_SESSION['CMS_X_Cliente']['tipo'] === 'cliente' antes de usar o id (produtor compartilha a mesma estrutura de sessão, só com tipo='produtor' — não pode virar cliente_id). Fallbacks antigos mantidos depois, por segurança.
  • action.php: lê metadados do rascunho (rnt_order_meta_array()) e copia só o campo rnt_atrib pro $meta novo antes de serializar — sem reutilizar nenhum outro campo antigo do carrinho (evita reabrir o híbrido que o BK-464 de 20/09 já tinha corrigido).

Validado com execução real (dados de teste criados e removidos depois, nenhum cliente real afetado): sessão CMS_X_Cliente/tipo=cliente/id=775 (conta real de teste) grava cliente_id=775 corretamente; sessão tipo=produtor continua sem gerar cliente_id (como esperado); rnt_order_meta_array() extrai rnt_atrib certo quando existe e não quebra quando não existe (rascunho antigo). php83 -l limpo nos 2 arquivos. Monitor de integridade do Mercado Pago pausado antes da edição (action.php é arquivo monitorado) e re-selado depois da revisão.

O que NÃO foi resolvido, de propósito: casamento de visitante anônimo que nunca loga antes de comprar (exigiria reconciliação por cookie puro, fora do escopo autorizado); nenhum backfill do dado histórico (a campanha do BK-556 continua sem atribuição retroativa). Falta acompanhar nos próximos dias se o cron de reconciliação horária (vinculos=0 hoje, sempre) passa a encontrar vínculos — registrado como item de acompanhamento no BK-557.

[2026-10-15] [BK-549b] Mesmo bug do BK-549 (vid_ordem) em outros 4 cadastros de vídeo

Origem: achado durante o BK-549 (cadastro de vídeo em peça falhava sempre por faltar vid_ordem no INSERT), documentado como fora do escopo daquela tarefa. Decisão do ADMIN: corrigir agora, enquanto o assunto estava fresco, em vez de só registrar backlog.

Causa raiz: o mesmo padrão de bug do BK-549 (tb_videos.vid_ordem é NOT NULL sem DEFAULT; com sql_mode=STRICT_TRANS_TABLES o INSERT falha sempre, ERROR 1364) existia, intocado, em outros 4 arquivos com o mesmo bloco case "cadastr_video":

  • admin/modulos/eventos/action.php
  • painel/modulos/eventos/action.php
  • produtor/modulos/pecas/action.php
  • produtor/modulos/eventos/action.php

Mudança: mesma correção do BK-549 repetida nos 4 — adicionado $camp[] = "vid_ordem"; $val[] = 0; ao array do INSERT, em cada um.

Validado: php83 -l limpo nos 4 arquivos. Testado diretamente no banco o INSERT exato que o código corrigido gera (INSERT INTO tb_videos (vid_titulo,vid_url,vid_modulo,vid_vinculo,vid_ordem) VALUES (...,0)), confirmado sucesso (linha inserida e removida depois). Revisado por Codex (gpt-6-astra), que confirmou a correção correta nos 4 lugares e sem colisão com vid_capa.

Validação pendente (registrada, não contornada): NÃO foi feito teste de ponta a ponta via UI/sessão real (login → botão → AJAX → resposta) pra nenhum dos 4, diferente do BK-549 original (testado com a sessão real do ADMIN numa peça real). Os 3 módulos de evento exigem sessão de produtor ou vínculo id_produtor/produtor_email que não foi possível simular sem alterar dados reais de propriedade — mais invasivo que a própria correção. Como a mudança só ADICIONA uma coluna antes ausente (sem ela o insert já falhava sempre, em qualquer caminho), o risco de regressão é baixo, mas o fluxo completo fica como pendência pro primeiro cadastro de vídeo real em cada um desses 3 módulos.

[2026-10-15] [BK-553] Vídeo da galeria de peça.php dava "Erro 153 - erro de configuração do player"

Origem: ADMIN - ao clicar num vídeo da galeria (depois do BK-552 já ter corrigido o clique chegar no player), o YouTube mostrava "Erro 153". Reportou também que o vídeo em formato Shorts testado "continua sem subir".

Causa raiz (erro 153): o site manda referrer-policy: same-origin pra todo o domínio. O #rnt-lightbox-iframe (player do lightbox) não tinha nenhum referrerpolicy próprio, então ao carregar o embed do YouTube (cross-origin) o navegador não mandava Referer nenhum — e o YouTube exige Referer pra liberar a reprodução embutida (documentado pelo próprio Google: https://developers.google.com/youtube/terms/required-minimum-functionality). Outro iframe na mesma página (mapa do Google Maps) já contornava o mesmo problema com referrerpolicy="no-referrer-when-downgrade".

Mudança: adicionado referrerpolicy="strict-origin-when-cross-origin" no #rnt-lightbox-iframe — o mesmo valor que o widget oficial de oEmbed do YouTube usa (conferido em https://www.youtube.com/oembed?url=...).

Sobre o Shorts "que não sobe": não é bug de código. Conferido via oEmbed público do YouTube (https://www.youtube.com/oembed?url=https://www.youtube.com/shorts/8hvFZj8WTfs) que esse vídeo específico retorna 403 Forbidden, e carregando o embed direto no navegador o próprio YouTube mostra "This video is private" — o vídeo de teste que o ADMIN usou está marcado como privado no YouTube, precisa ser tornado público/não listado pra testar de novo.

Validado: Playwright real contra produção, abrindo o player de dentro do lightbox (não só setando src) — antes do fix, o vídeo público sZGtDCaapD0 mostrava "Error 153"; depois, carrega normal (nome do canal, thumbnail, "Watch on"). Revisado por Codex (gpt-6-astra): fix correto e documentado oficialmente pelo Google.

[2026-10-15] [BK-551] Galeria de peça.php: miniaturas do lado da imagem (desktop) e setas de navegação no celular

Origem: ADMIN, em https://rionoteatro.com.br/uma-velha-cancao-quase-esquecida - "qdo tem mais de uma foto as miniaturas estao ficando ao lado da imagem grande, qdo deveriam ficar embaixo dela no desktop. No mobile nem tem que aparecer miniatura, só ter o botao de seta para os lados para rodar entre fotos e videos, qdo tiver mais de 1."

Causa raiz (miniaturas do lado): .rnt-gallery é display:flex sem flex-direction, então o flex-row padrão colocava a faixa de miniaturas (#rnt-galeria-thumbs) como item irmão ao LADO da imagem principal, em vez de abaixo.

Mudança:

  • .rnt-gallery: adicionado flex-direction: column (miniaturas empilham abaixo da imagem no desktop); removido overflow:hidden/max-height:690px do container (o teto de altura fica só em .rnt-main-image, que já tinha o seu próprio).
  • Celular (@media max-width:767px): .rnt-thumbnails { display:none } — some a faixa de miniaturas, como pedido.
  • Navegação inline: 2 botões (<button>, não <span> — ver nota de acessibilidade abaixo) sobre a imagem principal, só visíveis no celular e só quando há mais de 1 item ($tem_galeria), navegando circularmente entre todos os itens (fotos e vídeos) da mesma galeria que já existia pro lightbox em tela cheia. Indicador de "▶" sobre a imagem quando o item atual é vídeo. Clicar na imagem abre o lightbox já no item que estava sendo mostrado ali (antes sempre abria no item 0).
  • Removido de js/peca-redesign.js um handler duplicado em .rnt-thumb.video a que abria um modal de vídeo próprio sem CSS nenhuma (invisível) — o lightbox de peca.php já trata o mesmo clique corretamente.

3 bugs reais encontrados e corrigidos durante a implementação (todos via teste real com Playwright/headless chromium contra produção, não só leitura de código):

  1. A função que atualiza a imagem ao clicar nas setas usava por engano a variável da <img> ESCONDIDA do lightbox em tela cheia (#rnt-lightbox-img) em vez da <img> VISÍVEL do cartaz (#rnt-lightbox-trigger) — a seta "não fazia nada" porque atualizava o elemento errado.
  2. Corrigido isso, as setas (ancoradas em top:50% de uma caixa sem teto de altura no celular — BK-495) ficavam atrás do badge de desconto (.rnt-discount-badge, sempre fixo no topo, z-index:10) quando a imagem trocava pra uma proporção mais baixa (ex: thumbnail de vídeo, 4:3) — a caixa encolhia o suficiente pra a seta subir pra dentro da zona do badge, que roubava o clique. Fix: min-height:220px em .rnt-main-image, só em páginas com galeria (classe rnt-capa-wrap-com-galeria vinda do PHP — não usamos :has() porque esse seletor só tem suporte a partir de Safari 15.4/Chrome 105, e o público do site inclui celular mais antigo).
  3. Acessibilidade (achado do Codex): as setas eram <span role="button">, sem foco por Tab nem ativação por Enter/Espaço — trocado por <button type="button"> de verdade, com reset de estilo (border/padding/-webkit-appearance) pra manter a aparência circular escura. Também corrigido o fallback do alt da imagem, que ficava preso na legenda do item anterior quando a legenda do item novo vinha vazia.

Limitação conhecida, documentada e não resolvida agora: em viewport muito curto (320×568, ex. iPhone 5/SE de 1ª geração — raro em 2026), a seta pode ficar atrás do botão flutuante "COMPRAR" (position:fixed; bottom:0; z-index:1000) logo no carregamento da página. Recuperável com um scroll pequeno (confirmado com Playwright: 40px de scroll já libera o clique). Resolver por completo exigiria ou limitar a altura da galeria nesse tamanho específico (contrariando o BK-495, que tira o teto de altura no celular justamente pra não cortar arte alta) ou redesenhar a área de clique do botão flutuante — maior que o pedido original.

Validado: Playwright real contra produção (celular 390×844, 375×667, 360×640 e desktop 1280×1000) — ciclo completo capa→foto→vídeo1→vídeo2→capa (circular) com troca de imagem, badge de vídeo e seta funcionando; selo de promoção (.rnt-capa-selos) confirmado visível por padrão numa peça com selo real configurado (sem galeria de vários itens — toggle de esconder validado por leitura de código, mesma lógica já comprovada com o badge de vídeo); desktop com miniaturas empilhadas abaixo da imagem e clique em thumbnail de vídeo ainda abrindo o lightbox certo; nenhum modal duplicado órfão. Revisado em 2 rodadas por Codex (gpt-6-astra), que sugeriu as 3 correções de acessibilidade/robustez acima, aplicadas e revalidadas.

[2026-10-15] [BK-552] Thumbnail de vídeo na galeria de peça.php não abria o player; fundo escuro das miniaturas

Origem: ADMIN - em https://rionoteatro.com.br/uma-velha-cancao-quase-esquecida, clicar numa thumbnail de vídeo não abria o player (fotos abriam certo). Pediu também para remover o fundo escuro var(--bg-input) atrás da faixa de miniaturas.

Causa raiz (vídeo): em css/redesign_2026.css, .rnt-thumb.video::after desenha o badge de play (círculo com ▶) com position:absolute cobrindo o centro da miniatura, sem pointer-events:none. elementFromPoint no centro da thumb de vídeo acertava a própria <div class="rnt-thumb video"> (dono do pseudo-elemento), não o <a class="rnt-galeria-abrir"> por dentro dela — e clique não se propaga para baixo na árvore DOM, então o addEventListener do lightbox (inline em peca.php, ~linha 3196) nunca disparava. Confirmado ao vivo com Playwright (headless real) antes e depois do fix.

Mudança:

  • css/redesign_2026.css: pointer-events: none; em .rnt-thumb.video::after (badge deixa de bloquear o clique) e .rnt-thumbnails { background: var(--bg-input) → transparent; }.
  • includes/inc_peca.php: redesign_2026.css?ver=2.2 → ?ver=2.3 (cache-busting, pra visitante não continuar servindo o CSS antigo).

Validado: Playwright real contra produção — antes do fix, clique na thumb de vídeo não abria o lightbox (display:none); depois, elementFromPoint acerta o <img> e o lightbox abre com o iframe do YouTube certo (display:flex, iframe com src do embed). Fundo da faixa de miniaturas confirmado rgba(0,0,0,0). Revisado por Codex (gpt-6-astra): fix correto e suficiente; evento.php não tem essa galeria de vídeo, não precisa do mesmo fix; achado fora do escopo (não corrigido): css/redesign.css (legado, não carregado em nenhum PHP encontrado) tem a mesma duplicação de fundo/badge sem pointer-events:none — manutenção preventiva, sem uso ativo hoje.

[2026-10-15] [BK-549] Cadastro de vídeo (YouTube) em peças falhava sempre, não só nessa URL

Origem: ADMIN - não conseguiu adicionar https://www.youtube.com/watch?v=sZGtDCaapD0 na aba Vídeos de uma peça, viu erro.

Causa raiz: tb_videos.vid_ordem é NOT NULL sem DEFAULT, e o INSERT em admin/modulos/pecas/action.php (case cadastr_video) nunca incluía essa coluna. Com sql_mode=STRICT_TRANS_TABLES ativo, isso falhava com ERROR 1364 pra QUALQUER vídeo, não só essa URL — o bug não tinha nada a ver com o parsing do link do YouTube (que já funcionava certo). Não aparecia no log porque Sql::insert() nunca loga mysql_error() em caso de falha, só devolve "Falha ao tentar alterar!" genérico.

Mudança: adicionado vid_ordem = 0 ao insert (mesmo valor que os 62 registros existentes já têm, implícito antes do modo estrito pegar isso).

Validação: php83 -l ok. Fluxo AJAX ponta a ponta reproduzido e corrigido com a URL real do ADMIN (peça 1652): erro confirmado antes do fix, sucesso confirmado depois, linha real gravada em tb_videos, thumbnail do YouTube extraída certo. Revisão do gpt-6-astra (plano + diff): aprovado.

Achado fora do escopo (registrar backlog): o mesmo padrão de INSERT incompleto existe em admin/modulos/eventos/action.php, painel/modulos/eventos/action.php, produtor/modulos/pecas/action.php e produtor/modulos/eventos/action.php — continuam quebrados do mesmo jeito. Conserto na raiz sugerido pelo Codex: ALTER TABLE tb_videos ALTER COLUMN vid_ordem SET DEFAULT 0 (não destrutivo, resolve os 5 pontos de uma vez em vez de patch disperso) — decisão de fazer isso fica pro ADMIN.

[2026-10-15] [BK-550] Botão "Gerar vídeo para Storie" ganha dica quando falta só o selo

Origem: ADMIN - "https://rionoteatro.com.br/admin/modulos/pecas/index.php?status=ativas&origem=admin#artepromo nao ta podendo clicar no botao Gerar video para Storie". Investigado: não é bug. O botão (admin/inc-gestor.php, componente "gestor" compartilhado por várias telas admin) fica desabilitado até 4 condições — imagem carregada, moldura montada no servidor, pelo menos 1 selo ativo, nada pendente. O ADMIN confirmou que não sabia que precisava adicionar um selo primeiro; o único sinal disso era o title (tooltip, só aparece passando o mouse).

Mudança: admin/js/gestor-arte-promo.js (atualizarBotoes()) — nova dica de texto visível (#artePromoVideoDica, HTML em admin/inc-gestor.php) que aparece só quando a condição que falta é exatamente a ausência de selo (as outras 3 já ok), pra não ficar redundante quando falta outra coisa.

Validação: node --check e php83 -l ok. HTTP real confirma o elemento presente na tela. Revisão do gpt-6-astra: confirmou que a condição faltaSoSelo reproduz exatamente os outros fatores de videoOk (sem erro de digitação na cópia), aprovado.

[2026-10-15] [BK-545 ajuste 2] "Vendas por origem registrada": separa Share Site do tráfego real da mesma fonte

Origem: ADMIN - pediu o mesmo ajuste de rótulo ("Whatsapp" → "Share Site") no bloco "Vendas por origem registrada" (gráfico + tabela), e perguntou se dá pra saber de onde a pessoa que compartilhou veio, pra mostrar os dois juntos (ex: "ShareSite/GoogleAds").

Resposta sobre a origem de quem compartilhou: não temos. O link do botão "Compartilhar Oferta" (includes/rnt_share.php) é genérico — todo mundo que clica gera o mesmo link, sem id de sessão/cliente embutido. Sabemos que a venda veio por indicação (alguém usou o botão) e por qual canal foi compartilhado (ex. WhatsApp), mas não quem compartilhou nem de onde essa pessoa veio.

Mudança: dashboard-roi.php — a classificação por origem ($por_fonte, usada no gráfico de pizza e na tabela "Vendas por origem registrada") agora dá uma chave própria sharesite:<canal> quando utm_medium for share/indicacao, antes de cair no balde genérico por fonte. Isso evita misturar venda por indicação com tráfego real de campanha que use a mesma fonte (ex. uma futura campanha de WhatsApp de verdade não ficaria somada com as indicações via WhatsApp). Rótulo mostrado: "Share Site/{canal}" (ex. "Share Site/Whatsapp") — mostra o canal de compartilhamento real, sem sugerir que sabemos a origem de quem compartilhou.

Validação: php83 -l ok. HTTP real no período 01/10-15/10: "Share Site/Whatsapp" aparece separado de "Origem desconhecida", R$180,00/2 pedidos preservado (mesmo total já confirmado no ajuste anterior). Revisão do gpt-6-astra: reconciliação confirmada (cada pedido entra em exatamente 1 grupo), rótulo considerado honesto, aprovado.

[2026-10-15] [BK-545 ajuste] Ranking "Campanhas UTM": rótulo "Share Site" em vez de "Whatsapp/share"

Origem: ADMIN - viu "Whatsapp/share" no ranking "Campanhas UTM com maior venda atribuída" (BK-545) e achou estranho (confundiu com campanha de WhatsApp disparada). Investigado junto: são 2 pedidos reais (73978 de 10/10, 73984 de 11/10, R$90 cada) atribuídos à UTM compartilhar-peca — não tem nada a ver com cliques no botão "Compartilhar" de dentro de uma campanha de WhatsApp (mecanismo totalmente separado); é o botão "Compartilhar Oferta" do site (includes/rnt_share.php), venda por indicação entre clientes.

Mudança: dashboard-roi.php — quando utm_medium for share ou indicacao (mesmo recurso, nome antigo e atual, ver includes/rnt_wa_campanha_publico.php:235), mostra "Share Site" em vez de "{fonte}/{medio}".

Validação: php83 -l ok. HTTP real: "Share Site" aparece corretamente no período 01/10-15/10. Revisão do gpt-6-astra aprovada.

[2026-10-15] [BK-546] Home do admin: qualidade do WhatsApp API oficial numa linha

Origem: ADMIN - "em admin/index.php abaixo do WhatsApp Online... incluir a medicao do nosso qualidade na api oficial do whatsapp, se está verde, amarela ou vermelha, qtas mensagens por dia podemos enviar... em somente 1 linha... bem simples".

Mudança:

  • divulgai.com.br/campanha.php: o cron "qualidade" (roda a cada 2 min, ja existia) passa a escrever tambem uma copia simples em /tmp/divulgai_qualidade_rnt.json (qualidade/status/pode_enviar/limite_conta/timestamp, sem segredo nenhum) — /tmp ja esta no open_basedir do rionoteatro.com.br, diferente de /www/server/divulgai/, que NAO esta (decisao: nao abrir open_basedir pra isso, e mudanca de seguranca compartilhada). Escrita atomica via tempnam()+rename() (evita seguir symlink se algo plantar um link no caminho previsivel em /tmp).
  • rionoteatro.com.br/admin/index.php: nova linha "WhatsApp API oficial: qualidade {verde/amarela/vermelha}" com cor e "Envio: {LIMITED/...} · limite da conta: {TIER_2K/...}", lendo so esse cache. Não mostra o bloco se o dado tiver mais de 600s (10min) de idade — nao apresenta cache velho como atual. Validacao de tipo antes de indexar o mapa de cores (JSON corrompido nao quebra a pagina).

Validação: php83 -l ok nos 2 arquivos (projetos/repositorios diferentes: rionoteatro2025 e divulgai). HTTP autenticado real: linha aparece certa ("qualidade amarela", "Envio: LIMITED", "limite da conta: TIER_2K" — dado real da conta hoje). Revisão do gpt-6-astra: 1ª rodada achou riscos reais (escrita nao-atomica/symlink, falta de checagem de atualidade, falta de validacao de tipo, texto fixo de limite em vez de dado real) — corrigidos pelo próprio Codex (sandbox danger-full-access, projeto já trusted, sem a flag --sandbox workspace-write que causava a falha de permissao bwrap em rodadas anteriores desta sessão), revisados por mim e aprovados na 2ª rodada.

[2026-10-15] [BK-545] Dashboard de ROI: remove bloco legado incompatível, ticket por ingresso

Origem: ADMIN - dashboard-roi.php mostrava "Receita Bruta: 0 vendas realizadas" na mesma tela que o bloco de cima (BK-461) mostrava 69 ingressos/R$2.247,00 pro mesmo período (15/09 a 15/10). Pediu correção completa ("tudo de uma vez"), incluindo "Ticket por pedido" (que confundia pedido com múltiplos ingressos).

Causa raiz: o bloco "Receita Bruta"/"ROI Médio"/"Lucro Líquido"/rankings/gráficos vinha de v_campanhas_performance/campanhas_roi — sem filtro de período nenhum (soma a view inteira) e com join legado (campanha→tracking→cliente→pedido) que nunca bate com nenhuma venda real hoje (68 campanhas na view, soma de vendas = 0 em todas, sempre, independente do período). Já estava documentado como pendência desde BK-534 ("ROI e CPA da view legada"), mas continuava visível mostrando números incompatíveis com o bloco real ao lado.

Mudança (admin/modulos/campanhas/dashboard-roi.php):

  • Removidos: 4 KPIs legados, gráfico "Evolução do ROI" (campanhas_roi), gráficos/rankings "por fonte"/"melhores-piores campanhas" (v_campanhas_performance), tabela de fontes duplicada no fim da página.
  • Substituídos por consultas reais em pedidos (mesma base do extrato BK-461: status_transacao IN (3,4), pedidos.created no período), agregadas em PHP a partir de UMA única query: "Vendas diárias" (gráfico real), "Vendas por origem registrada" (via rnt_pedido_atribuicao, mostrando "Origem desconhecida" quando não há registro — não inventa número), "Campanhas UTM com maior venda atribuída" (top 5, sem ranking de piores — ROI por campanha continua pendente, ver BK-534).
  • Pedido com mais de uma linha de atribuição conflitante entra em "atribuição ambígua" em vez de escolher uma arbitrariamente (sem UNIQUE(pedido_id) no banco, só proteção de aplicação).
  • Adicionado "Ticket por ingresso" (vendas_brutas/ingressos) ao lado do já existente "Ticket por pedido" (vendas_brutas/pedidos) — eram conceitos diferentes sendo confundidos em um só número.

Validação: php83 -l ok. HTTP autenticado real no período exato reportado (15/09 a 15/10/2026): reconciliação confirmada — total da nova query bate exatamente com o extrato (31 pedidos, R$2.247,00 nos dois). Por origem: desconhecida 29 pedidos/R$2.067, whatsapp 2 pedidos/R$180 (soma = R$2.247, confere). Ticket por ingresso R$32,57 vs ticket por pedido R$72,48, ambos conferidos por cálculo manual. Testado período sem nenhuma venda: 200 OK, sem erro, sem divisão por zero. Revisão do gpt-6-astra em paralelo com a investigação (pergunta aberta antes de eu decidir o plano) e 4 rodadas de revisão do diff real — achou e eu corrigi: chave de agrupamento por campanha usando só o nome (sem distinguir fonte/meio, corrigido pra chave via serialize()) e chave reservada 'ambigua'/'desconhecida' colidindo com possível utm_source real literal (corrigido com prefixo src: nas fontes reais).

[2026-10-15] [BK-537 ajuste] "Dashboard de ROI" removido do submenu Campanhas

Origem: ADMIN - "Dashboard de ROI pode retirar do submenu de campanhas. deixa só em Analise." (a mesma tela tinha 2 entradas de menu desde o BK-537, uma em cada grupo, de propósito na época).

Mudança: só banco (tb_menus), sem código. Apagada a linha men_id=75 (men_pai=53, grupo Campanhas). Achado no caminho: setar men_status='I' não funciona pra remover item de SUBMENU — admin/inc-menu.php só filtra men_status='A' nos itens de nível raiz (men_pai=0); a query de filhos (linha ~119) não filtra status, então "inativo" continua aparecendo. Registrado aqui como nota pra próxima vez: desativação de submenu precisa apagar a linha, não só marcar inativo.

Validação: HTTP autenticado real — "Dashboard de ROI" aparece 1 vez só agora (grupo Análises, men_id=80).

[2026-10-15] [BK-544] Lembrete de pedido pendente volta ao texto humano (pré-BK-533)

Origem: ADMIN - "a mensagem que enviamos após uma pessoa nao concluir a compra, deve voltar a ser essa que estavamos recebendo retorno das pessoas, deve estar mais humano", colando o texto exato desejado (nome completo, saudação por horário, peça em caixa alta, desconto em percentual, assinatura "Jorge | Equipe RIONOTEATRO").

Mudança: admin/cron/lembrete_pedido_pendente.php, montar_mensagem_lembrete() — volta do texto "oferta"/negrito/riscado do BK-533 (09/10/2026, que parou de gerar retorno de cliente) pro formato humano anterior: {Saudação por horário}, {Nome completo}! Tudo bem? / Vimos que você não concluiu a compra para o espetáculo "{PEÇA EM CAIXA ALTA}" com incríveis {X}% de desconto no {link da peça}. / Alguma dúvida ou dificuldade? Estou aqui a disposição! 😊 / Jorge | Equipe RIONOTEATRO. Saudação (Bom dia/Boa tarde/Boa noite) usa o mesmo padrão de fuso America/Sao_Paulo já usado em convite_parceria_produtor.php (VPS está com date.timezone=PRC). O texto colado pelo ADMIN não tinha link; perguntado, ele decidiu que o próprio link da peça substitui o texto "RIONOTEATRO.com.br".

Validação: php83 -l ok. --preview com pedido real reproduziu o padrão exato do texto colado pelo ADMIN (mesmo cliente/peça do exemplo — Tereza Cristina Elias Pereira / Lisa, Lesa & Louca —, só o percentual difere por ser pedido com preço diferente: 63% vs 66%, fórmula confirmada correta). --test-send entregue ao gestor (21999915554), aprovado ("ok, manda ver"). Revisão do gpt-6-astra no diff real: aprovado.

[2026-10-15] [BK-543] Dashboard de Facebook Ads: orçamento real (ad set, não campanha)

Origem: ADMIN - "So a coluna orçamento que consta R$0 para todos. Tinha q ser R$5 o u o valor total q eu temos aind a p gastar em cada campanha?".

Causa raiz: nesta conta (sem Advantage Campaign Budget), o orçamento mora no ad set, não na campanha — daily_budget/lifetime_budget da campanha sempre vêm NULL. Confirmado via API: as 4 campanhas MOTOR têm R$5,30/dia cada, só que no ad set. O botão "editar orçamento" já existente também sempre apontou pra campanha (updateCampaignBudget) — nunca teria funcionado pra essas campanhas reais (embora nem aparecesse, já que exigia daily_budget setado na campanha).

Mudança:

  • facebook_ads.php: getAccountAdSets() (1 chamada pra conta inteira) e updateAdSetBudget().
  • fb_dashboard.php: rnt_fb_dashboard_orcamento() decide a origem explícita (campanha-diário/campanha-total/conjunto-diário-único/conjunto-total-único/múltiplos conjuntos/nenhum) sem somar diário com vitalício; só oferece edição quando o alvo é inequívoco. Não mostra "resta hoje" (budget_remaining) — semântica de reinício diário não confirmada com certeza suficiente. Form de editar orçamento manda target_type+target_id explícitos (não assume mais campanha por padrão) e some na aba "finalizadas" (mesma lógica do botão "Ativar").
  • 3 camadas de defesa no handler de escrita (mais rígido que a tela de leitura, por ser mutação real): target_type restrito a campaign/adset via in_array estrito; servidor reconsulta a API e recalcula o orçamento real antes de qualquer POST pra Meta, recusando se o alvo enviado não bater (campo oculto pode ser adulterado); se a consulta de ad sets vier com paging.next (não viu todos os conjuntos da conta), recusa a edição por segurança em vez de arriscar o alvo errado. pause/activate/update_budget agora exigem $result['success'] === true estrito, não só isset().

Validação: php83 -l limpo. HTTP autenticado real: as 4 MOTOR mostram "R$ 5,30 (diário · conjunto)" (antes R$0,00); POST com CSRF válido mas budget_target_id adulterado foi recusado com "O alvo enviado não corresponde mais ao orçamento real desta campanha" sem mutar nada na Meta (caminho de sucesso real não testado, pra não alterar orçamento de campanha ativa). Revisão do gpt-6-astra em paralelo com a investigação (não só depois do plano fechado) — 4 rodadas de revisão do diff real, cada uma achando um problema genuíno (mistura diário+vitalício ambíguo tratado como único; handler assumindo campanha por padrão quando alvo ausente; target_type aceitando qualquer valor não-"adset"; escrita sem bloqueio de paginação incompleta), todos corrigidos antes da aprovação final.

[2026-10-15] [BK-542] Dashboard de Facebook Ads: métricas reais e 3 abas por status

Origem: ADMIN - "Conseguimos incluir mais informacoes? mais colunas de informacoes como valor do click, do ROI, e etc como tem no Gerenciador de anuncios do Facebook/Meta? e divide a pagina com aba de anuncios ativos e anuncios pausados e finalizados".

Mudança:

  • facebook_ads.php: novo método getAccountInsights() — 1 chamada só pra conta inteira (level=campaign), em vez de 1 chamada por campanha (evita N+1/rate limit). Campos: gasto, impressões, alcance, frequência, cliques/CPC/CTR gerais e de link. getCampaigns() passou a pedir também effective_status/start_time/stop_time.
  • fb_dashboard.php: 3 abas (Ativas/Pausadas/Finalizadas) classificadas por rnt_fb_dashboard_classificar() — ARCHIVED/DELETED ou stop_time no passado vira "finalizada" mesmo que o status ainda diga PAUSED (agenda encerrada ≠ pausada-pode-reativar); por isso campanha finalizada não oferece mais botão "Ativar", só o texto "agenda encerrada". Seletor de período (hoje/ontem/7d/30d, padrão 30d) via ?periodo=. Tabela com gasto, impressões, alcance, cliques de link, CPC de link, CTR de link, por campanha. Erro real na consulta de métricas aparece como alerta visível, não como "—" silencioso (evita confundir falha de consulta com ausência real de entrega).
  • Sem ROI/ROAS fabricado de propósito: a conversão de compra desta conta está contaminada por ação que não é compra (achado desta mesma sessão) — nota fixa "ROAS/retorno indisponível" em vez de inventar uma métrica que enganaria.

Validação: php83 -l limpo nos 2 arquivos. HTTP autenticado real: 5 campanhas em Ativas, 0 em Pausadas, 3 em Finalizadas (as 3 com stop_time de 2024/2025/abr-2026, todas no passado — confirma a lógica). Dados reais por campanha (ex.: MOTOR p1085 Cinderela Negra: R$25,78 gasto/30d, 3.602 impressões, 188 cliques de link, R$0,14 CPC, 5,22% CTR). ?periodo=today muda os números corretamente (R$2,76 hoje vs R$25,78/30d); período inválido na URL cai pro default sem erro. Revisão do gpt-6-astra em paralelo com a investigação (não só depois de decidido o plano) e revisão final do diff — aprovado, com 2 ressalvas corrigidas na hora (alerta de erro visível, botão "Ativar" suprimido em finalizada) e 1 aceita como limite documentado da 1ª versão (paginação com limit=500, hoje bem abaixo do real de 8 campanhas).

[2026-10-15] [BK-541] Dashboard de Facebook Ads: token real, CSRF e entrada no menu

Origem: ADMIN notou admin/modulos/campanhas/api/fb_dashboard.php (corrigido de layout no BK-539) mostrando "Token inválido ou expirado" e "Nenhuma campanha encontrada", e perguntou por que a tela não estava no menu. "vamos resolver as coisas e nao só deixar em backlog!!"

Causa raiz: admin/modulos/campanhas/api/facebook_ads.php (classe FacebookAdsAPI) lia app_id/app_secret/access_token/ad_account_id do .env. FACEBOOK_ADS_ACCESS_TOKEN e FACEBOOK_ADS_AD_ACCOUNT_ID nunca existiram lá (mesmo achado do BK-540), e o FACEBOOK_APP_ID do .env é de um app DIFERENTE do que emitiu o token real — por isso a validação (debug_token) falhava mesmo quando o token funcionava para ler campanhas.

Mudança:

  • facebook_ads.php: app_id/app_secret/access_token passam a vir, juntos, da mesma linha de config_facebook (tabela), com fallback por FONTE inteira pro .env (nunca mistura token de um app com credencial de outro). ad_account_id ganhou default real act_269864494837203 (conta pré-paga das campanhas MOTOR atuais), com override por .env se configurado. Self-contido: garante conexão mysqli mesmo quando o caller (test_api.php, sales_webhook.php) não passa por bootstrap_v2.php antes.
  • fb_dashboard.php: os 2 forms POST (pausar/ativar e atualizar orçamento) mudavam campanha real sem nenhum token de sessão — mesma classe de falha do BK-539. Adicionado rnt_request_csrf_token/rnt_request_csrf_validate (padrão já usado em action.php/alertas.php).
  • tb_menus: nova entrada "Facebook Ads - Campanhas" (men_id=82) sob o grupo Campanhas (men_pai=53) → campanhas/api/fb_dashboard.php. threads_callback.php não entrou no menu de propósito: é só callback OAuth do Threads, nunca deve ser acessado por clique direto.

Validação: php83 -l limpo nos 2 arquivos. HTTP autenticado real: "Token válido" (antes "inválido" mesmo funcionando), 8 campanhas carregando com dados reais (conta 269864494837203, R$ 308,67 gasto), POST com csrf errado rejeitado com "Sessão expirada ou token inválido" sem executar a ação. Revisão do gpt-6-astra (2 rodadas: achou mistura de credencial de fontes diferentes no fallback por campo, corrigido para fallback por fonte inteira; aprovado na 2ª rodada).

Limite conhecido (pré-existente, não ligado a este fix): includes/db.php ainda usa die() se a conexão cair (sem fallback suave); o mecanismo antigo refreshToken()/token_cache.json só persiste em arquivo e nunca ganha prioridade sobre o token do banco na próxima carga de página — já era 100% inalcançável antes deste fix (dependia de um access_token do .env que nunca existiu).

[2026-10-09] [BK-534 nginx] Chegadas por campanha contadas no registro de acessos do servidor

Origem: ADMIN - "precisamos tambem ter acesso aos acessos tanto do nginx quanto do analytics" / "pode seguir com o quadro de acessos como preferir".

Mudanca:

  • /root/rnt-tools/acessos_nginx_campanhas.php (cron /etc/cron.d/rnt-acessos-nginx, minutos 20 e 50): le o access log do nginx do dia e do anterior, considera so GET de pagina com resposta 200 e utm_campaign no endereco, separa gente de robo pelo navegador declarado e grava em rnt_campanha_acessos_nginx (tabela nova, InnoDB) por dia, campanha e variacao: chegadas de pessoas, pessoas distintas (IP + navegador, aproximacao) e chegadas de robos. Nenhum IP e gravado;
  • admin/modulos/campanhas/detalhes.php: quadro "Chegadas ao site por esta campanha (registro do servidor, 14 dias)", para qualquer campanha que tenha dado, com a contagem do registro proprio do site ao lado.

Validacao: php83 -l ok; contador rodado (698 linhas com campanha, 32 gravadas); quadro renderizado fora do painel para as campanhas 95 (motor), 93 (WhatsApp) e 1 (sem dado, nao aparece). Na 95, 13 pessoas + 49 robos = 62, igual as 62 visitas do registro do site - que inclui os robos. Tela logada NAO aberta; sem revisao do gpt-6-astra.

Limite conhecido: a analise de anuncio da Meta acessa com navegador de celular (Instagram/Facebook) e conta como pessoa: 57 das 62 chegadas "de gente" com a marca do motor em 09/10 foram ANTES de os anuncios serem ligados (14h e 15h). O Analytics (GA4) continua sem acesso por API (BK-462).

[2026-10-09] [BK-534 item 1] Detalhes da campanha: pedidos pagos e venda calculados certo

Origem: ADMIN - "sim" para atacar o BK-534 comecando pelo funil e pela receita.

Mudanca (admin/modulos/campanhas/detalhes.php, quadro "Pedidos atribuidos"):

  • pago passa a ser status 3 (Paga) e 4 (Disponivel). Antes o status 1 (aguardando) contava como pago e os pagos entravam como "nao concluidos";
  • a venda e preco x quantidade e so dos pedidos pagos (antes somava o preco unitario de todos os status). O rotulo virou "Venda dos pedidos pagos";
  • o pedido e achado pelo registro de origem (rnt_pedido_atribuicao.ultimo_utm_campaign). O texto em metadados ficou so como apoio para pedido antigo sem registro, com a marca inteira entre aspas;
  • a tabela por status mostra o nome do status em vez do codigo;
  • a etapa "Compra atribuida" do funil nao deixa mais pedido virar sessao (podia dar conversao acima de 100%).

Validacao: php83 -l ok; consulta nova executada no banco para quatro campanhas (sem erro; hoje nenhuma tem pedido com registro de origem de campanha). Revisao do gpt-6-astra: dois P2, corrigidos. A tela logada NAO foi aberta.

Continua no BK-534: ROI e CPA da view legada, investimento sem periodo comum, quadro do WhatsApp, permissao e CSRF do modulo.

[2026-10-09] [BK-460 exclusao] Excluir campanha do motor pausa na Meta antes

Origem: analise do gpt-6-astra pedida pelo ADMIN (P1).

Mudanca: admin/modulos/campanhas/action.php, case deletar: campanha com fonte_importacao = 'motor' e pausada na Meta antes de o cadastro ser apagado; se a Meta nao confirmar, nada e apagado e o painel avisa. Antes o anuncio seguia gastando e sumia do painel. detalhes.php: o bloco do motor ignora valor de tipo inesperado vindo de dados_externos (evita erro fatal no PHP 8.3) e os titulos das colunas dizem o periodo de cada numero.

Validacao: php83 -l ok nos dois; bloco renderizado fora do painel com dados propositalmente errados (lista no lugar de texto) e com a campanha 97 real, sem aviso nem erro. A exclusao logada NAO foi exercitada. Achados nao corrigidos do painel: BK-534.

[2026-10-09] [BK-460 acompanhamento] Tela de detalhes da campanha mostra os anuncios do motor

Origem: ADMIN - "rionoteatro.com.br/admin/modulos/campanhas tem algo para acompanharmos os anuncios?".

Mudanca: admin/modulos/campanhas/detalhes.php ganha o bloco "Anuncios na Meta (motor de anuncios)", so para campanha com fonte_importacao = 'motor': verba diaria do conjunto, gasto de hoje/7 dias/total, e uma linha por anuncio (foto, video, foto com musica) com situacao na Meta, gasto, exibicoes, cliques, visitas contadas pelo pixel, visitas registradas pelo site, pedidos pagos e botao "Abrir na Meta"; embaixo, vendas vindas do anuncio em 7 dias e o que as regras pedem agora. A tela so le dados_externos; quem grava e a rotina do servidor (/root/rnt-tools/motor_anuncios_rotina.php), que passou a importar os numeros por anuncio.

Validacao: php83 -l ok; bloco renderizado fora do painel com a campanha 97 (do motor) sem aviso nem erro, e vazio para a 93 (WhatsApp). A tela logada NAO foi aberta. Sem revisao do gpt-6-astra nesta mudanca.

[2026-10-09] [BK-533] Lembrete de pedido pendente: texto novo, falando em "oferta"

Origem: ADMIN - mandou o texto atual e o novo ("alterar para").

Mudanca: admin/cron/lembrete_pedido_pendente.php, montar_mensagem_lembrete() (fase 1). De "Oi Fulano! Notamos que voce nao concluiu a compra para ... Ficou com alguma duvida ou teve algum problema no pagamento? ..." para "Oi, Fulano! Tudo bem? Notamos que voce nao conseguiu obter a oferta Peca (dd/mm as hh:mm) de ~R$X~ por somente R$Y !! / Ficou com alguma duvida ou dificuldade? Estou a disposicao para lhe ajudar. / Para facilitar segue link da oferta 👉 link". A oferta inteira fica em negrito; o valor cheio continua riscado (decisao anterior do dono); os dois erros de digitacao do texto enviado foram corrigidos. A fase 2 (cliente que ja pagou) nao mudou.

Validacao: php83 -l ok; --dry-run ok; --test-send entregue ao gestor (amostra do pedido 73972).

Incidente da rodada: a primeira edicao (16:15) foi SOBRESCRITA pelo deploy do push das 16:14 - o arquivo voltou ao texto antigo as 16:16, sem aviso. Reaplicada e commitada em seguida. Regra ja registrada (nao editar arquivo versionado na janela de ~2 min apos um push) e descumprida aqui.

[2026-10-09] [BK-460 atalho] Atalho que abre o anuncio do motor no Gerenciador da Meta

Origem: ADMIN - o link direto do Gerenciador abria o aplicativo do Facebook no celular, sem ir para a edicao.

Mudanca: admin/modulos/campanhas/ir_gerenciador.php (novo). Recebe o id da campanha do painel, tira conta e anuncio do dados_externos e redireciona para o Gerenciador (destino fixo; nao e redirecionador aberto). Entra por link ASSINADO (HMAC com config/motor_link_secret.php, fora do Git) sem pedir login, ou, sem assinatura valida, pelo login do admin. .gitignore ganhou o arquivo do segredo.

Validacao: php83 -l ok; por curl: link assinado de foto e de video devolve 302 para o anuncio certo; campanha que nao e do motor, 404; assinatura errada vai para o login.

NAO resolvido: no celular do ADMIN o link nao abriu a edicao do anuncio (09/10, ele pediu para ver depois). E o login do admin, vindo de um link com retorno, caiu no inicio do painel em vez de voltar ao endereco pedido - nao reproduzido sem a senha; no servidor o retorno e repassado.

[2026-10-09] [BK-460 painel] Pausar campanha do motor de anuncios pelo painel pausa de verdade na Meta

Origem: ADMIN - "tenho que poder ver os dados das campanhas e parar elas direto do nosso painel de campanha".

Mudanca:

  • admin/modulos/campanhas/includes/motor_anuncios_helper.php (novo): rnt_motor_pausar_na_meta() envia status=PAUSED para a campanha da Meta guardada em dados_externos e confere relendo pela conta. Nenhuma funcao escreve ACTIVE;
  • admin/modulos/campanhas/action.php, case alterar_status: em campanha com fonte_importacao = 'motor', Pausar/Finalizar so grava depois de a Meta confirmar; se recusar, o painel nao muda e avisa. Ligar pelo painel e recusado. O pedido de pausa so e aceito com origem no proprio site;
  • case editar: a tela de edicao nao troca status, gasto nem marca de campanha do motor (antes dava para gravar "pausada" com o anuncio gastando).

Campanhas que nao sao do motor seguem como antes.

Validacao: php83 -l ok nos dois arquivos; pausa chamada por CLI na campanha 94 (ja pausada): confirmada pela Meta; campanha 93 (WhatsApp) recusada como "nao e do motor"; endpoint sem login devolve 302. Revisao do gpt-6-astra com 3 achados, corrigidos; a versao final nao foi revista de novo. O clique logado no botao NAO foi testado.

[2026-10-09] [BK-532] Copia de banco de 2024 retirada do site e do repositorio; nginx bloqueia dump e compactado

Origem: achado de 09/10/2026 ao procurar backups antigos do cadastro (BK-531); ADMIN mandou bloquear e remover, mantendo uma copia fora do Git.

O que havia: um dump completo do banco de 03/11/2024 (17 MB, cadastro de ~50 mil clientes) versionado desde 15/10/2025 em painel/ e painel_old/. A copia de painel/ respondia 200 pela internet: a regra do vhost so negava nome TERMINADO em .sql, e o arquivo terminava em .gz.

Mudanca:

  • vhost nginx: duas regras novas - .sql em qualquer ponto do nome e todo compactado (gz|tgz|bz2|xz|zip|7z|rar|tar|dump) respondem 404. Conferido no access log de 5 dias que nenhum compactado e servido de proposito;
  • os dois arquivos sairam do Git e do webroot; .gitignore ganhou .sql.gz, .sql.zip e .sql_*;
  • uma unica copia guardada fora do webroot e do Git, em pasta so do root (700/600); uma terceira copia identica numa pasta antiga de configuracao do nginx foi apagada.

Validacao: nginx -t ok e reload; o endereco passou de 200 para 404 (publico e direto na origem); home, pagina de peca, pecas.xml e CSS seguem em 200.

Acessos: no access log de 02/04/2026 a 09/10/2026 so ha os dois pedidos de cabecalho do proprio teste. Antes de abril nao ha log.

Fica em aberto (decisao do ADMIN): o arquivo continua no HISTORICO do repositorio no GitHub; as senhas daquele dump sao hash antigo (ver pendencia do SHA-1 sem salt); a copia guardada ainda nao esta cifrada com senha.

[2026-10-09] [BK-530 parte 2] Carrinho reaproveitado ganha registro de origem, sem duplicar

Origem: ADMIN - "pode fazer a parte 2 do BK-530".

Mudanca:

  • includes/atribuicao_helper.php: nova rnt_atrib_garantir_pedido() - para pedido que JA existe. Com linha, nada muda (a origem fica congelada no nascimento); sem linha, amarra primeiro o retrato orfao cuja chave ja viaja no metadados e, nao havendo, grava o retrato de agora com regra_versao = 'v1-tardio'. Trava GET_LOCK por pedido; sem a trava nao grava;
  • rnt_atrib_vincular_interno() e os dois UPDATE da funcao nova ganham NOT EXISTS: nenhum caminho amarra um segundo retrato a pedido que ja tem linha (a tabela so tem indice unico em chave). Nenhuma consulta depois do UPDATE - a reconciliacao le mysqli_affected_rows();
  • config/functions_new.php (gerenciar_pedido_carrinho(), ramo de reuso do rascunho): preserva metadados['rnt_atrib'] e chama a funcao nova.

action.php NAO foi alterado (sem re-seal do monitor do Mercado Pago).

Validacao: php83 -l ok (e php -l 5.6 no functions_new.php); 7 casos exercitados no banco dentro de transacao desfeita (pedido com linha, sem linha, repeticao, orfao recuperado, segundo retrato recusado, vinculo normal, chave invalida) e trava ocupada por outra conexao (nao grava, 2s); pagina de pedido renderizada como visitante; tres passadas do gpt-6-astra, um P2 em cada, os tres corrigidos. O reuso real com cliente logado nao foi exercitado antes do commit.

Continua no BK-530 (aberto): action.php ~1015 ainda regrava o metadados sem a chave no reuso do efetuacompra (a linha fica amarrada por pedido_id, entao so afeta a reconciliacao); INSERTs sem origem em action.php:1971 e :2331, painel/modulos/pedidos/action.php:99 e fluxos PagSeguro - recriacao medida em 2 de 101 pedidos desde 01/07/2026.

[2026-10-09] [BK-530 parte 1] Pedido de cliente ja cadastrado passa a nascer com registro de origem

Origem: ADMIN - "pode investigar o BK-530" / "pode aplicar a parte 1" (achado do BK-529: a linha "pedidos atribuidos ao link do botao" do relatorio de campanha dava 0 sempre).

Causa: o pedido de quem ja tem cadastro nasce em pedido.php, por gerenciar_pedido_carrinho(), que so grava a origem sob function_exists('rnt_atrib_carimbar') - e as paginas de pedido nao carregavam includes/atribuicao_helper.php. De 22/09 a 09/10/2026, 7 de 25 pedidos ganharam registro, todos criados junto com o cadastro (action.php).

Mudanca: pedido.php e pedido_pagseguro.php carregam o atribuicao_helper.php (so define funcoes; nenhuma captura de visita ali).

Validacao: php83 -l ok nos dois; pagina de pedido da peca 1024 renderizada como visitante (POST + redirecionamento), sem erro fatal e sem criar pedido; consulta previa e revisao final do gpt-6-astra sem P1/P2. O caminho logado NAO foi exercitado (criaria pedido real): a prova e o proximo pedido de cliente cadastrado gerar linha em rnt_pedido_atribuicao.

Continua no BK-530 (aberto): rascunho reaproveitado nas ultimas 24h, exclusividade por pedido_id, preservacao da chave no metadados e os demais caminhos de INSERT sem origem. Pedidos antigos sem registro nao sao recuperaveis.

[2026-10-09] [BK-529] Relatorio de vendas da campanha da API oficial perdia a compra de quem recebeu a mensagem (fuso)

Origem: ADMIN - "Ja tivemos vendas sim. Tem que ver como estamos pegando essa informacao ... precisa cruzar se os compradores sao pessoas que receberam a mensagem da parte 1 ou 2 e conferir pq nao computamos".

Causa: rnt_wa_campanha_vendas() (includes/rnt_wa_campanha_publico.php) transformava a hora do primeiro envio com date(). A API bot/whatsapp/prefs_api.php nao carrega o functions_new.php, entao valia o php.ini da VPS (PRC) e o corte saia 11 horas adiantado: para a leva 2 (12h00) o relatorio procurava pedidos criados depois das 23h. pedidos.created e gravado na hora do Rio.

Mudanca: o corte passa a ser montado com DateTime + America/Sao_Paulo explicito.

Validacao: php83 -l ok; funcao reproduzida nos dois fusos com a fatia C da leva 2 (PRC: 0 pedidos; Sao Paulo: 1 pago, 2 ingressos, R$ 90,00); campanha.php vendas rodado nas quatro campanhas depois da correcao - leva 2C passa a mostrar o pedido 73970 (cliente recebeu as 12h07 e comprou as 12h17), leva 1 e fatias A/B seguem em 0, conferido contra a tabela pedidos. Revisao gpt-6-astra sem P1/P2.

Fica em aberto: a linha "pedidos atribuidos ao link do botao" continua em 0 por outro motivo - ver BK-530.

[2026-10-09] [BK-528] Botao PRODUTOR no filtro de origem da lista de pecas do admin

Origem: ADMIN - "temos os botoes admin bot aguardando mas nao temos produtores para listar as pecas de produtores. Juliana Palmer incluiu um evento e nao consigo ver".

Mudanca: admin/modulos/pecas/index.php ganha o valor origem=produtor e o botao PRODUTOR no grupo Origem, ao lado de ADMIN e BOT. Lista toda peca com produtor real vinculado (id_produtor preenchido, fora origem bot e o cadastro bot@rionoteatro.com.br). Antes, peca de origem produtor sem parceria_online nao cabia em ADMIN nem em BOT e so aparecia em "Todas as Pecas" sem origem marcada. As de produtor com parceria_online=1 aparecem em PRODUTOR e seguem tambem em ADMIN - a regra do ADMIN nao foi alterada.

Validacao: php83 -l ok; consulta do filtro conferida no banco (25 pecas, nenhuma ativa em 09/10); revisao gpt-6-astra sem P1/P2; ADMIN testou a tela no ar e aprovou.

[2026-10-09] [BK-527] "Sair" digitado tratado tambem pelo numero oficial, sem confirmacao em dobro

Origem: ADMIN - "Sempre que escrever sair sozinho, sem outras palavras ja nao envia mais mensagens para essa pessoa (do rionoteatro, com outros clientes analisamos dps)".

Mudanca:

  • Divulg AI (repo proprio, webhook.php + lib_prefs.php): mensagem de texto que e SO a palavra "sair" (maiuscula, acento e pontuacao nao importam; "quero sair" nao vale) passa pelo mesmo caminho do botao "Parar de Receber": acao assinada optout do site (cadastro sair = 1 + lista de bloqueio) e confirmacao ao cliente, na hora, sem depender do bot do whatsapp-web.js;
  • includes/whatsapp_inbound_analyzer.php: o monitor do bot (5 em 5 min) nao manda a segunda confirmacao quando o telefone ja esta na lista de bloqueio (rnt_wa_campaign_is_blacklisted). Antes so olhava clientes.sair, entao quem nao tem cadastro receberia duas.

O bot continua tratando sozinho "parar", "cancelar", "remover", "descadastrar" e "nao quero receber", como antes.

Validacao: php83 -l ok; regra do "sair" exercitada com 12 textos. O caminho inteiro com um cliente real digitando "sair" NAO foi exercitado (gravaria um pedido de saida de verdade); o trecho de gravacao e o mesmo do botao, que funcionou em 08/10.

[2026-10-09] [BK-526] Regulamento das pecas ativas e regras pre-definidas: "RG" vira "documento com foto cadastrado"

Origem: ADMIN - "pode olhar so das pecas ativas e aonde setamos as regras pre-configuradas" (continuacao do BK-525).

Mudanca (so dado, 8 linhas): pecas.regulamento das 4 pecas ativas que citavam RG (1024, 1085, 1342, 1634) e tb_regras_predefinidas.conteudo das 4 regras (adulto/infantil, com/sem assento). Quatro frases por texto: "Basta apresentar o seu RG cadastrado", "apresentando somente seu RG (documento de identificacao com foto) cadastrado", "APRESENTANDO SOMENTE SEU RG CADASTRADO" e "O RG do comprador" (virou "O documento cadastrado do comprador"). As 1.024 pecas inativas com RG no regulamento ficaram como estavam: peca reativada precisa do regulamento novo (reaplicar a regra pre-definida).

Como: /root/rnt-tools/bk526_regulamento_documento.php (ensaio por padrao; --aplicar grava). pecas e MyISAM, sem rollback: o texto original das 8 linhas esta em /root/rnt-tools/backups/bk526_regulamento_antes_20261009-013325.json; so foi alterada a linha em que as quatro trocas fecharam sem sobrar "RG".

Validacao: ensaio antes (8 de 8 com 4 trocas e 0 sobrando); depois, 0 pecas ativas e 0 regras com "RG"; pagina /discutindo-relacao em 200 com o texto novo.

[2026-10-09] [BK-525] Texto de retirada: "RG cadastrado" vira "documento com foto cadastrado"

Origem: ADMIN - "apresentando somente o RG cadastrado alterar para apresentando somente o documento com foto cadastrado (pq atualmente o novo CPF tem foto e a pessoa pode apresentar somente o CPF se esse for o documento cadastrado)" e, sobre os demais lugares do site, "sim, pode trocar la tambem".

Mudanca (so texto, 11 ocorrencias em 10 arquivos): action.php (e-mail da compra), action_pagseguro.php, apps/Helpers/PaymentView.class.php (tela de pagamento), confirm_pagamento.php, painel/modulos/pedidos/index.php (painel do cliente, 2), pedido.php, pedido_pagseguro.php, politica_de_devolucao.php, index.php e pecas.php (chamada da home). A saudacao automatica do WhatsApp (Divulg AI) recebeu a mesma troca.

Ficou como estava, de proposito: o termo de parceria e as telas do produtor (termo-parceria.php, painel/modulos/eventos/editar.php e incluir.php), que ja dizem "RG (ou documento original com foto cadastrado...)" e sao texto de contrato; a mensagem de erro "CPF, E-MAIL ou RG" (fala do campo do cadastro); e arquivos sem acesso (pedido_mysqli.php, peca_nova.php, index_2026.php, copias datadas). O regulamento gravado em cada peca no banco nao foi conferido.

Validacao: php83 -l ok nos 10; todas as linhas do diff sao a frase trocada; arquivos em UTF-8. Monitor do Mercado Pago pausado para o action.php e selado depois do commit.

[2026-10-09] [BK-523] Conversas do Divulg AI: etiquetas Cliente / Produtor e filtros como os do WhatsApp

Origem: ADMIN - "inclua as tags de cliente / produtor / e os filtros igual tem no app do whatsapp de nao lidas, grupos e etc" e "veja algumas coisas que fizemos tbm no whatsapp do painel do admin do rionoteatro".

Mudanca (site):

  • includes/rnt_wa_conversas_tags.php (novo, so leitura): rnt_wa_conversas_classificar() recebe ate 200 telefones e devolve, para cada um, cliente (tem cadastro), nome, produtor (linha ATIVA em tb_produtores pelo id_cliente), e o rotulo proprio / "fixo" que a tela admin/modulos/bot/whatsapp.php ja usa (admin/runtime/whatsapp_labels.json). Uma consulta para o lote (8 digitos finais) + conferencia exata com rnt_wa_prefs_phone_variants(). Do cadastro so sai o nome;
  • bot/whatsapp/prefs_api.php: acao assinada classificar.

Mudanca (Divulg AI, repo proprio): selos Cliente / Produtor / Fixo e nome do cadastro na lista e no topo da conversa; fileira de filtros Todas, Nao lidas, Aguardando resposta, Clientes, Produtores, Sem cadastro, Campanha, com contagem; conversa fixada sobe; "lido" guardado no servidor; cache das etiquetas por 6h (1h para quem nao tem cadastro).

Ficou de fora (no BK-522): etiquetas coloridas do aplicativo WhatsApp Business e filtro de Grupos (a API oficial nao entrega mensagem de grupo).

Validacao: php83 -l e node --check ok; 99 numeros classificados em 68 ms (97 clientes, 1 produtor); chamada assinada real 200 em 109 ms; a caixa do painel aberta pelo ADMIN ja buscou e guardou as etiquetas de 97 contatos sozinha. Tela NAO conferida pela IA (sem login). gpt-6-astra antes e depois (dois pedidos): sem P1; P2 de normalizacao de telefone corrigido, os demais no BK-522.

[2026-10-08] [BK-521] Campanha da API oficial nasce sozinha na tela Campanhas; visitas por botao x por indicacao

Origem: ADMIN - "ja cria a campanha corretamente pra acompanharmos pelo rionoteatro para irmos treinando a automacao sem depender de nos" e "para sabermos quais botoes clicaram, para criarmos os funis".

Mudanca:

  • includes/rnt_wa_campanha_publico.php: rnt_wa_campanha_registrar() - cria/atualiza a campanha por utm_campaign (so marca no formato wa-p<peca>-l<leva>[letra]). Linha nova: planejada/ativa, fonte_importacao = divulgai. Linha auto-detectada ou ja marcada divulgai: regrava nome, descricao e publico e vira planejada. Linha cadastrada ou editada a mao: so investimento_total e dados_externos (o texto do admin fica). dados_externos guarda os numeros (enviadas, entregues, lidas, toques nos botoes) em JSON;
  • mesma funcao de vendas (rnt_wa_campanha_vendas): visitas_pelo_botao deixa de somar as visitas com utm_medium = indicacao, que saem em visitas_por_indicacao (link encaminhado pelo "Compartilhar Oferta", que agora leva a marca da campanha);
  • bot/whatsapp/prefs_api.php: acao assinada campanha_registrar.
  • Lado do Divulg AI (repo proprio): o enviador registra ao aprovar e a cada rodada; investimento_total e ESTIMATIVA (entregues x R$ 0,3217) e e regravado a cada rodada - valor digitado a mao na campanha e sobrescrito.

Validacao: php83 -l ok; funcao exercitada com linha de teste (wa-p999999-l99: criada -> atualizada -> so_custo depois de editada a mao -> adotada quando detectada; marca fora do formato recusada) e a linha apagada; pela API assinada de verdade a campanha 87 respondeu so_custo e manteve o texto. A consulta de desenho ao gpt-6-astra caiu por tempo; a revisao final foi feita em cinco pedidos separados: sem P1, P2 no BK-522. Do P2 do site saiu um ajuste: campanha criada pelo enviador e editada na tela (admin/modulos/campanhas/action.php) perde a marca divulgai e o texto do admin nao e mais regravado.

[2026-10-08] [BK-520] Campanhas: mover auto-detectada para Planejadas e campanha 87 preenchida

Origem: ADMIN - "Quero ter a opcao de incluir essas campanhas Auto-detectadas em planejadas" e "incluir em editar.php?id=87 o custo da campanha e demais informacoes".

Mudanca:

  • admin/modulos/campanhas/action.php: acao mover_planejada (so quando a origem e detectada; mesmo id, entao cliques, vendas e link curto ficam) e, no editar, promocao detectada -> planejada quando o formulario manda origem=planejada. So nesse sentido: voltar a detectada deixaria o "Criar campanha" adotar a linha (BK-498). O nome perde o sufixo final "(Auto-detectada)";
  • admin/modulos/campanhas/editar.php: campo "Aba da listagem" nas auto-detectadas (padrao: manter);
  • admin/modulos/campanhas/index.php: botao "Mover para Planejadas" em cada linha da aba Detectadas.
  • Dado: campanha 87 (wa-p1024-l1, leva 1 da peca 1024 pela API oficial) com nome, publico, descricao e investimento R$ 26,06 (81 entregues x R$ 0,3217, estimativa), origem planejada, sem data_fim (o dashboard-roi.php esconde a campanha em periodo posterior a data de fim).

Validacao: php83 -l ok nos tres; POST sem login em action.php devolve 302 para o login; v_campanhas_performance da 87 conferida. Fluxo AJAX do botao NAO exercitado logado pela IA - teste do ADMIN. gpt-6-astra antes e depois: sem P1; P2 (padrao do campo promovia sem querer) corrigido.

[2026-10-08] [BK-519] Publico da campanha pela API oficial: levas, conferencia de SAIR na hora e marca de WhatsApp

Origem: ADMIN - "pode seguir pela campanha"; regras dadas ao longo da noite de 08/10 (ver BK-519 no backlog).

Parte do site; o enviador mora no Divulg AI (campanha.php, so linha de comando).

Mudanca:

  • includes/rnt_wa_campanha_publico.php (novo, so leitura): quem ja comprou na area da peca, em PARTES pela compra

mais recente (24 meses, depois de 12 em 12) e em LEVAS de envio (partes vizinhas juntadas ate ~100 numeros);

celular no campo celular ou telefone; so DDD 21/22/24 (21 nas levas, 22/24 e numero antigo com o 9

acrescentado na ultima); fora quem pediu SAIR, lista de bloqueio, ja tem ingresso, preferencia de outra regiao;

  • bot/whatsapp/prefs_api.php: acoes publico (resumo das levas; com leva, tambem nome e telefone daquela leva -

unica acao que devolve dado de cliente, pela mesma assinatura HMAC) e marcar_whatsapp;

  • includes/rnt_wa_prefs.php: get passa a dizer sair (qualquer cadastro do numero) e bloqueado (lista de

bloqueio), que o enviador confere na hora de cada envio; rnt_wa_prefs_marcar_whatsapp() grava o retorno da API

nas colunas da checagem do BK-385 com origem cloud_api, sem rebaixar um "tem" confirmado por mensagem recebida.

  • /root/rnt-tools/publico_campanha_api.php usa a mesma funcao (ensaio e envio nunca divergem).

Medido para a peca 1024 (zona): levas 96 / 168 / 231 / 142 / 206 / 109 / 47; 93 celulares achados so no campo

telefone; 183 compradores fora por DDD de fora do RJ.

Validacao: php83 -l ok; pela API assinada: resumo sem linhas, leva 1 com 96 linhas so DDD 21 e 13 digitos,

leva e peca inexistentes (422), get com sair/bloqueado. Revisao do gpt-6-astra antes do primeiro envio: os

P1 do lado do site (lista de bloqueio lida com falha = sem publico; SAIR de qualquer cadastro) corrigidos.

Nao validado (na entrega): nenhum disparo real ainda; marcar_whatsapp so com numero inexistente.

Complemento de 08/10/2026 21h - primeiro disparo real e relatorio de vendas: a leva 1 da peca 1024 saiu pelo

Divulg AI entre 21:04 e 21:09 (96 enviadas, 81 entregues, 4 nao entregues 131026, 5 retidas pela Meta 130472).

marcar_whatsapp gravou 81 cadastros como "tem WhatsApp" (origem cloud_api). Um cliente tocou em "Parar de

Receber" as 21:09:01 e foi tratado: rnt_wa_prefs_log com optout e 1 cliente, clientes.sair = 1 e o telefone

na lista de bloqueio das campanhas. Nova acao vendas na API + rnt_wa_campanha_vendas(): visitas pelo link,

pedidos atribuidos a marca da campanha (rnt_pedido_atribuicao), pedidos da peca feitos por quem recebeu, venda

paga (valor x qtd) e margem pela regua do margem_helper.php. O rastreio do site ja tinha criado sozinho a

campanha wa-p1024-l1 (id 87) e registrado 11 visitas minutos depois do envio.

[2026-10-08] [BK-518] Cidade (municipio) no cadastro dos teatros e no pecas.xml

Origem: ADMIN - terceiro passo combinado das preferencias pelo WhatsApp ("So no meu municipio" e pecas em

Friburgo, Teresopolis etc.): o teatro so tinha bairro.

Mudanca:

  • banco: teatros.cidade VARCHAR(60) NULL (copia da tabela antes em

/root/rnt-tools/backups/teatros_antes_cidade_20261008-185023.sql.gz);

  • preenchimento automatico pelo bairro, so onde a deducao e segura: 170 de 184 teatros (157 Rio de Janeiro,

6 Niteroi, 2 Nova Iguacu, 1 cada em Saquarema, Duque de Caxias, Itaguai, Macae e Nilopolis). Regra: municipio

escrito no bairro, cidade da Baixada, bairro com zona da capital no rnt_zonas_do_bairro() ou um dos 10 bairros

da capital que o mapa de zonas nao conhece;

  • admin/modulos/teatros/editar.php + action.php: campo "Cidade (municipio)" com os 92 do RJ (Rio e Niteroi

primeiro); a gravacao so aceita nome da lista do IBGE e so mexe na cidade se o formulario mandar o campo;

  • atualizar_feed.php: <rnt:cidade> no pecas.xml e no pecas_inteira.xml.

Validacao: php83 -l ok nos tres; feed regerado, XML valido, as 4 pecas com Rio de Janeiro e o resto do feed

identico. Nao validado: a tela de editar teatro no admin (exige login).

Ficaram sem cidade (14, bairro vazio ou invalido) - ADMIN preencher: #88 e #98 VillageMall, #99 Fashion Mall,

#177 Kinoplex Shopping Leblon, #179 Teatro Negro Chica Xavier, #180 COC Campo Grande, #181 Waldir Onofre,

#182 Sergio Britto, #183 IFRJ, #185 Midrash Barra, #186 Citta America, #187 Jardim Suspenso do Valongo,

#188 Dercy Goncalves (Grajau Country Club), #189 Igreja Batista Atitude Cosmos.

[2026-10-08] [BK-516] Preferencias de recebimento respondidas pelo WhatsApp gravadas no cadastro

Origem: ADMIN - depois de aprovar o formulario no numero de teste do Divulg AI: "sim, pode seguir nessa ordem"

(modelos reais com botoes -> gravar as respostas no cadastro -> cidade nos teatros).

Como funciona: o cliente toca em "Escolher o que receber" numa mensagem de divulgacao enviada pela API

oficial; o Divulg AI responde com um formulario dentro do WhatsApp (tipo de espetaculo, de onde receber, regioes

em caixas de selecao, municipio numa lista dos 92 do RJ) e entrega a resposta aqui. "Parar de receber" marca SAIR.

Mudanca:

  • banco: clientes ganha pref_tipo (adulto|infantil|ambos), pref_alcance (zonas|municipio|estado),

pref_municipio (nome IBGE) e pref_atualizado_em; tabela nova rnt_wa_prefs_log (hash da mensagem ja

aplicada). ALTER ... ALGORITHM=INPLACE, LOCK=NONE; copia da tabela antes em

/root/rnt-tools/backups/clientes_antes_pref_20261008-184120.sql.gz;

  • includes/rnt_wa_prefs.php (ler, gravar, sair) e bot/whatsapp/prefs_api.php (so POST, HMAC-SHA256 de

timestamp.corpo, janela de 5 min; segredo em config/divulgai_prefs_secret.php, fora do Git);

  • includes/data/municipios_rj.json: os 92 municipios do RJ (IBGE), para validar o que chega.

Decisoes (consulta ao gpt-6-astra antes de implementar):

  • cliente SO por telefone completo com DDD; o nono digito so e posto/tirado em celular antigo (8 digitos comecando

em 6-9). O casamento por sufixo de 8 digitos do analisador antigo ignora o DDD e poderia gravar na pessoa errada;

  • o municipio da preferencia NAO vai para clientes.cidade (endereco de pagamento; o perfil do painel so conhece 24

municipios e gravaria NULL por cima);

  • zonas_interesse segue sendo o CSV de sempre (zonas -> nomes; estado -> "Todo o RJ"; municipio -> vazio);
  • gravar preferencia nunca mexe em sair; a mesma resposta nunca e aplicada duas vezes (marca + gravacao na mesma

transacao); o "sair" poe o telefone na lista de bloqueio das campanhas antes de marcar, e nao envia mensagem daqui.

Validacao: php83 -l ok; pela API: leitura do cadastro do ADMIN (1 cliente), numero inexistente, tipo invalido,

sem zonas e municipio falso (422), assinatura errada, timestamp velho (403) e GET (405); um evento assinado simulado

do numero real gravou "Ambos / Centro, Zona Sul, Zona Norte, Zona Sudoeste" no cadastro do ADMIN (as escolhas que

ele fez no teste), recusou codigo de formulario errado e nao duplicou na reentrega; "sair" de numero inexistente foi

para a lista de bloqueio (registro de teste removido depois). Revisao final do gpt-6-astra: 2 P1 corrigidos antes do

commit (nono digito casando celular com fixo; reentrega sem fim em recusa definitiva).

Nao validado: um cliente real tocando no botao (os modelos reais com botoes ainda estao em analise na Meta e

nenhuma campanha saiu) e o "sair" num cadastro de verdade. Ainda nao existe: filtro de campanha que use esses

campos, e os campos novos no perfil do painel (BK-517).

[2026-10-08] [BK-515] pecas.xml ganha <rnt:nome> (nome limpo da peca)

Origem: envio de teste dos modelos de WhatsApp pelo Divulg AI (pedido do ADMIN: "enviarmos pelo test p ver

como vai ficar"). O g:title do feed leva "NN%OFF bairro" colado, o que estraga a frase "X esta em cartaz".

Mudanca (atualizar_feed.php): cada item passa a trazer <rnt:nome> com o nome da peca sem oferta nem bairro.

Validacao: php83 -l ok; feed regerado (itens=4), XML valido, quatro nomes corretos e o resto do feed

identico ao anterior (diff sem a linha nova). Usado no envio de teste entregue no celular do ADMIN.

[2026-10-08] [BK-514] Tela de pedido: atualizar a pagina volta para a peca, sem "reenvio de formulario"

Origem: ADMIN - ao atualizar o pedido.php o navegador mostrava "Confirmar reenvio do formulario /

ERR_CACHE_MISS"; "o ideal seria voltar para a tela de selecao pois algumas pessoas com medo ira fechar a pagina".

E o texto do botao ainda falava em Pagbank/PagSeguro.

Causa: a tela e o resultado direto de um POST da pagina da peca; navegador nenhum reexibe POST sem perguntar.

Mudanca (pedido.php, bloco no topo): Post/Redirect/Get de uso unico. O POST com os seis campos que a

peca envia (page_return, clubernt, data, quantity, id, assentos) guarda a selecao em

$_SESSION['rnt_pedido_prg'][codigo] e responde 303 para /pedido.php?sel=<codigo>&peca=<id>. Esse GET exibe a

tela uma vez, com os mesmos dados; atualizar de novo, voltar do pagamento, codigo vencido (30 min) ou de outra

sessao levam para /peca.php?id=<id>. POST fora desse padrao e GET sem codigo (volta do login/cadastro, carrinho

de 5 min em $_SESSION[$ip]) seguem o caminho antigo.

Por que uso unico (consulta ao gpt-6-astra antes de implementar): reexibir a cada refresh rodaria de novo o

gerenciar_pedido_carrinho() do cliente logado, que sobrescreve valor/metadados do rascunho e poderia desfazer um

cupom ja aplicado. Com uso unico a tela roda uma vez so por selecao, como hoje.

Texto: "* Clique em Confirmar para ser direcionado ao Mercado Pago e efetuar o pagamento com seguranca do

Mercado Pago, em nome de Jorge Luiz (parceiro operacional)."

Validacao: php83 -l ok; oito caminhos ensaiados por curl como visitante (envio 303; primeira exibicao 200

com quantidade, total e economia iguais; atualizar 302 para a peca; GET sem codigo 200; codigo inventado 302;

POST com campo a mais 200 sem redirecionar; codigo em outra sessao 302; GET puro 200 sem fatal); revisao final do

gpt-6-astra sem P1/P2. Nao validado: cliente LOGADO, cupom, e o gesto de atualizar num navegador de verdade.

Mesmo assunto, nao alterado: a pagina ainda cita "Saldo PagSeguro" no bloco de formas de pagamento.

[2026-10-08] [BK-513] Tela de pedido: "Valor total economizado" no resumo e titulo do cupom maior

Origem: ADMIN - "acho interessante incluirmos o valor que a pessoa vai economizar diminuindo o valor

promocional do valor total da compra inteira"; e "titulo do cupom pode aumentar".

Mudanca (pedido.php):

  • os dois resumos ganham a linha Valor total economizado, logo abaixo de "Valor total do pedido":

sec_valor_de da sessao x quantidade - total do pedido. So aparece se a sessao tem preco cheio e sobra economia;

  • no resumo do cliente logado o total pode mudar com o cupom (js/voucher.js troca o texto de .total-checkout);

um script pequeno na propria pagina recalcula a economia quando isso acontece (o voucher.js nao foi tocado);

  • titulo "Cupom de Desconto" (.rnt-coupon-section h4) de 12px para 20px (mesma causa do BK-512).

Validacao: php83 -l ok; script inline com node --check e a conta ensaiada em 4 casos (inclusive milhar e

cupom); tela aberta como visitante com 2 ingressos da sessao de 11/10/2026 19:30 da peca 1342: "Valor do ingresso

R$22,00 / Valor total do pedido R$44,00 / Valor total economizado R$76,00" (preco cheio da sessao: R$ 60,00).

Nao validado: o resumo do cliente LOGADO e a troca ao aplicar cupom, que exigem sessao de cliente.

[2026-10-08] [BK-512] Tela de pedido: aviso do Painel retirado e nome da peca legivel

Origem: ADMIN - "achei confuso esse texto. Pode confundir as pessoas. Pode retirar. e o campo que aparece o

nome da peca pode aumentar. Esta muito pequeno."

Mudanca (pedido.php): saiu o paragrafo "* Com o email e senha informados, acesse seu Painel para ver o

status do seu pedido. Ao final do pagamento voce podera acessar o Painel."; o nome da peca (.rnt-order-header h3

e .rnt-summary-header h3) passou a 26px (22px ate 767px de largura).

Causa do nome pequeno (medida): o css/redesign_2026.css pede font-size: 1.2rem, mas o css/bootstrap.css

fixa html { font-size: 10px } - saia com 12px. A regra nova fica so no <style> do pedido.php.

Validacao: php83 -l ok; GET /pedido.php 200, sem fatal novo no log. Nao validado: a tela com um pedido

de verdade em andamento (precisa escolher sessao e quantidade) - conferencia do ADMIN.

Mesma causa, nao alterado: o titulo "Cupom de Desconto" (.rnt-coupon-section h4) usa o mesmo 1.2rem.

[2026-10-08] [BK-511] pecas.xml passa a trazer o video da peca (<rnt:video>)

Origem: ADMIN, sobre os modelos de mensagem do Divulg AI - "qdo tiver video .mp4 cria o modelo da peca com

video .mp4 qdo tiver so foto cria o modelo com a foto da peca".

Mudanca (atualizar_feed.php): nova rnt_feed_video_da_peca() procura em arquivos/pecas/<id>/video/*.mp4,

prefere o story com a arte da promocao (-story-arte-, gerado pelo arte_promo_video.php), senao o mais recente,

e ignora arquivo acima de 16 MB (limite do WhatsApp). Havendo video, o item ganha <rnt:video> no pecas.xml e

no pecas_inteira.xml. Sem video, a tag nao e escrita.

Validacao: php83 -l ok; feed regerado (itens=4), XML valido, as 4 pecas com video e as 4 URLs em 200

video/mp4; o resto do feed ficou identico ao anterior (diff sem a linha nova). O catalogo da Meta ja convivia

com tags rnt: (sessoes, teatro, bairro).

[2026-10-08] [BK-510] Zoom com os dedos liberado na pagina da peca (mapa de assentos no celular)

Origem: ADMIN - "no mobile tem que poder dar zoom na tela do celular p aumentar os assentos".

Causa: o <meta viewport> do includes/inc_peca.php trava o zoom (maximum-scale=1, user-scalable=no) em

todas as paginas que usam esse cabecalho.

Mudanca: peca.php define $rnt_viewport_permite_zoom = true antes de carregar o cabecalho, e o

inc_peca.php (dentro de LoadHeader(), por isso a variavel entrou no global) passa a escrever

width=device-width, initial-scale=1, maximum-scale=5 so nesse caso. Home, cadastro e demais paginas seguem

com o viewport travado de antes.

Validacao: php83 -l ok nos dois; viewport conferido na resposta real: peca com maximum-scale=5, home e

cadastre-se.php inalterados. Nao validado: o gesto num celular de verdade - conferencia do ADMIN.

Efeito colateral possivel: no iPhone, com o zoom liberado, tocar num campo de texto com letra menor que 16px

aproxima a tela sozinho.

[2026-10-08] [BK-509] Painel do produtor mostrava o WhatsApp pessoal do gestor no botao de parceria

Origem: ADMIN - "a mensagem veio pro meu whatsapp pessoal, nao pode passar esse meu contato para os

produtores" e, sobre o aviso: "manda tanto por push qto whatsapp pro numero do rionoteatro".

Causa (lida no codigo e conferida com a conversa recebida as 14:50): em painel/modulos/eventos/editar.php

o botao "Salvar e Contactar via WhatsApp" abria a conversa em parceriaAdminNumber = '5521999915554', o numero

pessoal do gestor, desde o BK-228. O incluir.php ja usava o oficial. A produtora mandou o texto "FECHEI

PARCERIA GRATUITA + VENDA COMISSAO!!" direto no pessoal. Coincidiu com a queda do bot (QR da coexistencia do

Divulg AI, 14:40-15:00), mas nao foi causado por ela.

Mudanca:

  • painel/modulos/eventos/editar.php: o botao abre o numero oficial 5521999605790.
  • painel/modulos/eventos/ajax_notificar_parceria.php: o aviso de parceria (checkbox marcado e parceria

aceita) deixa de acrescentar a forca o numero pessoal e passa por enviar_whatsapp_admin(): push de admin

+ WhatsApp para BOT_WHATSAPP_NUMERO_ALERTA (hoje 5521999605790). Antes saia so por WhatsApp, sem push.

Validacao: php83 -l ok nos dois arquivos; git grep 999915554 -- painel/ nao acha mais nada entregue ao

navegador (as ocorrencias que sobram sao destino de alerta no servidor, em painel/modulos/creditos/).

Nao validado: clique real de produtor no navegador, e se o push sai de dentro de requisicao web (historico

de 11/09 diz que nao saia; causa em aberto) - conferir em admin/logs/push_dispatch.log na proxima parceria.

Complemento no mesmo dia (ADMIN: "pode alterar para o rionoteatro"): painel/modulos/creditos/action.php e

painel/modulos/creditos/callback_mp.php tambem deixam de acrescentar o numero pessoal ao alerta de compra de

credito e passam por enviar_whatsapp_admin() (push + WhatsApp no numero do RNT). php83 -l ok nos dois;

git grep 999915554 -- painel/ = 0. O callback_mp.php e vigiado pelo monitor do Mercado Pago: re-selado

depois do commit. Nao validado com compra real de credito.

[2026-10-07] [BK-508] Salvar campanha: Data de Inicio ja preenchida e aviso de qual campo falta

Origem: ADMIN - "quando clica em copiar link rastreavel ele para de funcionar o salvar campanha".

Causa (medida no log de acessos): nao era o botao de copiar. As tres tentativas de salvar (23:23, 23:23

e 23:56 de 06/10) chegaram ao servidor e foram recusadas com "Preencha todos os campos obrigatorios": a

tela aberta direto (sem vir do botao da peca) nascia com a Data de Inicio vazia, a biblioteca de

validacao (parsley) nao e carregada nessa pagina e o aviso nao dizia qual campo faltava.

Mudanca:

  • incluir.php: Data de Inicio nasce com a data de hoje (hora do Rio); antes de enviar, a tela confere os

campos que o servidor exige (nome, URL de destino, UTM Source, UTM Campaign, Data de Inicio), marca o

campo, mostra "Falta preencher: ..." acima dos botoes e leva ate ele. Erro do servidor tambem aparece

nesse aviso.

  • incluir.php: os dois botoes de copiar confirmam no proprio botao ("Copiado!"), sem alert().
  • action.php: a recusa por campo vazio passa a dizer quais campos faltam.
  • editar.php (segundo relato do ADMIN, mesma noite): ao salvar, o navegador mostrava a resposta crua

{"success":true,"message":"Campanha atualizada com sucesso!",...}. A tela chama js/parsley/parsley.min.js,

que nao existe no servidor (404); $('#form-campanha').parsley() estourava, o envio por AJAX nunca

era ligado e o formulario ia pelo envio comum. A alteracao era gravada, so a tela ficava no JSON. Agora o

parsley so e usado se existir; os campos ja tem required do navegador. Ensaiado em Chrome automatico

com a campanha 83 e o action.php interceptado: envia por AJAX, mostra a mensagem e volta para a lista.

  • Validacao: Sintaxe PHP ok (2 arquivos); JS inline validado e Fluxo AJAX validado ate o envio, em

Chrome automatico com a tela real renderizada com login simulado e o action.php interceptado (nada

gravado): salvar sem tocar na data envia com a data de hoje; copiar e depois salvar envia; data apagada

ou nome/origem vazios nao enviam e mostram o aviso com os campos. A gravacao real fica com o ADMIN.

[2026-10-06] [BK-506] Campanhas: origem "Meta" com o canal preenchido pela propria Meta

Origem: ADMIN - anuncio de catalogo roda em varios canais da Meta, nao so Facebook ou Instagram; a

tela de campanha so tinha as duas origens separadas.

Mudanca:

  • incluir.php e editar.php: nova opcao de UTM Source Meta (anuncio em varios canais) (meta).
  • incluir.php: ao escolher Meta, o UTM Content (se vazio) recebe {{site_source_name}}_{{placement}} -

variaveis que a Meta troca pelo canal de cada clique (ig_Instagram_Stories, fb_Facebook_Feed...) - e

o UTM Medium vazio vira cpc. Texto digitado pelo admin nao e sobrescrito; trocar de origem limpa so o

que a tela preencheu. Aparece o aviso de usar o link completo.

  • incluir.php, editar.php e action.php (campanhas_tracking_destination): o link gerado mantem as

chaves {{...}} literais (antes viravam %7B%7B, e a Meta nao trocaria). Chave solta continua

codificada.

  • action.php + index.php: depois de salvar campanha de origem Meta, o aviso na lista mostra o **link

completo** para colar no anuncio (no link curto a Meta nao troca as variaveis).

  • Ajustes da revisao final do gpt-6-astra (sem P1; 2 P2 e 1 P3, os tres aplicados): a tabela de

campanhas ganhou o botao Copiar link completo (anuncio Meta) nas campanhas de origem Meta (o aviso

temporario some ao recarregar); editar.php tambem preenche o UTM Content ao trocar a origem para Meta;

o aviso com o link completo aparece mesmo se o link curto falhar.

  • Nao mudou: as origens facebook/instagram (usadas pelas rotinas automaticas), o link curto das

demais campanhas e o catalogo.

  • Validacao: Sintaxe PHP ok (4 arquivos); funcao do servidor ensaiada (link com as variaveis intactas;

link comum igual ao de antes); JS inline validado em Chrome automatico com os campos e o script reais

da tela: escolher Meta preenche content/medium e mostra o aviso, trocar de origem limpa, texto digitado

e preservado, sem erro de JS. A tela nao foi aberta com login nem salva pelo admin: o salvar de ponta a

ponta fica para o primeiro uso do ADMIN.

[2026-10-06] [BK-505] Push programado: "enviado" so com pelo menos uma entrega

Origem: ADMIN - "resolve isso agora", sobre o achado do gpt-6-astra na revisao do changelog do dia.

Problema: admin/cron/push_agendados.php tratava como sucesso qualquer resposta com a linha

Enviadas: N, inclusive Enviadas: 0 | Falhas: 117, e tambem "nenhuma inscricao". O gestor recebia o

aviso com titulo "Push programado enviado" sem nada ter sido entregue.

Mudanca: nova pa_resultado() le os numeros da resposta do sender. So fica enviado com 1 entrega ou

mais; zero entregas, ninguem para receber ou resposta ilegivel viram erro e o aviso sai como "Push

programado FALHOU", com o resumo na frente ("NENHUMA entrega. Entregues: 0 | Falhas: 117 | Expiradas: 6").

No sucesso o aviso tambem passa a abrir pelos numeros.

  • Validacao: Sintaxe PHP ok; funcao ensaiada com 6 respostas (110 entregues -> enviado; 0 com falhas,

0 sem falhas, sem inscricao, erro fatal e resposta vazia -> falhou); --dry-run do cron sem erro.

Revisao final do gpt-6-astra: P2 (texto de uma falha individual podia ser lido como o resumo)

corrigido - so vale a linha de resumo inteira - e reensaiado com os 2 casos dele. Nenhum

push enviado no teste. A prova real e o agendamento 7 (Cinderela Negra, 07/10 10h).

[2026-10-06] [BK-504] Eventos duplicados no pixel da Meta: etiqueta do Tag Manager pausada

Origem: ADMIN - "corrige o achado no bk504" (eventos extras vistos na extensao da Meta).

Causa (medida): o conteiner GTM-KTLMMVG tinha ativa a etiqueta

FB_CONVERSIONS_API-897210417852424-Web-Tag-Pixel_Template (tipo Meta Pixel, template cvt_5RM3Q, criada

~16 dias antes), apontada para o pixel 897210417852424. Ela disparava em TODO evento do Google no

dataLayer e traduzia o nome: view_item -> ViewContent, purchase -> Purchase, begin_checkout ->

InitiateCheckout, page_view -> PageView; o resto ia com o nome cru (scroll, gtag.config). O id de

evento dela nao e o do site, entao a Meta nao deduplicava: cada pagina de peca mandava 2 PageView e 3

ViewContent (2 sem id de peca), e a compra confirmada podia gerar um segundo Purchase.

Correcao: o ADMIN pausou a etiqueta no painel e publicou a versao 13 do conteiner (conferido no

gtm.js publicado: nenhuma etiqueta cvt_5RM3Q ativa). Nenhum codigo do site mudou: a IA chegou a

bloquear a etiqueta por gtm.blocklist em meta-analytics.php, sem commit, e retirou a linha depois da

pausa (arquivo de volta ao commitado, conferido byte a byte).

  • Validacao em Chrome automatico, pagina real, com a v13 e sem bloqueio no codigo:
  • Meta: PageView, ViewContent (com id) e AddToCart (no clique da sessao). Home: so PageView.

Antes: mais 1 PageView e 2 ViewContent vindos do GTM.

  • GA4 e Clarity: iguais ao que eram. Sem erro de JS.
  • Nao houve compra de teste: o fim do Purchase duplicado fica para conferir na proxima venda.
  • Google Analytics (mesma noite): o ADMIN pausou tambem ...-Web-Tag-GA4_Config e ...-Web-Tag-GA4_Event

(versao 14 do conteiner; so o Clarity ficou ativo nele). Elas reenviavam a G-0JKS304GTN o que o site ja

manda pelo gtag direto. Medido: view_item por pagina de peca caiu de 3 para 1 e o evento gtm.dom

sumiu; page_view segue 1; a propriedade G-587082M9FW nao mudou. Relatorios do GA4 com view_item

vao mostrar essa queda a partir de 06/10/2026 - e a contagem correta, nao perda de visita.

  • Deixam de ir para a Meta (iam so por essa etiqueta, seguem indo ao Google): clique_compra_externa,

clique_compra_externa_sessao, redirecionamento_compra_externa e os eventos sem uso (scroll etc.).

[2026-10-06] [BK-504] AddToCart da Meta so quando o cliente escolhe a sessao

Origem: ADMIN - "pode mudar o AddToCart p disparar so qdo escolher a sessao". Achado na analise do

alerta do catalogo (BK-501), confirmado pelo gpt-6-astra.

Problema: o AddToCart saia ao ABRIR a pagina da peca (meta-analytics.php), com o preco cheio, e so

para peca com pecas.valor_de > 0. Contava visita como intencao de compra e deixava de fora peca com

preco so na sessao (Lisa, Lesa & Louca).

Mudanca:

  • meta-analytics.php: sai o disparo na abertura; entra window.rntFbAddToCart(valor, sessaoId), que

manda o evento uma vez por sessao em cada abertura da pagina, com o id da peca, content_type: 'product'

e id de evento cart_<evento>_<sessao>.

  • peca.php: o clique no cartao da sessao chama a funcao com o preco daquela sessao (o promocional).
  • Efeito esperado nos relatorios: o numero de AddToCart cai bastante (antes era quase igual ao de

visitas) e passa a valer para todas as pecas com sessao.

  • Validacao: Sintaxe PHP ok (2 arquivos); JS inline validado em Chrome automatico na pagina real -

Cinderela Negra: nenhum AddToCart ao abrir, 1 ao clicar na sessao (valor 15, id 1085), 0 no segundo

clique; Lisa, Lesa & Louca: 1 por sessao clicada (valor 22, id 1342), sem erro de JS.

  • Erro de processo nesta rodada: a primeira edicao foi feita ~1 min depois de um push de documentacao e o

deploy a regravou. Reaplicada depois do deploy; nada ficou quebrado no ar.

[2026-10-06] [BK-501] Purchase da Meta: mesmo valor e mesmo id no navegador e no servidor

Origem: ADMIN - "corrige a anotacao antiga e corrige o erro de valores diferentes no meta". Achado

na investigacao do alerta do RNT CLICKS (o alerta em si segue no backlog, aguardando o ADMIN).

Problema: no pagamento por PIX, o Purchase do navegador (js/checkout_pix.js, dois pontos) saia

com 15% do total (o comentario dizia "10%") e eventID = 'mp_<pedido>'; o do servidor (CAPI) saia

com o total e event_id = 'rnt_pedido_<pedido>'. Com ids diferentes a Meta nao deduplica: a mesma

venda podia contar duas vezes, com dois valores.

Mudanca:

  • js/checkout_pix.js: funcao rntPixelCompra() devolve o total do pedido e o id rnt_pedido_<id>;

os dois Purchase e o InitiateCheckout passaram a usar. Pedido que nao e ingresso (ClubeRNT-...)

nao tem evento na CAPI e leva id proprio. checkout_pix.php: so a versao do script (cache).

  • js/voucher.js: cupom aplicado na tela do PIX sem recarregar atualiza tambem o total que o Pixel le

(achado do gpt-6-astra na revisao final; antes sairia o valor sem o desconto).

  • Anotacao antiga corrigida em rnt_valor_midia_pedido() (margem_helper.php), conversao_meta_helper.php

e confirm_pagamento.php: "venda cheia" e o total realmente pago (preco promocional x quantidade),

nao a inteira nem a margem. Saiu o que nao se sustentava (mandar margem "nao melhora nada", "corta

alcance", a conta de "82%"); ficou o que sustenta a decisao. Nenhum valor mudou nesses tres arquivos.

  • Decisao (revista com o gpt-6-astra em 06/10): a Meta recebe a venda paga; a comissao de 20% continua

sendo a regua interna (CPA-alvo e painel). A Meta nao cobra percentual sobre o valor do evento.

  • Catalogo de produtos nao foi tocado (admin/cron/feed_facebook_catalogo.php segue publicando

inteira e promocional).

  • Validacao: Sintaxe JS ok (node --check), Sintaxe PHP ok (4 arquivos). rntPixelCompra() ensaiada

fora do navegador: pedido 73961 total 120 -> valor 120, id rnt_pedido_73961; ClubeRNT-55 -> id

proprio; total zero/ausente -> 0. O total do checkout (rnt_order_gateway_unit_value() * qtd) e o

mesmo numero que o servidor grava em rnt_conversoes.valor_total. Monitor de integridade do Mercado

Pago pausado so para os dois arquivos e selado de novo ao fim. Nao houve compra real de teste: a

conferencia no Gerenciador de Eventos fica para a proxima venda por PIX.

  • Complemento (06/10 noite, pedido do ADMIN): o Purchase do servidor passou a levar `content_type:

'product' e content_ids` com o id da peca, como o do navegador ja levava

(includes/conversao_meta_helper.php). Sintaxe PHP ok; nenhum evento de teste foi enviado a Meta.

  • Revisao final do gpt-6-astra: sem P1. P2 do cupom corrigido acima; P3 (comentario) corrigido. O

Purchase sem id que ele achou em pedido_mysqli.php nao esta no ar (o nginx devolve 404 para o arquivo).

[2026-10-06] [BK-500] Programar push da peca para data e hora

Origem: ADMIN - precisa programar um push de peca infantil para o dia seguinte as 10h, na propria

tela admin/modulos/pecas/push.php.

Mudanca:

  • push.php: campo "Ou programar para (hora do Rio)" + botao "Programar notificacao" (act agendar),

que grava em rnt_push_agendados (SQL/rnt_push_agendados.sql) o titulo, texto, link, botao e zona

como estao na tela; painel "Pushes programados desta peca" com status, resultado e Cancelar (act

cancelar_agendamento). Aceita de 2 minutos a 60 dias a frente. O disparo imediato nao mudou.

  • admin/cron/push_agendados.php + /etc/cron.d/rnt-push-agendados (a cada minuto, fora do Git): envia

pelo mesmo sender da tela, com --tipo cliente explicito e --origem peca_agendado (entra no historico

rnt_push_envios), e avisa o gestor por push (so admin) com o resultado.

  • Travas: a linha e "pega" (agendado -> enviando) antes do envio, entao duas rodadas nao mandam o mesmo

push; horario vencido ha mais de 3h nao e enviado (vira erro e o gestor e avisado); peca que saiu do

ar vira cancelado. Tudo em hora do Rio - o php.ini esta em PRC, nada usa date() puro.

  • Validacao (nenhum push chegou a cliente): Sintaxe PHP ok; --dry-run do cron com uma linha vencida

(117 inscricoes na Zona Oeste seriam notificadas, linha intacta); --teste-admin (3 aparelhos de

admin); rodada real com uma linha vencida ha 5h (erro) e uma de peca inativa (cancelado), segunda

rodada sem repetir nada, 0 envio a cliente com origem peca_agendado. Tela renderizada pela CLI com

login simulado: campo e botao presentes, agendar para 07/10 10:00 gravou certo e listou com Cancelar.

Linhas de ensaio removidas; fila vazia.

  • Revisao do gpt-6-astra: sem P1; 3 P2 corrigidos e reensaiados - reenvio do formulario duplicava o

agendamento (agora redireciona depois de gravar e o mesmo pedido nao entra duas vezes); programar e

cancelar sem token de formulario (agora exigem); zona desconhecida virava "todas as regioes" (agora

e recusada).

  • Regra de regiao (ADMIN, 06/10): quem escolheu regiao so recebe peca da regiao dele; quem nao

escolheu nada recebe de todas. O sender ja aplica isso quando recebe --zona; o furo era a tela, que

oferecia "Todas as regioes" e tratava zona vazia/desconhecida como todas - a peca ia para quem

escolheu OUTRA regiao. Agora a zona e obrigatoria no disparo imediato e no programado (sem a opcao

"Todas"; pre-selecionada pela localizacao do teatro). Medido: 152 inscricoes ativas de cliente, 56 com

regiao escolhida, 96 sem preferencia. Ensaiado com o sender desligado: sem zona, "Todo o RJ" e zona

desconhecida sao recusados; com zona segue.

  • Zona automatica (ADMIN, 06/10): a zona nao e escolha de quem dispara, sai do bairro do teatro.

includes/rnt_zonas.php (rnt_zonas_do_bairro) cobre 162 dos 184 teatros; os outros (Niteroi,

Saquarema, Macae, bairro em branco) nao tem zona entre as que o cliente pode marcar, e so nesses a

tela ainda pede a zona. Na tela a zona virou texto fixo; o que vier no POST e ignorado.

Barra, Recreio e Jacarepagua valem como "Zona Sudoeste" e "Zona Oeste" ao mesmo tempo, num envio

so: o site nunca explicou a diferenca ao cliente e sempre ficava alguem de fora. Para isso o sender

(/www/rnt-push-sender/send.php, fora do Git; backup *_BK-500-multizona) passou a aceitar lista em

--zona. Simulado: Zona Oeste 118, Zona Sudoeste 119, as duas juntas 123 inscricoes; zona invalida

dentro da lista e recusada. Ensaio da tela: pecas 1085 e 1024 (Barra) -> Sudoeste + Oeste, 1634

(Centro) -> Centro, 1028 (Niteroi) -> pede a zona; POST forjado com outra zona grava a do teatro.

  • Nao validado: a tela no admin real com login, e um envio programado real a clientes.

[2026-10-06] [BK-497] Selos e video tambem no modo Editar (borracha)

Origem: ADMIN - o botao "Editar (borracha)" das fotos tambem precisa dos botoes de gerar video.

Mudanca (gestor-arte-promo.js, inc-gestor.php, arte_promo_lib.php): no modo Editar, quando a

foto e a arte padrao (600x1067), aparecem os 3 botoes de selo, o tamanho, o Mover (para arrastar o

selo) e "Gerar video para Storie". Se a foto ja tem posicao de selos gravada (rnt_pecas_selos), os

mesmos selos ligam sozinhos no mesmo lugar - o video sai igual ao que alterna na pagina. Os precos dos

selos vem do cadastro da peca (arte_promo_dados passou a devolver tambem selos_cfg). Foto que nao e

a arte padrao (banner, foto de cena) continua so com a borracha.

  • Validacao: Sintaxe JS ok, Sintaxe PHP ok; navegador headless com o HTML real do Gestor: arte padrao

(ligar 2 selos, arrastar, pedir video com a posicao nova, salvar copia), banner 1128x634 (sem selos nem

video), arte com posicao gravada (3 selos ligados sozinhos, tamanho 134, Salvar desabilitado ate algo

mudar) e saida da edicao. Zero erro de JS. Nao testado no admin real.

  • Limite: a base do video e a foto como esta. Se ela ja tem um selo desenhado, os selos animados

precisam ficar em cima dele (e o que acontece quando a posicao gravada e reaproveitada).

[2026-10-06] [BK-498] Salvar campanha adota a "(Auto-detectada)" em vez de recusar

Origem: ADMIN - em admin/modulos/campanhas/incluir.php, ao salvar a campanha cwa a tela disse que

ela ja existia; perguntou se tinha sido criada sozinha.

Causa (medida no access log e no banco): foi. O rastreamento (tracking/save_tracking.php) cria uma

campanha "(Auto-detectada)" quando alguem abre um link com utm_campaign desconhecido. O link ja estava

no WhatsApp: 19:45:44 o robo da Meta buscou a previa; **19:47:06 um visitante abriu o link e 19:47:07

nasceu a campanha 77 "Cwa (Auto-detectada)"**; 19:47:14 e 19:47:29 o ADMIN clicou em Salvar e recebeu

"Ja existe uma campanha com este UTM Campaign". Nao houve duplo envio nem gravacao pela tela.

Mudanca (action.php act criar, e o aviso em incluir.php): quando o UTM ja existe e a campanha

existente tem origem = 'detectada', o formulario a adota: mesmo id (os acessos ja registrados

continuam nela), dados do formulario, origem passa a planejada, e o shortlink e criado como numa

campanha nova. Campanha cadastrada por gente continua recusada com a mesma mensagem.

  • Validacao: Sintaxe PHP ok; adocao ensaiada com os helpers reais (db_insert/db_update/db_delete)

numa campanha de teste criada e apagada na hora: origem detectada -> planejada, nome e status do

formulario, mesmo id; repetir o update (0 linha alterada) segue contando como sucesso.

  • Nao testado: o clique real em Salvar no admin. A campanha 77 nao foi alterada - o ADMIN salva de novo.
  • Botao "Campanha" na coluna Acoes da lista de pecas (pecas/index.php): abre

campanhas/incluir.php?origem=pecas&peca_id=<id> ja com nome, link da peca, datas (ate a ultima

sessao), conteudo e termos. Source e Medium ficam em branco para o ADMIN escolher o canal; ao

escolher, o UTM Campaign e o nome ganham o canal no fim (<peca>_<bairro>_<data>_whatsapp_canal),

ate o campo ser editado a mao. Sem isso duas campanhas da mesma peca no mesmo dia colidiam no codigo.

Testado: pre-preenchimento da peca 1024 e, em navegador headless, a troca de canal (5 casos).

  • Corrigido junto: a tela usava date('Y-m-d') puro e o php.ini esta em PRC - depois das 13h do Rio a

campanha nascia com data de inicio e codigo do dia seguinte. Agora campanha_hoje() usa

America/Sao_Paulo. O fluxo vindo dos Disparos de WhatsApp nao mudou de comportamento alem da data.

  • Link curto a mao. O shortlink ja era criado ao salvar (rnt_shortlinks, ex.: campanha 80 ->

rionoteatro.com.br/5s8k), mas a tela redirecionava sem avisar e a lista so mostrava o link para

abrir - o ADMIN colou o link completo de 300 caracteres. Agora: depois de salvar, a lista abre com um

aviso "Link curto para divulgar" e botao Copiar; cada linha da lista tem Copiar ao lado do link curto

(abrir o link conta como clique); e a ajuda da previa diz para preferir o link curto. Testado em

navegador headless (aviso, copia, volta do botao), zero erro de JS.

  • UTM sem espaco nem "+". O conteudo padrao era centro + zona_sul e o termo uma frase com espacos:

no link viravam centro+%2B+zona_sul e promocao+teatro+.... Agora centro_zona_sul e termos unidos

por _. Nada no sistema dependia do texto antigo (procurado no codigo). E quando a campanha nasce do

botao da peca, Content e Term vem vazios (descrevem disparo segmentado de CRM e anuncio pago, nao

post de canal): o link sai so com source, medium e campaign. O fluxo dos Disparos mantem os dois.

  • Campanha 77 (cwa) convertida a pedido do ADMIN: origem detectada -> planejada, nome "Discutindo a

Relacao | WhatsApp Social Post | 2026-10-06", data_fim = 18/10/2026 (fim da temporada da peca 1024),

e link curto criado. Backup da linha em SQL/bkp_campanha77_*.json.

  • Mesmo pedido: opcao canal ("Canal (ex.: Canal do WhatsApp)") no UTM Medium de incluir.php e

editar.php, para separar post no Canal do WhatsApp de disparo de CRM. A coluna e varchar, nada

mais valida o valor.

[2026-10-06] [BK-497] Selos alternando na pagina da peca e video para stories - parte 2

Origem: ADMIN escolheu as opcoes 1 e 3: a capa continua um PNG parado (e o que vai para WhatsApp e

Facebook), os selos alternam por cima do cartaz na pagina da peca, e ha um video vertical para stories.

Pagina da peca (opcao 1):

  • Tabela nova rnt_pecas_selos (SQL/rnt_pecas_selos.sql): uma linha por peca com o arquivo da capa, o

tamanho dele em bytes, a lista de selos e a posicao (fracoes da imagem). Gravada pela aba ao salvar

como capa com 2 ou 3 selos (act arte_promo_selos_salvar); com menos selos a linha e apagada;

excluir a foto apaga a linha.

  • peca.php: quando a capa atual e exatamente aquele arquivo (nome e tamanho), o cartaz ganha um

wrapper do tamanho da imagem e os selos por cima. O que entra faz fade sobre o que esta visivel, que

so some depois - nunca fica sem selo. Trocar a capa desliga a animacao sozinha. Respeita

prefers-reduced-motion. Sem linha na tabela a pagina sai igual a antes.

  • selo_promo.php (publico, so leitura): desenha o selo com o preco atual da peca. So responde para

peca ativa, com selos configurados, e para um tipo da lista dela; o resto e 404. ETag + cache em /tmp.

  • selo3.png (logo) refeito sobre a silhueta do selo 1: era um pouco menor e deixaria aparecer o

contorno do selo fixo do arquivo (2.424 pixels medidos; agora 0).

Video para stories (opcao 3):

  • Botao "Gerar video para Storie": a aba manda a arte sem selo e a posicao; o act arte_promo_video

so valida (PNG 600x1067, peca, precos) e enfileira em admin/runtime/arte_promo_video/.

  • admin/cron/arte_promo_video.php + /etc/cron.d/rnt-arte-promo-video (a cada minuto, fora do Git):

gera com ffmpeg um MP4 1080x1920 H.264 de 13,2 s com faixa de audio muda, confere com ffprobe e so

entao grava (por rename) em arquivos/pecas/<id>/video/<slug>-story-arte-<data>.mp4 - a pasta da aba

Storie. Esse arquivo passa a ser a midia dos stories automaticos da peca (o cron social usa o

video da pasta). Pedido que falha vai para falhas/ e nao se repete.

  • Depois de pedir o video a aba consulta a lista da aba Storie a cada 10 s (ate 3 min) e, quando o

arquivo aparece, atualiza a aba e avisa na tela; o cron manda push ao gestor (so admin) quando o

video fica pronto e quando falha. Pedido do ADMIN depois do primeiro video real (peca 1634, 18:58 ->

pronto 18:59:15): ele abriu a aba Storie 12 s depois do clique e nao viu o arquivo.

  • O ffmpeg nao roda na requisicao web de proposito: o php-fpm encerra requisicao longa e cada clique

prenderia um worker do site (consulta previa ao gpt-6-astra).

  • Validacao: Sintaxe PHP ok (6 arquivos), Sintaxe JS ok.

Pagina real, com uma configuracao de teste ligada por ~1 min na peca 1024 e depois removida: wrapper

igual a imagem, selo em 76,0%/49,0% com 32,0% de largura no desktop (388x690) e no celular (320x569),

3 selos carregados, sequencia de visibilidade sempre com 1 selo (2 so durante o fade), clique sobre o

selo ainda abre a imagem ampliada, zero erro de JS. Sem configuracao: sem wrapper e selo_promo 404.

Video: 3 pedidos numa fila de teste (3 selos, 2 selos, incompleto) - 2 MP4 validos (h264 1080x1920 30fps

yuv420p + aac, 13,2 s, ~700 KB, ~16 s cada para gerar), o incompleto foi para falhas/; quadros

extraidos mostram a alternancia. Enfileirar como usuario www e processar pelo cron: ok. Fila, log e

script do cron nao sao servidos pela web (403/404/404).

Aba em navegador headless: salvar como capa grava a posicao; com 1 selo apaga; sem o check de capa

nao mexe; o pedido de video leva a arte sem selo.

  • Revisao final do gpt-6-astra: sem P1; 1 P2 corrigido - o bloco dos selos no peca.php nao tinha

try/catch, e uma falha da lib ou da tabela derrubaria a pagina de venda. Agora qualquer Throwable

ali so desliga os selos.

  • Nao validado: a aba no admin real com login, e nenhum video foi gerado na pasta real de stories.

[2026-10-06] [BK-497] Selos dinamicos no meio da arte (aba Arte promo) - parte 1

Origem: ADMIN mandou os 3 selos que vao no meio das imagens (De/Por, % OFF, logo) e pediu botoes

para inclui-los, posicionar e dimensionar, com os valores e a % atualizados sozinhos. Em seguida pediu

que os 3 fiquem na mesma posicao alternando com fade ("impressao de video").

Feito nesta parte:

  • arte_promo_lib.php: rnt_arte_selo() desenha o texto sobre os modelos arte_promo/selo{1,2,3}.png

(os arquivos do ADMIN em dobro de resolucao, com a area do texto limpa nos selos 1 e 2). Selo 1 em

Oswald Bold (a mais proxima do modelo entre 4 testadas), selo 2 em Sora ExtraBold; texto encolhe para

caber (testado com R$1.250 / R$899,90). Act arte_promo_selo no action.php (so leitura).

  • Aba: 3 botoes (De / Por, % OFF, Logo), controle de tamanho, arrastar o selo no quadro. Todos os selos

ligados ficam na MESMA posicao e tamanho; com 2 ou 3 ligados a previa alterna entre eles com fade.

Mudar preco ou % refaz os selos junto com o topo e o rodape.

  • O arquivo salvo ainda e um PNG parado, com o primeiro selo ligado.
  • Validacao: Sintaxe PHP ok, Sintaxe JS ok; navegador headless: ligar, arrastar, ampliar, alternancia

(6 estados distintos em 7 s), troca de preco refletida no selo e no rodape (63% -> 75%), salvar, e

preco invalido desligando os selos que dependem dele. Zero erro de JS. Nao testado no admin real.

  • Pendente de decisao do ADMIN: como a alternancia chega ao publico (so na pagina da peca, arquivo

animado como capa, ou video para stories). Ver BK-497 no backlog quando decidido.

[2026-10-06] [BK-496] Mapa de assentos cabe na tela, com o palco alinhado as poltronas

Origem: ADMIN - em /discutindo-relacao, ao clicar numa data, o palco passava do limite da tela e a

disposicao das poltronas estava ruim.

Causa: .teatro tinha width: 1150px !important. No celular (390px) a caixa "Palco" (805px) saia da

tela, o rotulo nem aparecia e o mapa (505px) era cortado; no desktop o mapa ficava pequeno (poltrona

20x22, letra de 8px), colado a esquerda, com o palco mais largo que as poltronas.

Mudanca (peca.php): palco e poltronas viraram um bloco so (.rnt-mapa), do tamanho do proprio

mapa e centralizado; rntAjustarMapa() escala o bloco para a largura disponivel (ate 1,5x quando sobra,

ate 0,7x quando falta; abaixo disso rola para os lados, comecando centralizado) e roda quando o mapa

aparece e quando a tela muda de tamanho. Palco com desenho de palco; letra da poltrona 8 -> 9px;

no celular o espaco entre poltronas caiu de 4 para 2px; legenda "Seleciondo" -> "Selecionado".

Nao mudou: texto, data-id e clique das poltronas - e o texto delas que vai para a compra.

  • Validacao em Chrome headless, clicando numa data real: pecas 1024 (teatro 15, 19 poltronas de largura)

e 1085 (teatro 79, 14), em 1366, 1024, 390 e 360px. Pagina nunca rola para os lados; palco sempre dentro

da tela e alinhado as poltronas (diferenca <= 3px). Desktop: poltrona 30x33, letra 13,5px. Celular 390:

teatro 15 em 0,746x (poltrona 15x16, letra 6,7px), teatro 79 em 1,12x (22x25, 10px).

Fluxo AJAX validado: selecionar 2 poltronas preenche assentosselecionados, "Meus Assentos" e a

quantidade como antes. Peca sem mapa (1634) segue sem mapa. Zero erro de JS. Sintaxe PHP ok.

  • Revisao do gpt-6-astra: 1 P2 (mapa mais largo que a tela ficaria com poltronas inalcancaveis a

esquerda por causa do text-align: center). Nao reproduz: medido em tela de 280px, rolando tudo

para a esquerda a primeira poltrona fica em x=16 (area comeca em 15) e para a direita a ultima fica

em x=250 (area termina em 250) - o navegador alinha pelo inicio quando o conteudo estoura. Tambem

medido: girar a tela (280 -> 700px) e trocar de data reajustam a escala.

  • Limite: no celular, teatro largo como o 15 fica com poltrona pequena (15px). O zoom por pinca esta

desligado no site (user-scalable=no), entao nao ha como ampliar; a alternativa seria rolar o mapa.

  • Visto e nao alterado: na legenda a bolinha de "Disponivel" e azul-escura e a poltrona livre e verde.

[2026-10-06] [BK-495] Cartaz inteiro na pagina da peca (desktop cortava)

Origem: ADMIN - em /discutindo-relacao a arte nova aparecia grande e cortada; o certo e a imagem

inteira, com a altura maxima atual, podendo sobrar espaco dos lados.

Causa: no CSS inline do peca.php a .rnt-gallery tinha 690px fixos e a .rnt-main-image

preenchia tudo com object-fit: cover. Medido em 1366px: arte 600x1067 e banner 1128x634 exibidos

ambos em 531x690, ou seja, cortados (vertical em cima/embaixo, horizontal nas laterais). Sempre foi

assim no desktop - a Lisa, Lesa & Louca tambem estava cortada; no celular ja era contain.

Mudanca (peca.php, so CSS): 690px virou altura MAXIMA; a imagem usa width/height: auto,

max-width: 100%, max-height: 690px e object-fit: contain.

  • Validacao: Sintaxe PHP ok; pagina renderizada em Chrome headless antes e depois, desktop 1366 e

celular 390, em 3 pecas: 1024 e 1342 (600x1067) passam a 388x690; 1085 (1128x634) passa a 531x299;

nenhuma cortada; celular identico ao de antes (320x569 e 320x180).

  • Revisao do gpt-6-astra: 1 P2 corrigido - o teto de 690px na caixa valia tambem abaixo de 768px e

cortaria arte alta em tela de 700px; no bloco mobile a caixa voltou a max-height: none.

  • Efeito colateral esperado: banner horizontal fica mais baixo do que a coluna de informacoes ao lado

(antes ele era ampliado e cortado para preencher os 690px).

  • Nao alterado: peca_pagseguro.php tem o mesmo CSS antigo.

[2026-10-06] [BK-494] Aba "Arte promo" no Gestor de pecas

Origem: ADMIN - perdia tempo no Photoshop so para trocar preco, local e % na arte padrao. Pediu

uma aba no Gestor (ao lado de Fotos, Videos e Storie) para soltar a arte do cliente e sair com o

topo e o rodape, podendo mover, dar zoom e apagar logo de terceiros (Sympla, Eventim, Divertix).

Mudanca:

  • admin/modulos/pecas/arte_promo_lib.php + arte_promo/ (modelo topo.png/rodape.png recortado

da arte da peca 1342 e fontes Sora, licenca OFL): desenha a MOLDURA 600x1067 com o meio transparente.

Fontes ditadas pelo ADMIN: De = Sora Light, Por = Sora ExtraBold, Local e % = Sora Bold.

  • admin/modulos/pecas/action.php: acts arte_promo_dados (de/por/%/bairro da peca) e

arte_promo_moldura (PNG). So leitura; os dois entraram na lista de excecoes do bloco de campos.

  • admin/inc-gestor.php + admin/js/gestor-arte-promo.js: a aba. O navegador encaixa a arte sob a

moldura (arrastar, roda/barra de zoom, "Encaixar inteira"/"Preencher a faixa"), a borracha preenche

a area pintada com as cores vizinhas, e "Salvar" envia o PNG pelo upload_foto de sempre - a arte

entra na aba Fotos; virar cartaz continua sendo "Usar como capa".

  • Check "Usar como capa da peca" (marcado por padrao): ao salvar, a arte ja vira a capa pelo act

marcar_capa de sempre. Desmarcado, so entra nas fotos. Se a troca falhar, a tela avisa que a arte

foi salva mas nao e a capa. Pedido do ADMIN depois do primeiro teste real (o salvar funcionou).

  • admin/js/gestor-foto-zoom.js: na aba Fotos, clicar na miniatura abre a foto cheia (/large/) por

cima do modal; fecha com clique ou Esc. A miniatura e um recorte 348x228 e nao deixava ver qual era.

  • Botao "Editar (borracha)" em cada foto da aba Fotos (pecas_gestor_card_foto): abre a foto na

aba Arte promo em modo edicao - quadro grande no tamanho da propria foto, so a borracha, sem topo e

rodape. Salva uma copia (a original fica); se a foto editada era a capa, o check ja vem marcado e

a copia assume a capa. JPG continua JPG.

  • deletar_foto: excluir a capa deixava a peca sem capa mesmo com outras fotos, e o site caia no

logo. Aconteceu na peca 1024 as 17:25 de 06/10 (o ADMIN excluiu o banner do Divertix; a arte nova

ficou com fot_capa = 0 e o og:image virou logo_2023.png). Agora a primeira foto da ordem

assume. Na 1024 a capa foi reposta a mao (foto 2966) e pecas.imagem/imagem_formato acertados.

  • Primeiro selo do rodape: "Assento marcado" ou "Entrada por fila", conforme a regra predefinida da

peca. A escolha do select "Regras Predefinidas" nao e gravada (so preenche pecas.regulamento), entao

le-se o texto: o item 1.3 das duas regras "sem assento" diz "entrada e por ordem de fila, sem assento

marcado". A tela traz o selo ja escolhido num select editavel e **avisa quando a regra e as sessoes

discordam** - caso real: peca 1342 (Lisa, Lesa & Louca) tem regra "sem assento" e vende com mapa de 27

lugares. CLI: --entrada=assento|fila. Com "assento" a imagem continua identica a aprovada.

  • Peca com preco so nas sessoes (pecas.valor vazio, caso da 1342): a aba usa a proxima sessao a venda.
  • CLI /root/rnt-tools/arte_promo.php passou a usar a mesma lib (imagem identica, 0 pixel de diferenca).
  • Validacao: Sintaxe PHP ok (php83 -l nos 4 .php), Sintaxe JS ok (node --check). Tela exercitada

em navegador headless com o HTML real do inc-gestor.php e um roteador de teste sobre a mesma lib:

dados da peca 1024 preenchidos, arte quadrada e vertical, borracha sobre o logo do Divertix (apagou

em ~1,7 s), desfazer, preencher + arrastar + roda, troca de preco/local, preco invalido bloqueando o

salvar, e o PNG salvo saindo 600x1067. Zero erro de JS. Sem login no admin: action.php responde 302.

  • Nao validado: a tela dentro do admin real (exige login) nem o upload_foto real. Falta o teste do ADMIN.
  • Revisao do gpt-6-astra: consulta antes (lista de excecoes, resposta binaria, cache do JS - aplicados) e

revisao final com 2 P2 corrigidos (salvar com moldura desatualizada; resposta atrasada de outra peca).

  • Defeito antigo corrigido junto (fallout do PHP 8.3, BK-348): os acts fora da lista de excecoes e

sem categorias[] - list-picture e list-video, ou seja, abrir o Gestor - morriam em

implode(', ', $_POST['categorias']) com TypeError; o Gestor abria sem listar as fotos ja enviadas.

Medido no log: excecao as 17:01:32 de 06/10, antes desta edicao (resposta de 20 bytes). O Gestor e

pouco usado (1 POST em 12 dias), por isso passou. Agora sem categorias[] o campo vai vazio, como

era no PHP 5.6. cadastrar/editar continuam recebendo o array do formulario.

  • Limites conhecidos: a borracha so fica boa em fundo liso; arte 9:16 nao cabe inteira na faixa (pedir a

quadrada); arte salva em peca fora de temporada e apagada pelo cron de limpeza de cartaz, como

qualquer foto; o modulo de pecas segue sem CSRF (ja era assim no upload_foto).

[2026-10-06] [BK-492] Push ao gestor a cada disparo de lista de compradores

Origem: ADMIN - "pode me enviar por push tbm sempre que disparar uma lista"; e em seguida: o push

e so do admin, o produtor segue no WhatsApp porque pode nao ter o push liberado.

Mudanca (z_dispara_diario.php): depois do e-mail e do WhatsApp de cada sessao, chama

enviar_push_admin() com "Lista enviada: <peca>" (ou "Lista NAO enviada" quando nada saiu), a sessao,

as contagens de e-mail e WhatsApp e o link da lista. Dedup 0: cada disparo avisa. Envio ao produtor

(e-mail e WhatsApp) e a regra que marca sec_enviolista = 0 nao mudaram.

  • Validacao: Sintaxe PHP ok (php83 -l); o bloco foi ensaiado em script isolado com as mesmas

variaveis e o push de teste chegou (log push_dispatch.log: 3 enviadas, 0 falhas). O cron real nao

foi rodado - reenviaria listas a produtores; a primeira prova em producao e a proxima sessao com

venda (Discutindo a Relacao, 10/10).

  • Achado, nao alterado: $ok_wpp nao e zerado a cada sessao no laco; com e-mail desligado, uma

sessao sem telefone poderia herdar o "ok" da anterior e ser marcada como enviada. Hoje o e-mail

esta ligado nos dois crons, entao nao dispara. BK-493.

[2026-10-06] Ferramenta teatros.php e telefones da lista (peca 1024)

  • /root/rnt-tools/teatros.php (so leitura): buscar, mapa, blocos e assentos, que traduz o

pedido do ADMIN ("B e C centrais", "bloqueio c e f", "C1 a C14 exceto C7") no CSV do sec_mapa.

Conferida contra os dois cadastros do dia: 18 poltronas no teatro 15 e 27 no teatro 79, iguais ao

que esta gravado. Intervalo compara o numero, entao B6-B14 casa B06 (contorna o BK-490).

  • Peca 1024: telefone_lista = 21981154040 (produtor), 21999915554 (ADMIN) e 21999605790

(numero do RNT - assumido; o ADMIN nao disse qual e).

[2026-10-06] Discutindo a Relacao reativada no Teatro dos Grandes Atores (Divertix)

Origem: ADMIN - "atualize a peca https://divertix.com.br/teatro/dr-discutindo-a-relacao de 120 por

45, assentos B e C centrais", com a ordem de trocar o teatro (a temporada anterior era no Vannucci).

Dados (peca 1024, sem mudanca de codigo): teatro 21 -> 15 (Grandes Atores - Sala Vermelha);

sessoes novas 19326-19329 (sab 10/10 20h, dom 11/10 19h, sab 17/10 20h, dom 18/10 19h), R$ 120 ->

R$ 45, 18 lugares cada: bloco central das fileiras B e C (B06-B14, C06-C14); temporada 10/10 a

18/10; oferta 63%; status I -> A; duracao 100 -> 80 minutos e classificacao 12 -> 14 anos, como no

Divertix; sinopse com "10 anos / 500 mil espectadores"; cartaz do Divertix (740x380, horizontal) em

arquivos/pecas/1024/ + tb_fotos. Backup: SQL/bkp_peca1024_20261007_032008.json.

  • Validacao: pagina /discutindo-relacao 200 sem erro, com as 4 datas, 120,00, 45,00 e o teatro novo;

og:image e imagem em 200; Fluxo AJAX validado - act=entradas devolve as 18 poltronas nas 4

sessoes; check_temporada.php sem alerta (inclusive cartaz); peca na home.

  • Nao conferido: o mapa do Divertix exige login, entao as 18 poltronas saem da frase do ADMIN e do

nosso mapa do teatro. O Divertix nao diz a sala; usada a Sala Vermelha, unica do teatro no sistema.

  • email_lista: saiu so bilheteriateatrovannucci@gmail.com (bilheteria do teatro ANTERIOR; o campo

tambem da acesso aos pedidos no painel do produtor - P2 da revisao do gpt-6-astra). Ficaram o interno

e os dois da producao. Nao alterado: ficha tecnica (vazia no Divertix) e telefone_lista (vazio).

  • Mapa conferido depois com a captura do Divertix enviada pelo ADMIN

(arquivos/download/20261006-162115-...png): B06-B14 e C06-C14 aparecem bloqueados la, igual ao

nosso sec_mapa. A mesma captura mostra diferencas no nosso mapa do teatro 15 fora de B/C (BK-491).

  • Doc: secao 10 nova no runbook de cadastro. Achado virou BK-490 (assentos.php e poltrona B06).

[2026-10-06] [BK-489] Trava: push com edicao nao commitada e recusado

Origem: o deploy de um push de docs apagou a edicao viva do BK-488 (ver entrada abaixo).

Mudanca: pre-push em .git/hooks/pre-push (fora do Git; fonte em

/root/rnt-tools/git-hooks/pre-push-rnt). Recusa git push para a main deste repositorio

enquanto houver arquivo versionado modificado sem commit (staged ou nao) e lista os arquivos.

Nao conta arquivo nao rastreado nem docs/, que o rsync do deploy nao regrava (EXCLUDE do

deploy.yml). Se o git status falhar, recusa. Bypass consciente: RNT_PUSH_SUJO=1 git push origin main.

  • Validacao: bash -n ok; em repositorio de teste - arvore limpa passa, .php modificado ou

staged recusa (inclusive em git push real: nada chegou ao remoto), so docs/ modificado

passa, outra branch e outro remoto passam, bypass passa, falha do git status recusa.

  • Revisao do gpt-6-astra: sem P1; 3 P2 corrigidos (falha do git status liberava o push;

bloqueava docs/, que o deploy nao toca; bloqueava remoto sem deploy).

  • Limite: hook de .git/hooks nao e versionado - um clone novo nasce sem a trava (reinstalar com o

cp do cabecalho do script). Protege o push feito nesta VPS, nao push de outra maquina.

  • Correcao em 06/10 17:37: instalada em .git/hooks/, a trava so valeu ate o deploy seguinte - o

chmod 644 do deploy tirou o bit de execucao e o git passou a ignora-la (visto no push do BK-494:

hint: ... ignoredHook). Movida para /usr/local/libexec/rnt/git-hooks/ via core.hooksPath, fora

do alcance do deploy. Retestada: push sujo recusado, push limpo passa. O teste original nao pegou

porque foi feito antes de qualquer deploy rodar por cima.

[2026-10-06] [BK-488] Produtor nao ve mais a coluna Transacao na tela de pedidos

Mudanca (produtor/modulos/pedidos/index.php e action.php): o cabecalho e a celula

"Transacao" (codigo da transacao com link de busca no PagSeguro) so aparecem quando

$_SESSION['CMS_X']['tipo'] == 'admin', mesmo criterio que a tela ja usava para "Acoes" e

"E-mail". Vale para a carga inicial e para as tres listagens paginadas por AJAX do action.php.

  • Editado em 03/10/2026 e ficou no ar sem commit. Em 06/10 o deploy de um push de docs regravou os

dois arquivos com a versao do repositorio e apagou a edicao (14:48:59); reaplicada a partir

do diff no mesmo minuto - arquivos com o mesmo tamanho de antes (19.218 e 27.100 bytes). A causa

virou o BK-489 (trava de pre-push, entrada acima).

  • Validacao: Sintaxe PHP ok (php83 -l nos dois); cabecalho e linhas saem com o mesmo numero de

colunas nos dois perfis (conferido no codigo); 3 acessos reais ao modulo entre 03/10 e 06/10, todos

200, sem erro no log do PHP. Tela nao foi exercitada com login de produtor nesta rodada.

Origem: o ADMIN colou a mensagem do Teatros da Barra ("de 50 por 15", link

bileto.sympla.com.br/event/105219, "Bloqueio c e f") e pediu para deixar documentado.

Dados (peca 1085, sem mudanca de codigo do site): sessao nova 19325, dom 18/10/2026 16:00,

copia da 19224 (R$ 50 -> R$ 15, 27 lugares, fileiras C e F); inicio_temporada/fim_temporada =

18/10; status I -> A; datas da sinopse trocadas ("Dia 18 de Outubro / Domingo as 16h").

O cartaz tinha sido apagado pelo cron de limpeza em 07/09 com pecas.imagem ainda apontando para

ele: reposto a partir do Sympla (large/media/thumb + linha em tb_fotos).

Backup: SQL/bkp_peca1085_20261007_010333.json (o nome sai em horario PRC do php.ini).

Ferramenta: /root/rnt-tools/sympla_evento.php passou a ler link Bileto (antes devolvia

"nenhum evento encontrado"): sessoes, tipos de ingresso, fileiras fora da venda no Sympla,

descricao e cartaz (--cartaz). Doc: §9 novo em docs/RUNBOOK_CADASTRO_EVENTO_PRODUTOR.md.

  • Validacao: Sintaxe PHP ok (php83 -l na ferramenta); pagina /cinderela-negra 200 sem erro,

com 18/10, 50,00 e 15,00; og:image e imagem em 200; Fluxo AJAX validado - act=entradas

devolve os 27 assentos de C e F; check_temporada.php sem alerta; peca na home. O mapa do Sympla

confirma o bloqueio: C1-C14 e F1-F13 fora da venda la.

  • Lista de compradores, por ordem do ADMIN: email_lista = `lista@rionoteatro.com.br,

katiaporto2013@gmail.com (antes so o da produtora); telefone_lista = 21970672515, 21999915554`.

[2026-10-03] [BK-487] Lista digital mostra "comprado por" em pedido transferido

Origem: o ADMIN nao achou na lista digital a compra do aviso de venda do pedido #73957.

O pedido constava, mas tinha sido transferido no mesmo dia: o aviso sai com o nome de quem

comprou e a lista mostrava so o novo titular.

Mudanca (produtor/listaopen.php): quando pedidos.metadados traz cliente_original

diferente do cliente_id atual, a celula do nome ganha (comprado por <nome>), fora do link de

marcar ingresso retirado. Consulta, ordenacao, total e produtor/fechamento.php nao mudaram.

O mesmo texto vai no e-mail do "enviar lista" da tela, que reutiliza o mesmo HTML.

  • Validacao: Sintaxe PHP ok (php83 -l); tres listas renderizadas em 200 sem erro no log -

sessao 19316 mostra o "comprado por", sessoes 19310 e 19278 (sem transferencia) iguais a antes,

totais iguais. Clique de marcar/desmarcar ingresso nao foi exercitado (link nao alterado).

  • Revisao final do gpt-6-astra: sem P1/P2.
  • Conferido na mesma rodada, sem mudanca: pedidos #73955 e #73956 constam na lista de 02/10 e no

fechamento de outubro da peca 1634 (5 pedidos, 9 ingressos, R$ 270,00 bruto).

[2026-10-03] [BK-486] WhatsApp de compra aprovada: "Aprovada a compra de...", local do evento e link sem https

Origem: pedido do ADMIN, a partir da mensagem recebida pelo cliente do pedido #73957.

Mudanca (includes/whatsapp_helper.php, rnt_msg_compra_aprovada_cliente()):

  • "E 1 ingresso para o Evento..." / "Sao N ingressos..." virou

"Aprovada a compra de 1 ingresso para o Evento..." / "Aprovada a compra de N ingressos...";

  • linha nova Local: <teatro> (<bairro>), lida de pecas.teatro -> teatros pela funcao nova

rnt_local_do_evento(). Sem teatro cadastrado, sem conexao ou com erro de consulta a linha some e

a mensagem sai igual - o local nunca impede o aviso;

  • o link de detalhes saiu de https://rionoteatro.com.br/peca.php?id=N#regras para

www.rionoteatro.com.br/peca.php?id=N#regras, a pedido do ADMIN, para nao virar link.

Nao provado: o WhatsApp costuma tornar clicavel tambem o endereco com www.; o ADMIN confere

na mensagem de teste.

Vale para os tres caminhos (PIX/cartao pelo cron BK-478, carteira em action.php, rota PagSeguro em

notifications.php), porque todos chamam a mesma funcao. O rodape (numero do pedido + assinatura,

os ultimos 100 bytes que o anti-spam compara) nao mudou, e os crons que procuram a mensagem no

historico casam por Compra foi Aprovada e #pedido*, nao pela frase trocada.

  • Validacao: Sintaxe PHP ok (php83 -l e php -l 5.6); texto gerado com o pedido real #73957

(singular, com local) e com caso sintetico (plural, peca inexistente, sem a linha do local);

teste enviado so ao numero do gestor, marcado TESTE BK-486, HTTP 200.

  • Monitor MP: arquivo monitorado, baseline selada apos a edicao, --check OK.
  • gpt-6-astra consultado antes (sem P1; P2 preventivo de instanceof mysqli + Throwable aplicado)

e na revisao final (sem P1/P2).

[2026-10-01] [BK-484] Quantidade nao numerica no pedido.php dava fatal no PHP 8.3

Origem: alerta do vigia_erros_runtime - FATAL em pedido.php:105 (15x),

TypeError: Unsupported operand types: string * float.

Quem bateu no erro (medido): um unico IP, 15 POSTs em 17 segundos (15:55:28-15:55:45), dentro de

uma varredura de 147 requisicoes em 3 minutos que sondou swagger.json/openapi.json e injetou lixo

nos formularios de login, cadastro e pedido. Nenhum cliente foi afetado: o ultimo POST legitimo

(15:41) respondeu 200 e nao ha outra fatal no dia. Nenhum outro 5xx nessa varredura e nenhum payload

de exploracao nas URLs; o corpo dos POSTs nao fica no access log.

Causa: $quantity vem do POST e entra em valor * quantity (linhas 105, 116 e o calculo do

pedido mais abaixo) sem checagem numerica. O BK-423 blindou so o valor com (float). No 5.6 texto

valia zero; no 8.3 string totalmente nao numerica e fatal.

Correcao (pedido.php): a validacao de quantidade que ja existia (vazio ou 0) passou a recusar

tambem o que nao e inteiro positivo, com o mesmo aviso "Voce nao escolheu uma quantidade de ingressos".

  • Validacao: Sintaxe PHP ok (php83 -l); condicao isolada (1, 2, 10 passam; texto, negativo,

decimal, vazio e zero recusados); POST real com quantidade invalida direto no vhost respondeu 200

com o aviso e sem fatal nova no log.

  • Nao testado por HTTP: o caminho de compra valida (criaria pedido de teste). A mudanca so

acrescenta casos de recusa.

  • Sem re-seal: pedido.php nao esta na lista do monitor MP (mp_guard_file_paths()).
  • Revisao final do gpt-6-astra: sem bloqueador.
  • Detalhe que engana: a mensagem string * float nao diz qual lado e a string - o lado esquerdo

ja era (float); a string era a quantidade.

  • Ponta aberta: o alerta nao diz de quantos IPs vieram as fatais - BK-485.

[2026-09-30] [BK-374] Debug de pagamento dos conferir_* sai do webroot

O debugLog() de admin/modulos/pedidos_2/conferir_mercadopago.php e conferir_pagseguro.php gravava

debug_mp.txt/debug_ps.txt em __DIR__, dentro do webroot, com id de pedido + transaction_id do MP em

claro (servidos 200 ate a allowlist do BK-376; 404 desde entao).

  • A funcao agora usa error_log() tipo 0, que grava no log do pool rnt

(/www/wwwlogs/rnt83-php-error.log) com prefixo [RNT-DEBUG-MP]/[RNT-DEBUG-PS].

  • Por que nao um arquivo proprio em /www/wwwlogs/: o open_basedir do .user.ini nao inclui

wwwlogs e o @file_put_contents falharia em silencio. error_log tipo 0 nao sofre open_basedir

(provado em CLI: tipo 3 e file_put_contents devolvem false no mesmo cenario).

  • Uma linha por registro e os marcadores do vigia_erros_runtime (PHP Fatal error, PHP Parse error,

PHP_EXCEPTION) neutralizados, para dado vindo do gateway nao virar alerta falso de fatal.

  • Historico arquivado em /www/wwwlogs/rnt-debug-pagamentos/ (750, arquivos 640 www:www), SHA-256

conferido antes de remover os .txt do webroot (estao no .gitignore; o deploy nao repoe).

  • Sem re-seal: os conferir_* nao estao na lista do monitor MP (mp_guard_file_paths()).
  • Validacao: Sintaxe PHP ok (php83 -l) nos dois; teste CLI isolado da funcao com open_basedir

restritivo. Nao testado via FPM (sem GET em /admin/, que alerta o gestor): no primeiro uso do

botao, conferir com grep RNT-DEBUG /www/wwwlogs/rnt83-php-error.log.

  • Outros escritores de dado de pagamento no webroot ficaram no BK-374b.

[2026-09-29] [BK-482] WhatsApp de compra "sem conta" que tinha conta (LID) + alerta diz se o problema e do numero ou do sistema

Origem: alerta do BK-361 as 08:49: #73952 (cartao, cliente DDD 61) sem a confirmacao, com o texto

"e a mesma falha do BK-359 voltando". Nao era: o cron do BK-478 achou o pedido em 3 min e o dono,

o push e o Ads receberam; so o WhatsApp ao cliente falhou, com 422 No LID for user.

Causa (medida): o 422 so sai quando o erro vem do sendMessage() - o isRegisteredUser() tinha

aceitado o numero (journal sem "Numero invalido"). O /check-number mostrou que 5561998021609 e

556198021609 resolvem para o MESMO 143396828086521@lid: o cliente TEM WhatsApp; o WhatsApp Web e que

nao liga aquele @c.us ao LID. Todo numero resolve para @lid hoje, inclusive os DDD 21 que funcionam.

Gateway (bot/whatsapp/server.js):

  • sendWithLidFallback(): envia para o @c.us de sempre; so em No LID for user (nada foi enviado)

repete UMA vez para o id do getNumberId(). Vale para texto, midia e enquete.

  • Antes do reenvio registra LID e variante sem o 9 -> numero do cadastro (o message_create pode chegar

antes do sendMessage() voltar - P1 do gpt-6-astra no desenho). O webhook gravou 5561998021609.

  • /check-number devolve tambem o wid (so leitura).
  • Primeiro desenho (trocar o id pelo resolvido, aceitando so @c.us) nao teria consertado nada: o

resolvido e @lid. Pego no teste antes de fechar.

**Avisos (pedido do ADMIN: "o alerta precisa dizer o motivo ... conferir celular e telefone e se o numero

ja esta com o 9"):**

  • rnt_ca_candidatos_fone(): celular, telefone, cada um com o 9 acrescentado se estiver no formato

antigo, e fixo por ultimo.

  • avisos_compra_aprovada.php: consulta cada candidato no /check-number (rnt_wa_check_consultar_bot,

BK-385); so passa ao proximo quando o WhatsApp CONFIRMA que nao ha conta (falha de envio para, senao

duplicaria - astra). Estado novo sem_whatsapp; o que foi conferido fica em erro.

  • verifica_wa_compra_aprovada.php: acha a mensagem pelo #<pedido>* em qualquer numero e o alerta diz

o motivo: "Cliente sem WhatsApp - nao e falha do sistema" (com os numeros conferidos), "falha no

envio" (nosso) ou "nao entrou na fila de avisos". Aviso preso >30 min em pendente vira alerta.

Validado: node --check e php83 -l; gateway reiniciado 3x sem QR; DDD 21 com --test-send 2x

(recebido pelo ADMIN) sem acionar o reenvio; #73952 entregue as 12:05 pelo reenvio ao LID;

verificador em simulacao ve o #73952 como saiu=SIM | aviso=enviado | ok; 8 casos da regra de

candidatos; simulacao dos dois crons limpa; revisao final do astra sem P1 (3 P2 corrigidos).

Nao exercitado ao vivo: os textos novos de alerta sem_whatsapp/falhou (dependem de um cliente

real sem WhatsApp). Pontas abertas: BK-482b e BK-483.

[2026-09-28] [BK-481c] Modulo Campanhas em utf8mb4 (emoji virava "?") + editar.php quebrado desde 30/04

Pedido do ADMIN: "corrige as outras telas de campanhas tambem" (emoji aparecia como "?") e trocar

o presente do desconto por fogo no titulo sugerido do push por peca.

Causa: admin/modulos/campanhas/includes/db.php conectava em utf8 (3 bytes); o MySQL troca emoji

(4 bytes) por "?". O site principal (config/connect.php) ja era utf8mb4 - o modulo era o unico fora.

Por que nao bastava trocar a conexao: o servidor esta em STRICT_TRANS_TABLES e 4 tabelas gravadas

pelo modulo eram utf8: com conexao utf8mb4, emoji nelas daria ERRO e perderia o registro - inclusive

o rastreio de visitas do site publico, que grava pelo tracking/save_tracking.php deste modulo.

O que mudou:

  • Backup: /root/rnt-tools/backups/dump_bk481c_20260928-181205_utf8.sql.gz (600, fora do webroot).
  • ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci (mesma semantica de

comparacao do utf8_general_ci) em robot_config (9 linhas), campanhas_alertas (0), campanhas (44)

e cliente_tracking (45.140 linhas, 3,2 s); contagem igual antes/depois; lock_wait_timeout=10.

  • includes/db.php: conexao em utf8mb4. campanhas/push.php perdeu o ajuste local, agora redundante.
  • editar.php: usava db_escape(), que so existe no social_squad_helper.php (nao carregado) e devolve

sem aspas - a tela Editar dava erro fatal em toda campanha com UTM desde 30/04/2026 (commit

4b0e72b08). Trocado pelo escape() do modulo.

  • admin/modulos/pecas/push.php: titulo sugerido com 🔥NN%OFF no lugar de 🎁 (commit 02ad23256).

Titulos ja enviados ficam como foram (historico).

Validado: php83 -l; POST real no save_tracking.php com emoji na URL gravou 🔥🎭 e nao criou

campanha (linha de teste apagada); db_update com emoji em campanhas dentro de transacao desfeita;

8 telas renderizadas sem erro (index, dashboard-roi, alertas, detalhes, editar, incluir, redes_sociais,

push). Emoji gravado ANTES desta data nessas 4 tabelas ja tinha virado "?" na gravacao: nao ha como

recuperar.

[2026-09-28] [BK-481] Relatorio de Notificacoes Push em Campanhas (envios, cliques, base)

Pedido do ADMIN: submenu de push dentro de Campanhas "com relatorios de envios, dados e etc";

escolheu gravar cada disparo em tabela e depois pediu clique real, graficos e detalhe por disparo.

O que mudou:

  • SQL/rnt_push_envios_BK481.sql (idempotente): rnt_push_envios (1 linha por disparo) e

rnt_push_envio_itens (1 por aparelho: dispositivo, zonas no momento, resultado, codigo HTTP,

clique). Nao guarda endpoint nem chaves.

  • /www/rnt-push-sender/send.php (fora do Git; backup ao lado): grava o disparo, os itens como

pendente ANTES do envio e o resultado depois; so marca concluido com os itens gravados por

inteiro (senao erro = historico incompleto). Qualquer falha dessa gravacao vai so para

/www/wwwlogs/rnt_push_audit.log - saida Enviadas: N e codigo de saida dos callers inalterados

(testado forcando erro). Novos --origem, --operador, --peca-id. Para cliente, acrescenta

envio_N ao utm_content (o admin de pecas ja poe o bairro ali) e manda o numero do envio no payload.

  • sw.js: no toque, avisa push_action.php (act=clique) em paralelo com a abertura da pagina.
  • push_action.php: act=clique so marca se aquele aparelho recebeu aquele envio (item aceito ou

pendente + endpoint da inscricao), 1o clique vale.

  • admin/modulos/pecas/push.php: passa origem, peca e quem disparou.
  • admin/modulos/campanhas/push.php (novo) + tb_menus #74 "Notificacoes Push" sob Campanhas:

KPIs, taxa de clique, filtro de periodo, grafico aceitas x cliques x visitas dos ultimos

disparos, crescimento semanal da base, aparelhos, zonas, visitas por campanha, funil do banner

(so Apple, com aviso), detalhe por disparo (aparelho, zona, logado, botao x texto, tempo ate o

clique) e aba de alertas internos so para perfil admin.

  • /root/rnt-tools/push_backfill_log.php: importou 310 disparos do push_dispatch.log (5 ao publico,

305 alertas; 622 SUPRIMIDOS pulados; contagem ausente fica desconhecida). Liga campanha antiga por

data E peca. Idempotente.

Validado: php83 -l e node --check; disparo real sem inscritos e com 3 aparelhos (itens

pendente->ok, HTTP 201); clique: 5 cenarios no endpoint + **toque real no celular do ADMIN

(envio #1244, 11 s, "corpo")**; tela renderizada em 6 cenarios, JS inline ok, anonimo -> login.

Revisao do gpt-6-astra: 0 P1, 6 P2 - todos corrigidos (utm_content do bairro, clique durante o

envio, clique em item falho, campanha de outra peca no importador, totais cortados em 200, status

concluido antes dos itens). Pontas no BK-481b.

[2026-09-28] [BK-480] Push de admin nao e mais rebaixado para cliente quando a sessao cai

Achado: o push de teste do vigia do Divulg AI (BK-474) nao chegava ao ADMIN. O celular dele estava

inscrito como cliente: a re-sincronizacao silenciosa do js/rnt-push.js roda em toda pagina e o

push_action.php regravava tipo/admin_id pela sessao do momento. Sem a sessao do admin, a

inscricao do gestor virava cliente sem aviso - perdia os pushes de admin e passava a receber

divulgacao. Com o login do admin no celular, a mesma inscricao (#223) voltou a admin e o teste chegou.

O que mudou: no upsert do push_action.php, navegador que ja e admin segue admin quando a

requisicao vem sem sessao de admin (admin_id = COALESCE(novo, atual)); cliente segue cliente e a

promocao a admin continua pelo login. Consequencia aceita: voltar um navegador de admin para cliente

so corrigindo no banco. Inscricoes de admin hoje, todas do Jorge (admin_id 2): #223 Android, #54 Mac,

#272 Windows.

Validado: php83 -l; o SQL extraido do proprio arquivo em tabela temporaria, 7 casos (1a vez

cliente, promocao a admin, sessao caida, sem login, cliente estavel, 1a vez ja admin); POST real de

re-sincronizacao sem sessao em /push_action.php com a inscricao #223 -> {"ok":true} e continuou

admin (antes rebaixaria); push de teste recebido no celular do ADMIN.

[2026-09-27] [BK-479c] Botao "conferir" diz "sem mudanca" quando nada mudou

Pedido do ADMIN: o "conferir" Mercado Pago/PagSeguro da tela de Pedidos respondia "Status atualizado

de 3 para 3". Agora: "Status confirmado: Paga (sem mudança)" ou "Status atualizado de Aguardando

pagamento para Paga", com o nome do status. A gravacao nao mudou (continua o mesmo UPDATE).

admin/modulos/pedidos_2/conferir_{mercadopago,pagseguro}.php. Validado: php83 -l e php56 -l,

arquivos UTF-8, regra da mensagem testada nos 3 casos (igual, pago, cancelado).

[2026-09-27] [BK-479] Senha do banco fora do codigo; duas paginas a serviam para qualquer um

Pergunta do ADMIN: "senha do banco no codigo e perigoso? e um erro?" Sim. Medido: o usuario do

banco so entra de localhost e a 3306 esta fechada no ufw, entao a senha sozinha nao abre o banco

pela internet - o risco e quem le o repositorio e qualquer outro site da VPS comprometido.

Pior do que parecia: a senha estava em 20 arquivos versionados (a 1a contagem, 12, buscou o

nome do usuario, nao a senha) e dois caminhos a serviam em 200 a qualquer um:

admin/debug_env.php (imprimia a senha e print_r($_SERVER), sem nenhum chamador) e 2 .md de

docs/ (manual_usuario/GUIA_SYNC_BANCO.md, documentacao_tecnica/campanha_track/README_CAMPANHAS_V2.md).

Logs desde 02/04/2026: nenhum acesso externo a eles; de 10/12/2025 a 02/04/2026 nao ha log.

O que mudou:

  • config/db_env.php (novo): leitor puro do .env, so chaves DB_*, sem fallback de senha,

sintaxe 5.6.

  • 8 arquivos que conectam sozinhos trocaram SO o literal da senha por rnt_db_env('DB_PASS')

(usuario/host/nome intactos, por fidelidade): `admin/modulos/pedidos_2/{conferir_mercadopago,

conferir_pagseguro,sync_mercadopago_pendentes}.php, admin/modulos/campanhas/includes/db.php`,

img/cupom/data/config.php, painel_old/{seguranca.php,data/config.php},

PagSeguroLibrary/painel/seguranca.php.

  • 2 .md mascarados. admin/debug_env.php e 16 copias mortas (.bkp, connect.php.bak_,

config_backup_*, geral/remote/path/...) movidos para /root/.rnt_security/bk479_quarentena/

(reversivel); os rastreados sairam do Git. Backups da rodada fora do webroot, de proposito: um

backup ao lado conteria a senha.

Validado: php83 -l e php56 -l nos 9 arquivos; harness sem imprimir segredo (leitor igual a

senha antiga, require resolvendo para config/db_env.php, literal ausente, constante DB_PASS do

cupom igual); conexao real pelo db.php de campanhas; open_basedir inclui a raiz; paginas

vazadas agora 404 / sem a senha. Fora do .env e dos backups dele (BK-459), nenhum arquivo do

webroot tem mais a senha. Endpoints do pedidos_2 nao executados (gravam) - teste do ADMIN no

BK-479b. Erro meu na rodada: uma sondagem HTTP em PagSeguroLibrary/painel/ bateu num FATAL

antigo e disparou 3 alertas ao gestor (conferir a regra do vhost em vez de sondar URL).

Continua valendo: a senha segue no historico do Git; so a troca (BK-375) resolve.

[2026-09-27] [BK-478b] Conferencia com o Mercado Pago a cada 10 min (so leitura)

Resposta a pergunta do ADMIN "se nao aprovar na hora, o sistema confere de novo?": nao conferia.

admin/cron/sync_mercadopago.php (cron /etc/cron.d/rnt-sync-mercadopago) pergunta ao MP pelos

pedidos com sessao a partir de hoje-7 e escreve no log o que diverge do banco. **Nao grava nem envia

nada.** Validado: php83 -l; --pedido=73950 confere; 8 pedidos cancelados conferidos um a um;

janela normal 7 pedidos e --janela=90 62 pedidos, 0 divergencias. Revisao do gpt-6-astra: 3 P2

corrigidos (status 6/7 incluidos, prioridade da tentativa aprovada entre todas, sem teto de 60); o

P1 (webhook sobrescreve pago com tentativa recusada atrasada) ficou no BK-478b como condicao para

liberar escrita.

[2026-09-27] [BK-478] Compra com cartao nunca avisava ninguem: avisos saem de um cron que le o banco

Sintoma: o verificador do BK-361 acusou o pedido #73950 (cartao, 2 ingressos, pago 15:35):

o comprador nao recebeu o WhatsApp de compra aprovada, e o dono tambem nao recebeu a "Nova venda".

Causa: os avisos saiam de dentro do webhook_mp_ingressos.php, e so na TRANSICAO de status

(status_antigo != 3/4). No cartao quem grava o pagamento e o action.php

(efetuacompra_cartao_mp), um segundo antes do webhook; o webhook chegava com o pedido ja pago e

pulava o bloco inteiro: WhatsApp ao comprador, WhatsApp + push "Nova venda" ao dono e

sales_webhook (receita para o ajuste de Ads). A conversao do BK-464 escapou porque ja ficava fora.

O PIX funcionava porque quem aprova e o webhook. Havia mais dois gravadores com o mesmo buraco: a

consulta de status do navegador (action.php) e a tela de Pedidos do admin

(pedidos_2/sync_mercadopago_pendentes.php).

Por que so apareceu agora: as duas vendas de cartao anteriores (#73908 de 31/08 e #73909 de

01/09) sairam como "sem celular" no verificador. O cliente tinha o numero so em clientes.telefone,

e o verificador (como o webhook) so olhava celular - a falha passou calada. Medido hoje: 15.993 de

51.084 clientes estao assim.

O que mudou:

  • admin/cron/avisos_compra_aprovada.php (novo, /etc/cron.d/rnt-avisos-compra, 1 min, php83):

le o BANCO, nao o caminho. Pedido de Mercado Pago pago, com sessao a partir de hoje-7 (janela

pedida pelo ADMIN), vira uma linha por efeito na tabela nova rnt_avisos_compra

(UNIQUE(pedido_id, efeito, destinatario)), com claim por UPDATE (affected_rows=1) e flock.

Efeitos: wa_cliente, wa_dono (uma linha por numero), push_dono, sales_webhook e

alerta_mudanca (pedido que ja foi pago sai de pago -> WhatsApp so ao gestor 21 99991-5554,

sem janela de data, com ate 3 tentativas). Sem retry nos demais: repetir WhatsApp duplica.

Antes de enviar, procura o pedido no historico (#<id>); o que ja saiu vira enviado_previo.

Flags --dry-run, --test-send=<pedido>, --status, --instalar [--confirmo].

  • includes/compra_aprovada_avisos.php (novo): regra de telefone (celular com prioridade,

telefone de reserva, como o BK-383) e textos do dono (copiado do webhook) e do alerta.

  • webhook_mp_ingressos.php: bloco de avisos removido (UPDATE, carteira e conversao intactos).
  • action.php: consulta de status tratava >= 3 como pago (inclusive 6 devolvida e 7 cancelada);

agora IN (3,4).

  • admin/cron/verifica_wa_compra_aprovada.php: mesma regra de telefone e busca pela mensagem DO

pedido (#<id>*), nao so "alguma compra aprovada deste telefone".

  • includes/whatsapp_helper.php: "E(Sao) 2 ingresso(s)" virou "E 1 ingresso" / "Sao *2

ingressos*" (pedido do ADMIN); o mesmo nas mensagens do dono e do alerta.

Implantacao: 132 pedidos pagos de MP com sessao de 180 a 8 dias atras semeados como legado

(nao recebem aviso, entram no alerta de estorno). Dentro da janela quem decidiu foi o historico: os

6 PIX (#73944-#73949) viraram enviado_previo; o #73950 mandou so o que faltava (dono, push,

sales_webhook - HTTP 200). A confirmacao da compradora do #73950 foi enviada a mao antes, as 16:02.

Validado: php83 -l nos 6 arquivos; webhook real do #73950 reenviado (JSON ok, conversao

ja_existia); 1a rodada real conferida pedido a pedido; 2a rodada e duas execucoes simultaneas nao

repetiram nada; --test-send ao gestor; mensagens com 1 e 2 ingressos renderizadas, pedido segue nos

ultimos 100 bytes (BK-412); monitor do MP pausado por arquivo e selado (--check OK).

Revisao do gpt-6-astra: desenho consultado antes (1 P1 de semeadura resolvido pela prova no

historico; 4 P2 absorvidos) e revisao final. O P1 que sobrou - nada consulta o MP sobre pedido

pendente - e anterior a esta rodada e virou o BK-478b. Os P2 restantes estao no BK-478c.

[2026-09-24] [BK-459] .env em 644 no deploy: fechado, e os 13 backups dele tambem

O ADMIN perguntou por que o monitor devolve o .env de 644 para 640 sem avisar, e se dava

para resolver. Dava — e a origem ja estava resolvida sem ninguem ter marcado.

O conserto ja existia desde 20/09/2026 17:15 (commit 0be70ae3d): o rsync ja excluia o

.env, o find . -type f -exec chmod 644 virou find . -type f ! -name '.env' ..., e o

/usr/local/libexec/rnt/publicar_env.sh cria o arquivo em 640 por temporario + rename, antes

de publicar. O backlog e que seguiu marcando ABERTO. Conclusao delimitada, na redacao sugerida

pelo gpt-6-astra: corrigido o caminho conhecido de publicacao insegura do .env; estado atual

640 www:www; ultimo autoheal em 21/09 17:17 e nenhum ate 24/09, com o cron rodando no intervalo.

Os 6 desvios historicos daquele dia nao podem ser atribuidos por arquivo — o log so guarda a

contagem. Essa limitacao virou o BK-459b.

O que o conserto de 20/09 deixou passar, achado hoje: o filtro ! -name '.env' nao casa com

os backups do arquivo. Havia 13 na raiz (.env.bkp_original_, .env_bkp_ e

.env.bkp_security_*), todos em 644, e o deploy os reabria a cada push. Conteudo conferido

arquivo a arquivo: os 13 tem DB_PASS e token do Mercado Pago preenchidos; um tem tambem

GA4_API_SECRET; nenhum tem META_CAPI_TOKEN. Nao vazavam pela web (404, conferido), mas

numa VPS multi-site qualquer usuario do sistema lia.

E um 14o arquivo, que nenhum filtro por .env jamais pegaria: private/rnt.env, em 644,

versionado no Git, com PAGBANK_TOKEN e PAGBANK_BRIDGE_API_KEY preenchidos. Tambem 404 na

web. A permissao foi fechada agora; a credencial estar no repositorio e outro problema, e depende

do ADMIN — BK-473.

Feito: os 13 backups e o private/rnt.env fechados para 640 www:www (zero ainda legiveis por

outros). No workflow, a normalizacao passou a excluir .env, .env.bkp, .env_bkp e

private/rnt.env — e nao .env.* generico, que excluiria tambem os tres .env.example

(modelos sem segredo, que devem seguir 644).

O -exec nao servia para isto. O gpt-6-astra reproduziu: com o comando interno falhando, o

SCRIPT_AFTER ainda terminava em exit 0, apesar do set -Eeuo pipefail — find -exec nao

propaga a falha. Entao o endurecimento virou laco explicito com || exit 1, e ganhou um passo

final que confere o resultado (-perm -004) e aborta o deploy se algum segredo tiver escapado.

Validado em sandbox, com a arvore reproduzida: .env, backups e private/rnt.env terminam 640;

.env.example, peca.php e arquivo em subpasta terminam 644; e, plantando um backup em 644

depois do hardening, o passo de conferencia detectou e abortaria. Mais yaml.safe_load e

bash -n no SCRIPT_AFTER extraido.

Por que o monitor cala, para ficar registrado: e decisao do BK-356. Permissao ele sabe

consertar sozinho, entao corrige e segue; desvio de hash continua alertando normalmente. O

problema nunca foi calar — foi calar sem registrar o que corrigiu, o que transformou uma causa

recorrente (deploy) em algo invisivel. Isso e o BK-459b.

Dois numeros meus que a revisao do gpt-6-astra derrubou, e que eu havia passado ao ADMIN:

  • eu disse que o guard.log nao tinha rotacao. Tem, desde 18/09 (/etc/logrotate.d/rnt-mp-guard,

mensal, 12 arquivos, compress). Ainda nao ha arquivo rotacionado porque o corte e mensal;

  • eu disse "401 autoheals". Nao existem 401: sao 153 linhas METADATA CORRIGIDA + 248

CHECK AUTOHEAL OK, duas categorias diferentes que eu somei num grep so, e que podem pertencer

a mesma execucao. E 153 tambem nao e o total historico (segunda correcao do astra, conferida):

essa linha so passou a existir em 26/08/2026, com o BK-356, enquanto CHECK AUTOHEAL OK

existe desde 07/04 — ha 133 dessas antes da primeira METADATA CORRIGIDA. O honesto e dizer

"153 linhas dessa categoria desde 26/08", sem converter em total de correcoes.

[2026-09-24] [BK-377] feedback pos-evento provado ponta a ponta, por evidencia de producao

O BK-377 pedia um teste de ponta a ponta. **Correcao importante, achada na revisao final do

gpt-6-astra: o teste da campanha 123 ACONTECEU — o ADMIN respondeu.** O texto anterior

deste changelog, e o de PENDENCIAS_ADMIN.md, diziam que ele nao tinha respondido. Errado:

| queue_id | telefone | resposta | quando | contexto |

|---|---|---|---|---|

| 9993 | ****5554 | msg 3444, texto 5 | 07/09 12:19:09 | ctx 1, concluida, estrelas=5, evento 23 |

| 9994 | ****9992 | msg 3449, texto 5, depois Nao | 07/09 12:51:42 | ctx 2, concluida, estrelas=5, evento 1593 |

O que ficou sem explicacao em 07/09: as duas conversas chegaram a etapa=concluida com

estrelas=5 gravadas em post_event_feedback_conversa, mas hoje **nao ha avaliacao do

cliente_id=775** em tb_avaliacoes. As avaliacoes de 07/09 (ids 4 a 11) sao de outros clientes,

do disparo do /root/g.sh.

Investigado depois, e a leitura mudou: provavelmente nao foi falha de gravacao.

save_comment() e chamado antes do INSERT do contexto e lanca RuntimeException se a query

falha — se tivesse falhado, o contexto nao existiria, e ele existe (ordem identica nas duas versoes

do helper daquele dia). E tb_avaliacoes esta com auto_increment=19 e ids 4..11 + 15..18:

faltam 1, 2, 3, que cairiam exatamente antes da avaliacao id=4, de 07/09 13:13. Compativel com

avaliacao de teste criada e depois apagada. Sem log de DELETE, nao e reconstituivel. Registrado

como BK-472, rebaixado de bug para ponta historica.

O que continua certo e o principal: o ADMIN respondeu, e o item passou 17 dias dizendo que nao.

A prova veio de outro lugar: clientes reais, em 16/09/2026. A campanha 136

(post_event_feedback_20260912) enviou 4 mensagens as 15:07-15:08 e a cadeia inteira

esta registrada no banco, item por item:

| queue_id | telefone | enviada | resposta | texto | contexto | avaliacao |

|---|---|---|---|---|---|---|

| 10363 | ****1309 | 15:07:25 | 15:09:43 | 5 | ctx 17, estrelas=5 | aval 17, nota 5 |

| 10365 | ****5834 | 15:08:58 | 15:09:27 | 4 | ctx 16, estrelas=4 | aval 16, nota 4 |

| 10366 | ****0659 | 15:08:59 | 18:29:20 | 2 | ctx 19, estrelas=2 | aval 18, nota 2 |

| 10364 | ****2445 | 15:07:26 | — | — | — | nao respondeu |

Cada elo confere: whatsapp_campaign_queue -> whatsapp_mensagens (enviada) ->

whatsapp_mensagens (recebida, com o numero em claro) -> post_event_feedback_conversa

(estrelas gravadas) -> tb_avaliacoes. Tres notas diferentes (5, 4 e 2), cada uma

batendo com o texto que o cliente mandou. Taxa de resposta 3 de 4.

Desenho revisado pelo gpt-6-astra antes de qualquer acao. Ele recusou aceitar as

tres avaliacoes como prova so por serem do mesmo dia do lote ("indicio, nao prova de

causalidade") e exigiu a cadeia com IDs e horarios — que e a tabela acima. Tambem

corrigiu um erro meu: eu tinha concluido que as notas 2 e 4 derrubavam a suspeita do

BK-450. Nao derrubam. Ler numero corretamente e inventar 5 quando nao ha numero sao

comportamentos independentes; o BK-450 tem aceite proprio (abaixo).

O que este item NAO prova, e fica registrado: a selecao automatica de publico. As

campanhas de feedback de 13/09 a 22/09 (ids 135, 137-139, 145-151) foram geradas e

aprovadas com inserted: 0. A hipotese e falta de publico: de 10/09 a 23/09 so houve

sessao com pedido pago em 12/09 (4 pagos), e setembro inteiro teve 17 pedidos / 10 pagos.

Hipotese, nao causa estabelecida — sem reconciliar elegivel por elegivel, inserted: 0

correto e inserted: 0 por bug sao indistinguiveis. Aceite separado, anotado no BK-452.

Limite de escopo, apontado pelo gpt-6-astra: o BK-377 original tambem cobria a

exibicao no PalcoFan (primeiro nome, evento fora de cartaz — ver a entrada de 07/09 sobre

o fluxo de 2 passos e a inversao do default de exibir_nome). A prova nova termina em

tb_avaliacoes: ela cobre envio -> resposta -> gravacao. As avaliacoes 16 e 17 estao

aprovado e a 18 segue pendente. **A exibicao publica dessas tres nao foi verificada nesta

rodada** — o que vale para ela sao as validacoes anteriores, nao esta.

Nenhuma mensagem foi enviada para fechar este item. O global_kill_switch continua

1, com updated_at de 18/09/2026 21:49 intacto.

[2026-09-24] [BK-450] confirmado por teste isolado: o parser nao inventa nota

O BK-450 ja estava entregue em 19/09, com testes de fluxo registrados naquela entrada.

O que faltava nao era toda a prova, e sim atualizar PENDENCIAS_ADMIN.md, que seguia

avisando o ADMIN que "qualquer resposta sem numero vira nota 5" — o que nao e mais verdade

desde 18/09. Este e um teste adicional, de unidade, sobre a funcao de parse.

rnt_post_event_feedback_extract_rating() foi exercitada isoladamente, **sem banco, sem

envio e sem gravacao**, em 25 entradas. Todas corretas:

  • nota explicita: 5->5, 4->4, 2->2, 1->1, nota: 3->3, nota 4->4, 4/5->4,

4 estrelas->4, 5 - Excelente->5, cinco->5, minha nota e cinco->5

  • sem nota, o caso do bug — otimo, adorei o espetaculo, muito bom!,

gostei muito, parabens, Nao, Zona Sul, vazio: todos 0, nenhum vira 5

  • armadilhas: dou 2 ingressos->0, nao dou 5->0, nota 4/10->0, dia 4/5->0
  • fora da escala: 0->0, 6->0, 10->0

O 0 significa "sem nota reconhecida". Nao significa que o sistema sempre volta a pedir

(ressalva do gpt-6-astra): ha filtros antes, silencio na reconciliacao e limite de tentativa

de aviso. O teste cobre a funcao de parse, nao a politica de repergunta.

Ressalva honesta: as tres avaliacoes de 16/09 sao anteriores a correcao de 18/09 e

todas continham numero explicito, entao elas nao exercitam o caminho do bug. Quem prova o

BK-450 e o teste isolado acima, nao elas.

[2026-09-24] Campanha 141 cancelada: 181 promocionais represadas ha 8 dias

lisa_prorrog_lote2_20260916 (prorrogacao de Lisa, Lesa & Louca) ficou com 181 linhas

pending desde 16/09, seguradas so pelo kill switch. Era **o unico lote que dispararia

sozinho** no instante em que a flag caisse: o dispatcher so despacha campanha approved

ou running (includes/whatsapp_campaign_queue.php:831), e ela estava running.

Recomendacao do gpt-6-astra, seguida: nao liberar como estava. Oito dias exigem

revalidar datas, preco, link, urgencia do texto e quem ja comprou ou pediu SAIR.

Antes de cancelar, o registro foi preservado: as 200 linhas (com message_body)

exportadas para /www/wwwlogs/rnt-campanhas-preservadas/campanha_141_saldo_20260924_171029.csv,

fora do webroot, modo 600. Cancelamento pela ferramenta oficial, mirando por ID e nao

por chave (campaign_key nao e unica — ver BK-461). Depois: 181 cancelled, 17 sent e

2 blocked preservadas, campanha cancelled.

A reversao e manual, e o CSV nao e backup integral (ressalva do gpt-6-astra, conferida):

resume so recupera linha paused, nao desfaz cancelled; e o CSV e um recorte de 8 colunas,

sem os demais metadados da fila nem o estado da campanha. Retomar essa promocao significa

campanha nova com publico e texto revalidados, preservando os 17 enviados e os 2 bloqueados.

Ganho colateral, medido as 17:15 de 24/09/2026: naquele instante, desligar o kill switch

nao dispararia nada. O que sobrava era 363 linhas de campanhas imported (sem approved_at,

o process devolve campaign_not_approved_for_real_send) e 307 paused da campanha 5, de

abril. A flag deixou de ser a unica coisa entre a fila e 181 clientes.

Isso e um inventario do instante, nao uma garantia futura (ressalva do gpt-6-astra): o

cron das 19:00/19:02 gera e aprova sozinho a campanha de feedback do dia. Havendo publico,

amanha existe fila aprovada de novo. Conferir a fila antes de afirmar silencio, sempre.

[2026-09-21] [BK-471] Add Peca com IA ja traz as sessoes da Sympla

Pedido do ADMIN depois de cadastrar a Labios que Beijei (peca 1634) a mao: "melhorar essa pagina

pra ja pegar as sessoes tbm". Desenho proposto pelo gpt-6-astra antes de implementar e revisado por

ele no fim (sem P1; os dois P2 foram corrigidos na mesma rodada).

Fonte. O endpoint publico event-page.svc.sympla.com.br/api/event-bff/purchase/event/<id>/tickets

devolve um grupo por apresentacao ("03/out - sabado, 19h") com os ingressos e o preco sem taxa

(netValueMonetary). O inicio da sessao e o endDate do ingresso, conferido contra o nome do grupo;

divergencia vira linha "Conferir". Link do Bileto nao e lido (outra API, exige chave).

Fluxo. add_ia.php mostra as sessoes no preview e guarda um rascunho separado na sessao PHP ->

o form da peca avisa -> ao salvar, action.php (cadastrar/editar) manda para

sessoes.php?id=X&ia_sessoes=1 -> o admin confirma inteira, promocional, associado e quantidade ->

act=importar_sessoes_ia grava. Arquivo novo: admin/modulos/pecas/add_ia_sessoes_lib.php.

Regras. Importacao so aditiva (nunca altera, remarca ou apaga sessao; pedidos.data casa

por datetime exato). Lista de sessoes vem da $_SESSION, nunca do POST. CSRF no import. GET_LOCK

por peca (a tab_sessoes e MyISAM sem indice unico). Preco da Sympla e so referencia. O rascunho

leva um token que o form da peca devolve, para a aba errada nao herdar as sessoes de outra; lote

parcial mantem as sugestoes nao marcadas.

Dois bugs corrigidos no caminho:

  • criar_sessao/editar_sessao por dia da semana: o laco zerava $camp/$val com "" e voltava a

usar [] -- no PHP 8 e fatal a partir da segunda sessao (gravava 1 e quebrava). Fallout do

BK-348 ainda nao disparado: o debug.log nao tem criar_sessao desde 01/08.

  • Abrir peca ativa pelo Add IA e salvar a desativava: o rascunho traz status = 'I' e o

detalhe.php aplicava. Agora o status do banco e mantido em peca existente.

Validado: php83 -l nos 5 arquivos; lib contra o banco real (10 sessoes da 1634, validacoes,

recusa de outra peca, reenvio sem duplicar, insert real numa peca inexistente 999999 com as linhas

apagadas em seguida, lote parcial e troca de aba); telas renderizadas logadas por CLI (preview,

rascunho gravado, detalhe mantendo Ativo, painel em sessoes.php). Nao validado ponta a ponta:

o POST de importar_sessoes_ia logado (teste do ADMIN no navegador). Pontas abertas no BK-471.

[2026-09-21] Dois incidentes causados por mim nesta rodada, e o que ficou corrigido

1. 33 alertas do monitor do Mercado Pago, madrugada inteira

O que aconteceu. Do commit do BK-464 em action.php (20/09 22:31) ate o selo de 21/09 12:05, o

monitor de integridade ficou em drift e mandou 33 alertas, a cada ~31 minutos, para 2 numeros de

WhatsApp mais push -- incluindo 02:15, 02:46, 03:17 da manha. Os drifts eram action.php,

webhook_mp_ingressos.php (os dois do BK-464/464b) e .env (as chaves do GA4 e da Meta).

Por que e grave. Nao por risco -- eram alteracoes minhas e autorizadas --, mas porque alerta de

seguranca que dispara 33 vezes por coisa conhecida ensina o gestor a ignorar o proximo. E eu

tinha construido a pausa do monitor (BK-455) exatamente para isso, horas antes, e nao usei. A

regra ja estava na memoria do projeto ("re-seal obrigatorio apos editar arquivo monitorado").

Verificacao antes de selar -- selar sem conferir anula o monitor:

  • action.php e webhook_mp_ingressos.php identicos ao HEAD, vindos de tres commits do BK-464;
  • zero drift ENV: o monitor confere o valor de cada credencial do MP por HMAC, separado do

hash do arquivo. Nenhuma credencial do Mercado Pago mudou;

  • o conjunto de chaves do .env e identico ao que o deploy.yml escreve (38 chaves).

Selado as 12:05; o cron confirmou CHECK OK as 12:06.

2. 50 vendas historicas foram para a Meta com data de 20/09

O que aconteceu. No primeiro ciclo depois de o META_CAPI_TOKEN entrar no .env (21/09 01:08),

a CAPI mandou 50 compras reais de 04/06 a 12/09/2026, R$ 3.073, todas com event_time de

20/09/2026 22:30 -- um pico de vendas que nunca existiu.

Causa. A data do evento caia para recebido_em quando aprovado_em era nulo. recebido_em e

quando a reconciliacao criou a linha, e a cadeia nasceu em 20/09 -- entao toda conversao

historica tinha recebido_em de ontem. O guarda de 7 dias media a idade pelo mesmo campo e deu

zero dia. O cabecalho do arquivo dizia desde o comeco "nao envia conversao historica como se fosse

de hoje". Estava escrito e nao estava implementado.

Correcao (da57ab46b): data do evento = aprovado_em, senao pedidos.created; recebido_em

nunca. A semeadura da fila usava o mesmo campo e foi corrigida junto. A conversao que restava

(compra de 12/09) passou a ser recusada com evento_antigo. Nada saiu depois da correcao.

Nao da para desfazer -- a Meta nao apaga evento enviado. O dano pratico e limitado porque **nao

ha campanha ativa desde marco**, entao nenhum lance foi decidido em cima disso. Mas o conjunto de

dados 897210417852424 guarda o pico, e **ao voltar a anunciar, os primeiros dias de relatorio da

Meta vao mostrar 50 compras que nao vieram de anuncio nenhum**.

[2026-09-20] BK-461 etapa 1 — extrato auditavel de vendas, e o ROI antigo marcado como invalido

Desenho pedido ao gpt-6-astra antes de escrever codigo. Ele recomendou aposentar a procedure

como motor de calculo, nao corrigi-la -- e comecar pelo menor corte que ja elimina falsa precisao.

Foi o que entrou.

admin/modulos/campanhas/includes/margem_helper.php calcula o periodo direto de pedidos,

sem JOIN com eventos de navegacao, e com valor qtd -- porque pedidos.valor e o *preco

unitario**, nao o total do pedido (o metadados repete o mesmo numero em valor_unitario). Eu

mesmo tinha errado isso na primeira medicao: somar so valor deu R$ 384 onde havia R$ 752.

Com o imposto definido pelo ADMIN como incidente sobre a comissao, a tela mostra: pedidos,

ingressos, vendas brutas, ticket, comissao (20%), imposto (10% da comissao) e **contribuicao antes

do MP (18% da venda). Deixa escrito que vendas brutas nao sao receita do RNT** -- 80% e repasse.

O que nao da para saber sai como —, com o motivo na tela, nunca como zero: a taxa do Mercado

Pago nao e guardada em nenhuma coluna de pedidos (precisa vir do pagamento), e o custo de midia

nao e importado desde maio/2026 (GA4) e marco/2026 (Meta Ads). A tela lista a cobertura real de

cada fonte. Denominador zero devolve indefinido, nao zero.

O bloco antigo ganhou aviso em destaque: nao serve para decidir investimento, porque multiplica

receita no JOIN, compara investimento total com receita diaria e consolida por media de

percentuais. Ele continua visivel como historico.

Validacao: Sintaxe PHP ok nos dois arquivos; o helper conferido contra a medicao manual (365

dias: 198 pedidos, 397 ingressos, R$ 10.829,70 — identico); periodo sem venda devolvendo ticket

indefinido em vez de zero; e a tela renderizada com sessao simulada (37 KB, os quatro blocos

presentes, sem erro no HTML).

Segue no BK-461, com o desenho ja escrito: motor determinista publicando por execucao, atribuicao

persistida com um evento por pedido, separacao de data de criacao/pagamento/toque, tratamento dos

265 registros historicos (parte deles ficara com ROI permanentemente desconhecido), periodo

aplicado aos indicadores legados, leitura da view instalada e a falha de recalculo que hoje nao

impede a geracao de alertas.

[2026-09-20] Campanhas: dois endpoints sem login tirados do ar, e o mapa do que existe

Levantamento a pedido do ADMIN (anuncios automaticos e melhoria das telas de campanhas). O que foi

feito hoje; o resto virou BK-460 a BK-463.

Dois endpoints respondiam HTTP 200 sem login em admin/modulos/campanhas/:

debug_db_structure.php e update_db_schema.php. Eram restos de ambiente local (XAMPP), com

root/root escrito no codigo, e fatalavam com stack trace expondo o caminho absoluto do servidor.

O update_db_schema.php rodaria ALTER TABLE se a conexao tivesse funcionado — na VPS ela e

negada, entao nao houve DDL. Os dois foram para /www/rnt-quarantine/20260920-campanhas/ (nao

apagados) e agora respondem 404.

Medicoes que mudam as decisoes seguintes:

  • ultimos 30 dias: 16 pedidos pagos, R$ 384,00, ticket R$ 24,00, em 2 pecas (de 8 com

parceria_online=1). A 20% de comissao, R$ 76,80 de margem bruta no mes antes de taxa e imposto;

  • a importacao de metricas esta morta: GA4 parou em 20/05/2026 e Meta Ads em 31/03/2026. A

causa nao e bug de codigo — as quatro variaveis do Google estao MISSING no .env, porque o

deploy recria o arquivo a partir dos 36 Secrets e elas nao estao la (mesmo padrao que apagou as

credenciais da Cloud API em agosto);

  • duas chaves privadas de service account estao versionadas no Git; o .gitignore cobre a

pasta campanhas_v2, nao a campanhas. O nginx nao as serve (404 — a allowlist do BK-376

esta aplicada e funcionando, ao contrario do que o PENDENCIAS_ADMIN.md ainda dizia);

  • a conta de ROI de hoje esta errada, conferida por mim na procedure instalada: soma receita

sobre o JOIN com eventos (receita multiplicada), compara investimento TOTAL com receita diaria, e

o dashboard consolida com AVG(roi_percentual). Virou BK-461, e bloqueia o BK-460 — decidir

gasto por um ROI errado seria pior do que nao decidir.

Sobre a pergunta do acompanhamento de qualidade (historico de 30 dias, motivos de qualidade

baixa/media, qualidade do modelo): isso e metrica da WhatsApp Business Platform, que a Meta

calcula para quem usa a Cloud API. Nao usamos — nosso envio e o servico local. Nao existe hoje, e

nenhuma tela poderia mostrar: falta a fonte, nao a interface. Um painel interno de 30 dias e

possivel (tentativa, aceite, opt-out, falha, resposta, compra atribuida), mas sent na fila **nao

prova entrega nem leitura**, e blocked interno nao prova bloqueio pelo destinatario. Se for feito,

tem que se identificar como metrica interna, sem imitar a nota oficial da Meta.

[2026-09-20] BK-459 — o .env deixou de ser publicado em 644

Desenho do gpt-6-astra, pedido antes de escrever codigo. O ponto que mudou o plano: **excluir o

.env do rsync nao bastava**, porque o SCRIPT_AFTER roda find . -type f -exec chmod 644 no

que ja esta no servidor, tenha vindo no deploy ou nao. Eram duas fontes do 644, nao uma.

Publicador proprio, fora do webroot (/usr/local/libexec/rnt/publicar_env.sh, root:root 700):

  • valida antes de publicar: 9 chaves obrigatorias com valor, minimo de 20 variaveis e **nenhuma

duplicada** — duplicada importa porque o consumidor (config/connect.php:16) fica com a

primeira ocorrencia, entao chave repetida publica um valor diferente do que se pensa ter

publicado. Nenhum valor e impresso em lugar nenhum, so nome de chave;

  • dono e modo finais sao aplicados ao candidato ANTES de ele existir com o nome final, e a

publicacao e por rename no mesmo filesystem: o nome final sempre aponta para um arquivo

completo, o antigo ou o novo. Nada de sobrescrever o .env vivo in-place;

  • guarda o .env anterior no cofre /www/rnt-secrets/ (fora do Git e do webroot, 600 root);
  • confere no fim que o www le o arquivo publicado e que o modo e 640; se nao for, falha alto.

Modo de falha escolhido: qualquer validacao que falhe deixa o .env antigo valendo. Deploy

sem segredo novo e ruim; site sem segredo e pior.

No workflow (.github/workflows/deploy.yml):

  • o .env deixou de nascer dentro da arvore do rsync: e montado em $RUNNER_TEMP/rnt.env com

umask 077 (36 linhas trocadas), e /.env/ entrou no EXCLUDE;

  • o find de normalizacao passou a pular o .env, com chown www:www + chmod 640 explicitos

logo em seguida;

  • passo novo depois do rsync e da correcao de permissoes: sobe o candidato por scp para

/www/rnt-secrets/incoming.env, poe 600 e chama o publicador. A chave de deploy e escrita com

umask 077 e apagada no fim.

Validacao (no servidor, com o publicador ja instalado): candidato incompleto recusado, com a

lista de chaves faltando; candidato com chave duplicada recusado; --dry-run aprovando o

.env real (36 variaveis, 9 obrigatorias); e publicacao real de uma copia fiel do .env atual

— hash identico antes e depois (60449b83…), www:www 640, backup criado no cofre e candidato

removido. bash -n no publicador e YAML do workflow conferido com parser, listando os 8 passos na

ordem certa.

O que so o proximo deploy prova: o caminho scp + ssh a partir do runner. Se falhar, falha

alto e o .env antigo continua no ar — mas fica registrado que esta parte nao pode ser testada

daqui.

Nao entrou, e segue no BK-459/BK-458: mover o .env para fora do webroot (/etc/rnt/) com grupo

proprio de leitores, e trocar a normalizacao recursiva por politica por classe de arquivo. Enquanto

o diretorio do projeto for gravavel pelo www, um .env ali pode ser substituido mesmo com dono

e modo corretos.

[2026-09-20] BK-458 etapa 1 — o monitor do MP saiu do webroot

Desenho pedido ao gpt-6-astra antes de escrever codigo. A frase dele que definiu tudo:

arquivo root:root dentro de diretorio gravavel pelo www nao satisfaz a fronteira — quem pode

escrever no diretorio substitui o arquivo. Entao chown root nos scripts seria conserto de

mentira.

O que mudou:

  • a copia que o cron roda como root agora vive em

/usr/local/libexec/rnt/monitor_mercadopago_integrity.php (root:root 700, diretorio `root:root

755), fora do alcance do www. Conferido no fim da instalacao: www` nao le o arquivo e

nao escreve no diretorio;

  • o repositorio continua sendo a fonte (versionada, revisavel). A promocao e ato deliberado:

/root/rnt-tools/instalar_monitor_mp.sh mostra hashes e diff, roda php83 -l antes, guarda a

versao anterior e troca por rename. Nada de sincronizar sozinho — copiar automaticamente o

arquivo vivo devolveria exatamente o problema que isto resolve;

  • os subprocessos de envio largaram o root: rodam como www (sudo -n -u www) e recebem a

mensagem por STDIN. Antes eu passava por arquivo no storage do monitor, que e 0700 root —

abrir aquele diretorio para o filho seria furar a propria fronteira;

  • RNT_MP_REPO_ROOT diz ao monitor instalado onde fica o projeto vigiado.

Dois defeitos que os testes acharam, os dois meus:

  1. caminho de projeto errado virava alarme de invasao. Rodei a copia instalada sem a variavel:

o monitor deduziu /usr/local, nao achou nenhum arquivo monitorado e mandou *"10 alteracoes

detectadas"* — 2 alertas reais ao gestor por erro de operacao. Agora ele confere marcadores

do projeto (/admin/cron, /config/connect.php) e aborta com erro em vez de alertar;

  1. o filho como www morria antes de enviar. O mp_guard_ensure_storage_dir() roda no inicio

e o www nao tem acesso a /root/.rnt_security — resultado: saida=1, sem envio. Os modos de

envio passaram a ser despachados antes de qualquer coisa que exija o storage.

Inventario que ficou pronto para as proximas etapas: das 19 rotinas que rodam como root a

partir do webroot, 17 nao tem necessidade aparente de root. So o watchdog_systemd.sh

(systemctl) e o verifica_wa_compra_aprovada.php (escreve em /root) precisam de tratamento

especifico; duas outras so escrevem log do painel bt. Detalhe e plano por familia no BK-458.

Validacao: Sintaxe PHP ok; bash -n no instalador; instalacao conferindo que www nao le nem

escreve; copia instalada rodando --check, --status e --heartbeat como o cron roda; execucao

sem a variavel abortando com saida 2 sem alertar; filho executado como www recusando

mensagem vazia sem enviar. Nao testei o envio real pelo fallback como www — seriam mais

mensagens ao gestor, e o canal que entrega de fato e o direto. Fica registrado como nao coberto.

Custo desta rodada: 2 alertas (total do dia: 10), os do defeito 1.

Etapa seguinte, ja desenhada e nao feita: BK-459 (publicar o .env sem janela, tirando o find

destrutivo do deploy) e a migracao das rotinas de negocio para www, uma primeiro e depois lotes.

[2026-09-20] BK-457c — ultima volta do monitor: 6 correcoes, incluindo uma regressao minha

Segunda verificacao do gpt-6-astra sobre o 95f3f3a36. Dos 9 pontos anteriores, **4 estavam

resolvidos, 3 parciais e 1 virou regressao minha**. Corrigidos:

  1. Regressao [reproduzida por ele]: eu gravava last_attempt_failed_at antes do envio, e o

heartbeat lia qualquer valor ali como falha consumada. Com os dois crons se cruzando a cada 10

minutos, ele reproduziu o heartbeat acusando "alerta nao entregue" **no meio de um envio que

estava indo bem** — e isso ainda queimava a hora de cooldown dele. Agora existe

last_attempt_pendente_at: pendente recente e envio em andamento; pendente ha mais de 10 min e

resultado desconhecido (pode ter entregue e morrido antes de gravar), que e diferente de

falha comprovada. Testado nos dois cenarios;

  1. die("texto") sai com codigo 0 no PHP — medido por ele e confirmado aqui. O subprocesso do

BK-457b lia isso como sucesso e registrava "repassado" sem envio nenhum. Agora o filho **imprime

MP_GUARD_ENVIO_OK depois de concluir, e o pai exige codigo 0 e** o marcador;

  1. A baseline instalada nao tinha hmac_key_id (foi promovida as 07:41, antes de o campo

existir), entao a protecao do BK-457b simplesmente nao entrava: ele reproduziu chave trocada

devolvendo 5 falsos "credencial mudou". Baseline sem identidade agora bloqueia a comparacao

com mensagem propria, e a baseline viva foi reselada com o campo;

  1. A gravacao da tentativa era ignorada: disco cheio ou erro de rename deixavam de novo a

tentativa sem registro. As duas gravacoes agora sao conferidas e reclamam no log e no stderr;

  1. O estado era lido antes do lock, entao dois processos liam a mesma versao velha e o segundo

regravava por cima do primeiro. O --check agora rele o estado sob o lock, e --seal e

--migrar-v2 passaram a disputar o mesmo lock. Falha ao ABRIR o lock deixou de se disfarcar de

"outra execucao";

  1. O --status nao enxergava o heartbeat.json. Separar o estado resolveu a disputa mas

escondeu tentativa, falha e ultimo aviso do heartbeat; voltaram para o status, junto da

tentativa em andamento.

Tambem: os timeouts do envio direto cairam para 10s/5s por numero. O astra somou os limites e viu

que uma rodada ruim passava de 115 segundos, mais que o intervalo do cron; agora o pior caso

cabe em ~80s, e o flock faz a rodada seguinte sair na hora em vez de empilhar.

Custo desta rodada: mais 2 alertas (total do dia: 8). Os dois foram legitimos — a baseline

estava mesmo sem identidade de chave — mas sairiam de qualquer jeito se eu tivesse reselado antes

de trocar a regra. Ordem errada minha.

Regra de parada combinada com o ADMIN, para a revisao nao virar loop: fecha quando nao sobrar

nenhum P1; o que for P2 vira BK. O astra sempre devolve lista, porque e isso que se pede a ele

— a evidencia de convergencia e a gravidade cair, e caiu: da primeira rodada (fila inteira

paralisada, credencial sem vigilancia) para esta (campos faltando no --status).

Abertos como BK em vez de entrar aqui: BK-458 (o cron roda como root arquivos que pertencem ao

www, entao escrita pelo lado web vira execucao root em ate 60s) e BK-459 (o deploy publica o

.env em 644 e o monitor conserta depois). Os dois saem do codigo do monitor e mexem em deploy e

em fronteira de privilegio.

[2026-09-20] BK-457b — nove achados do astra no BK-456/457, corrigidos no mesmo dia

Revisao final independente sobre o 9ab1b1707. Ele nao aprovou o fechamento, e tinha razao:

seis dos nove eram P1, todos reproduzidos por ele com dado sintetico antes de eu mexer.

  1. HMAC indisponivel podia virar OK. A comparacao so acontecia com os dois HMACs

preenchidos — se o valores.key sumisse, o valor atual saia vazio e a credencial ficava **sem

vigilancia, com o monitor dizendo OK**. E se a chave fosse recriada sozinha, TODAS as

credenciais viravam "mudou de valor": alarme falso indistinguivel de invasao. Agora a baseline

guarda hmac_key_id; chave ausente ou trocada bloqueia a comparacao com mensagem propria,

e so --seal/--migrar-v2 podem criar chave. HMAC vazio de qualquer lado tambem virou desvio;

  1. o fallback podia matar o monitor antes de ele registrar a falha. O envio acontecia antes da

gravacao do estado, e o bot_config.php faz die() sem BOT_WHATSAPP_API_KEY — com o stdout

do cron no lixo, nem a mensagem do die() aparecia. Agora a **tentativa e persistida antes do

envio, e o fallback (e o push) rodam em subprocesso** com timeout: se morrerem, morre so

o filho;

  1. "entrega" incluia bloqueado e enfileirado. O fallback usava return_blocked_as_success e o

helper devolve true ate quando apenas enfileira. Isso limpava a falha e comprava 30 minutos de

silencio sem ninguem ter recebido nada. Agora so o canal proprio confirma entrega (HTTP 2xx

por numero); o fallback e registrado como "repassado" e nao carimba cooldown;

  1. o heartbeat carimbava cooldown mesmo com todos os envios falhando — uma hora de silencio

justamente com o canal quebrado. Agora so entrega confirmada compra silencio;

  1. os dois crons disputavam o state.json. Gravacao nao atomica e sem exclusao: um heartbeat

atrasado sobrescrevia o check com estado velho, apagando falha de entrega e cooldown. O

heartbeat ganhou estado proprio (heartbeat.json), o --check roda sob flock e toda

gravacao virou temporario + rename;

  1. a migracao podia promover arquivo nao conferido. A segunda coleta comparava so o env e

salvava tambem os files novos: arquivo alterado entre as duas coletas entrava na baseline sem

passar pela conferencia. Agora compara os dois, exige HMAC das chaves exigidas e recusa sem

hmac_key_id;

  1. eu tinha errado a regra de chave duplicada. Escrevi que "o PHP fica com a ultima" — para

ESTE consumidor e falso: o config/connect.php:16 so grava se a chave ainda nao existir, entao

a primeira ocorrencia vence. O monitor comparava um valor que a aplicacao nao usa. Corrigido

e provado em teste, com a logica do consumidor reproduzida lado a lado;

  1. erro de leitura do .env desligava tambem a comparacao de arquivos (eu dava return):

quem tornasse o .env ilegivel ganhava ponto cego em todo o resto na mesma tacada. Agora pula

so as comparacoes de credencial;

  1. legada presente e vazia nao era denunciada (MERCADOPAGO_ACCESS_TOKEN= passava batido),

porque eu testava present em vez de existe. Nao ativa credencial, mas viola a politica.

Validacao: Sintaxe PHP ok; os 6 cenarios de falha que ele reproduziu, agora passando (chave

HMAC sumida, chave trocada, HMAC vazio, legada vazia, .env ilegivel e .env ilegivel COM arquivo

adulterado junto — este ultimo provando que a comparacao de arquivo continua); duplicada conferida

contra a logica do connect.php lado a lado; despacho dos modos isolados recusando arquivo

inexistente sem tentar enviar; lock do --check recusando execucao simultanea (OCUPADO);

--status, --check e --heartbeat OK ao fim. Nenhuma mensagem enviada nestes testes.

Segue aberto no BK-456 o que ele listou como proximo passo e nao cabia aqui: comparacao semantica

do .env inteiro, permissao do .env ja no deploy, auditoria de quem escreve o codigo que o cron

roda como root, e agrupamento de eventos por ciclo. Ele tambem lembrou que o heartbeat nao

cobre erro de sintaxe no proprio script, falha do PHP nem parada do cron — para isso seria preciso

um vigia externo a esta maquina.

[2026-09-20] BK-456 e BK-457 — o monitor do MP passou a enxergar credencial e a saber avisar

Desenho pedido ao gpt-6-astra antes de escrever codigo, a pedido do ADMIN ("sempre com

analise do codex p nao ter mais retrabalho"). Duas premissas minhas cairam na analise dele:

reescrever bytes identicos nao muda o SHA-256 (entao o deploy so gera drift se o conteudo

mudar mesmo), e selar com o leitor novo e conferir com o mesmo leitor nao gera enxurrada.

BK-456 — ver a credencial, nao so o arquivo

  • Leitor proprio do .env (mp_guard_ler_env_puro()): recebe caminho, devolve dado. Sem

putenv(), sem escrever em $_ENV/$_SERVER, sem require de aplicacao e sem banco. Nao da

para reaproveitar o mp_guard_parse_env_file() que ja existia: ele muda o ambiente inteiro **e

tira aspas, enquanto o carregador que a aplicacao usa (config/connect.php:7) preserva**.

Com valor entre aspas, os dois liam coisas diferentes do mesmo arquivo. A regra aqui e a do

consumidor. Incluir o connect.php estava fora de questao: ele abre banco com or die e isto

roda de minuto em minuto;

  • ausente, vazia e erro de leitura sao tres estados diferentes. .env ilegivel vira um desvio

proprio e para a comparacao ali, senao um erro de permissao viraria 7 alertas de "credencial

sumiu". Chave duplicada tambem e desvio: escolher uma ocorrencia em silencio esconde

configuracao ambigua (o PHP fica com a ultima, quem le o arquivo costuma olhar a primeira);

  • politica por chave em vez de lista solta. MERCADOPAGO_WEBHOOK_SECRET_PIX entrou (era

credencial de webhook sem vigilancia). As duas legadas nao sairam: viraram

esperada_ausente, porque checkout_pix.php:381 ainda as le como fallback — se aparecerem,

alguem pos credencial por fora, e isso alerta;

  • painel/modulos/creditos/callback_mp.php entrou na lista de arquivos: consome o segredo do

webhook e estava de fora;

  • HMAC-SHA-256 no lugar do SHA-256 puro nos valores, com chave exclusiva do monitor em

valores.key (0600), separada da baseline. Motivo concreto: BOT_WHATSAPP_NUMERO_ALERTA e um

telefone — hash simples de valor previsivel se quebra por tentativa. Sairam tambem length e

fingerprint: o alerta diz qual chave mudou, nunca um pedaco do valor;

  • baseline v2 com migracao controlada. Baseline em versao antiga desliga a comparacao e

avisa, em vez de comparar errado. O --migrar-v2 recusa migrar se houver desvio de arquivo

pendente contra a v1 (migrar congelaria esse desvio como estado correto), mostra o estado de

cada chave sem exibir valor, exige --confirmo, rele o .env na promocao e aborta se algo

mudou no meio. A v1 fica preservada.

BK-457 — o monitor conseguir avisar

  • canal de alerta independente. O caminho antigo passa por config/bot_config.php:337, que faz

die() se faltar BOT_WHATSAPP_API_KEY — ou seja, apagar essa chave **derrubava o aviso de que

ela foi apagada**. Agora existe alerta.conf (0600, fora do webroot, congelado por

--configurar-alerta) e envio por cURL direto, com timeout. O helper da aplicacao virou

fallback;

  • tentativa, entrega e cooldown separados. O last_alert_at era carimbado mesmo com o envio

falhando, adiando a proxima tentativa em 30 minutos justamente quando o canal estava ruim. Agora

so entrega carimba cooldown; falha grava last_attempt_failed_at e a proxima passada tenta de novo;

  • heartbeat em processo separado (--heartbeat, cron */10): alerta se a ultima execucao

passou de 15 min, se a baseline sumiu ou esta em versao velha, ou se o ultimo alerta nao foi

entregue, com cooldown proprio de 1h. Ele nao carimba ultima_execucao_at — se carimbasse,

manteria o marcador fresco sozinho e nunca perceberia o monitor parado;

  • o cron parou de jogar a saida fora. Era >/dev/null 2>&1: erro do proprio monitor sumia.

Agora o stderr vai para /www/server/cron/rnt_mercadopago_guard.log (o OK de cada minuto

segue descartado, para o log nao crescer a toa);

  • --status passou a mostrar versao da baseline, se o canal proprio existe, ha quanto tempo foi a

ultima execucao e se ha alerta nao entregue.

Validacao: Sintaxe PHP ok. 7 casos de comparacao por chave (valor trocado, credencial

sumida, credencial esvaziada, legada aparecendo, .env ilegivel, chave duplicada e caso sem

mudanca) e 10 verificacoes do leitor (aspas preservadas igual ao connect.php, vazio != ausente,

= dentro do valor, duplicada, espacos, CRLF, arquivo ausente, symlink recusado). Migracao rodada

de verdade: v1 -> v2 promovida com as 5 chaves exigidas SET e as 2 legadas AUSENTE, v1

preservada em baseline.json.bkp_v1_20260920-074158, --check seguinte OK. Heartbeat: detecta

"ultima execucao ha 60 min" e respeita o cooldown sem enviar.

Custo honesto desta rodada: 6 alertas de WhatsApp ao gestor. Quatro as 07:41, porque o cron de

minuto em minuto pegou a janela entre dois patches meus (snapshot ja v2, baseline ainda v1) — a

trava de versao que impede isso entrou logo depois. Dois as 07:44, num teste de heartbeat em que o

cenario nao montou e o alarme disparou de verdade; serviu de prova de ponta a ponta, mas foi erro

meu. Em ambos os casos o conteudo era alerta legitimo, nao mensagem de teste.

O que o astra apontou e NAO entrou (registrado para nao virar promessa esquecida): comparar o

.env inteiro semanticamente para distinguir "so formatacao mudou" de alteracao real; publicar o

.env ja com permissao correta no deploy, em vez de 644 seguido de correcao; auditar quem pode

escrever o codigo que o cron roda como root (nobody:nogroup em alguns caminhos); e agrupar

eventos por chave/fonte/tipo numa mensagem por ciclo.

[2026-09-20] BK-455b — a pausa do monitor MP podia calar o .env; corrigido no mesmo dia

Revisao final independente do gpt-6-astra sobre o commit 9d5b56913, com os achados conferidos

na fonte. O primeiro era grave e e por isso que a revisao existe:

[P1] A pausa podia esconder troca de credencial. A regra "ENV nunca e pausado" parecia

suficiente, mas os diffs ENV ... nunca acontecem — o monitor le credencial so do ambiente do

processo e o cron roda sem o .env, entao as 6 entradas ENV da baseline estao todas

present=NAO (virou BK-456, e e anterior a tudo isto). Com os diffs ENV mortos, o unico sinal

de .env alterado e FILE .env hash mudou — e a pausa aceitava silenciar esse. Agora o .env e

lista negra: nao e pausavel nem citado no escopo, nem por engano, nem de proposito.

Demais correcoes, todas reproduzidas por ele antes e conferidas por teste aqui depois:

  • escopo virou obrigatorio. --somente=, --somente=,,, e --somente action.php (sem =)

produziam escopo vazio, que o codigo lia como "pausar tudo". Uma variavel vazia no comando

viraria suspensao geral silenciosa. Nao existe mais pausa geral; o escopo tem que citar

arquivo que esteja na lista monitorada;

  • opcao em qualquer posicao. --motivo=x --pausar=60 nao pausava: o $argv[1] mandava, a

chamada caia no --check e podia disparar alerta em vez de pausar. E --pausar=60abc virava

60 calado. Agora --pausar e reconhecido em qualquer posicao e so aceita numero puro;

  • a promessa de "alerta na proxima passada" era falsa. Preservar o last_alert_hash nao basta:

se o mesmo desvio ja tinha alertado antes da pausa, o cooldown de 1800s ainda valia. Ele

reproduziu: pausa vencida, mesmo hash alertado ha 6 minutos, nao envia. Agora a supressao grava

pausa_suprimiu no estado e o primeiro check sem pausa forca o alerta, ignorando o cooldown;

  • --retomar dizia "cancelada" mesmo falhando, e a criacao ignorava o retorno da gravacao. Os

dois agora falham alto, com codigo de saida 1;

  • gravacao atomica do pausa.json (temporario + rename): o cron roda de minuto em minuto e

podia ler o arquivo no meio da escrita, concluir que nao ha pausa e mandar justamente o alerta

que a pausa existia para evitar.

Validacao: Sintaxe PHP ok. 8 casos da regra de escopo, agora incluindo .env no escopo,

.env sem escopo e .env com outro escopo — os tres tem que alertar. **7 casos de linha de

comando**, todos recusados como esperado: .env no escopo, escopo vazio, escopo so com virgulas,

sem --somente, arquivo fora da lista monitorada e minutos com lixo; mais --motivo antes de

--pausar, que agora pausa em vez de cair no check. Monitor em OK e sem pausa ativa ao fim.

Ressalvas que ficam registradas, tambem dele: renovar a pausa repetidamente estende a suspensao

(o teto de 2h e por criacao); --seal nao cancela pausa; e alteracao maliciosa dentro do arquivo

pausado tambem nao alerta — se for revertida durante a pausa, resta so o log, e se for incorporada

por um --seal distraido, deixa de ser drift. **Por isso o --seal exige revisar os desvios

antes**, e isso entrou no guia.

[2026-09-20] BK-455 — monitor do Mercado Pago ganhou pausa com prazo (e nao desligamento)

Pedido do ADMIN nesta rodada: "qdo for mexer nesse tipo de coisa nao seria melhor desligar isso e

dps ligar p nao ficar gastando em vao msg via whatsapp?".

O gasto e real e maior do que parecia. A trava de 30 minutos do monitor so vale para o **mesmo

hash** (monitor_mercadopago_integrity.php:511: hash diferente = alerta imediato). Como o cron roda

de minuto em minuto, cada salvamento novo de um arquivo monitorado vira um alerta na hora. Medido

no banco: editando o includes/whatsapp_helper.php durante o BK-441, sairam 15 mensagens para os

dois numeros do gestor entre 01:48 e 01:59 de 20/09/2026.

Nao foi feito o que ele sugeriu ao pe da letra, e o motivo esta aqui: desligar e religar a mao

depende de lembrar de religar, e isto e o monitor do fluxo de pagamento — um desligamento

esquecido atravessa a sessao e ninguem percebe. O que entrou foi uma pausa que expira sozinha:

  • --pausar=<minutos>, com teto de 2 horas. Passou do teto, o script corta para 120 e avisa;
  • --somente=a.php,b.php limita a pausa aos arquivos que voce vai mexer. Sempre usar: sem

escopo, a pausa cala o hash de todos os monitorados;

  • --motivo="BK-XXX" fica no log, para a pausa nao ser anonima;
  • --retomar solta antes da hora; --status mostra ate quando a pausa vale, o escopo e o motivo.

A pausa nao e um desligamento, e a diferenca importa:

  • ela continua conferindo e gravando todo desvio no log do guard, inclusive o que nao alerta;
  • ela cala apenas desvio de hash de arquivo. ENV (credencial), dono, grupo e permissao alertam

do mesmo jeito — nenhum deles e efeito colateral de editar codigo, e o BK-356 ja custou 6 horas de

.env em 644. Arquivo que some tambem nao e pausado;

  • ela nao toca no last_alert_hash, entao o desvio que continuar depois do prazo gera alerta na

primeira passada seguinte do cron. Pausa adia; nao apaga.

Validacao: Sintaxe PHP ok (php83 -l). 9 casos da regra de escopo, extraida do proprio arquivo

e exercitada fora dele: hash no escopo pausa; hash fora do escopo alerta; sem escopo pausa qualquer

hash; ENV mudando ou sumindo alerta; permissao e dono alertam; arquivo sumindo alerta; e

whatsapp_helper.php.bkp nao e confundido com whatsapp_helper.php por prefixo. Teste de ponta a

ponta em producao: pausa de 15 min com escopo no helper, drift real criado no arquivo, --check

devolveu DRIFT-PAUSADO, o log registrou o desvio com o hash e zero mensagens de WhatsApp

saíram no periodo (conferido em whatsapp_mensagens). O caminho de alerta nao foi disparado de

proposito, para nao gastar mensagem: e o mesmo caminho de antes, sem alteracao.

Fluxo novo documentado em docs/01_GUIA_MANUTENCAO.md: pausar com escopo antes de editar arquivo

monitorado, --seal ao terminar.

[2026-09-20] BK-441 — fila de recuperacao do WhatsApp: lock real, escrita atomica e id estavel

Fundacao da fila, na ordem recomendada pelo gpt-6-astra: antes de mexer em horario

(BK-451) ou em estado de envio (BK-454), a fila precisava parar de permitir duas drenagens ao

mesmo tempo e de gravar arquivo pela metade.

1. O lock que destravava a si mesmo. No bot/whatsapp/healthcheck.sh, o

trap 'rm -f "$LOCK_FILE"' EXIT era instalado antes da conferencia do lock. A execucao que

encontrava a trava ocupada saia por exit 0 — e o trap apagava a trava **de quem estava

rodando**. A execucao seguinte entrava junto. Reproduzido hoje com o backup do arquivo antigo:

com o dono do lock vivo, o codigo antigo deixa o arquivo de lock sem existir; o novo mantem.

Agora o lock e flock no descritor 9, mantido pelo processo inteiro; ninguem apaga arquivo para

destravar, e o kernel solta sozinho se o processo morrer. Some tambem o find -mmin +5 -delete,

que podia liberar processo vivo.

2. O processador PHP nao tinha lock nenhum. A trava existia so no bash, e

_whatsapp_processar_fila() (includes/whatsapp_helper.php) e chamado de mais lugares do que

parecia: pelo painel, e a cada envio bem-sucedido (:1377) e na recuperacao apos erro HTTP

(:1402). Os dois consumidores agora disputam o mesmo arquivo de lock

(bot/whatsapp/queue.lock, _whatsapp_fila_lock()), em modo nao-bloqueante: quem perde vai

embora, porque quem ganhou vai percorrer a fila inteira. O corpo da drenagem virou

_whatsapp_processar_fila_travada(), chamado sob try/finally, e o retorno ganhou

ignorada => true. A tela admin/modulos/bot/stats.php passa a dizer "a fila ja estava sendo

drenada por outro processo" em vez de exibir "0 processadas", que parecia fila travada.

3. Escrita atomica. O JSON era escrito direto no nome final, no enfileiramento e no

incremento de tentativa. Agora vai para um temporario exclusivo na mesma pasta e entra por

rename/mv -f. No bash, o temporario tinha nome fixo ($MSG_FILE.tmp), compartilhado por

duas drenagens simultaneas; virou mktemp. Isso tambem explica os 5 .json.tmp de 0 byte de

17/09: o shell cria o destino do > antes de rodar o jq, entao jq que falha deixa o

arquivo vazio para tras. Reproduzido em teste isolado (x.json.tmp (0 bytes)); o codigo novo

apaga o temporario e registra o erro no log.

4. Identidade estavel. O nome do item era time() . '_' . md5(numero . mensagem): duas

mensagens identicas para o mesmo numero no mesmo segundo geravam o mesmo nome, e uma

sobrescrevia a outra. Agora cada item nasce com id aleatorio (_whatsapp_fila_id(), com queda

para mt_rand() onde random_bytes() nao existe, porque o log_telemetry.php ainda e chamado

pelo php do PATH, que e 5.6), mais schema_version e criado_em em epoch — o timestamp

antigo nao tem fuso e o epoch e o que serve para contar validade no BK-451. Medido: dois

enfileiramentos identicos no mesmo segundo agora dao 2 itens; antes davam 1.

5. php -> /usr/bin/php83 nas duas chamadas ao log_telemetry.php no healthcheck.

6. Cinco achados do gpt-6-astra na revisao final, corrigidos antes do commit. O primeiro

era regressao minha:

  • curl do envio sem --max-time (healthcheck.sh). Ja era assim antes, mas agora esse

curl roda segurando o lock da fila: um /send que aceita a conexao e nunca responde

paralisaria cron, painel e todo envio do PHP por tempo indefinido, e flock nao expira por

idade. Entraram --max-time 20 --connect-timeout 10 (os mesmos limites do lado PHP) e

timeout 30 nas duas chamadas ao bridge de telemetria, que tambem rodam dentro da secao

travada;

  • mensagem que chega durante a drenagem esperava o cron. O consumidor trabalha sobre uma

fotografia da fila, e o disparo imediato da mensagem nova bate no lock ocupado e desiste --

entao ela so sairia ate 5 minutos depois. Os dois consumidores passaram a fazer **ate 3

passadas**, relistando a fila, com marcacao de item ja tentado para ninguem ser enviado duas

vezes na mesma rodada. Isso estreita a janela, nao fecha: sobra o intervalo entre a ultima

listagem e a liberacao do lock. Latencia aceita e documentada; garantia de envio imediato exige

sinalizacao, e isso e BK-451. O comentario que eu tinha escrito ("o outro vai processar a fila

inteira") estava errado e foi corrigido no codigo;

  • erro ao abrir o lock virava "ja esta drenando". fopen() falhando devolvia o mesmo false

da contencao, entao o painel diria "aguarde" justamente quando ninguem esta drenando.

_whatsapp_fila_lock() agora separa ocupado de erro_abertura/erro_flock (usando o

$would_block do flock()), e o painel mostra erro pedindo para conferir a permissao do

queue.lock. Testado de verdade: como www, com o lock em 600 do root, da erro_abertura;

com dono www e 664, pega normal;

  • falha no enfileiramento nao chegava ao chamador. _whatsapp_enfileirar() agora devolve

bool, e enviar_whatsapp() deixa de responder true quando a gravacao falhou (disco cheio,

permissao, JSON invalido) -- antes isso virava "mensagem que nao existe em lugar nenhum,

contada como enviada". O caso em que o enfileiramento da certo e o cron conta como enviado

continua aberto e e o BK-454;

  • falha ao gravar a tentativa passava batido. No bash o mv ficava fora do if; no PHP o

retorno da escrita atomica era ignorado. Sem isso, um item podia repetir a mesma tentativa para

sempre, calado. Os dois agora conferem e registram no log.

Tambem por indicacao dele: os locks sao www:www 0664 em vez de 0666 (root escreve de qualquer

jeito e nao se abre o arquivo para outro usuario local), e o comentario sobre o flock foi

corrigido -- o lock e da descricao de arquivo aberta, entao **filho que herda o descritor segura o

lock junto**, e matar so o bash nao garante liberacao. E por isso que todo comando da secao

travada ganhou teto de tempo.

O que ele apontou e nao entrou aqui: o bk94_session e lido e reenviado pelo bash, mas o

consumidor PHP nao o encaminha (divergencia antiga, some quando o BK-451 unificar os

consumidores); e o id ainda nao vai como chave de idempotencia para a API, entao queda depois

da entrega e antes do unlink ainda reenvia.

Nao entrou nesta rodada, de proposito: janela de horario, validade/expiracao, vazao (BK-440) e

dedupe_key de intencao. Lock e dedup locais nao garantem entrega unica — se o transporte

envia e a resposta se perde, o retry continua ambiguo (bot/whatsapp/server.js nao usa chave de

idempotencia). E os dois "sucesso" no mesmo segundo de 17/09 seguem sem causa provada: o que

esta feito aqui fecha mecanismos capazes de duplicar, nao o episodio.

Os locks agora persistem em disco e entraram no .gitignore.

Validacao (repetida depois das correcoes acima): Sintaxe PHP ok (php83 -l no helper, no

stats.php e no log_telemetry.php), Sintaxe Shell ok (bash -n), git diff --check limpo. Testes com o servico conectado e a fila vazia: lock do healthcheck

ocupado -> segunda execucao sai sem apagar a trava alheia; lock da fila ocupado -> drenagem

pulada com aviso; rodada normal -> "Fila vazia"; incremento de tentativa em arquivo corrompido ->

item original preservado e nenhum .tmp deixado para tras. Os 2 itens de teste criados na fila

foram removidos antes de qualquer drenagem e nenhuma mensagem foi enviada (log do healthcheck

sem o numero de teste). Revisao final independente do gpt-6-astra.

[2026-09-19] BK-450 — feedback pos-evento nao inventa mais nota 5

rnt_post_event_feedback_extract_rating() devolvia 5 para texto vazio e para qualquer texto

sem digito, e pegava o primeiro 1-5 solto ("nota 10" virava 1). Como o contexto pendente vale

10 dias, a primeira mensagem da pessoa nesse periodo virava avaliacao, fosse "SAIR", "Zona

Sul" ou "quero ingresso". O "Nao consegui identificar sua nota" nunca disparava.

Estrago medido: das 21 notas recebidas pelo WhatsApp ate hoje, 1 estava errada: "Nao fui

😅🤭" virou 5 estrelas publicadas em "Cinderela Negra" (tb_avaliacoes.id=11). A correcao

nao altera avaliacao existente. Por decisao do ADMIN, a id=11 foi tirada do ar em 19/09/2026

03:09 (status aprovado -> rejeitado, o mesmo do botao do admin; backup da linha em

/www/rnt-backups/tb_avaliacoes_11_20260919-140914.json, com o nome no fuso PRC do php.ini).

A peca ficou sem nenhuma avaliacao aprovada.

  • extrator devolve 0 sem nota explicita. Aceita: digito sozinho (e o que o voto da enquete manda),

os 5 rotulos antigos da enquete, "nota/avaliacao 4" em qualquer ponto, "4/5" so como resposta

inteira, "4 de 5"/"4 estrelas", "dou 4" so no fim da mensagem, por extenso ("cinco"), so

estrelas (inclusive ⭐️ com U+FE0F). Recusa qualquer "nao" ("nao dou 5", "nao e nota 5", "nao

fui"), outra escala ("nota 4/10", "nota 4 de 10"), data ("dia 4/5", "4 / 5 / 2026") e "dou 2

ingressos"/"dou 2, um para cada amigo". Na duvida, pede de novo;

  • etapa 1 sem nota: SAIR e mensagem so de zonas ficam em silencio (o analyzer ja tratou); o resto

recebe o pedido de nota uma vez por item da fila. A marca e um UPDATE condicional

(last_error vira feedback_nota_invalida so se nao for feedback_received nem ela mesma), e so

responde quem gravou: duas mensagens juntas nao avisam duas vezes, e uma atrasada nao apaga a

nota que chegou antes;

  • a reconciliacao do admin (source=admin_whatsapp_chat_reconcile), que reprocessa mensagens

antigas ao abrir o chat, nunca responde nem consome a marca;

  • etapa 2: SAIR e mensagem so de zonas nao viram mais comentario publico. Na

rnt_post_event_feedback_so_zonas, toda palavra tem que ser de zona ou conectivo ("Zona Sul e

Norte", "Todas as zonas do Rio de Janeiro"); "Teatro no Centro, lindo" continua comentario.

Revisado duas vezes pelo gpt-6-astra, com os achados conferidos na fonte. O que ele achou e ja

existia antes virou BK-452. Validacao: Sintaxe PHP ok (php83 -l); 68 casos do extrator, 13 de

zonas e 11 do fluxo com banco e envio substituidos por stubs, todos passando; replay das 21 notas

reais pelo extrator final, com 1 divergencia, que e o proprio bug.

[2026-09-19] Lembrete e pedido de nota: uma vez por peca POR TEMPORADA

Regra do ADMIN: temporada e o mes/ano do pedido. Uma vez por cliente+peca+temporada. Se no

mes seguinte a pessoa fizer outro pedido e nao pagar, recebe outro lembrete. Se comprar e

assistir de novo numa nova temporada, recebe outro pedido de nota e comentario.

Lembrete (admin/cron/lembrete_pedido_pendente.php). O controle de repeticao era por

dia, e o pedido fica elegivel por 7 dias: o lembrete se repetia todo dia, sempre a

meia-noite (virada do dia). Em 60 dias, 5 clientes receberam 8 lembretes pela mesma peca.

  • fases 1 e 2: bloqueia se houve envio com sucesso para o mesmo cliente+peca por um pedido

do mesmo mes/ano (pedidos.created, via pedido_id do log), ou qualquer tentativa hoje (falha

nao conta como recebido, mas nao martela o numero, e tambem so vale na mesma temporada). O

envio de uma fase bloqueia a outra na mesma temporada;

  • fase 1 ganhou a guarda em memoria que a fase 2 ja tinha, e as duas agora usam

cliente+peca+mes/ano: dois pedidos da mesma temporada na mesma rodada geram uma tentativa so;

  • --test-send termina antes da fase 2. Se todos os candidatos da fase 1 fossem pulados por

falta de valor promocional, o teste caia na fase 2 e enviava ao celular real do cliente;

  • a hora e conferida de novo antes de cada envio: rodada que comeca 21:59 nao passa das 22h.

Pedido de nota (admin/cron/post_event_feedback_queue.php). Qualquer avaliacao da peca

bloqueava a pessoa para sempre. Agora so fica fora quem ja avaliou depois desta compra

(data_criacao >= pedidos.created) ou quem ja tem pedido de nota na fila (enviado ou ainda

pendente/pausado) por um pedido da mesma peca no mesmo mes/ano.

Avaliacao por temporada. tb_avaliacoes nao tem indice unico, mas os dois pontos que gravam

sobrescreviam a avaliacao existente. A nota do ano seguinte apagaria a anterior:

  • WhatsApp (save_comment): atualiza so a avaliacao feita depois da compra da conversa (pelo

pedido_id do payload da fila); a de uma compra anterior fica intacta e a nova vira outra

linha. Testado contra o banco so com leitura: mesma compra -> UPDATE, compra posterior -> INSERT;

  • site (includes/ajax_palcofan.php): atualiza so a avaliacao do mes corrente, pelo id. Antes

o UPDATE ia por cliente+item e, com duas temporadas, reescreveria as duas.

Revisado pelo gpt-6-astra em quatro passadas, com os achados conferidos na fonte. Os dois casos

de borda que dependem de a avaliacao saber de qual pedido veio viraram BK-453. Validacao:

Sintaxe PHP ok nos 4 arquivos. As queries do lembrete e do feedback foram executadas direto no banco sem erro (o script

do lembrete trata erro de SQL como "nenhum elegivel"). Audiencia do feedback nos ultimos 60 dias:

28 linhas antes, 19 depois, e as 9 que sairam ja tinham recebido o pedido de nota. --dry-run do

lembrete as 02:26: "fora da janela" e nenhum elegivel. Pela regra antiga havia 1: a mesma cliente

do caso das 00:00, que receberia outro lembrete as 10h.

[2026-09-18] Lembrete de pedido pendente so envia das 10h as 22h

Pedido do ADMIN: um lembrete ("Notamos que voce nao concluiu a compra...") chegou para uma

cliente as 00:00:08. O admin/cron/lembrete_pedido_pendente.php (BK-343) roda */5 o dia

inteiro e nao tinha nenhuma trava de horario. Nos 30 dias anteriores, **15 dos 23 lembretes

(65%) sairam entre 22h e 10h**, no maximo 1 por noite.

  • nova lembrete_dentro_da_janela(): envia das 10:00 ate antes das 22:00, com fuso

America/Sao_Paulo explicito. O php.ini da VPS e PRC, e as 21:55 de Brasilia ele diria

08:55, que bloquearia o horario comercial;

  • fora da janela, a rodada sai antes da query, sem enviar e sem gravar dedup (fases 1 e 2).

O pedido continua elegivel (a query cobre 7 dias) e sai na primeira rodada das 10h;

  • --dry-run avisa que esta fora da janela e continua listando; --test-send continua (vai so

para o gestor); o cron em /etc/cron.d/rnt-lembrete-pedido-pendente nao mudou.

Conferido: nenhuma outra automacao manda WhatsApp a cliente de madrugada. Credito sai as 10:15,

feedback entre 19h e 22h, convite a produtor entre 9h e 18h, e verifica_wa_compra_aprovada so

avisa o gestor.

Validacao: Sintaxe PHP ok (php83 -l); a funcao testada em 00:00, 09:59, 10:00, 15:30, 21:59,

22:00 e 23:59, todos certos; --dry-run as 21:54 sem erro; rodada real das 22:00:01

registrou Fora da janela 10:00-22:00 -- nada enviado.

[2026-09-18] O "rsync externo NBi7" e o deploy do proprio projeto

Investigado a pedido do ADMIN, que disse nao mexer nesse sincronismo ha muito tempo. Por quase um

mes isto foi tratado como sync externo desconhecido — e decisoes reais sairam dessa leitura

errada, como mover segredo para fora do .env.

E o .github/workflows/deploy.yml ("Deploy para Producao"), que roda em push na main e

usa easingthemes/ssh-deploy@v5.0.0 com o secret SSH_PRIVATE_KEY. A chave teatro@NBi7 (RSA

4096, SHA256:W1J04J…) em /root/.ssh/authorized_keys e a contraparte desse secret; a privada

nao esta na VPS. Os runners do GitHub rodam no Azure, o que explica os IPs que pareciam

aleatorios. A ultima alteracao do workflow e de marco/2026, coerente com "nao mexo ha muito

tempo".

Cada sintoma tem uma linha que o explica:

| sintoma | causa |

|---|---|

| .env vira 644 | ARGS: --chmod=D755,F644 e find . -type f -exec chmod 644 \; (deploy.yml:106-108) |

| chown -R www:www . | SCRIPT_AFTER (deploy.yml:106) |

| edicao no .env nao persiste | o .env e recriado do zero a partir de 36 GitHub Secrets (deploy.yml:40-65); nao vem do Git |

| mtime novo sem mudar conteudo | -re.iLsfxCIvu e a string de capacidade de protocolo, nao flags do usuario — nao ha -t nem -c |

| codigo nao e revertido | vem do checkout do commit empurrado; se a origem ja tem a edicao, ela volta igual |

O que fechou o caso: o deploy de 17/09 23:45:36 rodou o commit 0fecd7cbd (23:45:32), e o

rsync tocou os arquivos as 23:46:31/23:46:40. O gpt-6-astra achou no audit o rename de

.env.615KOj para .env — troca de inode, ou seja, substituicao de verdade, nao so chmod.

Duas coisas que eu afirmei e estavam erradas:

  • "um IP diferente por conexao": e um IP por execucao, com duas conexoes — a primeira faz o

rsync, a segunda roda o SCRIPT_AFTER. No par medido, as duas vieram de 20.220.110.153.

  • "a cada ~11min": e por push na main, portanto em rajada — 27 ciclos em 17/09, 62

conexoes em 07/09, e dias inteiros sem nada.

A disputa de permissao nao e incidente: o deploy poe .env em 644 e o

monitor_mercadopago_integrity.php:602 devolve para 640 em ate um minuto, sem alertar, por

decisao explicita no codigo. Na web o .env esta bloqueado por duas regras independentes do

vhost, entao o 644 e exposicao local, nao HTTP.

Risco que permanece, e nao e invasao: a chave autentica como root e a linha em

authorized_keys nao tem restricao (command=, from=). Reduzir o privilegio desse deploy e

melhoria legitima. O astra deixou uma sequencia segura, caso um dia se queira cortar — o workflow

tambem compila o scraper, instala Composer/Node, recarrega PM2 e ajusta api/eventos/pecas.json,

entao cortar sem substituir essas funcoes quebraria o site.

Alerta pratico levantado na investigacao: a VPS estava **7 commits a frente do

origin/main**. Como o deploy publica o commit empurrado, re-executar o ultimo deploy

sobrescreveria o que so existe aqui. Conferir git status -sb antes de qualquer deploy.

[2026-09-18] Baseline do monitor MP re-selada, e o log dele ganhou rotacao

O monitor de integridade do Mercado Pago passou a alertar no WhatsApp a cada rodada com dois

drifts: webhook_mp_ingressos.php e includes/whatsapp_helper.php. Eram as minhas proprias

alteracoes desta madrugada — BK-431 (marcador de pedido de teste) e BK-438 (nao descartar mensagem

por falso negativo).

Conferido antes de selar, porque selar por cima e o jeito de o monitor perder o sentido: os

dois arquivos estao identicos ao HEAD do git (git diff --quiet HEAD limpo), o hash atual de cada

um bate exatamente com o hash "novo" que o alerta reportou, e a baseline anterior era de

11/09 20:46 — antes das edicoes. Drift autorizado, entao. Re-selada em 18/09 03:21; a

verificacao seguinte devolveu OK.

De passagem: o guard.log do monitor estava com 243.397 linhas / 15,2 MB, acumulando

desde 03/04/2026 sem rotacao nenhuma — o cron roda a cada minuto e grava uma linha por

verificacao, ~1.440 por dia. Criado /etc/logrotate.d/rnt-mp-guard: mensal, 12 copias,

comprimido, copytruncate (o monitor mantem o arquivo aberto). Retencao longa de proposito, por

ser a unica trilha de quando cada drift foi detectado.

Outro dado que ficou visivel no log: nas ultimas 20.000 linhas ha **2.620 registros de DRIFT com

alerta=suprimido**. Investigado logo em seguida — ver a entrada do rsync do NBi7 abaixo. Duas

coisas que eu havia dito aqui e corrijo: a frequencia nao e "a cada ~11min" (e em rajada, 27

ciclos ontem), e o sync nao re-sela o baseline do guard; essa conclusao ja constava como

errada no registro do projeto, e eu a repeti por descuido — quem re-selava era um hook local

(/root/cerebro/guard/postedit.sh).

[2026-09-18] BK-446 fechado: o CLI basta, sem tela no admin

Decisao do ADMIN em 18/09/2026, sobre a unica ponta que restava do BK-446: o acesso ao historico

de pedidos com peca removida fica so no CLI (/root/rnt-tools/pedidos_orfaos.php), sem tela

no admin. O gpt-6-astra defendia a tela; pesou o fato de serem 11 ids com **94% do valor

concentrado num unico evento de 2017**. Nao ha mais nada aberto neste BK.

[2026-09-18] As 3 pontas do BK-446: job nao para por DELETE, e R$ 18,5 mil sem tela (BK-446b)

(1) Os jobs de audio/video ficam como AUDITORIA, sem cascata — decidido contra a minha

inclinacao inicial, por uma razao que o gpt-6-astra apontou e eu confirmei na fonte: o

processamento continua depois da resposta HTTP (fastcgi_finish_request em

admin/modulos/campanhas/redes_sociais/texto_for_audio.php), entao mudar status no banco ou

apagar a linha nao interrompe nada. Cascata daria a ilusao de parar o job.

A protecao certa e recusar o INICIO. Os dois caminhos de geracao de video (redes_sociais.php) e

o de voz (voz_audio.php) usavam LEFT JOIN pecas e seguiam sem a peca, com o slug caindo para

peca-<id> e a geracao recriando arquivos/pecas/<id>/ — ressuscitando a pasta orfa de uma

peca recem-excluida. Agora recusam com mensagem clara. Os 12 jobs atuais tem peca existente, logo

a guarda e preventiva.

Correcao de contagem minha: eu escrevi "4 tabelas, 13 linhas". Sao **3 tabelas de job com 12

linhas; a quarta que eu contei, social_story_presets, e preset**, nao job.

(2) Encerrar o processamento onde a linha fica como auditoria — 3 correcoes feitas, 2 recusadas.

Feitas: rnt_post_event_feedback_conversa_ativa() passou a exigir INNER JOIN pecas (sem isso, a

proxima mensagem do cliente era capturada como feedback de peca fantasma); o SELECT de

admin/cron/palcofan_nota_pendente_alerta.php tambem (avisar o gestor sobre nota de peca

inexistente e ruido que ele nao pode resolver); e pending_approval entrou na limpeza de fila de

sync_facebook_events.php, que era o estado de 240 das 255 linhas — a limpeza nao alcancava

quase nada. 144 linhas serao canceladas no proximo run. Nos dois primeiros a exigencia e

EXISTIR, nao estar ativa: feedback depois do fim da temporada e legitimo.

A propria correcao criou um risco, que peguei antes de deixar valer. O upsert do planner revive

status so a partir de 'error', entao linha canceled nao voltava: a peca era prorrogada e o

post nunca mais saia, em silencio. Os 4 upserts passaram a reviver tambem canceled, **mas so

quando last_error = 'peca_inativa_ou_removida'** — o cancelamento automatico. Cancelamento

manual do gestor continua valendo. Provado em banco: a linha automatica voltou para

pending_approval com last_error limpo; a manual ficou canceled.

Recusadas com motivo: o astra pediu guarda em post_event_feedback_queue.php:82 e

whatsapp_campaign_queue.php:887. Nao apliquei porque o cenario nao pode ocorrer — feedback

exige pedido PAGO (status_transacao IN (3,4)) e peca com pedido nunca e apagavel, a guarda

recusa; e os orfaos historicos sao de ate nov/2025, fora da janela da fila, que e por data de

sessao. Guarda que nao pode disparar e codigo morto. Se a guarda de pedidos for afrouxada algum

dia, isto volta a valer. Ele corrigiu, com razao, uma premissa minha: a fila e identificavel

por peca, porque id_evento vai no source_payload_json

(post_event_feedback_queue.php:252) — mas a conclusao acima nao depende disso.

(3) Orfaos historicos: PRESERVAR, e o acesso passou a existir. Nao apagar e nao anular

id_evento: sao a unica pista do que foi vendido. So que "deixar e medir" era insuficiente,

porque esse dinheiro nao aparecia em tela nenhuma — produtor/fechamento.php:98 so consulta os

pedidos if ($peca) e admin/modulos/pecas/relatorios.php sai antes quando nao acha a peca.

Criado /root/rnt-tools/pedidos_orfaos.php, somente leitura (resumo, --detalhe,

--evento=<id>, --csv). Medido em 18/09/2026: 1.190 pedidos, R$ 34.802,40, dos quais

661 pagos = R$ 18.499,10. O detalhe encolhe o problema: esta em 11 ids, e **94% do valor

pago e de UM evento** — o 222, "Terapia do Riso - Especial 12 anos", 625 pedidos pagos,

R$ 17.479,90, sessoes de jun a dez/2017. Dois dos 11 sao evento de teste, com R$ 0. O nome sai de

pedidos.item (texto congelado na compra, com ate 4 grafias para o mesmo evento); produtor e

destino de repasse nao sao reconstruidos, seria suposicao.

Erro meu, corrigido: eu reportei "aprovados orfaos: 0". Era falso negativo — status_transacao

e codigo numerico (3 Paga, 4 Disponivel, 6 Devolvida, 7 Cancelada, conforme

getStatusTransacao() em painel_old/modulos/pedidos/bootstrap.php:37) e eu comparei com texto.

Arquivos: admin/cron/sync_facebook_events.php, includes/post_event_feedback_helper.php,

admin/cron/palcofan_nota_pendente_alerta.php,

admin/modulos/campanhas/redes_sociais.php, admin/modulos/campanhas/redes_sociais/voz_audio.php,

e o novo /root/rnt-tools/pedidos_orfaos.php.

[2026-09-18] Excluir peca passa a RECUSAR peca com acervo (BK-446)

O ADMIN deu o eixo que faltava: "a pagina da peca nunca morre". Quando a temporada acaba, o

site apaga a foto e a ficha tecnica (na proxima temporada o elenco pode ser outro) e a peca vira

evento.php com informacao basica — URL, SEO e comentarios ficam no ar, e a pagina serve de

convite para o produtor mandar a atualizacao. Isso ja funcionava e nao foi tocado

(admin/cron/cleanup_old_pecas_images.php apaga tb_fotos e os arquivos e preserva a peca).

Consequencia: o botao de excluir e para LIXO — duplicata, teste, erro do captador. E ele

estava aceitando apagar coisa que nao e lixo.

Achado que motivou a guarda. As 13 pecas criadas SO para pendurar o acervo do Facebook

(ids 1600-1612, BK-411/413 — so nome, slug, status='I' e zero pedidos) estavam **todas

apagaveis por um clique**. Entre elas a 1604, com comentario aprovado e visivel no PalcoFan, e a

1601/1604 com decidido_admin = 1, que e decisao final do ADMIN e que o registro do projeto diz

que nenhuma automacao pode desfazer. Pior: palcofan.php:48 usa JOIN pecas (inner), entao

guardar a linha do comentario nao o mantem no ar — apagar a peca o faz desaparecer em

silencio. No total, 16 pecas apagaveis carregavam 160 comentarios de arquivo.

Guarda nova (GUARDA): a exclusao e recusada — sem "excluir mesmo assim" — quando a peca tem

comentario de arquivo, avaliacao de espectador, link curto publico, linha no mapa de promocoes do

import do Facebook, ou vinculo em produtos. Falha de consulta tambem recusa (fail-closed): nao

afirmar ausencia de acervo sem ter conseguido olhar. Medido: de **326 pecas elegiveis, recusa 16 e

deixa 310 apagaveis. Visualizacao em cliente_tracking nao** bloqueia (seriam 69, e isso

tornaria o botao inutil), mas entra como aviso na resposta.

O astra defendeu recusar em vez de avisar, e convence: um aviso com confirmacao nao protege a

decisao do dono, permite viola-la em um clique.

Tres cascatas que eu escrevi e ele me fez REVERTER, com razao funcional que eu nao tinha visto.

Eu ia apagar a memoria do captador junto com a peca. Isso **faz o bot recriar o que acabou de ser

excluido** — autossabotagem, porque o botao serve justamente para apagar duplicata:

  • bot_pecas_sugestoes e a memoria de decisao por URL (bot/handlers/event_matcher.php:33:

SELECT ... WHERE url_externa). Sem a linha, a proxima captacao trata o evento como inedito.

  • bot_eventos_log e a barreira de dedup por hash (bot/handlers/deduplicator.php:34:

WHERE evento_hash). Apagar derruba a barreira.

  • bot_valor_overrides e consultado por sugestao OU peca (event_matcher.php:1229), entao

apagar so por peca_id e incoerente.

Quarta reversao: o convite saiu da cascata. Apagar rnt_convite_parceria_log remove a trava de

25h por PRODUTOR e pode antecipar o convite de outra peca dele — e **restaurar a linha do backup

nao desfaz um WhatsApp enviado**. Manter a linha orfa nao quebra nada. Isso nao contradiz a ordem

do ADMIN de apagar o orfao da peca 1613: aquela foi limpeza pontual, ja feita, e a linha tinha 8

dias, fora da janela.

social_post_queue nao e apagada, e CANCELADA. Linha posted e registro do que foi publicado

em rede social e nao se reescreve; linha ainda executavel vira canceled, o que preserva o rastro

e impede que alguem aprove depois e gere post sobre peca inexistente. De passagem: o cron

sync_facebook_events.php:4395 ja cancela post de peca inativa/removida, mas **omite

pending_approval** — que e o estado de 240 das 255 linhas da fila.

Testes, todos com peca descartavel e conferencia independente:

  • peca 244 (no mapa de promocoes) e as 1600/1604 (acervo): recusadas, citando a razao;
  • guarda de produtos provada criando o vinculo, recusando, removendo o vinculo e apagando;
  • memoria do bot (sugestao, log, override) e log de convite: sobrevivem a exclusao;
  • social_post_queue: a pending_approval virou canceled e a posted ficou intacta;
  • lixo sem acervo: continua apagando normalmente;
  • ao final, acervo conferido intacto: 13 pecas, 4.837 comentarios, 118 linhas de mapa, e o maior

id de pedidos de volta em 73939.

O inventario completo das 26 tabelas e o destino de cada uma ficaram no BK-446, junto com tres

pontas abertas: os 4 social_*_jobs (cascata que exige interromper job e cobrir os arquivos no

backup), o encerramento de processamento nas tabelas mantidas como auditoria (dois casos medidos:

post_event_feedback_helper.php:418 e o pending_approval acima) e os orfaos historicos.

Arquivo: admin/modulos/eventos/action.php.

[2026-09-18] Revisao do gpt-6-astra sobre a exclusao de peca: 6 correcoes (BK-437e)

Pedi a ele para revisar o BK-437d/443 com o case 'deletar' completo e o trecho de dedup do cron

em recorte (sem mandar o CHANGELOG, que tem 8.000+ linhas). Ele achou defeitos reais, todos

conferidos na fonte antes de eu mexer:

1. Minha conclusao sobre o convite estava errada — e o erro era meu, nao dele. Eu escrevi que

a trava por produtor "nao depende" da linha apagada. Falso: a clausula e

l2.id_produtor = p.id_produtor AND l2.ultima_tentativa_em > NOW() - INTERVAL 25 HOUR, isto e,

qualquer linha recente daquele produtor bloqueia o convite de todas as pecas dele. Apagar

uma linha com menos de 25h pode antecipar o convite de outra peca do mesmo produtor. Para a

linha da 1613 que eu apaguei nao houve efeito (ela tinha 8 dias), mas o comentario no codigo

generalizava e estava errado. Corrigido tambem que a trava por peca exige

enviado = 1 OR tentativas >= CONVITE_MAX_TENTATIVAS, nao "qualquer registro".

2. As imagens eram destruidas antes da ultima recusa possivel — o achado mais grave. O

unlink acontecia ANTES da reconferencia de pedidos do BK-443 e ANTES do DELETE da peca. Se um

pedido aparecesse ali, a peca ficava, mas sem imagens — e o JSON guarda caminhos, nao bytes.

A recuperabilidade que eu tinha anunciado era falsa. Agora o passo e o ultimo e deixou de ser

destrutivo: a pasta inteira e movida para junto do backup com rename() (atomico, mesmo

filesystem, device 2049 conferido nos dois lados). Testado: com pedido presente as 3 imagens

sobrevivem intactas; sem pedido, vao para peca_<id>_<ts>_imagens/ com os bytes preservados.

3. O backup gravava cada valor DUAS vezes. fetch_array devolve MYSQL_BOTH, entao a peca

saia com 120 chaves para 60 colunas. Isso dobrava o arquivo e quebrava a restauracao: um

restore que percorre as chaves bate em Unknown column '0'. Descobri isso porque fui fazer a

prova que ele cobrou. Agora so as chaves nomeadas entram — 60 chaves, 5.203 -> 3.234 bytes.

4. A prova de recuperacao, que eu nao tinha feito. Conferir que o JSON "tem as secoes" nao

demonstra nada. Ciclo completo executado: peca criada -> excluida pelo action.php real ->

restaurada so a partir do material preservado -> comparada com o estado original. nome,

sinopse (com acento), status, contagem de sessoes, foto, convite e os bytes das duas imagens:

todos IGUAIS. Depois excluida de novo.

5. DELETE condicionado a uma fotografia antiga. As remocoes de foto e convite so rodavam se o

SELECT anterior tivesse achado linha. Linha inserida depois do SELECT ficava orfa e a resposta

dizia sucesso sem aviso. Agora o DELETE roda sempre e ha conferencia de que sobrou zero —

prova mais forte que contar linhas afetadas, e sem depender de affected_rows, que a classe Sql

nao expoe ($conx e privado). Cheguei a escrever $obj_sql->affected_rows(), que seria fatal

por metodo inexistente; peguei antes de seguir.

6. Backup: colisao de nome e conteudo bloqueante. O nome era peca + segundo, gravado sem

criacao exclusiva — duas exclusoes no mesmo segundo se sobrescreviam. Agora leva microsegundo,

usa fopen(..., 'xb') (falha se existir) e chmod 0640. E o conteudo do convite ia no mesmo

json_encode obrigatorio: um campo com UTF-8 invalido bloqueava a exclusao inteira, contradizendo

o "best-effort". Agora, se falhar, tenta de novo sem os dados do convite e registra o motivo.

Correcao factual que ele me fez: eu havia comentado que "sem transacao (MyISAM) nao ha como

travar". Falso — MyISAM aceita LOCK TABLES (testei: LOCK TABLES pecas WRITE e aceito, e

inclusive ficou esperando um SELECT longo soltar o read lock). Fechar a janela de verdade exige

coordenar esta tela com o webhook que INSERE o pedido, que hoje nao valida se a peca existe. Isso

e mudanca de arquitetura, nao de botao: BK-445.

Um ponto em que ele apontou e eu conferi que nao ha bug: $cnv_rs usado num if distante.

Esgotar o resultado no while nao transforma um result set valido em false. Ele mesmo concluiu

o mesmo.

Arquivo: admin/modulos/eventos/action.php.

[2026-09-18] Excluir peca reconfere pedidos duas vezes (BK-443)

Achado do gpt-6-astra: entre o COUNT dos pedidos e o DELETE existe uma janela em que uma

compra pode cair — um checkout iniciado antes da inativacao, cujo webhook chega no meio da

exclusao. Sem transacao nao ha como travar (pecas, pedidos, tab_sessoes e tb_fotos sao

todas MyISAM), mas da para estreitar a janela e escolher o dano menor:

  • reconferencia 1, logo antes do primeiro DELETE: reduz a janela ao tempo de gravar o

backup. Se apareceu pedido, cancela sem apagar nada;

  • reconferencia 2, logo antes de apagar a peca: se um pedido apareceu mesmo assim, a **peca

fica**. Pedido apontando para peca inexistente quebra fechamento financeiro e historico do

cliente; sessao e foto perdidas estao no backup e sao recuperaveis. Entre os dois danos, este

e o menor — e a resposta diz isso ao operador em vez de fingir sucesso.

Testado com peca descartavel: o caminho completo segue funcionando (peca 1623 excluida, sessao e

backup conferidos) e a peca com pedido e recusada com o pedido e as sessoes intactos. **A janela

concorrente em si nao foi simulada** — a recusa medida veio da guarda inicial, porque o pedido

ja existia quando a requisicao entrou; a reconferencia e identica em forma aquela guarda, o que e

semelhanca, nao prova.

O pedido de teste criado para esse ensaio (73940, conta de teste 50877) foi removido e conferido:

o maior id de pedidos voltou a ser 73939, o da tentativa real da Silvas. Cuidado herdado do

BK-412, em que um teste com transacao vazou pedido aprovado falso numa tabela MyISAM.

Arquivo: admin/modulos/eventos/action.php.

[2026-09-17] Excluir peca tambem limpa o log de convite de parceria (BK-437d)

A exclusao da peca 1613 (Silvas da assessora, cuja parceria acabou fechada na peca 1620)

deixou uma linha orfa em rnt_convite_parceria_log. Antes de apagar, a duvida certa era se esse

log funciona como travamento de reconvite — apagar poderia fazer a produtora receber convite de

novo. A conferencia em admin/cron/convite_parceria_produtor.php:140-158 mostrou que a

deduplicacao e dupla, e que as duas metades se comportam de formas diferentes:

  • por peca_id: permanente, mas inutil quando a peca nao existe mais;
  • por id_produtor: limitada a INTERVAL 25 HOUR, ou seja, expira sozinha.

A linha era de 10/09, oito dias fora da janela de 25h. Apagar nao liberava nada que nao

estivesse liberado — por isso foi seguro. Linha removida com backup em

scratchpad/orfaos_1613/rnt_convite_parceria_log_1613.json.

No codigo, case 'deletar' passa a incluir a tabela na cascata (o comentario do BK-437c dizia o

oposto, que era preservada de proposito; foi revertido por decisao do ADMIN). A diferenca

importante e que aqui a limpeza e best-effort e nao bloqueante, ao contrario de sessoes e

fotos: falha de leitura ou de DELETE nao cancela a exclusao, so avisa na resposta. A assimetria

e do dano — sessao orfa quebra venda, foto orfa deixa cartaz inalcancavel, linha orfa de convite

nao quebra nada. Acoplar o botao de excluir a uma tabela auxiliar criaria um modo de falha novo

em troca de nada.

Dois defeitos meus vieram junto, os dois achados por teste real (peca descartavel 1621/1622

criada, excluida pelo action.php de verdade via arnes CLI, e conferida por consulta

independente) — nenhum dos dois o php83 -l pegaria:

1. o diretorio de imagens nunca era removido, e a tela dizia sucesso. O caminho usava

dirname() TRES vezes, o que da .../admin — certo para admin/logs/, errado para

arquivos/, que fica na raiz do site. Procurava em admin/arquivos/pecas/<id>, que nunca

existiu; is_dir() dava false, o loop era pulado inteiro, e como $arquivos_sobraram so era

marcado DENTRO daquele if, a resposta declarava limpeza completa. Efeito: linha da foto

apagada e arquivos orfaos no disco — exatamente o que o BK-437c existia para evitar. Agora sao

quatro dirname(), e ha guarda nova: se arquivos/pecas (a pasta base) nao existe, o caminho

esta errado e isso e reportado em vez de virar silencio.

2. o backup morava dentro do webroot. .json e servido de proposito pelo vhost

(/api/eventos/pecas.json e export publico), entao admin/logs/pecas_excluidas/*.json ficava

legivel pela web para quem adivinhasse o nome — e o arquivo traz o telefone do produtor. Pior:

o rsync externo do NBi7 reescreve o webroot a cada ~11min, e este JSON e o UNICO rollback

possivel (todas as tabelas envolvidas sao MyISAM). Migrado para /www/rnt-backups/pecas_excluidas

(750, www:www), mesma convencao do /www/rnt-push-sender. Nao ha open_basedir no pool, e a

escrita como www foi testada.

Guardas reconferidas no mesmo teste, todas recusando sem apagar nada: id nao numerico

('123abc'), peca com status A, e id inexistente.

Arquivo: admin/modulos/eventos/action.php.

De passagem: as duas entradas anteriores estavam datadas 2026-09-18, efeito do

date.timezone=PRC do php.ini (o PHP ve 18/09 10:55 de Pequim quando aqui sao 17/09 23:55).

Os commits correspondentes sao de 17/09 21:25 e 23:45. Corrigidas.

[2026-09-17] Mensagem nao e mais descartada por falso negativo de destinatario (BK-438)

Em 17/09 a mensagem ao cliente 51222 (pedido 73939, Silvas) foi descartada as 20:00:45 com

Numero sem conta WhatsApp ativa (No LID), minutos depois do QR ser escaneado (~19:57). O

/check-number do proprio servico responde exists: true para o mesmo numero com a sessao

estavel. O cliente era alcancavel, e a mensagem foi jogada fora.

O que tornava isso permanente eram duas regras em includes/whatsapp_helper.php: qualquer HTTP

422, e o substring no lid independentemente do codigo HTTP. A segunda foi apontada pelo

gpt-6-astra — e importa, porque o false do precheck (bot/whatsapp/server.js:1209) sai como

HTTP 500, nao 422, entao trocar so o 422 nao resolveria nada.

  • bot/whatsapp/server.js: grava bot/whatsapp/READY_SINCE no evento ready e apaga em

auth_failure e disconnected;

  • includes/whatsapp_helper.php: novo _whatsapp_sessao_aquecendo(). Dentro de

RNT_WA_AQUECIMENTO_SEG (180s), "sem conta WhatsApp" passa a ser transitorio e a

mensagem nao e descartada. Sem a marca, o comportamento antigo e mantido — nao inventa

veredito a partir de ausencia de informacao.

Testado nos tres estados: sem marca -> null (mantem permanente); ready ha 0s -> true

(transitorio); ready ha 600s -> false (permanente vale). A marca foi restaurada ao estado

original depois do teste, e o servico nao foi reiniciado (a sessao esta conectada).

Ressalvas honestas:

  • os 180s sao margem operacional, nao garantia. Nao existe N comprovado;
  • a causa exata do falso negativo nao esta provada. O 422 medido nao veio do precheck — deve

ter vindo da resolucao de destinatario dentro do sendMessage(). O que esta provado e que o

veredito estava errado;

  • tornar transitorio nao e reter. A mensagem ainda morre depois de 5 tentativas

(whatsapp_helper.php:909). Arquivar em vez de apagar continua no BK-438.

[2026-09-17] BK-437b/431d: correcoes da revisao do astra sobre o botao de excluir

Excluir evento (admin/modulos/eventos/action.php) — tres defeitos no que eu entreguei ontem:

  • o backup podia passar vazio. A validacao era $gravou === false, e isso aceitava: o SELECT

das sessoes falhar (backup com sessoes: []), o json_encode devolver false (gravando string

vazia) e escrita parcial (0 !== false). Agora confere a consulta, a serializacao e se os

bytes gravados batem com o tamanho do JSON;

  • os DELETE nao eram conferidos. Se apagar as sessoes falhasse, a peca era apagada de todo

jeito — deixando exatamente a sessao orfa que a guarda existe para evitar. Agora confere que

sobraram zero sessoes antes de apagar a peca, e **erro na conferencia final nao vira

sucesso** (antes, $conf_rs === false caia no ramo de sucesso);

  • o (int) truncava o id. '123abc' virava 123 e apagaria a peca 123. Agora exige

ctype_digit e recusa formato invalido.

admin/js/deletar.js: a mensagem "A peca NAO foi excluida" no .fail() estava errada —

timeout ou JSON invalido podem acontecer depois de o servidor ter apagado. Passou a dizer que

nao foi possivel confirmar.

Marcador de teste no sales_webhook.php (BK-431d) — minha correcao de ontem nao funcionava,

e a medicao que a "provou" era invalida. O require config/connect.php estava dentro do

processSale(), entao $link/$conexao ficavam locais ao metodo, e o resolvedor do helper

procura globais. Eu tinha testado carregando o connect em escopo global — contexto que nao existe

no endpoint. Mesmo erro de metodo que o astra ja tinha me pegado no BK-431b.

Corrigido dando ao helper a capacidade de abrir a propria conexao como ultimo recurso, lendo o

.env, read-only e com falha aberta. Nao usei o connect.php de proposito: ele faz

or die(mysql_error()) (config/connect.php:93), o que colocaria texto fora do JSON e passaria a

exigir banco num endpoint que hoje so usa arquivo. Medido no contexto real do endpoint (so

facebook_ads.php carregado, sem globais): conexao resolvida, 50877 = true, 50220 = false.

[2026-09-17] Excluir evento no admin passa a funcionar, com guarda por pedido (BK-437)

O botao de excluir ja existia na listagem (admin/modulos/eventos/index.php:493, so para evento

inativo), o admin/js/deletar.js ja estava carregado e o case 'deletar' ja existia em

action.php:3548. Nada disso funcionava, por tres defeitos empilhados:

  1. A condicao checava o dono errado. Era

WHERE id = X AND id_produtor = $id_produtor AND status = 'I', e $id_produtor no admin e

$login->GetId() — o id do admin logado, nao o produtor da peca. E codigo copiado do painel

do produtor sem adaptar. Como nunca casava, o admin recebia sempre

"Voce so pode excluir eventos pendentes de aprovacao!".

  1. O JS mentia. Mostrava "Registro Deletado com sucesso!" e sumia com a linha no .done(),

sem olhar a resposta. Por isso o botao parecia nao funcionar em vez de mostrar a recusa.

  1. O DELETE deixava orfao — tocava so pecas, e as sessoes ficavam penduradas.

O achado que mudou o desenho: a condicao quebrada estava, por acidente, evitando um estrago

maior. O botao aparece para toda peca inativa — sao 1.283, e 957 delas tem pedido.

Simplesmente "consertar o que estava errado" transformaria um botao inofensivo num destruidor de

historico de venda: pedidos.id_evento apontaria para peca inexistente, quebrando fechamento

financeiro e historico do cliente.

Por isso a guarda que entra no lugar e por pedido, nao por dono. Ressalva: a guarda

confere o COUNT antes do DELETE e nao ha trava — um pedido criado nesse intervalo nao e barrado

(config/functions_new.php:946 insere sem conferir a peca). Ver BK-443.

  • evento precisa existir e estar inativo;
  • zero pedidos, em qualquer status — e, se houver, a recusa diz quantos;
  • backup das linhas (peca + sessoes) em admin/logs/pecas_excluidas/peca_<id>_<ts>.json antes

de apagar, e a exclusao e cancelada se o backup nao gravar;

  • apaga tab_sessoes e depois pecas, confere que as sessoes sairam antes de apagar a peca,

e nao chama de sucesso quando a propria conferencia falha (corrigido no BK-437b).

Sem transacao, de proposito: pecas, tab_sessoes e pedidos sao todas MyISAM —

START TRANSACTION/ROLLBACK nao desfaz nada nelas. Foi a licao do BK-412, em que um ROLLBACK

deixou vazar um pedido aprovado falso. Em MyISAM o que protege e conferir antes e ter o backup.

O admin/js/deletar.js e compartilhado por 10+ modulos (teatros, categorias, pedidos,

newsletter, indexfotos...). Conferi que todos respondem com status = 'success', entao a correcao

vale para todos. Ressalva: sao 16 modulos que carregam o JS e 13 tem case 'deletar';

config e notificacoes carregam sem o case, e clubentpedidos tem botao sem action.php. E o JS

sozinho nao "conserta a mentira": varios handlers continuam declarando sucesso sem conferir o

DELETE (ex.: admin/modulos/categorias/action.php:57). Ver BK-444.

Simulacao das guardas: peca 1613 (inativa, 0 pedidos) -> apaga com 3 sessoes; peca 1620 (ativa) ->

recusa; peca 17 (inativa, 63 pedidos) -> recusa dizendo quantos.

[2026-09-17] BK-421b/431c: dois defeitos que a revisao final do astra achou

1. admin/modulos/bot/stats.php:263 — eu furei a propria protecao que tinha criado.

Ao dar $forcar = true nos "caminhos manuais" do BK-421, incluí esse — mas ele nao e o botao de

reiniciar, e a acao "processar fila". Com o servico em qr_pending, a saude devolve offline e

esse caminho reiniciaria o servico, **gerando um QR novo e invalidando o que o operador esta

tentando escanear** — exatamente o que o BK-420 existia para impedir. Voltou ao comportamento

automatico. Agora so dois caminhos forcam, e os dois sao o botao explicito de reiniciar

(admin/whatsapp_restart.php:56 e stats.php:205).

2. admin/modulos/campanhas/api/sales_webhook.php — o marcador de teste continuava inerte la.

O BK-431b corrigiu 3 dos 4 contextos sem conexao; este ficou. O endpoint e uma chamada HTTP

independente e o bootstrap dele so carrega facebook_ads.php, que nao abre banco. Medido:

```

conexao=NENHUMA rnt_pedido_e_teste(50877)=false

```

Ou seja, **venda de conta de teste continuava entrando no log de vendas e no gatilho de orcamento

de anuncio**. Passou a carregar config/connect.php. Depois: conexao=achou, 50877=true,

50220=false.

Correcao de um exemplo meu: eu escrevi que '1.5:30' dava 5400 no 5.6. Errado — 5.400 e so a

parcela das horas; o total (horas + minutos) e 7.200. A conclusao nao muda (o (float) e

que reproduz o 5.6, o (int) nao), mas o numero que eu citei estava errado.

[2026-09-17] BK-423b: a blindagem usava (int) onde o 5.6 usava float

Erro meu, achado por teste proprio ao preparar a revisao final. Eu tinha declarado a blindagem do

BK-423 "identica byte a byte ao 5.6" — e era, nos formatos que eu testei ('', '00:00',

'01:00', '02:00, 01:00', '0:30'). Ampliando o corpus, apareceu a diferenca:

```

'1.5:30' 5.6: h*3600 = 5400 com (int): 3600

'0x10:00' 5.6: h*3600 = 57600 com (int): 0

```

O 5.6 multiplicava a string direto, com semantica de float: '1.5' * 3600 = 5400. Meu

(int)'1.5' trunca para 1. Trocado para (float) nos 24 pontos (12 locais x 2 linhas) dos 11

arquivos. Com (float) a saida volta a bater com o 5.6 em todos os formatos testados.

Menos um, que nao tem conserto: '0x10'. O PHP 5 interpretava string hexadecimal em contexto

numerico; o PHP 7 removeu isso da linguagem. Nenhum cast traz de volta — a diferenca e da versao,

nao da blindagem. Dado assim nao existe na base (pecas.tempo_antes de peca ativa so tem

'02:00, 01:00', '01:00', '' e '00:00').

A licao: "identico byte a byte" so vale para o corpus que foi testado. Eu afirmei equivalencia

geral a partir de 5 formatos bem-comportados.

Validado: php83 -l e php56 -l limpos nos 11; home, /silvas, /pe-de-cachimbo,

/lisa-lesa-e-louca e /eventos.php em 200; act=entradas respondendo; zero erro novo no log.

[2026-09-17] BK-432 (parte 1): 4 automacoes saem do PHP 5.6

Migradas, com o metodo que o gpt-6-astra recomendou — e a correcao que ele fez ao meu plano:

em vez de require config/connect.php (que abre uma segunda conexao, sobrescreve $link/$conexao

e troca o charset), basta mysqli_report(MYSQLI_REPORT_OFF) antes do bootstrap. No 8.3 o padrao

do mysqli e lancar excecao, e @ nao suprime excecao: sem isso, todo if (!$result) desses

scripts viraria codigo morto.

| Script | O que precisou | Como provei |

|---|---|---|

| atualizar_feed.php | nada no codigo | rodado pelo wrapper real; os 4 arquivos regerados |

| admin/cron/sync_social_metrics.php | mysqli_report(OFF) antes do setup.php, que abre a conexao antes do connect.php | rodado no 8.3, exit 0, zero deprecation |

| api/eventos/gerar_json.php | mysqli_report(OFF) + guarda nos 2 num_rows() + error_reporting | JSON byte a byte identico ao do 5.6 |

| admin/cron/analisar_roi_campanhas.php | mysqli_report(OFF) antes do new mysqli | saida identica a do 5.6 (44 campanhas, 41 ativas, R$ 70,00) |

Detalhes que valem registro:

  • gerar_json.php: Sql::num_rows(false) cai no $this->last_query e chega a

mysqli_num_rows() com algo que nao e resultado. Medi no 5.6: devolve NULL, e NULL > 0 e

false — o bloco era pulado. No 8.3 seria TypeError. O if ($result && ...) reproduz o 5.6.

  • gerar_json.php: as 13 chamadas a utf8_encode() (deprecated desde o 8.2) cuspiam

1.417 linhas de Deprecated por rodada, a cada 2h. Silenciei a categoria, como

z_dispara_pagamento.php:2 ja faz. Trocar o utf8_encode de verdade e outro trabalho — ha

conversao possivelmente duplicada preexistente.

  • atualizar_feed.php: os arquivos gerados MUDAM, e e conserto. O PHP 5.6 tem tzdata 2016.10

e ainda acredita no horario de verao brasileiro, extinto em 2019 — a partir de 18/10/2026 ele

emitia -02:00 onde o certo e -03:00. O 8.3 tem tzdata 2024.2. Sao 10 sessoes futuras de peca

ativa na faixa afetada. Isso vale para qualquer script em 5.6 que formate data futura.

  • armadilha silenciosa conferida e nao disparada hoje: atualizar_feed.php:210 usa

$row['oferta'] > 0, e 'abc' > 0 e false no 5.6 e true no 8.3. Na base real, pecas.oferta

de peca ativa so tem NULL, '', '63%' e '70%'.

/root/v83.sh s estava mentindo, e o erro era meu. O detector que eu adicionei no BK-417

procurava o caminho explicito /www/server/php/56/bin/php — mas /usr/bin/php e /bin/php sao

symlink para o 5.6, entao wrapper nenhum precisa citar o 5.6. Ele dizia cron bt .. PHP 8.3

com 4 scripts ainda no 5.6 — o mesmo tipo de ponto cego que eu tinha criticado no proprio v83.sh.

Reescrito com a logica invertida: wrapper que invoca script do RNT e nao usa binario 8.3

explicito esta em 5.6. Agora acusa os 6 que faltam.

Nao migrados, e por que: z_dispara_pagamento.php e z_dispara.php (decisao de produto — ver

abaixo), sync_facebook_events.php (posta em publico; precisa de forma de validar sem postar) e

report_monthly.php (bloqueado: 7 arquivos do vendor/tecnickcom/tcpdf nao compilam no 8.3).

[2026-09-17] Destaques do fim de semana voltam a ser postados (BK-419)

admin/cron/post_highlights.php pedia inc-config.php e inc-conexao.php, que **nao existem no

projeto. Resultado: PHP Fatal error em 49 execucoes seguidas**, com o wrapper do painel bt

imprimindo ★ Successful logo abaixo — ele nao olha o codigo de saida do PHP. O RNT ficou meses

sem postar destaque automatico no Facebook e no Instagram e ninguem viu.

  • carga corrigida na mesma ordem do cron irmao que funciona (sync_facebook_events.php:15-16):

admin/data/setup.php traz a classe Sql que o FacebookEventService.php:29 instancia, e

config/connect.php publica o $link

  • novo --dry-run: o script publica em rede social publica assim que roda, e **nao havia como

testar sem postar**. Agora ele escolhe as pecas, monta o texto, resolve a imagem e para antes de

publicar, imprimindo o que sairia. A guarda fica antes das tres chamadas de publicacao

(createFeedPost, createInstagramMedia, publishInstagramMedia)

  • UTM duplicado corrigido: duas linhas seguidas acrescentavam os mesmos tres parametros, a

primeira com weekend_highlights fixo e a segunda com o nome que varia por dia da semana. Todo

link saia com utm_source, utm_medium e utm_campaign repetidos. So apareceu porque o

--dry-run deixou ler o texto antes de publicar

  • wrapper do cron passou de /www/server/php/56/bin/php para o 8.3 (era um dos 9 do BK-432);

backup em /root/rnt-tools/backups/cron/

Nao ha risco de enxurrada ao religar: o script e sem estado. Cada execucao consulta sessoes dos

proximos 4 dias (ORDER BY RAND() LIMIT 4, so peca com status = 'A') e monta um post. Nao

existe fila acumulada dos meses parados.

Validado com --dry-run no 8.3: post montado com 4 pecas em cartaz, imagem resolvida, links com

UTM unico. Nada foi publicado.

Aprendizado, que vale para as outras tarefas de /www/server/cron/: o ★ Successful do

wrapper do painel bt nao prova nada — ele imprime isso incondicionalmente depois do PHP, sem

olhar o codigo de saida. Foi isso que escondeu 49 fatais seguidos aqui, e o mesmo padrao apareceu

no z_dispara_pagamento.php, que falha calado a cada minuto. **Conferir sempre o log da propria

automacao. Proxima execucao real: 21/09 as 06:04** (o cron roda dias

1, 5, 9, 13, 17, 21, 25 e 29).

[2026-09-17] BK-431b: o marcador de teste era INERTE em 4 dos 6 consumidores

Revisao do gpt-6-astra sobre o 326350c5e, confirmada testando o bootstrap de cada consumidor:

rnt_clientes_teste_ids() procurava so $GLOBALS['conexao'] e $GLOBALS['link']. Dos seis,

apenas z_dispara_pagamento.php publica essas globais (via config/connect.php). Os outros:

  • lembrete e lista do produtor usam a classe Sql, que guarda a conexao em $this->conx (privada);
  • o feedback usa um $db local devolvido por rnt_wa_campaign_db();
  • o webhook de campanhas e uma chamada HTTP independente.

Nesses quatro a lista voltava vazia, o fragmento SQL saia string vazia e o filtro **nao

filtrava nada** — em silencio, porque a falha aberta e justamente nao mudar o SQL. Medido:

```

connect ids=[50877] filtro=ativo

admin ids=[] filtro=VAZIO (nao filtra!)

produtor ids=[] filtro=VAZIO (nao filtra!)

campanha ids=[] filtro=VAZIO (nao filtra!)

```

Meu erro de validacao: eu provei o filtro executando o SQL numa conexao que eu mesmo tinha

aberto no script de teste. Isso valida o fragmento, nao o consumidor. A prova certa e carregar o

bootstrap de cada um e perguntar ao helper se ele acha a conexao.

Corrigido:

  • novo rnt_pedido_teste_conexao(): tenta as globais, depois rnt_mysql_link() — o resolvedor

do proprio shim do BK-348 (config/mysql_compat.php:38), que guarda o link aberto pelo

mysql_connect() da classe Sql — e por fim rnt_wa_campaign_db(). Os quatro contextos passaram

a resolver

  • NULL preservado: NULL NOT IN (50877) nao e verdadeiro, entao pedido com cliente_id

NULL sumia do resultado. Em consulta com LEFT JOIN (a lista do produtor) isso mudava

comportamento para pedido legitimo. Agora o fragmento e

AND (p.cliente_id IS NULL OR p.cliente_id NOT IN (...))

  • try/catch: mysqli em modo estrito lanca excecao e @ nao suprime excecao no PHP 8
  • validacao estrita do CSV: ctype_digit, porque o (int) cru aceitava '50877abc' como 50877

Validado: 4/4 contextos com filtro ativo, os 36 pedidos da conta de teste ainda excluidos, e

pedidos com cliente_id NULL preservados.

[2026-09-17] BK-423 (complemento): z_dispara.php tambem blindado

A auditoria do BK-432 achou um buraco na entrega do BK-423 de hoje: z_dispara.php:129-131 tem o

mesmo padrao (explode em campo vazio + aritmetica) e ficou de fora. O filtro de "arquivos

vivos" que eu usei para montar a lista excluiu z_dispara* por engano, e eu declarei 12 arquivos

cobertos.

Aqui a conta e soma, nao multiplicacao: date('H') + $tempo_antes_h. No 5.6, 12 + '' da 12;

no 8.3 e TypeError: Unsupported operand types: int + string. A peca 1619 tem exatamente o dado

que dispara (tempo_antes = '').

O script esta duplamente desligado hoje — fora do crontab e com o kill switch

RNT_LISTA_EMAILS_ENABLED ausente do .env — entao nao havia falha em producao. Mas religar sem

isto traria o fatal junto.

Fidelidade medida como no resto do BK-423: rodei o trecho original no 5.6 e o blindado no 8.3 sobre

'', '00:00', '01:00', '02:00, 01:00' e '0:30' — janelas de inicio e fim identicas.

[2026-09-17] BK-431: marcador de pedido de teste

Nao existe sandbox no checkout do RNT: o fluxo usa token de producao do Mercado Pago e callback de

producao, e so abrir pedido.php logado ja cria pedido (config/functions_new.php:905). Pior,

a janela para desfazer nao e de 10 minutos: z_dispara_pagamento.php roda a cada minuto e

seleciona status_transacao = '1' OR transaction_id = '' sem carencia — e-mail ao cliente em

menos de 60s. Em 11/09/2026 um teste ja vazou o pedido 73919 aprovado em nome de uma cliente real

(pedidos e MyISAM, ROLLBACK nao desfaz).

Novo includes/pedido_teste.php:

  • o marcador nunca vem do POST. E derivado no servidor do cliente_id contra uma allowlist em

site_config.clientes_teste_ids — tabela que nenhum codigo escreve, so acesso ao banco

altera. Assim nao existe parametro que um visitante mande para se declarar "teste" e escapar de

cobranca ou ganhar ingresso

  • falha aberta de proposito: consulta com erro ou chave ausente devolve lista vazia, e o SQL

fica identico ao de hoje. Falhar fechado silenciaria notificacao de compra REAL, que e pior

  • rnt_sql_sem_pedidos_teste($alias) devolve fragmento SQL e sanitiza o alias
  • rnt_log_pedido_teste() grava em admin/logs/pedido_teste.log o que teria sido enviado —

sem isso o teste fica cego sobre se a notificacao seria disparada

Conta de teste autorizada pelo ADMIN: 50877, Nea Barros (neaparente@gmail.com).

Regra: suprime o que alcanca CLIENTE ou PRODUTOR; mantem o que vai para o GESTOR, porque e

assim que o operador enxerga o teste acontecendo.

| Suprimido | Canal |

|---|---|

| z_dispara_pagamento.php | e-mail ao cliente, a cada minuto |

| admin/cron/lembrete_pedido_pendente.php | WhatsApp ao cliente (2 consultas) |

| webhook_mp_ingressos.php | WhatsApp de compra aprovada ao cliente |

| admin/cron/post_event_feedback_queue.php | pesquisa de satisfacao ao cliente |

| produtor/listaopen.php | lista de compradores do produtor |

| admin/modulos/campanhas/api/sales_webhook.php | orcamento de anuncio |

Mantidos de proposito: notifica_pedido_push.php e verifica_wa_compra_aprovada.php (gestor).

Validado: php83 -l e php56 -l limpos nos 7 arquivos; o helper devolve o mesmo resultado nas

duas versoes (o cron mais perigoso roda em 5.6); o SQL montado de cada consulta foi executado

no banco; e a prova direta — numa consulta com 41 linhas de duas clientes, o marcador removeu

exatamente os 36 pedidos da conta de teste, e a lista do produtor caiu de 2 para 1.

Pendencia conhecida: o celular da conta de teste esta gravado como (55) 2 1979-9799, que

normaliza para 5555219799799 e o dispatcher rejeita (regra ^55219[0-9]{8}$). Teste que

envolva WhatsApp para ela nao chega — e nao por causa do marcador.

Nao coberto: uma compra de teste ainda marca sec_enviolista = 1, o que pode disparar o

e-mail diario ao produtor mesmo com a lista vazia. Ver BK-431 no backlog.

[2026-09-17] BK-423: blindagem dos campos vazios que viravam fatal no 8.3

Fallout do BK-348 que so apareceu no BK-422: explode(':', '') devolve array de uma posicao,

o indice [1] fica indefinido e a multiplicacao seguinte recebe string vazia. No 5.6 isso era

Notice suprimido (peca.php:4) e a conta dava zero; no 8.3 e TypeError fatal, e derruba

a pagina inteira.

14 pontos em 12 arquivos, todos com a mesma transformacao — vazio ou ausente vira zero:

| Defeito | Arquivos |

|---|---|

| tempo_antes vazio na aritmetica | peca.php:998, config/functions.php:308 e :351, painel/data/functions.php:264, painel/modulos/pedidos/action.php:389, produtor/data/functions.php:258, admin/data/functions.php:294, img/cupom/data/functions.php:222, produtor/listaopen.php:79, produtor/modulos/pedidos/enviar.php:102, admin/modulos/pecas/enviar.php:96 |

| indice [1] sem isset (warning, nao fatal) | painel/modulos/pedidos/index.php:546 |

| pecas.valor vazio em valor * quantidade | pedido.php:103 e :112 — caminho de checkout |

| divisao sem conferir denominador | peca.php:652 (valor_de = 0 dava DivisionByZeroError) |

A fidelidade foi medida, nao assumida. Rodei o trecho original no 5.6 e o blindado no 8.3

sobre os mesmos seis formatos ('', '00:00', '01:00', '02:00, 01:00', '0:30',

' 01:00 '): a saida e identica byte a byte. Ou seja, as 10 pecas ativas que ja tem

antecedencia preenchida nao mudam de comportamento, e o vazio volta a significar

zero antecedencia (venda ate a hora da sessao), como era antes da virada.

Validado depois de aplicar: php83 -l e php56 -l limpos nos 12; home, /silvas,

/pe-de-cachimbo, /lisa-lesa-e-louca, /a-noite-dos-vampiros, /eventos.php e

/teatros.php em 200; admin/login.php e painel/login.php em 200; action.php act=entradas

devolvendo as 27 poltronas da peca 1342 (com mapa) e as 30 entradas da 1620 (sem mapa);

nenhum erro novo em rnt83-php-error.log.

Nao tocado de proposito: painel/modulos/pedidos/index.php:48 ja tinha blindagem anterior com

default de 2 horas em vez de zero. Na pratica esse default nunca dispara (explode sempre

devolve o indice 0, e (int)'' e zero), entao nao conflita — mas e semantica de outra pessoa.

Continua aberto no BK-423: preencher/validar tempo_antes antes de habilitar venda

interna (aprovacao em admin/modulos/eventos/action.php:2926 e ativacao de parceria em :3382

nao preenchem), e conferir se existe peca ativa com valor = '' ou valor_de = 0.

[2026-09-17] BK-422: slug duplicado da Silvas escondia uma pagina de venda quebrada

Duas pecas "Silvas" com o mesmo slug: 1613 (inativa, parceria_online=0, da produtora de

comunicacao) e 1620 (ativa, parceria_online=1, do produtor que fechou parceria). Era o unico

dos 27 grupos de slug duplicado do banco que envolvia peca ativa.

  • pecas.slug da 1613 passou a silvas-1613. Zero pedidos, nenhum shortlink apontando para ela,

3 sessoes nunca configuradas (sec_quantidade = 0). Backup em

SQL/bkp_peca1613_slug_20260917_121716.json

Ao tirar a 1613 da frente, /silvas passou a responder HTTP 500 — e a causa nao foi a troca:

```

PHP Fatal error: Uncaught TypeError: Unsupported operand types: string * int

in peca.php:1000

```

peca.php:211 trata parceria_online = 1 como venda propria e mantem a peca no layout de venda;

sem parceria, delega para evento.php (peca.php:254), que reconsulta exigindo

status='A' AND editado_produtor=0 (evento.php:796). Por isso a 1613 ganhava o /silvas,

delegava, e o evento.php acabava renderizando a 1620 — o HTML mostrava imagens da 1620 antes

da troca, o que confundiu o diagnostico. Com a 1613 fora, a 1620 finalmente foi renderizada como

pagina de venda e chegou na linha 1000 com pecas.tempo_antes vazio.

O slug duplicado estava mascarando uma pagina de venda quebrada, nao causando o defeito.

  • pecas.tempo_antes da 1620 preenchido. Primeiro pus '01:00' (o valor de 7 das 10 pecas

ativas) e estava errado: medido nos dois binarios com o trecho literal de peca.php:996-1000,

tempo_antes = '' no 5.6 da corte na hora exata da sessao, nao uma hora antes —

'' 3600 vale zero la. '01:00' teria fechado a venda as 18:00 numa sessao de 19:00 *do

mesmo dia**. Corrigido para '00:00', que reproduz o 5.6 exatamente. Apontado pelo gpt-6-astra,

conferido rodando o trecho no 5.6 e no 8.3 antes de aceitar

Validado: /silvas HTTP 200, marcador id=1620 em_cartaz=1, sessao de 17/09 na pagina, e

action.php act=entradas devolvendo 30 entradas disponiveis nas duas datas. Nenhum fatal novo.

A correcao e de DADO, nao de codigo — a proxima peca com parceria e antecedencia vazia

quebra igual. Virou BK-423.

[2026-09-17] BK-421: restart manual do WhatsApp voltou a funcionar (regressao do BK-420)

Revisao do gpt-6-astra sobre o BK-420 achou o que eu nao vi: as travas novas (nao esperar em

SAPI web, abortar em qr_pending, cooldown de 10 min) valiam tambem para o **restart pedido a

mao pelo operador**. Consequencias:

  • admin/whatsapp_restart.php decide sucesso por $sucesso && $online; com o retorno sempre

false na web, o endpoint podia responder HTTP 500 mesmo com o servico online;

  • admin/modulos/bot/stats.php:205 e :263 (botao "reiniciar", com log

RESTART MANUAL - Solicitado pelo admin) passavam a reportar falha.

Nao se recusa um restart pedido a mao, nem se devolve "falhou" so porque nao esperamos.

  • _whatsapp_tentar_restart($forcar = false): com $forcar, pula a checagem de qr_pending,

pula o cooldown e espera o resultado mesmo na web. Sem $forcar, o comportamento do BK-420

fica de pe para a recuperacao automatica dentro de um envio

  • os tres caminhos manuais passam true; os tres automaticos

(whatsapp_helper.php:767, :1247, :1348) seguem com as travas.

O :767 ja estava correto por outro motivo: so roda quando o servico esta inalcancavel,

e ai nao existe qr_pending para ler

Validado com o servico real em qr_pending: a chamada sem $forcar registra

RESTART - ABORTADO: servico esperando leitura do QR Code e o MainPID do rnt-whatsapp

fica identico antes e depois — o guard segura de verdade, nao so no papel.

Pendencias que o astra levantou e ficaram abertas (nao entram neste BK):

corrida entre ler o mtime e o touch do cooldown; qr_pending pode ficar true com QR

obsoleto porque bot/whatsapp/server.js:553 nao limpa qrDisplayed no auth_failure; e a

fila bot/whatsapp/queue/ apaga a mensagem apos 5 tentativas sem entregar

(whatsapp_helper.php:909).

[2026-09-17] BK-420: aprovar peca dava erro com o WhatsApp caido (e o restart matava o QR)

O ADMIN nao conseguia aprovar a peca 1619 (Pe de Cachimbo). A aprovacao funcionava —

pecas.aprovada_em = 2026-09-17 00:37:34, status A, 6 sessoes, editado_produtor = 0. O que

quebrava era a tela, depois do banco ja ter gravado.

Sequencia, pelo rnt83-php-error.log:

```

00:37:34 [DEBUG APROVAR] Evento aprovado com sucesso!

00:37:34 [WHATSAPP] SAUDE - Servico OFFLINE

00:37:34 [WHATSAPP] RECUPERACAO - Tentando restart automatico...

00:38:19 [WHATSAPP] RESTART - Resultado: FALHOU (timeout=45s)

```

_whatsapp_tentar_restart() dormia 9 x 5s esperando o servico subir, **dentro da requisicao

web. O nginx registrou HTTP 499** duas vezes as 00:38:09 (cliente desistiu antes da

resposta). A tela dava erro numa operacao que tinha dado certo, e reenviar custava mais 45s.

Tres correcoes em includes/whatsapp_helper.php:

  1. restart nao bloqueia requisicao web. O nohup ja deixou o restart correndo; esperar so

prende a pagina. Em SAPI != cli a funcao dispara e devolve false, o chamador enfileira — o

estado final e o MESMO de antes, sem os 45s. No CLI (crons) a espera continua, porque la nao

custa tela de ninguem.

  1. nao reiniciar quando o servico esta em qr_pending. Esse era o pior:

_whatsapp_verificar_saude() devolve false tanto para "processo morto" quanto para

"esperando alguem ler o QR", e o codigo tratava os dois como "reinicie". Sao opostos — um

pede restart, o outro pede gente. Como restart.sh faz systemctl restart, cada tentativa

derrubava a sessao e gerava um QR novo, invalidando o que o operador tentava escanear.

Foram 10 restarts em 12 horas com o servico parado em qr_pending desde 16/09 18:55.

  1. cooldown de 10 min entre restarts (bot/whatsapp/ULTIMO_RESTART). O restart.sh nao

tinha trava nenhuma e cada envio falhado reiniciava o servico de novo.

O healthcheck.sh:169 ja fazia certo ("API online, mas WhatsApp desconectado. Sem restart

forcado.") — quem estava errado era so o lado PHP. A correcao alinha os dois.

Nenhuma mensagem foi perdida: as 3 que estavam na fila (bot/whatsapp/queue/), inclusive o

aviso de aprovacao ao produtor, saem quando o numero reconectar.

Junto: /root/qr.sh mostra o QR Code atual limpo e escaneavel. O admin nao tem tela de QR, e o

QR que sai no log do systemd fica deslocado pelo prefixo do journalctl, entao nao le. O QR

nao vai para o webroot de proposito: quem escaneia conecta um aparelho na conta do RNT.

Fechado como: entregue. Validado que o guard aborta o restart com o servico real em

qr_pending; o caminho web se prova no proximo [WHATSAPP] RESTART - disparado sem esperar.

[2026-09-17] BK-417: captador saiu do PHP 5.6 (cron do painel bt e botao do admin)

O BK-348 virou web+cron para 8.3 em 25/08/2026, mas o captador ficou para tras porque mora no

terceiro lugar de agendamento: /www/server/cron/ (tarefas do painel bt), que nem

crontab -l nem /etc/cron.d mostram.

  • /www/server/cron/bfc5bb6c... (diario 04:01, --verbose) e /www/server/cron/f9659feb...

(a cada 7 dias, --reprocessar): /www/server/php/56/bin/php -> /www/server/php/83/bin/php.

Atencao: esses arquivos sao gerados pelo painel bt; editar a tarefa pela interface dele

reescreve o script e devolve o 5.6. Backup em /root/rnt-tools/backups/cron/

  • admin/modulos/bot/action.php: o bloco que escolhia o binario testava PHP_BINARY (que sob

php-fpm nao e um CLI utilizavel), depois 5 caminhos que nao existem nesta VPS, e caia em

php do PATH — que e o 5.6. Nao era "pior caso": as 6 execucoes gravadas em

admin/logs/debug.log mostram PHP Bin: php nas 6. Agora o caminho e explicito e a ausencia

dele devolve erro visivel em vez de rodar a versao errada calado

Compatibilidade verificada antes de trocar, nao depois:

  • php83 -l limpo nos 13 arquivos do runtime do captador
  • const_undef.php e var_null_array.php (BK-362/363) acusaram 22 constantes e 2 variaveis —

todos falso positivo, conferidos na fonte: as BOT_LOG_* estao em config/bot_config.php

(o scanner nao segue o include para fora de bot/), e o $saida de sympla_scraper.php:2352

e parametro por referencia com o chamador inicializando

  • dry-run 5.6 x 8.3 no mesmo momento: **22 captados / 20 cadastrados / 0 atualizados /

0 duplicados / 2 invalidos, exit 0 e zero deprecation nos dois**

  • exec() esta ON nos dois CLIs (o 8.3 carrega php-cli.ini proprio, nao o php.ini

compartilhado que proibe); o Puppeteer de sympla_scraper.php:793 nao esta instalado em

nenhum dos dois, entao falha do mesmo jeito

Nao coberto pelo dry-run: refinarEvento() so roda em modo real. A rodada automatica das

04:01 de 17/09 e a validacao; rollback = restaurar os dois backups de cron.

Junto, em admin/modulos/bot/index.php (BK-416): o painel passou a marcar rodada de --dry-run

com selo "simulacao". Um dry-run escreve no MESMO log e com o mesmo cabecalho de resumo, entao

sem essa marca o painel apresentaria uma simulacao como captacao real — o defeito que o BK-416

acabara de remover. Apareceu porque os testes desta rodada o provocaram.

Fechado como: entregue, com a rodada real das 04:01 pendente de conferencia.

[2026-09-16] BK-416: painel do bot mostrava "Nunca executou" com o captador rodando todo dia

admin/modulos/bot/index.php lia bot_eventos_log — tabela morta desde 11/02/2026. O unico

INSERT e Deduplicator::registrarLog() (bot/handlers/deduplicator.php:318) e **nenhum arquivo

chama esse metodo**; sobraram 428 linhas congeladas de 09 a 11/02. Resultado: os 4 cards mostravam

zero e "Ultima Execucao: Nunca", e a lista "Logs Recentes" exibia eventos de FEVEREIRO como se

fossem de agora — enquanto o cron do painel bt (/www/server/cron/, ainda em PHP 5.6) roda o

captador todo dia as 04h. Ninguem percebeu porque a tela nao dava erro: dava zero.

  • cards passam a ler o resumo que o proprio cron escreve no log da rodada

(bot/logs/captador_AAAAMMDD.log), com o ULTIMO bloco RESUMO DA EXECUCAO do arquivo mais novo;

rotulo mudou de "(7 dias)" para "(ultima rodada)", que e o que o numero realmente mede

  • numero ausente no log vira travessao, nao zero — zero e afirmacao, ausencia nao e
  • card de erros ganhou os descartados pelo validador (33 de 47 em 16/09, numero que nao aparecia

em lugar nenhum da tela)

  • "Logs Recentes" virou "Sugestoes recentes do captador", lendo bot_pecas_sugestoes, com selo

de pendentes de aprovacao (20 em 16/09)

  • fonte e nome_captado passaram a sair com htmlspecialchars: vem raspados de Sympla/Eventim

e eram impressos crus

  • fallback por filemtime converte explicitamente para America/Sao_Paulo. O php.ini desta VPS

esta em date.timezone=PRC: na primeira versao o teste de borda mostrou 10:14 (China) para um

arquivo de 23:14. O log do captador nao sofre disso porque bot/config.php:15 fixa o timezone

  • a bot_eventos_log NAO foi ressuscitada de proposito: os SELECT de dedup por hash

(deduplicator.php:34,56,108,122) dependem dela, e volta-la a popular muda o que o captador

suprime. Decisao separada, com risco real; o painel so parou de depender de tabela morta

Validado: php83 -l; bloco novo executado contra log e banco reais (47 captados / 2 cadastrados /

2 atualizados / 10 duplicados / 33 invalidos / 0 erros, e 20 pendentes — bateu com o log de 16/09);

6 casos de borda (sem diretorio, sem log, log 0 byte, log sem resumo, dois resumos no mesmo arquivo,

varios arquivos); JS inline conferido identico; encoding UTF-8 sem mojibake.

Fechado como: entregue. Validacao no navegador pendente (tela de admin, exige login).

[2026-09-12] BK-415: paginacao do historico de compras na ficha do cliente

Nunca funcionou, nem no 5.6. Na aba Compras de admin/modulos/clientes/detalhe.php a carga

inicial contava a tabela pedidos inteira (~72 mil: links 1-5 e "Ultimo" na pagina 1.448, vazia),

e cada clique voltava sem link de pagina porque o total vinha de um #list-total que a ficha nao

tem (BK-414). Na pratica so atinge 3 clientes com mais de 50 pedidos, quase tudo cancelado.

  • action.php: total contado no servidor por cliente e status nos 3 cases do historico

(paginate-historic, search-historic, paginate-search-historic), paginacao pela mesma

clientes_render_pagination() da lista de clientes

  • clientes_render_pagination() foi para bootstrap.php (a ficha usa na carga inicial) e parou

de repetir o link "1" nas paginas 2 e 3 (afeta tambem a lista de clientes, para melhor)

  • o filtro de status sobrescrevia TODAS as tabelas da ficha: o JS usava .panel-body tbody, e

esse .panel-body envolve a pagina inteira (Abandonos, WhatsApp, Creditos viravam compras).

Seletores restritos a #historic-tab; search-client-historic.js ver=1.7

  • SQL: setQueryStatus() so aceita codigos 1..10, user com (int), termo da busca com escape
  • paginate-search-historic montava WHERE 1AND clientes.id (sem espaco): no 5.6 falhava calado,

no 8.3 lanca mysqli_sql_exception

  • revisao do gpt-6-astra (sem regressao) apontou 4 defeitos antigos da mesma aba, conferidos e

corrigidos: apagar 1-2 letras da busca limpava a tabela; trocar status durante a busca trazia o

backup antigo de volta; id[] fazia preg_match lancar TypeError; termo sem escape no HTML

  • teste CLI no 8.3 (cliente 15716, 289 cancelados): paginas 1 a 6 certas, 7 vazia, injecao em

status/user neutralizada. Validacao no navegador pendente (ver BACKLOG)

[2026-09-12] BK-414: filtro do historico de compras do cliente quebrava no PHP 8.3

Alerta "EXCECAO em admin/modulos/clientes/action.php:350 (9x)". Os botoes de status do

historico de compras em admin/modulos/clientes/detalhe.php nao listavam nada: o JS le o total

de #list-total, que so existe nas telas de listagem, e mandava total="". No 5.6 "" / 50

dava 0; no 8.3 e TypeError: Unsupported operand types: string / int.

  • (float) $_POST['total'] / 50 no case paginate-historic (fiel ao 5.6: vazio vira 0)
  • a mesma linha em notificacoes, cupons, pedidos_2 e clubent; em notificacoes havia gatilho

real (tabela sem registro deixa #list-total vazio), as outras recebem sempre COUNT(*)

  • teste CLI no 8.3: backup lanca o TypeError, versao nova devolve a lista (clientes e notificacoes)
  • nao mudou: a paginacao do historico continua sem links de pagina (total 0), como no 5.6

[2026-09-11] BK-413: lembrete mensal de credito na carteira por WhatsApp

Pedido do ADMIN: quem tem credito esquece. Novo admin/cron/lembrete_credito_carteira.php

(/etc/cron.d/rnt-lembrete-credito, diario 10:15, janela 10h-18h de Brasilia) manda um

WhatsApp curto, sem valor, a quem tem saldo disponivel > 0 e sair=0.

  • no maximo 1 a cada 30 dias, contados do ultimo lembrete OU do credito mais recente
  • dedup e historico em rnt_lembrete_credito_log (tabela nova); 1 tentativa por dia; 20 por rodada
  • flags --dry-run, --preview, --test-send (so para o numero do gestor, sem gravar log)
  • instalado sem enviar nada: os 3 clientes com saldo hoje (21061, 48841 e a conta de teste

50877) foram registrados como avisados em 11/09, entao o primeiro disparo real e 11/10/2026

  • texto ditado pelo dono, com ortografia corrigida e dominio .com.br (ele escreveu .com)

[2026-09-11] BK-412: credito de cliente aparecia como R$ 0,00 no checkout

Cliente sem linha em tb_produtores tinha o credito gravado no ledger, mas checkout, carteira e

estorno leem tb_produtores.creditos — viam R$ 0,00. Nao era tarifa: o pedido 73917 virou

R$ 44,00 de credito corretamente. 3 clientes presos (R$ 44, R$ 40, R$ 25).

  • config/functions_cliente_carteira.php: sem linha de produtor, o saldo e o ledger
  • admin/modulos/pedidos_2/action.php: "Virou credito" do admin creditava so o valor unitario
  • detalhes: docs/changelog/2026/CL-2026-09-11-BK-412-credito-cliente-invisivel.md

[2026-09-10] BK-210: e-mail da lista diaria reativado e causa raiz do incidente corrigida

O certificado TLS do servidor de e-mail nao cobre mail.rionoteatro.com.br, o host que estava

no codigo: o PHPMailer recusava por hostname mismatch, todo envio SMTP falhava e caia no

mail() local — origem das 426 mensagens presas no MTA em abril. Corrigido o host, o envio

voltou (validado com mensagem recebida, em 8.3 e em 5.6).

  • credenciais SMTP saem de 16 arquivos vivos para o .env (novo config/mail_credentials.php)
  • a senha do sac@ em z_dispara_pagamento.php nao autenticava mais (SMTP 535); a valida foi para o .env
  • each() (3x) e FILTER_FLAG_HOST_REQUIRED corrigidos: eram fatais em PHP 8 dentro do PHPMailer
  • disparaemail.php: guard de CLI (respondia 500 por HTTP) e isMail() -> setSMTP()
  • crons da lista diaria migrados para php83 e e-mail religado por flag no wrapper
  • detalhes e pontas abertas: docs/changelog/2026/CL-2026-09-10-BK-210-reativacao-email-lista.md

[2026-09-10] BK-410: convite automatico de parceria 3 dias apos a aprovacao da peca

Peca cadastrada pelo produtor e aprovada pelo ADMIN, sem parceria_online, passa a receber

um WhatsApp oferecendo a parceria gratuita (20% de comissao) com o link do termo, a partir de

24h depois da aprovacao, na janela comercial de 9h-18h (todos os dias).

  • Novo carimbo pecas.aprovada_em (nao existia data de aprovacao no banco), gravado nos dois

caminhos de aprovacao do admin em admin/modulos/eventos/action.php.

  • Nova pagina publica termo-parceria.php — o termo so existia no modal do painel (com login)

e num PDF que o nginx devolve 403.

  • Novo admin/cron/convite_parceria_produtor.php + /etc/cron.d/rnt-convite-parceria-produtor

(a cada 30 min, 9h-20h), com --dry-run, --preview, --test-send e dedup de 1 convite por peca.

[2026-09-10] Triagem dos itens soltos do backlog: 7 viraram BK, 3 ja estavam entregues

Ultimo pedaco da higiene do BACKLOG.md. Tres secoes sem numero de BK — `PROXIMOS

PASSOS (ordem sugerida), BACKLOG (sem ordem definida) e Marketing/ROI/Ads

(03/03/2026)` — foram triadas item a item.

Viraram BK numerado e entraram na lista do Studio: BK-403 (tela para baixar os CSVs

de assets), BK-404 (refinamento via Cerebro com limite RSA/PMAX), BK-405 (servico local

do WhatsApp com detached Frame), BK-406 (limpeza do notify_state/), BK-407 (link de

compra do produtor no bot captador), BK-408 (seletores com IA, adiado como contingencia)

e BK-409 (UI do Scraper Studio, congelado). A ponta aberta do BK-170 virou stub proprio.

Sairam porque ja estavam entregues — conferido antes de apagar:

  • "WhatsApp para admin ao detectar erro no log" (unico item de PROXIMOS PASSOS,

proposto com debounce de 30 min): ja implementado. admin/logs/debug_helper.php:167

chama _debug_log_deve_alertar_whatsapp() / _debug_log_enviar_alerta_whatsapp(),

ambas definidas no mesmo arquivo (linhas 23 e 72) — conferi que existem, porque

chamada sob function_exists para funcao inexistente ja mordeu aqui no BK-359. O

alerta de erro de runtime e coberto por outro caminho: vigia_erros_runtime.php,

no cron a cada 5 min, le o rnt83-php-error.log e avisa por WhatsApp + push.

  • ROI Rio no Teatro (SQL/collation + cron id=19): e o BK-86, ja no changelog.
  • "Expandir debug_log para todo o site": e o BK-89, ja no changelog.
  • Todo o historico [x] de Marketing/ROI/Ads (BK-96, 97, 105, 106, 118, 170, 171):

conferido um a um, todos com registro no changelog ou em changelog/2026/.

Fundido: "Migracao completa mysql_* para mysqli" era duplicata do BK-87, que ja

estava na lista numerada. Os dados uteis da duplicata (ja migrados pecas e

indexfotos) foram para a linha do BK-87.

Removido tambem: os dois rodapes Ultima atualizacao: ... de marco/2026, que

repetiam em prosa fechamentos que ja estao no changelog, e o paragrafo gigante de

fechamentos no cabecalho do arquivo.

BACKLOG.md fechou o dia em 998 linhas, contra 2689 pela manha.

[2026-09-10] Prorrogacao de temporada: Lisa, Lesa & Louca (peca 1342)

Pedido operacional do ADMIN, sem BK: prorrogar a peca em 7 datas, mantendo as

fileiras C e F. Nao houve mudanca de codigo do site — o registro esta aqui porque a

rodada virou runbook e ferramenta.

Estado antes: temporada 08/08 a 27/09, sessoes de sabado 20h no Barra Point

(teatro 79), mapa C1-C14,F1-F13 (27 assentos), R$ 60,00 -> R$ 22,00.

Duas das sete datas ja existiam (12/09 e 26/09, sec_id 19179 e 19181) e nao foram

tocadas. Criadas as 5 restantes, com o sec_dia_semana = date('w'):

| data | dia | hora | sec_id |

|---|---|---|---|

| 11/10 | domingo (0) | 19:30 | 19276 |

| 18/10 | domingo (0) | 19:30 | 19277 |

| 24/10 | sabado (6) | 20:00 | 19278 |

| 01/11 | domingo (0) | 19:30 | 19279 |

| 15/11 | domingo (0) | 19:30 | 19280 |

Valores, mapa e estoque copiados da sessao que ja estava no ar. pecas.fim_temporada

estendida de 27/09 para 15/11. Backup do estado anterior em

SQL/bkp_peca1342_20260910_213636.json.

Validacao: pagina /lisa-lesa-e-louca em 200 sem fatal, as 7 datas no seletor e o

rotulo "Sabado 20h e Domingo 19h30"; POST /action.php act=entradas nas 5 datas novas

devolve 30 entradas disponiveis com o CSV C+F completo; nenhum residuo do horario 19:00

da temporada de marco/abril.

Duas coisas que a rodada corrigiu na fonte:

  1. sec_quantidade maior que o sec_mapa nao e bug. O runbook mandava igualar os

dois; medido em producao, a peca roda com sec_quantidade = 30 para 27 poltronas, e

isso e a regra 1.4 do proprio regulamento (esgotando a marcacao, a venda segue e o

lugar sai na bilheteria). Do jeito que estava escrito, o proximo agente baixaria 30

para 27 e tiraria estoque vendido de proposito. Corrigido em

docs/RUNBOOK_CADASTRO_EVENTO_PRODUTOR.md (§4 e checklist).

  1. Sessao alem do fim_temporada e falha silenciosa. A sessao vende pelo link direto

mas a peca some da home, do carrossel e do grid (index.php e peca.php filtram por

fim_temporada >= hoje) e passa a ser tratada como fora de cartaz pelo cron de limpeza

de cartaz. A pagina da peca continua mostrando as datas, entao parece certo. Virou a

§7 (Prorrogacao de temporada) do runbook e a ferramenta

/root/rnt-tools/check_temporada.php (exit 1 se achar, --fix <peca_id> estende ate a

ultima sessao). O detector foi provado contra o estado pre-correcao: com fim_temporada

em 27/09 ele acusa peca 1342, 5 sessoes alem. Hoje sai limpo nas 10 pecas ativas com

sessao futura.

Nao foi feito: cartaz e link de YouTube continuam manuais no painel.

[2026-09-09] menu do painel do cliente fatal em Meu Perfil (BK-402)

Fechado como: CORRIGIDO em 09/09/2026 · migrado do BACKLOG.md em 10/09/2026.

Chegou pelo vigia de erros (WhatsApp + push) as 17:52 de 09/09/2026, 2 ocorrencias.

  • sintoma: GET /painel/modulos/profile/ responde 200, mas o corpo para no

meio do menu lateral. Nao vira 5xx, entao so o vigia enxerga.

  • erro: [PHP_EXCEPTION] Undefined constant "MODULO_ACTIVE_MENU" em

painel/inc-menu.php:72.

  • causa: modulos/profile/bootstrap.php (e tambem modulos/bootstrap.php,

clubent, clubernt) define MODULO_SLUG mas nao define

MODULO_ACTIVE_MENU. No 5.6 a constante indefinida virava a string

"MODULO_ACTIVE_MENU", a comparacao dava false e o item so nao ficava ativo;

no 8.3 e Error fatal. Mesma classe do BK-362/BK-363, fallout da virada de 25/08.

  • por que so a linha 72: as outras 6 linhas do mesmo arquivo (12, 18, 33, 97,

105, 111) ja tinham a guarda defined(). A 72 — dentro do laco do menu vindo do

banco — ficou de fora na passada de endurecimento.

  • correcao: guarda defined('MODULO_ACTIVE_MENU') && ... na linha 72, igual as

demais. Fidelidade conferida nos dois binarios: no 5.6 o codigo antigo dava

inativo; no 8.3 o antigo era fatal e o novo devolve inativo com a constante

ausente e ativo quando ela existe e casa.

  • linha 84 (MODULO_SLUG) nao foi tocada: painel/data/setup.php ja garante a

constante com fallback vazio, entao nunca fatalou.

  • efeito colateral util: a validacao deste BK expos que php -l nesta VPS

compila no 5.6 (o php do PATH nunca mudou; o site e 8.3 desde 25/08).

docs/00_PROTOCOLO.md mandava php -l em 9 lugares e foi corrigido para

php83 -l; o guard da VPS passou a avisar (rnt-php-versao-lint) quando o

binario errado toca arquivo do webroot. Todos os /etc/cron.d/rnt-* ja usavam

/usr/bin/php83.

  • latente, fora deste BK: painel_old/inc-menu.php:57,

painel_old/inc-menu_2026.php e img/cupom/inc-menu.php tem o mesmo uso sem

guarda. So disparam se alguem abrir essas telas.

[2026-09-07] voto de enquete virava o comentario publico (BK-401)

Fechado como: CORRIGIDO em 07/09/2026 · migrado do BACKLOG.md em 10/09/2026.

Achado pelo ADMIN minutos depois de religar a enquete, testando: ele votou e o fluxo

pulou a pergunta do comentario e mandou o fecho, como se ele ja tivesse comentado.

Causa. A conversa dele ja estava na etapa comentario (de um teste anterior). O voto

chega ao webhook como mensagem de texto "5" — de proposito, e o que faz o BK-389

reaproveitar todo o fluxo. So que na etapa do comentario qualquer texto e aceito, entao o

"5" virou o comentario: gravou comentario="5", concluiu e agradeceu.

Por que isso ficou serio agora. Enquanto so existia a via de texto, era dificil

acontecer. Com as duas vias ligadas (BK-400), o caminho e natural: a pessoa responde a

nota digitando, recebe a pergunta do comentario, volta e toca na enquete — e o toque

vira o comentario publico dela.

Correcao, com duas guardas na etapa do comentario:

  • is_poll_vote no payload, que o server.js ja mandava desde o BK-389 e ninguem usava.

E o sinal confiavel;

  • formato: um digito de 1 a 5 sozinho e nota repetida, nao comentario.

Conferido que comentario legitimo continua passando: "5 estrelas! Adorei" e

"nota 5 mesmo" sao gravados; "5", "4" e voto de enquete sao ignorados; "Não"

continua pulando o comentario; anexo continua ignorado pelo BK-388.

Dado limpo: a avaliacao id 2 tinha ficado com comentario="5". Zerada.

Segundo achado, menor: o ADMIN recebeu a enquete sem a mensagem de abertura. Isso

foi artefato do meu teste — chamei rnt_post_event_feedback_enviar_enquete() direto, sem

mandar o texto antes. No fluxo real a fila manda a abertura e o gancho de

mark_queue_sent manda a enquete em seguida. Nao e defeito do produto, mas vale registrar

para nao assustar em teste futuro: testar a enquete isolada da abertura induz ao erro.

[2026-09-07] enquete religada (BK-400)

Fechado como: FEITO em 07/09/2026 · migrado do BACKLOG.md em 10/09/2026.

A enquete tinha sido desligada no BK-391 porque o voto nao voltava. Com o BK-399 provado

em producao, foi religada: RNT_PEF_ENQUETE_ATIVA = true, e o texto de abertura voltou a

dizer "Toque na enquete abaixo ou responda apenas com o número".

As duas vias continuam valendo, de proposito. A enquete e a via facil — um toque, sem

ambiguidade de interpretacao. O texto e a via que sobrevive: se o WhatsApp mudar de novo o

contrato interno (e mudou duas vezes so hoje, de 2.3000.1046948731 para

2.3000.1046951358), a nota continua chegando por escrito. Desligar e uma linha, e o

fluxo nao para.

Alarme armado: o diagnostico do BK-397 grita ALARME no boot se nem _serialized nem

$1 servirem. O proximo rename aparece no log, nao em recurso mudo por semanas.

[2026-09-07] correcao do contrato da chave de mensagem (BK-399)

Fechado como: RESOLVIDO em 07/09/2026 · migrado do BACKLOG.md em 10/09/2026.

A correcao que os BK-393 a 398 levaram o dia inteiro para poder escrever com seguranca.

O que estava errado: o WhatsApp Web renomeou _serialized para $1 nas chaves de

mensagem (Wid nao mudou). A lib 1.34.7 le ._serialized de chave de mensagem em ~20

lugares e recebia undefined.

Por que patch e nao getter no prototipo (a opcao que eu ia escolher, derrubada pelo

gpt-6-astra com argumento verificado no codigo): Utils.js faz

Object.assign({}, msg.id, ...) ao mandar o modelo para o Node, e **Object.assign nao

copia propriedade herdada**. Um getter no prototipo se perderia exatamente onde o

downloadMedia() le this.id._serialized no Node — consertaria a pagina e deixaria o

download quebrado do mesmo jeito.

Seis alteracoes, em patches/apply_rnt_patches.py (versionado, idempotente, com

backup .rnt-bak e --desfazer):

  1. window.WWebJS.__rntKeyId(), adaptador central;
  2. o modelo que atravessa para o Node ganha _serialized como propriedade propria;
  3. e 4. getChatModel busca a ultima mensagem pela chave boa;
  4. o gancho do voto usa a chave boa em parentMsgKey;
  5. o bulkUpsert original passa a rodar sempre, mesmo se a observacao falhar.

A alteracao 6 conserta um dano que a lib causava. Medido: voto real, sonda nunca

disparou e a tabela de votos ficou vazia. O gancho quebrado nao so deixava de avisar —

impedia o proprio WhatsApp de gravar o voto. Ele atrapalhava mais do que ajudava.

Regras que o patch segue, herdadas da revisao do astra:

  • prefere o nativo: se o WhatsApp voltar a expor _serialized, e ele que vale; $1 so

entra depois de validado por round-trip no MsgKey. Nunca aceitar string solta do objeto

como id;

  • nao toca em Wid, que nao quebrou;
  • idempotente, e npm install nao apaga o script — so exige rodar de novo.

Base de decisao, medida antes de escrever (BK-398): com a chave boa, getChats passa

60 de 60 em todas as etapas (era 0 de 120). Para midia, a chave acha a mensagem; o

resto ficou indeterminado porque a amostra era uma imagem nunca baixada

(mediaStage=INIT e o esperado nesse caso, nao prova de segunda quebra).

TESTADO EM PRODUCAO, com voto real:

[Voto] onPollVoteEvent chamado: 1 voto(s) opcoes=1 temParent=true

[Enquete] voto de 5521979979992: 4

banco: recebida | +55219797***** | "4"

temParent=true e a prova do patch da lib — e exatamente ali que quebrava

(Msg.get(parentMsgKey._serialized) com undefined). A linha seguinte e a prova do

BK-397: a versao anterior do handler descartaria o voto por esperar string de telefone

num objeto.

Prova independente: o message_id voltou. Eram 72 ocorrencias de N/A no log de

hoje antes do patch; depois, id real. Era o mesmo defeito, visivel o dia inteiro sem

ninguem entender.

getChats: 60 de 60 em todas as etapas, contra 0 de 120 pela manha.

Download de midia: NAO testado, e sem previsao de ser. O canal de arquivo foi criado

para outra finalidade (trazer os zips do historico do WhatsApp, BK-387), e o ADMIN cortou

com razao: para a enquete nao serve. Fica registrado que a chave acha a mensagem, que

era o que falhava; o resto do caminho de download segue sem medicao.

Rollback: python3 patches/apply_rnt_patches.py --desfazer e reiniciar.

[2026-09-07] medir etapa por etapa ANTES de escrever a correcao (BK-398)

Fechado como: PRONTO em 07/09/2026 · migrado do BACKLOG.md em 10/09/2026.

O gpt-6-astra recusou prometer que consertar a chave recupera os tres recursos, e listou o

que faltava medir. Este item e essa medicao — para entrar no conserto sabendo o tamanho

dele, em vez de cair num ciclo de consertar-testar-descobrir-outra.

A ideia: em vez de escrever a correcao e descobrir no teste, simular a correcao.

O /diag-steps usa a chave validada ($1 hoje, o nativo se voltar) e refaz as etapas na

mao, registrando qual passa e qual quebra. So leitura: nao envia, nao baixa midia de

verdade, nao busca historico.

1. getChats, decomposto em chat.serialize(), chat.formattedTitle e

Msg.get(chave boa), contados separadamente. Se a busca passar com a chave boa mas

serialize continuar quebrando, a chave nao basta — e a gente descobre agora, nao

depois.

2. Midia: se acha a mensagem pela chave boa, e se tem mediaStage, directPath e

mediaKey, que sao os campos que o download exige. Sem eles, a chave sozinha nao devolve

o download.

3. Voto: se ha voto persistido na WAWebPollsVotesSchema. Isso decide entre dois

mundos:

  • ha voto gravado -> existe caminho alternativo (client.getPollVotes), que recupera o

voto mesmo sem o evento disparar;

  • nao ha -> o gancho falha antes de gravar, e consultar a tabela nao adianta.

4. Sonda de voto, permanente. O window.onPollVoteEvent foi embrulhado por uma versao

que registra quando e chamado e repassa para a original. Isso responde a pergunta que

faltava — onde o voto morre:

[Voto] onPollVoteEvent chamado: N voto(s) -> a pagina fez a parte dela, o problema e no Node

(nada no log) -> morre antes, na preparacao dentro do gancho

Sem isso, o BK-397 (handler consertado) e a correcao da chave seriam testados as cegas: se

o voto nao voltasse, nao daria para saber qual das duas pontas falhou.

Observacao nova, com peso na decisao: entre dois reinicios de hoje o WhatsApp Web

mudou sozinho de 2.3000.1046948731 para 2.3000.1046951358. O bundle troca em minutos.

Isso enterra de vez a ideia de fixar versao — seria mirar em alvo movel — e reforca a regra

do astra: preferir o nativo e revalidar a alternativa a cada boot, nunca assumir que

$1 e eterno. O $1 continuou valido na versao nova, o que e bom sinal para a correcao.

Precisa de restart para a rota e a sonda existirem.

[2026-09-07] duas armadilhas no MEU codigo, achadas pelo gpt-6-astra (BK-397)

Fechado como: CORRIGIDO em 07/09/2026 · migrado do BACKLOG.md em 10/09/2026.

Ao avaliar onde aplicar a correcao da chave, o astra achou dois defeitos no codigo que eu

mesmo escrevi hoje. Os dois sao do tipo que so aparece quando ja e tarde.

1. O diagnostico mentia. O health check do BK-394 testava _serialized num Wid

criado pelo WidFactory — e Wid nao quebrou. Ele dizia PRESENTE enquanto tres

recursos estavam mortos, e foi exatamente esse "PRESENTE" que me fez declarar a hipotese do

rename como morta, atrasando o diagnostico.

Agora testa chave de mensagem, que e o que getChats, downloadMedia e o voto de fato

leem, com a regra que o astra recomendou: prefere o nativo; se o WhatsApp voltar a

expor _serialized, e ele que vale. O $1 so entra como alternativa e **so depois de

validado** (round-trip pelo MsgKey e Msg.get achando a mensagem). Nunca aceitar uma

string qualquer do objeto como id. Tres saidas possiveis no log:

OK pelo nativo (_serialized)

DEGRADADO: _serialized ausente, usando $1 (validado) <- estado de hoje

ALARME: nem _serialized nem $1 servem <- proximo rename

2. O handler de voto descartaria o voto na porta. PollVote.voter recebe

data.sender, que e objeto, e eu fazia String(vote.voter) comparando com regex de

telefone. Todo voto cairia no return, com um aviso enganoso de "JID nao telefonico" —

ou seja, mesmo consertando o evento, o voto morreria aqui, e o log culparia o remetente.

Corrigido para extrair o jid do objeto (_serialized, $1, toString(), user@server) e

reusar o realPhoneForMessage(), que ja resolve LID desde as campanhas e fez 69

resolucoes hoje sem uma falha. Conferido que essa funcao tolera msg = null: todo acesso a

msg dentro dela tem guarda.

Nao resolve as tres quebras — a chave continua sendo o problema de fundo. Isto so tira

duas armadilhas do caminho: um diagnostico que dava falso conforto e um handler que

jogaria fora o resultado do conserto.

Precisa de restart para valer.

[2026-09-07] teste da chave de mensagem, cruzado por tipo de contato (BK-396)

Fechado como: PRONTO em 07/09/2026 · migrado do BACKLOG.md em 10/09/2026.

Fecha (ou derruba) a hipotese de que _serialized mudou de nome nas **chaves de

mensagem** — e ao mesmo tempo testa uma observacao do ADMIN que muda o desenho do teste.

A hipotese. Os tres recursos quebrados leem _serialized de chave de mensagem:

| Caminho | Campo |

|---|---|

| getChatModel | chat.lastReceivedKey._serialized |

| downloadMedia | this.id._serialized |

| Voto | vote.pollUpdateParentKey._serialized |

Evidencia: message_id=N/A em 68 de 68 mensagens processadas hoje (o campo vem de

msg.id._serialized), e o /send-poll devolvendo message_id vazio.

Meu erro anterior, corrigido pelo gpt-6-astra: eu tinha dado esta hipotese como morta

porque o health check mostrou wid._serialized: PRESENTE. Mas eu testei num Wid criado

pelo WidFactory, nao numa chave de mensagem — objetos diferentes. A hipotese nunca tinha

sido testada de verdade.

A observacao do ADMIN, que virou eixo do teste. Ele lembrou que o comportamento pode

variar por numero, principalmente nos que ja tem troca de mensagem e **contato salvo na

agenda**. Isso pode ser exatamente o que separa os 7 chats que funcionam dos 780 que

falham. Sem cruzar por essa variavel, a gente confundiria "chave quebrada" com "contato

desconhecido" — e concluiria errado de novo.

O teste (/diag-keys + /root/dk.sh), so leitura, sem enviar nada e sem buscar

historico:

  1. classifica cada chat por lid|cus x agenda|nao-agenda x com-hist|sem-hist e mede o

sucesso do getChatModel por categoria;

  1. radiografa a chave viva: Object.getOwnPropertyNames, _serialized, $1,

toString(), e valida se $1 e chave de verdade (round-trip pelo MsgKey e busca da

propria mensagem);

  1. compara a chave viva com a serializada — se o campo existir numa e sumir na

outra, o defeito esta na transformacao, nao na chave (distincao que o astra levantou).

Como decidir:

  • falha em toda categoria -> e a chave, independente do contato;
  • so agenda passa -> o ADMIN estava certo, o caminho muda por contato;
  • $1 com roundTrip=true e achaMsg=true -> achamos o nome novo, correcao local viavel.

Se der correcao local, ela e em dois lugares (astra): as leituras dentro do navegador

(chats e votos) e a normalizacao dos modelos que vao para o Node. Corrigir so o

msg.id do servidor ajudaria o download e nao resolveria chats nem votos. Riscos que ele

nomeou e que ficam registrados: $1 e nome minificado e pode mudar de novo; mexer em

prototipo pode afetar o codigo interno do WhatsApp; reconstruir chave por concatenacao

pode perder componentes. Regra: nada de escolher qualquer string do objeto como id.

Precisa de restart para a rota existir.

[2026-09-07] rastreio profundo: achar o erro real por tras do "r" (BK-395)

Fechado como: PRONTO em 07/09/2026 · migrado do BACKLOG.md em 10/09/2026.

O health check do BK-394 rodou e derrubou as duas hipoteses que tinhamos:

[Diag] ganchos: todos os 7 resolveram.

[Diag] ponte do voto (onPollVoteEvent): ok

[Diag] wid._serialized: PRESENTE

Ou seja: nao e modulo renomeado (os 7 resolvem, incluindo os dois

bulkUpsert), nao e a ponte do voto ausente, e **nao e o rename de

_serialized** que o PR upstream #201910 sugeria. A propriedade esta la.

Terceiro achado, e o que muda o plano: a lib ja pede a versao

2.3000.1017054665 por padrao — esta no DefaultOptions do Constants.js. Ela pede e

nao recebe: com strict: false (padrao), o RemoteWebCache.resolve() devolve null e a

lib segue com a versao que vier do ar. O log mostrou exatamente isso:

"pedi 2.3000.1017054665 e subiu 2.3000.1046948731".

Isso muda o que o "pin" significa: nao e escolher outra versao, e **forcar a lib a

conseguir o que ela ja tenta pegar sozinha**.

A hipotese que sobrou. Se os modulos resolvem e _serialized existe, a quebra esta

DENTRO das funcoes. E window.WWebJS.getChats() e literalmente:

const chats = window.require('WAWebCollections').Chat.getModelsArray();

return await Promise.all(chats.map((chat) => window.WWebJS.getChatModel(chat)));

Promise.all. Um unico chat que rejeite derruba o lote inteiro — e com 30 milhoes de

builds de diferenca basta um campo novo em uma conversa. O mesmo padrao explica o voto: o

callback monta os objetos antes de chamar onPollVoteEvent, e como o wrapper faz

return callback(...) em try/catch sem await, a rejeicao assincrona escapa.

O rastreio (/diag-deep + /root/dd.sh), so leitura:

  1. o erro REAL do getChats(), com nome e stack de dentro da pagina, no lugar do "r";
  2. chat a chat com Promise.allSettled: quantos serializam e QUAIS falham, com id e

stack de cada culpado. E o teste direto da hipotese;

  1. se o caminho de midia (WAWebDownloadManager.downloadManager) resolve.

Como decidir com o resultado:

  • falhas = 0 -> hipotese descartada, o problema e outro; ler o stack do getChats.
  • falhas >= 1 -> corrigir vira filtrar aqueles chats, mudanca muito menor e mais

segura que trocar a versao do WhatsApp Web (que tem risco de QR e, agora, retorno

duvidoso).

Recomendacao revista: eu ia sugerir fixar a versao. **Nao sugiro mais como primeiro

passo** — a evidencia aponta para falha dentro das funcoes, nao para bundle incompativel.

Medir antes de operar.

Precisa de restart para a rota existir (o server.js e Node, so le ao subir).

[2026-09-07] health check dos ganchos e conferencia da versao fixada (BK-394)

Fechado como: FEITO em 07/09/2026 · migrado do BACKLOG.md em 10/09/2026.

Fecha o buraco de observabilidade que deixou tres recursos quebrados sem uma linha de log.

1. Confere se a versao fixada e a que subiu. strict: true no webVersionCache so

impede o fallback quando o HTML pedido nao existe — **nao valida conteudo nem garante qual

versao a pagina executou**. Sem comparar, um pin poderia parecer aplicado e nao estar. Ao

conectar, o bot compara client.options.webVersion com o getWWebVersion() real e grita

se divergirem.

2. Health check dos ganchos, so leitura. Resolve por nome os 4 alvos de injecao e mais

tres caminhos que getChats()/downloadMedia() usam, e diz quais nao resolveram. Nao

chama bulkUpsert, nao envia nada.

WAWebBackendJobsCommon.mediaTypeFromProtobuf (util/Injected/Utils.js)

WAWebE2EProtoUtils.typeAttributeFromProtobuf (util/Injected/Utils.js)

WAWebAddonReactionTableMode.reactionTableMode.bulkUpsert (Client.js)

WAWebAddonPollVoteTableMode.pollVoteTableMode.bulkUpsert (Client.js)

WAWebCollections.Chat.getModelsArray

WAWebCollections.Msg.get

WAWebWidFactory.createWid

Correcao de rota minha: eu tinha afirmado ao ADMIN que existiam duas injecoes e que

o codex errara. Sao quatro — meu grep so varreu o Client.js, e as outras duas estao em

src/util/Injected/Utils.js. Duas delas sao do caminho de midia, o que reforca a hipotese

de causa unica em vez de enfraquecer.

3. Testa a hipotese do _serialized. O gpt-6-astra apontou um PR upstream (#201910,

03/09/2026) relatando rename de _serialized para $1 na 2.3000.1046738589 — vizinha da

nossa 2.3000.1046948731. Os tres recursos quebrados leem _serialized

(getChats monta o WID, downloadMedia identifica a mensagem, o voto le

sender._serialized), entao um unico rename explica os tres. O check cria um WID e reporta

se _serialized esta PRESENTE ou AUSENTE. E a diferenca entre saber e supor.

Segundo mecanismo de silencio, confirmado no codigo. Alem de injectToFunction

retornar calado quando o modulo nao resolve, o wrapper faz

return callback(...) dentro de try/catch sem await — e o callback do voto e

async. Rejeicao assincrona escapa do catch. Ou seja: mesmo instalado, um erro dentro do

gancho some sem rastro.

Ainda inativo: precisa de restart do bot. Havia 4 conversas de feedback abertas quando

isto foi escrito, e a janela de ~20s do restart pode comer a resposta de alguem.

[2026-09-07] versao do WhatsApp Web derivou e quebrou 3 recursos (BK-393)

Fechado como: CAUSA RAIZ ACHADA em 07/09/2026 · migrado do BACKLOG.md em 10/09/2026.

Por que getChats(), downloadMedia() e o vote_update (voto de enquete) falharam todos

no mesmo dia, todos em silencio, todos com erro de uma letra: "r".

Os numeros, que fecham o caso:

| | versao |

|---|---|

| WhatsApp Web rodando na sessao | 2.3000.1046948731 |

| referencia da whatsapp-web.js 1.34.7 (Constants.js, campo webVersion) | 2.3000.1017054665 |

Cerca de 30 milhoes de builds de diferenca. Os modulos internos em que a lib injeta

(WWebJS.getChats, WAWebAddonPollVoteTableMode) sao de um bundle que nao existe mais.

O "r" e nome de variavel minificada do bundle deles: o erro nasce dentro da pagina,

nao no nosso codigo.

Como passou despercebido: o Client era construido sem webVersionCache, entao a

sessao pegava a versao que viesse do ar, e nada registrava qual era. O .wwebjs_cache so

tinha HTML de marco de 2026. O BK-392 passou a logar a versao ao conectar — foi assim

que este numero apareceu, no primeiro restart depois dele.

Correcao preparada em /root/wv.sh, NAO aplicada. Fixa a versao via webVersionCache

remoto (repo wppconnect-team/wa-version) naquela que a lib conhece.

Por que nao apliquei sozinho: voltar ~30 milhoes de builds pode fazer o WhatsApp

recusar o token da sessao e pedir QR de novo, o que exige o celular do WhatsApp

Business na mao. Nao e coisa de fazer a distancia, no escuro, com conversa de feedback

aberta.

A rede de seguranca: o script copia a sessao inteira (auth/, 1,2 GB) antes de mexer;

/root/wv.sh u restaura ela e remove o webVersionCache, voltando ao estado atual sem QR.

Isso transforma um caminho sem volta em teste reversivel.

Segunda opiniao (groq, porque o codex esta bloqueado): confirmou a hipotese e apontou o

mesmo caminho. Dois pontos dela conferidos e descartados contra a lib instalada:

client.waitForReady() nao existe na 1.34.7, e webVersionCache do tipo remote

exige remotePath (RemoteWebCache.js lanca sem ele) — a forma sugerida quebraria a

inicializacao. Nunca repassar recomendacao de modelo externo sem conferir na fonte.

Codex segue inutilizavel pelos dois caminhos (retestado em 07/09 com gpt-5.3-codex e

gpt-6-astra): a conta ChatGPT em plano free, com assinatura vencida em 14/07/2026,

recusa todo modelo, e a OPENAI_API_KEY do ambiente esta invalida (`Incorrect API key

provided). Trocar o nome do modelo no config.toml` nao muda nada — o bloqueio e de conta.

[2026-09-07] a pagina do WhatsApp Web passou a gritar quando quebra (BK-392)

Fechado como: FEITO em 07/09/2026 · migrado do BACKLOG.md em 10/09/2026.

Tres recursos quebraram hoje em silencio, e cada um foi descoberto por acaso, testando.

Nenhum deixava rastro no log. Instalados ouvintes de pageerror e de erro de console

(filtrados para o que parece quebra de modulo) e log da versao do WhatsApp Web ao conectar

— informacao que nunca existiu e que e justamente a variavel que explica as quebras (ver

BK-393). Nao muda comportamento: so observa.

[2026-09-07] anexo virava comentario publico do cliente (BK-388)

Fechado como: CORRIGIDO em 07/09/2026 · migrado do BACKLOG.md em 10/09/2026.

Achado pelo ADMIN, que enviou um arquivo para o numero do bot e recebeu de volta o fecho

do feedback: "Muito obrigado pela sua avaliacao!".

O que acontecia. Documento chega ao webhook com o nome do arquivo no corpo da

mensagem. O passo do comentario aceitava qualquer texto, entao o nome virava o comentario.

Aconteceu de verdade: a avaliacao id 2 (peca 23) ficou com o comentario

"Conversa do WhatsApp com @rionoteatro.zip" e voltou de aprovada para pendente,

porque rnt_post_event_feedback_save_comment() reseta o status ao gravar.

Duas causas, as duas corrigidas:

  1. anexo era tratado como texto. Nova rnt_post_event_feedback_parece_anexo(), com

duas deteccoes: o campo has_media que o server.js passou a mandar no payload (sinal

confiavel), e uma heuristica de reserva para uma linha unica terminando em extensao

conhecida. Testada nos casos de borda — "Adorei o final.mp4 foi a melhor parte..."

continua sendo classificado como comentario, porque nao termina na extensao.

Anexo e ignorado em silencio: responder daria uma mensagem por arquivo enviado, e a

conversa fica aberta para o texto que vier depois.

  1. janela de 10 dias era larga demais. rnt_post_event_feedback_conversa_ativa()

considerava ativa qualquer conversa atualizada nos ultimos 10 dias, entao qualquer

coisa que a pessoa mandasse nesse periodo virava o comentario publico dela. Reduzido

para 24 horas: comentario se pede logo depois da nota.

Dado restaurado: avaliacao id 2 voltou a aprovado com comentario vazio, que era o

estado antes do bug, e a conversa aberta foi fechada. Conferido na pagina: /casar-pra-que

mostra "Jorge" e "Néa" e zero ocorrencia do nome do arquivo.

Vale para cliente real, nao so para o teste: qualquer pessoa que mandasse uma foto,

um audio ou um documento depois da nota teria isso gravado como avaliacao publica. Foto e

audio chegam com corpo vazio e ja paravam antes; documento nao.

[2026-09-07] marca quem ja provou ter WhatsApp, sem consultar nada (BK-386)

Fechado como: FEITO em 07/09/2026 · migrado do BACKLOG.md em 10/09/2026.

Pedido do ADMIN: a informacao nao pode ser so cache, tem que ser coluna no cliente para

nunca se perder; e quem ja nos responde pode ja ser marcado como tendo WhatsApp.

Sobre "so cache": nunca foi. O BK-385 ja gravava em coluna de clientes

(whatsapp_valido, whatsapp_checado_em, whatsapp_checado_numero), com UPDATE

persistente. A palavra "cache" na explicacao foi ruim e deu a entender que era volatil.

O que este BK acrescenta:

1. Backfill por prova, custo zero. Duas fontes, nenhuma consulta ao WhatsApp:

  • inbound — o cliente nos escreveu. Prova definitiva. 13 clientes.
  • envio — entregamos mensagem a ele (whatsapp_campaign_queue.status='sent'). Tambem

e prova, porque o bot recusa envio para numero sem conta

(bot/whatsapp/server.js ~754 e ~798). 888 clientes.

901 marcados, 905 no total com os 4 que ja existiam. So marca quando o numero provado

ainda e o numero atual da pessoa — prova antiga nao vale para numero novo. Zero pulados.

2. Coluna whatsapp_origem (inbound | envio | api), criada com

ALGORITHM=INPLACE, LOCK=NONE em 2,53s. Serve para saber o quanto confiar no valor.

3. Prova nao expira. O TTL de 180 dias do BK-385 existe para a resposta da API, que e

um palpite do momento. Valor vindo de inbound ou envio so deixa de valer se o numero

mudar. Sem isso o backfill seria reconsultado em 6 meses a toa.

4. Se mantem sozinho. O bot/whatsapp/webhook.php passou a marcar

whatsapp_valido = 1, whatsapp_origem = 'inbound' a cada mensagem recebida, com guarda

para nao reescrever a linha toda vez. Como o inbound voltou no BK-379, isso cresce sem

ninguem fazer nada.

Estado apos o backfill: 905 com WhatsApp confirmado, 0 confirmados sem, 50.168 ainda

desconhecidos — que vao sendo resolvidos conforme cadastram, compram e respondem, que foi

a decisao do ADMIN de nao validar a base inteira de uma vez.

[2026-09-07] confere WhatsApp no cadastro, com cache e teto (BK-385)

Fechado como: FEITO em 07/09/2026 · migrado do BACKLOG.md em 10/09/2026.

Sugestao do ADMIN: avisar no cadastro quem informou numero sem WhatsApp, para a pessoa

trocar e continuar recebendo promocao e aviso de cancelamento de sessao. Ele mesmo

levantou o risco — "cuidado para nao utilizarem para fazerem muitos cadastros em massa e

o WhatsApp derrubar nosso numero" — e pediu teto por minuto mais aviso ao gestor.

Depois acrescentou: guardar no banco para nao consultar o mesmo numero duas vezes, e

nao validar os 50 mil de uma vez.

Onde ficou: includes/whatsapp_check_helper.php, com rnt_wa_check_numero(). Usado

pelo cadastro (action.php, case "cadastro") e pelo cron do feedback, que passou a

compartilhar o mesmo cache — a funcao local rnt_pef_numero_tem_whatsapp() do BK-384 foi

substituida por ela.

Cache no banco. Tres colunas novas em clientes, criadas com `ALGORITHM=INPLACE,

LOCK=NONE` (InnoDB 5.7, 49 mil linhas, 19,6 MB — 5,77s sem travar a tabela):

| coluna | para que |

|---|---|

| whatsapp_valido | 1 tem, 0 nao tem, NULL nunca checado |

| whatsapp_checado_em | data da ultima checagem (TTL de 180 dias) |

| whatsapp_checado_numero | numero conferido; se o cliente trocar, recheca |

Medido: 1a chamada 213ms (vai no bot), 2a chamada 0ms (cache).

Os tres freios, todos deliberados:

  • teto duplo: 3 consultas/min por IP e 10/min global. O de IP existe para o

formulario publico nao virar um oraculo de "esse numero tem WhatsApp?". Reusa o

rnt_request_rate_limit_check() que ja e o mecanismo do projeto.

  • aviso ao gestor quando o teto estoura, por WhatsApp e push, dizendo o IP e que a

checagem foi pulada sem barrar ninguem.

  • falha-aberto: bot offline, timeout ou teto estourado devolvem null, e ai o

cadastro segue sem dizer nada. Cadastro NUNCA e barrado por esta checagem, e ninguem e

marcado "sem WhatsApp" por soluco de infraestrutura.

Sem validacao em massa, por decisao do ADMIN. A base e validada conforme as pessoas

se cadastram e compram.

Aviso ao cliente: toastr.warning de 20s no js/cadastre-se.js, separado da mensagem

de sucesso, so quando a resposta e um false conclusivo.

Testado: helper devolve true para celular real e false para fixo; cache grava e

le; dry-run do cron mostra checados_whatsapp batendo com a audiencia.

**action.php e arquivo monitorado pelo guard do Mercado Pago — baseline re-selado

depois da edicao**, senao o monitor alerta a cada 30 min.

[2026-09-07] confere se o numero tem WhatsApp antes de enfileirar (BK-384)

Fechado como: FEITO em 07/09/2026 · migrado do BACKLOG.md em 10/09/2026.

Perguntado pelo ADMIN: "nao podemos fazer a conferencia se o numero possui WhatsApp antes

de enviar?".

Ja conferia, mas tarde. O bot/whatsapp/server.js (~754 e ~798) recusa o envio para

numero sem conta, com a mensagem "nao possui uma conta ativa no WhatsApp". So que nessa

altura a linha ja entrou na fila e vira falha. O bot tambem ja expunha uma rota so para

isso, POST /check-number, que ninguem usava.

Agora o gerador pergunta antes. rnt_pef_numero_tem_whatsapp() chama a rota com as

constantes canonicas BOT_WHATSAPP_API_URL / BOT_WHATSAPP_API_KEY (as mesmas do

includes/whatsapp_helper.php — o cron nao herda WPP_API_KEY do systemd, entao getenv

sozinho nunca teria funcionado).

Dois limites de proposito:

  • teto de 50. Consultar numero em massa e o padrao que o WhatsApp trata como

enumeracao, e cliente nao-oficial toma ban por isso. A audiencia diaria deste cron e de

unidades; acima de RNT_PEF_CHECK_MAX a checagem e pulada inteira e o envio decide,

como era antes.

  • falha-aberto. Bot offline, timeout ou resposta inesperada mantem a pessoa na

fila. Ninguem pode ser descartado por soluco de infraestrutura.

Visivel no log: checados_whatsapp e sem_whatsapp entraram na saida JSON. Filtro

silencioso e como o problema comeca — se um dia derrubar gente demais, tem que dar para

ver no rnt_post_event_feedback_queue.log. Conferido: rota devolve exists:true para

celular real e exists:false para fixo; dry-run de 14/08 e 29/08 marcou

checados_whatsapp 1 e 3, sem_whatsapp 0.

[2026-09-07] cadastro antigo so tinha telefone, e o cron ignorava (BK-383)

Fechado como: FEITO em 07/09/2026 · migrado do BACKLOG.md em 10/09/2026.

Levantado pelo ADMIN: "os novos cadastros so possuem o campo celular mas os antigos tinham

o telefone somente".

Confirmado. O rnt_pef_audience() exigia c.celular IS NOT NULL AND c.celular <> ''.

Medido em 07/09/2026 sobre 51.073 clientes:

| | |

|---|---|

| com celular preenchido | 35.047 |

| sem celular, com telefone | 15.994 |

| desses, celular valido no telefone | 11.335 |

| fixo (10 digitos), nao serve | 1.544 |

Correcao: a consulta aceita os dois campos e o loop tenta celular primeiro,

caindo para telefone so se o primeiro estiver vazio ou reprovar. A validacao que ja

existia (rnt_wa_campaign_is_rio_mobile_phone) continua barrando fixo e formato

invalido — o fallback nao afrouxa nada.

Tamanho real do ganho, medido em vez de estimado: nos ultimos 120 dias de pedidos

aprovados sem avaliacao, a regra antiga alcancava 52 pessoas e a nova alcanca 53. **Ganho

de 1.** Os 15.994 sao majoritariamente cadastro antigo que nao compra ha tempo, entao o

numero grande nao se traduz em alcance no fluxo pos-evento. A correcao continua certa —

e o mesmo campo cego afeta qualquer campanha que filtre por celular — mas nao esperar

milhares.

[2026-09-07] avaliacao nova no PalcoFan nao avisava ninguem (BK-381)

Fechado como: FEITO em 07/09/2026 · migrado do BACKLOG.md em 10/09/2026.

Perguntado pelo ADMIN em 07/09/2026: "sempre que um novo cliente escrever no PalcoFan

eu recebo notificacao push?". Nao recebia. Nenhum dos dois caminhos de escrita

avisava ninguem — nem includes/ajax_palcofan.php (formulario do site) nem o fluxo de

feedback por WhatsApp. A avaliacao nascia pendente e ficava parada ate alguem abrir a

moderacao por acaso, o que na pratica significa que avaliacao boa podia nunca ser

publicada.

Correcao: includes/palcofan_notify.php, com rnt_palcofan_avisar_admin(), chamada

dos dois caminhos. Ponto unico de proposito, para o formato da mensagem nao divergir.

Canal: enviar_whatsapp_admin(), que ja replica a mesma mensagem como push nativo

para as inscricoes tipo=admin (includes/whatsapp_helper.php:1623). Os dois canais se

completam: o WhatsApp deixa copiar o texto, o push se le rapido. Nao foi preciso escrever

nada de push — so usar o helper canonico.

Um aviso por pessoa. No fluxo de WhatsApp o alerta fica no fim da conversa, e nao

dentro de rnt_post_event_feedback_save_comment() — aquela funcao roda tambem quando a

nota chega, entao alertar la dentro renderia dois avisos pela mesma avaliacao.

Mensagem: espetaculo, nota, primeiro nome de quem avaliou, origem (site ou WhatsApp),

o comentario entre aspas quando existe, o lembrete de aprovar em Admin > Comentarios e o

link da pagina. Titulo muda quando e so nota, sem comentario.

Nao quebra o site: a chamada do ajax_palcofan.php esta sob file_exists +

function_exists + @, porque nada ali pode atrapalhar o JSON da resposta.

Testado de verdade em 07/09/2026 12:57, com o destinatario sendo o proprio numero de

alerta do gestor: WhatsApp HTTP 200 e push Enviadas: 1 no admin/logs/push_dispatch.log.

Guard do Mercado Pago segue selado — whatsapp_helper.php e arquivo monitorado, mas foi

apenas chamado, nao editado.

[2026-09-07] feedback pos-evento: textos novos e fluxo de 2 passos (BK-380)

Fechado como: FEITO em 07/09/2026 (teste pendente) · migrado do BACKLOG.md em 10/09/2026.

Mudanca de produto pedida pelo ADMIN em 07/09/2026, depois de ler os textos antigos.

O fluxo passou de 3 para 2 passos. A etapa de consentimento de nome

("1 = com meu nome, 2 = anonimo") foi removida: agora e nota e depois comentario,

so isso.

Consequencia direta, decidida junto: sem a pergunta, exibir_nome fica sempre NULL.

A regra do BK-377 (publicar so com exibir_nome == 1) deixaria todo mundo como

"Espectador(a)", anulando o primeiro nome que o proprio ADMIN pediu de manha. Entao o

default da exibicao foi invertido: NULL publica o primeiro nome, e exibir_nome = 0

passa a ser o unico valor que esconde — reservado para linha antiga e para o opt-out

que vem depois. O ADMIN registrou a intencao de, mais adiante, deixar a pessoa **editar

o nome de exibicao ou apagar a avaliacao** pelo painel; isso ainda nao existe.

Textos, como ficaram:

  1. abertura — pede so a nota, cita a peca entre aspas e fecha com

"Sua nota e muito importante para o crescimento de nossa comunidade!".

Corrigido de quebra o "do espetaculo para "X"", que vinha de um ' para "'

colado na frase.

  1. depois da nota — "Anotamos sua nota. Obrigado!" mais o convite ao comentario, com

_NÃO_ para pular.

  1. fecho — inalterado.

Nao mexeu em ajax_palcofan.php nem na moderacao: avaliacao continua nascendo

pendente e so aparece depois de aprovada.

Validado: as tres mensagens conferidas por render direto; a regra de nome conferida

nos 7 casos de borda; e ao vivo, /casar-pra-que passou a mostrar "Néa" onde antes

mostrava "Espectador(a)". Site sem regressao.

Teste pendente: campanha 123 escopada so na Néa (+5521979979992, peca 1593

"MEU GAROTO"); o numero do gestor ficou paused nesta rodada. Roda com /root/f.sh,

confere com /root/v.sh.

[2026-09-07] inbound do WhatsApp morto ha 103 dias: webhook recusa tudo com 403 (BK-379)

Fechado como: RESOLVIDO em 07/09/2026 · migrado do BACKLOG.md em 10/09/2026.

Achado em 07/09/2026, ao conferir por que o teste do BK-377 nao andou. E mais grave

que o teste: o SAIR nao esta funcionando desde 27/05.

O sintoma. O bot Node recebe a mensagem e faz POST em bot/whatsapp/webhook.php.

O webhook responde 403 {"status":"error","message":"Webhook nao autorizado"} em

todas as chamadas, recebidas e enviadas. O journal do rnt-whatsapp mostra isso o

dia inteiro, hoje inclusive:

[Webhook] HTTP 403 enviada telefone=5521999915554 resposta={"status":"error",...}

A causa. rnt_local_webhook_authorized() confere getenv('WPP_WEBHOOK_TOKEN') e

getenv('WPP_WEBHOOK_SECRET'). O php-fpm limpa o ambiente por padrao

(clear_env = yes), entao o getenv() devolve false, e as duas checagens — token

direto e HMAC — falham sempre. O bot Node ja manda os dois valores certos

(Environment= em rnt-whatsapp.service); quem nunca teve foi o PHP.

Desde quando. A conferencia entrou no commit 086a08fbe, de 27/05/2026

("bot inbound — SAIR, zonas de interesse, banner painel e LID fix"). A ultima

mensagem recebida registrada em whatsapp_mensagens e de 27/05/2026 18:34 —

mesmo dia. Zero recebidas nos 103 dias seguintes.

O que isso quebrou, alem do teste:

  • SAIR nao funciona. Quem pediu para sair da lista desde 27/05 continua nela,

porque o opt-out depende deste webhook. Isso e o item mais urgente daqui.

  • zonas de interesse respondidas por WhatsApp nunca entraram.
  • o feedback pos-evento nao teria como funcionar nem com o envio ligado (BK-378),

porque a resposta com a nota morre no 403.

Correcao (/root/w.sh, aguardando ADMIN): duas linhas env[] no pool [rnt],

com os valores lidos da unit do bot em tempo de execucao para nunca sairem de

sincronia. Backup, php-fpm -t antes do reload, auto-reversao se o teste falhar, e

conferencia de que o worker enxerga as variaveis depois.

Cuidado que custou tempo: o arquivo certo e

/www/server/php/83/etc/php-fpm-rnt.conf, o master separado do BK-348. O

/www/server/php/83/etc/php-fpm.d/rnt.conf existe, tem o mesmo [rnt] dentro e e de

outro master — editar la nao teria efeito nenhum neste servico.

APLICADO pelo ADMIN em 07/09/2026 12:10:30. Backup em

/root/backups/rnt/php-fpm-rnt.conf.bkp_original_*, php-fpm -t ok, reload por USR2

com workers respawnados no mesmo segundo (master segue de 25/08). Confirmado por sonda:

POST no webhook com X-Api-Token valido devolve 400 "Dados invalidos" (passou pela

autorizacao e parou na validacao, antes de conectar no banco, entao nao grava nada); sem

token devolve 403. Site sem regressao.

Alarme falso que quase virou rollback: conferi primeiro por /proc/<pid>/environ do

worker e as duas variaveis apareciam AUSENTES, com a correcao ja funcionando. O env[]

do php-fpm entra no ambiente da requisicao, nao no do processo — /proc/.../environ

e o teste errado. A sonda HTTP acima e a certa, e ja esta dentro do /root/w.sh.

Ordem certa: BK-379 primeiro (feito), depois o teste do BK-377, so entao o BK-378.

[2026-09-07] feedback pos-evento nao chega ao comprador: kill switch global (BK-378)

Fechado como: RESOLVIDO em 07/09/2026 · migrado do BACKLOG.md em 10/09/2026.

Achado em 07/09/2026. O pipeline inteiro funciona; o unico bloqueio e uma flag.

global_kill_switch = 1 em whatsapp_campaign_settings desde 20/05/2026 15:28:46.

Os logs mostram cada etapa fazendo a sua parte e o disparo morrendo na flag:

19:00 fila gerada ....... {"ok":true,"campaign_id":122,"target_date":"2026-09-05","inserted":3}

19:02 campanha aprovada . {"ok":true,"campaign_id":122,"real_send_enabled":true}

19-22h dispatch 10/10min . {"ok":true,"processed":0,"reason":"global_kill_switch_on"}

  • 15 mensagens paradas, de 6 dias: 14/08 (1), 16/08 (1), 22/08 (6), 23/08 (1),

29/08 (3), 05/09 (3).

  • os unicos 3 enviados sairam em 20/05 as 12:25, tres horas antes de a flag ser

ligada no mesmo dia.

  • raio de acao pequeno: a campanha de feedback e a unica coisa pending na fila

inteira. Desligar a flag nao dispara mais nada.

  • mas as antigas ja passaram do ponto: perguntar a nota de um espetaculo de 24 dias

atras nao ajuda. O razoavel e cancelar as antigas e liberar so as recentes.

Decisao do ADMIN, porque e envio real para cliente. O agente esta impedido de

executar por classificador, entao qualquer disparo passa por script rodado por ele.

Decidido em 07/09/2026: libera para todos, so depois de o teste do BK-377

chegar no 21999915554 e o fluxo funcionar de ponta a ponta. E o teste, por sua vez,

depende do BK-379: hoje a resposta do cliente morre num 403 no webhook. Este item fica travado

atras daquele de proposito. A sugestao de corte por data (as de 14/08 tem tres

semanas) foi ouvida e recusada — se mudar de ideia depois do teste, e um filtro por

campaign_key na aprovacao.

Conferencia do teste: /root/v.sh (so le; mostra em que etapa a conversa esta, se

virou avaliacao, se falta aprovar na moderacao e o que a pagina exibe).

LIBERADO em 07/09/2026 13:11, a pedido do ADMIN depois do teste do BK-377 passar.

As 15 sairam em 6 voltas, cerca de 4 minutos, com o kill switch desligado so durante o

disparo e religado por trap (/root/g.sh). Fila zerada, global_kill_switch de volta

em 1. As 6 campanhas ja estavam approved desde o cron das 19:02 de cada dia — a flag

sempre foi o unico bloqueio.

Antes de disparar, os 15 textos foram regerados com a redacao do BK-380: os originais

foram montados entre 14/08 e 05/09, com o texto antigo. Na regeracao apareceu um segundo

problema, corrigido junto: o cadastro tem nome em CAIXA ALTA e em minuscula, e isso ia

sair como "Ola, EDUARDO!" e "Ola, andre!". Normalizado em

rnt_post_event_feedback_first_name() e em rnt_palcofan_nome_publico() — so quando o

nome esta todo em um dos dois casos, para nao estragar grafia proposital tipo "McDonald".

Item de copy, junto: a mensagem diz "o que voce achou do espetaculo para "X"".

O $linhaPeca de rnt_post_event_feedback_message() monta ' para "..."' e cola em

"espetaculo". E o ADMIN quer que a mensagem cite tambem o desconto obtido, o que hoje

ela nao faz. As duas coisas sao mudanca de texto de mensagem enviada a cliente, entao

esperam aprovacao explicita.

[2026-09-07] PalcoFan nao exibia a avaliacao do feedback pos-evento (BK-377)

Fechado como: CORRIGIDO em 07/09/2026 (teste de ponta a ponta pendente) · migrado do BACKLOG.md em 10/09/2026.

Achado em 07/09/2026, ao investigar por que o comprador nao recebe o pedido de nota.

A parte de envio e o BK-378. Este BK e so a exibicao, que estava errada de tres

jeitos independentes — todos os tres impediriam o resultado combinado que o ADMIN

descreveu ("exibir no PalcoFan, so o primeiro nome").

1. Peca fora de cartaz nao mostrava avaliacao nenhuma. peca.php:1116 tem

if (!$em_cartaz) { goto peca_public_footer; }, e o rotulo esta na ~2970. O bloco

PalcoFan fica na ~2524, ou seja, dentro do trecho pulado. E justamente o caso que

importa: o feedback pos-evento e sobre espetaculo que ja aconteceu, entao a peca

quase sempre ja saiu de cartaz quando a avaliacao chega. Provado antes da correcao:

/casar-pra-que (peca 23, em_cartaz=0) tinha uma avaliacao aprovada com 5 estrelas

e comentario, e o HTML nao trazia nem o bloco.

Correcao: o bloco passa a ser renderizado tambem no caminho de fora de cartaz,

logo apos o rotulo peca_public_footer:, guardado por if (!$em_cartaz) — sem a

guarda ele apareceria duas vezes nas pecas em cartaz, porque a execucao normal

tambem passa pelo rotulo.

2. Publicava o nome completo. includes/inc_palcofan.php imprimia

$av['nome_cliente'] cru. A funcao rnt_post_event_feedback_first_name() ja existia

no helper, mas nunca foi usada na pagina.

3. Ignorava o consentimento. exibir_nome era gravada e nunca lida — nem no

inc_palcofan.php, nem no ajax_palcofan.php, nem na moderacao. Quem respondesse

"2 = publicar de forma anonima" teria o nome publicado do mesmo jeito. O proprio

post_event_feedback_helper.php documenta a intencao ao gravar NULL: *"sem resposta,

nada e publicado com o nome dela"*.

Correcao (2 e 3): funcao rnt_palcofan_nome_publico($nome, $exibir_nome), que so

publica quando exibir_nome == 1, e ai so o primeiro nome. NULL, vazio e 0 viram

Espectador(a). NULL inclui a avaliacao feita pelo site, onde a pessoa nunca foi

perguntada — tratar como anonimo e o lado seguro.

4. Avaliacao de evento ficava invisivel. evento.php consulta

tipo_item = 'evento', mas o fluxo de WhatsApp grava sempre tipo_item = 'peca'

(hardcoded em rnt_post_event_feedback_save_comment). Sao a mesma linha de pecas,

renderizada por paginas diferentes.

Correcao: a leitura passa a usar tipo_item IN ('peca','evento') quando a pagina

e de peca ou evento. teatro continua exato, e outro item. Escolhida a normalizacao

na LEITURA para nao migrar dado nem mexer nos dois caminhos de escrita.

Validado em producao:

  • /casar-pra-que (fora de cartaz): bloco passou a renderizar, com as 5 estrelas e o

comentario que ja existiam no banco e nunca apareciam.

  • exibir_nome=1 -> renderiza Néa; exibir_nome=NULL -> Espectador(a). Testado ao

vivo alternando a coluna na linha de teste e revertendo.

  • funcao conferida nos 7 casos de borda (nome composto, espaco duplo, nome unico, vazio,

0, NULL, string vazia).

  • IN ('peca','evento') conferido por query: pagina tipo evento passou a achar a

avaliacao gravada como peca (antes achava 0); teatro segue isolado.

  • sem regressao: /lisa-lesa-e-louca (em cartaz, via peca.php) e tres paginas de evento

seguem com o bloco uma unica vez e a pagina completa.

Falta o teste de ponta a ponta, que depende de envio real de WhatsApp — bloqueado

para o agente pelo classificador. Campanha 123

(post_event_feedback_teste_bk377_20260907) ja montada com exatamente 2 destinatarios,

os numeros do proprio ADMIN, apontando para duas pecas fora de cartaz. Roteiro em

/root/f.sh; ele desliga o kill switch so durante o disparo e religa por trap.

[2026-09-07] campanhas de WhatsApp em CSV publicas; trocar deny por allowlist (BK-376)

Fechado como: RESOLVIDO em 07/09/2026 · migrado do BACKLOG.md em 10/09/2026.

Achado em 07/09/2026, logo depois de aplicar o BK-375, ao varrer a mesma classe em

outras extensoes.

  • admin/runtime/campaigns/*.csv — 77 arquivos, 14,9 MB, respondendo 206.

Colunas: cliente_id,nome,whatsapp,ultimo_teatro,ultima_peca,ultima_data,mensagem.

2.503 telefones distintos so no maior. E a base de CRM inteira, com historico

de compra por pessoa e o texto da mensagem enviada.

  • admin/*/.json (1.672) e *.jsonl (10) — fila, estado e preset de campanha.
  • admin/*/.md (64), .tmp (82), .z (179).
  • fora do /admin/: bot/temp/*.json, dumps de debug do scraper (sem PII).

A licao, e o motivo deste BK existir: BK-370, BK-375 e este sao o mesmo bug

tres vezes. Bloquear extensao por extensao nao converge — sempre sobra uma. A

correcao e inverter dentro do ^~ /admin/: allowlist do que navegador de

verdade pede, deny de todo o resto.

A lista nao foi chutada. Sai do access log (vivo + rotacionados), contando o

que foi servido com 200/206/304 sob /admin/:

.js 106845 | .php 54014 | .html 17153 | .css 8450 | .png 754 | .woff2 465

.jpg 255 | .gif 11 | .eot 8 | .woff 6 | .ttf 5 | .svg 4

Abaixo disso so ha numero de um digito, e **todos vieram de 72.60.252.173, que e a

propria VPS** — as sondas desta auditoria. Nenhum navegador real pediu .json,

.jsonl, .csv ou .txt sob /admin/ em todo o historico do log.

APLICADO em 07/09/2026 20:44. Backup do vhost salvo, nginx -t ok, reload feito.

Validado depois: 404 em 8 de 8 amostras — os CSVs de campanha, .jsonl de db-backup,

.json de log, .md, .z, .tmp e o bot/temp/. Nada quebrou: home, /pecas.php,

rota amigavel, /api/eventos/pecas.json, /manifest.json, ads.txt, sitemap.xml,

painel/login.php, produtor/login.php, o CSS do site e os assets do admin (css e js)

seguem 200; /admin/login em 301.

Armadilha do proprio script, que quase me fez concluir errado: o c.sh roda os testes

logo apos o nginx -s reload, e o reload e gracioso — os workers antigos ainda

atendem por alguns segundos. Os primeiros testes voltaram 206 (servido) e so na segunda

conferencia, minutos depois, apareceu o 404 correto. **Teste imediatamente apos reload de

nginx nao vale**; esperar ou repetir. O mesmo vale para o /root/t.sh.

Patch em /root/c.sh. Allowlist + deny final dentro do

^~ /admin/; fora dele, deny de .csv/.jsonl e de /bot/temp/. .json fica de

fora de proposito: /api/eventos/pecas.json e export publico deliberado

(api/eventos/gerar_json.php) e /manifest.json e usado no admin/head.php.

Arquivo sem extensao nao e afetado — a deny final e ancorada em extensao, entao rota

amigavel e diretorio continuam caindo no try_files.

[2026-09-07] Base de e-mails e logs de pagamento deixaram de ser servidos (BK-375)

Achado ao levantar contexto do BK-374 para pedir uma segunda opiniao. O BK-374

estava subdimensionado: nao eram 2 arquivos, era a classe inteira de .txt/.log

no webroot.

O que estava aberto, tudo conferido por curl anonimo:

| URL | tamanho | conteudo |

|---|---|---|

| /logs_20251014.txt | 37 MB | dump PagSeguro 2019-2026, 14.440 e-mails distintos |

| /admin/modulos/pedidos_2/logs.txt | 21 MB | idem, 13.940 e-mails, escrito ate 05/09 |

| /painel/recuplogs.txt | 2 MB | query de recuperacao de senha, 4.144 e-mails |

| /debug_db_output.txt | 13 KB | usuario e senha do banco em claro |

| /phpinfo_output.txt | 77 KB | phpinfo completo |

| + ~250 .txt/.log sob /admin/ | ~50 MB | inclusive telefone de cliente |

Uniao dos tres maiores: 17.417 e-mails distintos de cliente, sem login.

Causa, em duas partes. A deny de .log ja existia no vhost e funcionava

(/painel/error7.log dava 403). Mas .txt nunca esteve em lista nenhuma e o bloco

de estaticos do fim do arquivo o entregava com expires 30d; e sob /admin/ nem a

deny de .log valia, porque location ^~ desliga toda location regex — a mesma

causa estrutural do BK-370, na terceira aparicao.

Correcao (/root/t.sh, aplicado pelo ADMIN as 11:18:17): allowlist primeiro

(ads.txt, robots.txt, cookie.txt, security.txt, a verificacao de dominio e o

TERMO_PRODUTORES_GRATUITO_2026.txt, linkado em painel/modulos/profile/index.php:1336),

deny de \.txt$ no nivel do server, e a deny (txt|log|sql|ini|sh) **repetida

dentro do ^~ /admin/**.

Validacao: 404 em 27 de 27 URLs de vazamento, incluindo as 6 sob /admin/.

Allowlist em 200. Home, peca.php, pecas.php, painel/login.php,

produtor/login.php, sitemap, CSS e rota amigavel em 200; /admin/login em 301.

error.log do nginx sem nada alem das proprias sondas, nenhum fatal PHP, guard do

Mercado Pago selado. Rollback: /root/t.sh u.

Ordem importa: bloqueio por HTTP primeiro, faxina de arquivo depois. Varios

desses arquivos tinham mtime identico de 2026-09-06 01:00, compativel com o rsync

externo do NBi7 que sobrescreve o webroot — apagar sem bloquear pode ser desfeito no

proximo sync.

O que este patch NAO resolveu: o BK-374 (parar de gravar dado de pagamento

dentro do webroot — o BK-375 e contencao, nao origem) e o BK-376 (.csv, .json,

.jsonl, .md, .tmp e .z sob /admin/ continuam servidos; entre eles, 77 CSVs

de campanha de WhatsApp com nome, telefone e historico de compra de cliente).

[2026-09-06] Backups do webroot deixaram de ser servidos (BK-370)

O ADMIN rodou /root/n.sh as 00:51:30. O patch salvou o vhost antes

(/root/backups/rnt/vhost_rionoteatro_20260906-005130.conf), passou no nginx -t e

recarregou.

O item estava aberto desde 01/09 por causa de duas coisas que se somavam: o

location ^~ /admin/ desliga a avaliacao de qualquer location regex abaixo dele,

entao a deny de .bkp/.bak nunca rodava para nada sob /admin/; e a regex antiga

exigia ponto antes de bkp, deixando passar nomes como peca.php_bkp_29082024.

O patch acrescenta duas location de deny no nivel do server e **repete as mesmas

duas dentro do bloco ^~ /admin/** — sem a repeticao, o ^~ continuaria

curto-circuitando. Nenhuma regra existente foi tocada.

Cobertura: 1.874 arquivos passam a casar. Dos 1.755 backups de fonte achados por

nome, 1.752 ficam bloqueados. Sobram 3 copias em admin_v2026/modulos/eventos/

(*_ORIGINAL_ATUAL.php) que escapam por nao ter marcador no nome — mas terminam em

.php, entao sao executadas, nao servidas como texto: o codigo nao vaza.

Validado: 10 amostras por HTTP, 404 nas 10, sendo 4 sob /admin/. Site sem

regressao (home, /pecas.php, css, sitemap 200; /admin/login 301). error.log do

nginx vazio desde o reload e nenhum fatal PHP no dia; os unicos 404 no access log

pos-reload sao as sondas da propria validacao.

Falso positivo procurado: nenhum .php vivo cai na regra. A unica aparencia de

problema e admin/cron/cleanup_old_pecas_images.php, que casa pelo _old_ do meio do

nome — mas roda por CLI no /etc/cron.d, nunca por HTTP. Nenhum asset estatico

bloqueado e referenciado por pagina viva.

O que este patch NAO resolveu: o BK-374. debug_mp.txt e debug_ps.txt sao .txt

de nome limpo, sem sufixo de backup, servidos pelo try_files do bloco ^~ /admin/.

Nenhuma das duas regras casa e eles continuam 200 anonimo, com transaction_id do

Mercado Pago em claro. So fecha apontando o debugLog() dos conferir_* para fora do

webroot.

Rollback: /root/n.sh u.

[2026-09-05] Sync do Mercado Pago: escrita liberada, com guarda e auditoria (BK-367)

O ADMIN autorizou ligar a escrita do sync_mercadopago_pendentes.php, parada em

modo leitura desde 01/09.

Antes de ligar, o dry-run do dia (/root/p.sh) foi refeito: `0 mudariam | 20 iguais

| 0 erro HTTP`. Ligar hoje e no-op — o cenario mais seguro para destravar.

Mas o endpoint ainda respondia 200 anonimo. Ele nao estava entre os tres que o

BK-369 fechou, e rodava a sincronizacao inteira (ate 20 GETs na API do MP) para

quem chamasse. Liberar a escrita assim teria criado exatamente o risco que a

condicao do ADMIN existia para evitar. Entao, na ordem:

  1. guarda de sessao de admin no endpoint (403 JSON sem ela), igual a dos conferir_*;
  2. so depois $BK367_PODE_ESCREVER = true;
  3. trilha de auditoria em /www/wwwlogs/rnt-bk367-sync-writes.log a cada escrita —

pedido, de->para, status do MP, transaction_id e IP. Fora do webroot de proposito.

Validado: anonimo -> 403 sem tocar no banco nem no MP; com sessao de admin ->

200, escrita_liberada: true, atualizados: 0, erros: 0; site 200 e admin 302.

Fica de pe a ressalva: a query pega status 1 e 3, entao o sync pode rebaixar

pedido pago se o MP disser refunded/cancelled/charged_back. E o comportamento

pretendido; status_fixo = 'sim' e a trava por pedido, e o log novo e como isso

passa a ser visivel. Rollback e uma linha.

[2026-09-05] Endpoints anonimos que gravavam status de pedido, fechados (BK-369)

Tres arquivos respondiam 200 a qualquer um e gravavam status_transacao,

sem respeitar o status_fixo que o BK-367 tinha acabado de criar: sincronizacao.php

(raiz do webroot), conferir_mercadopago.php e conferir_pagseguro.php.

O ADMIN escolheu a opcao 1: aposentar o sincronizacao.php.

  • sincronizacao.php movido para /root/ (agora 404). Ele era o unico motivo para

os conferir_* aceitarem chamada sem cookie — os chamava por curl server-side,

sem sessao. Sem ele, exigir sessao nao quebra nenhum fluxo.

  • conferir_mercadopago.php e conferir_pagseguro.php passaram a exigir sessao de

admin (CMS_X.logged + tipo == 'admin'), respondendo 403 JSON sem ela. Os

botoes da tela de Pedidos chamam por AJAX de dentro do admin, entao o navegador ja

manda o cookie: o uso real foi preservado.

  • setup.php nao e carregado nesses dois de proposito — carimba

Content-Type: text/html e quebraria o contrato JSON. session_start() puro le a

mesma sessao porque o admin usa a sessao default. A sessao so abre se houver

cookie, senao cada acesso anonimo criaria um sess_* em /tmp.

Validado: anonimo -> 403 nos dois; com sessao de admin -> passa a guarda;

/sincronizacao.php -> 404; home, /eventos.php, /pecas.php, /teatros.php e

rota amigavel em 200; /admin/ e a tela de Pedidos seguem em 302 sem login.

Correcao de registro: o BK dizia que os conferir_* tiveram "13 acessos em

25/08". A contagem estava certa, a data nao — sao de abril. O log rotacionado

...log-2026-08-25.gz cobre 02/04 a 25/08, e o nome do arquivo foi lido como se

fosse a data das linhas. Uso humano real desses endpoints nao ocorre desde 15/04/2026.

Achado colateral, ainda aberto (BK-373): com a guarda passando, o

conferir_pagseguro.php revelou um fatal do 8.3 (`Non-static method

PagSeguroTransactionSearchService::searchResult() cannot be called statically`).

Nao e regressao desta mudanca: e fallout da virada de 25/08 que ninguem via porque

o botao nao e usado desde abril.

[2026-09-01] Endpoint publico gravava consulta sem escapar parametro (BK-372)

sincronizacao.php, na raiz do webroot e sem autenticacao, montava a consulta

por transaction_id interpolando $_REQUEST direto. O projeto nao tem sanitizacao

global — anti_injection() existe mas precisa ser chamada, e nao era. O caminho por

id ja estava protegido por (int); o por transaction_id, nao.

Corrigido com mysqli_real_escape_string($conexao, ...). **Deliberadamente nao usei

anti_injection()**: ela remove trechos do valor, e o transaction_id do MP precisa

chegar intacto para casar no banco — e a mesma armadilha que quebrou o reCAPTCHA por

10 meses (BK-364). Aproveitado tambem um acesso a indice de null logo abaixo

($pedido['id'] antes da checagem if (!$pedido)), mesma classe do BK-363.

Verificado depois da correcao: sem parametro responde a mensagem de sempre, ?id=

de pedido real volta success:true, e transaction_id inexistente cai em

"Pedido nao encontrado" em vez de erro.

Nao sondei o endpoint com payload de injecao: no 8.3 o erro viraria fatal e o

vigia acordaria o gestor. A confirmacao foi por leitura do codigo mais teste de

alcance, que basta.


[2026-09-01] Quatro endpoints publicos escrevem status de pedido (BK-369) - PARCIAL

Comecou como "3 endpoints sem login" e cresceu ao medir. O quadro real:

| endpoint | anonimo | escreve status? |

|---|---|---|

| sincronizacao.php (raiz) | 200 | sim, via os dois abaixo |

| pedidos_2/conferir_mercadopago.php | 200 | sim (UPDATE, linha 151) |

| pedidos_2/conferir_pagseguro.php | 200 | sim (UPDATE, linha 170) |

| pedidos_2/sync_mercadopago_pendentes.php | 200 | travado no BK-367 |

| pedidos_2/describe_pedidos.php | ~~200~~ 404 | nao (vazava o schema) |

Nenhum deles respeita status_fixo. Isso relativiza a trava do BK-367: ela

protege um caminho enquanto outros tres gravam livremente. Medido na pratica —

sincronizacao.php?id=73908 respondeu "Status atualizado de 3 para 3".

Feito: describe_pedidos.php movido para fora do webroot (0 referencias no

codigo, e os unicos 2 acessos do log eram meus). Agora 404.

Nao feito, e por que: os conferir_* tem uso real — 13 acessos de

177.192.8.144 em 25/08, navegador, provavelmente o ADMIN usando os botoes da tela

de Pedidos. Guarda de sessao manteria esse uso funcionando (o navegador manda o

cookie), mas quebraria a chamada interna do sincronizacao.php, que vai por

curl server-side sem cookie (linha 43). Resolver isso e decisao de desenho — segredo

compartilhado, require em vez de HTTP, ou aposentar o sincronizacao.php, que hoje

nao tem chamador nenhum no codigo. Mexe em pagamento, entao fica para o ADMIN.


[2026-09-01] 392 backups servidos em texto puro; 3 com credencial viva (BK-370)

Como apareceu: eu criei um backup de sync_mercadopago_pendentes.php seguindo o

protocolo (mesma pasta, sufixo .bkp_original_...) e fui conferir se estava

protegido. Respondeu 200, servindo o fonte inteiro — incluindo a linha 15, que

tem usuario e senha do banco em texto. Movido para fora do webroot em seguida.

Varredura completa: 1.494 arquivos de backup no webroot, 392 servidos em 200

(290 fonte PHP, 61 .md, 17 .json, 15 .js, 5 .sql).

Por que o bloqueio nao pegou — duas falhas distintas:

  1. location ^~ /admin/ (linha 337 do vhost). O ^~ manda o nginx parar e

nao avaliar nenhuma location regex. A deny de .bkp/.bak da linha 268 e

regex, entao todo backup sob /admin/ e servido, nao importa o nome. Isso

nao e questao de ordem no arquivo: e precedencia de modificador.

  1. Nome com underscore. A regra exige ponto antes de bkp (\.(bak|bkp)) ou

terminar em .php. Entao peca.php_bkp_29082024 e seguranca.php_bkp_mysqli

passam limpo.

Credenciais vivas encontradas e contidas (movidas para /root/backups/rnt/vazados-BK-370/, chmod 600):

| arquivo | vazava |

|---|---|

| painel/seguranca.php_bkp_mysqli | senha do banco de producao |

| admin/modulos/pedidos_2/conferir_pagseguro.php_bkp_20260407 | senha do banco de producao |

| painel/data/functions.php_bkp_original_20260520-191300_BK-336-... | secret do reCAPTCHA |

O terceiro corrige uma falha do BK-366 de ontem: eu redigi o secret nos quatro .md

e conferi que sumiram da web, mas nao procurei o mesmo segredo em backup de .php.

Ele seguiu publico mais 24h. Os tres .md de ontem foram reconferidos hoje: limpos.

Falso positivo verificado, para nao virar alarme: 4 backups de add_ia_shared.php

expoem uma apiKey de 32 chars que ainda esta viva — mas e a chave **search-only do

Algolia do Sympla** (appId IAOCIV5YVY), usada pelo bot captador. Chave Algolia

search-only e publica por design e nem e nossa. O token= num backup do modulo

facebook e a string $page_access_token, nome de variavel.

Pior que leitura: sob /admin/ esses backups sao EXECUTADOS. O bloco

location ^~ /admin/ tem um location ~ \.php$ aninhado que manda tudo para o

PHP-FPM. Entao admin/inc-menu_bkp_original_*.php nao e servido como texto — ele

roda, com codigo antigo e sem as correcoes de seguranca posteriores. A varredura

provou isso do jeito ruim: gerou 6 fatais reais e **o vigia de erros disparou

WhatsApp e push para o gestor as 08:10:32**, com 5 assinaturas

(inc-menu_bkp_original_*, indexfotos_bkp/bootstrap.php,

bootstrap_bkp_27112025.php, parse error em action_bkp_20251110_v2.php).

Alerta de producao causado pela propria medicao — nao era incidente real.

Os outros 389 continuam servidos. Nao ha credencial neles (varridos por senha do

banco, APP_USR-, EAA, AIza, sk-, 6L, e por atribuicao generica de

password/secret/token/api_key), mas e o codigo-fonte do site aberto, incluindo

versoes antigas de action.php, webhooks e rotinas de pagamento.

Correcao estrutural nao aplicada: exige mexer no vhost em producao. Ver BK-370

no backlog para o patch exato.

Protocolo corrigido junto: ele afirmava que "o sufixo .bkp_original_... existe

justamente porque o nginx o devolve como 404". Isso e falso e foi o que me fez

confiar no formato. Agora exige conferir o codigo HTTP do backup depois de cria-lo,

e mandar backup de arquivo com credencial direto para fora do webroot.


[2026-09-01] Tres endpoints do admin de Pedidos respondem sem login (BK-369) - ACHADO, NAO CORRIGIDO

Descoberto ao testar o BK-367: o POST anonimo em

sync_mercadopago_pendentes.php foi aceito. O arquivo se declara

"Standalone - nao depende de bootstrap.php" e, com isso, tambem ficou fora da

autenticacao do admin.

Varredura no modulo, sem sessao:

| endpoint | anonimo |

|---|---|

| admin/ | 302 (protegido) |

| pedidos_2/index.php | 302 (protegido) |

| pedidos_2/exportar.php | 302 (protegido) |

| pedidos_2/sync_mercadopago_pendentes.php | 200 |

| pedidos_2/describe_pedidos.php | 200 |

| pedidos_2/conferir_mercadopago.php | 200 |

describe_pedidos.php devolve o schema completo de pedidos (18 colunas, tipos)

para qualquer um — e um arquivo de debug que nunca deveria estar em producao.

conferir_mercadopago.php aceita um id de pedido sem autenticacao (respondeu

ID do pedido nao informado); nao foi sondado com id real, para nao extrair dado de

cliente. O sync consome o token do MP a cada chamada.

Nao corrigido de proposito: a correcao natural (exigir sessao) mexe num endpoint

chamado por AJAX pela propria tela de Pedidos, e derrubar isso quebra a tela. Precisa

ser feito conferindo o fluxo do AJAX, nao no mesmo movimento de outra tarefa.

Isso torna a decisao do BK-367 mais estreita: enquanto o endpoint for anonimo,

liberar $BK367_PODE_ESCREVER = true significa que qualquer pessoa na internet pode

disparar escrita de status de pedido. O efeito e limitado (so grava o que o MP

reporta), mas a ordem certa e fechar o BK-369 antes de liberar a escrita.


[2026-09-01] Coluna status_fixo criada, sync do MP ainda NAO liberado para escrever (BK-367)

ADMIN autorizou a opcao recomendada: criar a coluna e rodar em modo leitura antes.

Feito: `ALTER TABLE pedidos ADD COLUMN status_fixo VARCHAR(3) NULL DEFAULT NULL

AFTER status_transacao`. Rodou em 0,51s. A tabela e MyISAM (72.374 linhas,

15,5MB), entao o ALTER copia a tabela e trava escrita — por isso houve dump antes,

em /root/backups/rnt/pedidos_pre-BK-367_20260901-075549.sql.gz (chmod 600, fora do

webroot). As 72.374 linhas ficaram com status_fixo NULL, que e exatamente o

comportamento de hoje: nenhum pedido marcado como ajustado a mao.

Efeito imediato: a query do sync voltou a rodar e a tela de Pedidos para de dar

fatal (eram 4 so em 31/08: 19:24, 19:53, 21:20 e 21:38). O guard de

verificar_transacoes.php:74 (isset($pedido_local["status_fixo"])) tambem deixa

de ser sempre-falso; com a coluna NULL o isset() segue false, entao o

comportamento nao muda hoje.

Duas medicoes que mudam a leitura do risco:

  1. Nao existe pedido MP em status 1 (Aguardando). Nenhum, em toda a base. A

distribuicao e 125 em status 3 (Paga), 35 em 7, 4 em 6 e 2 em 11. Ou seja, a

funcao de "rede de seguranca para webhook perdido" nao tem trabalho a fazer

agora.

  1. Os 20 pedidos da janela do sync sao todos status 3. Como o mapa converte

rejected/cancelled/refunded em 7 e charged_back em 6, a unica coisa que

o sync consegue fazer hoje e rebaixar pedido pago. Nao ha upside na janela

atual, so risco — que e precisamente o que o status_fixo protege.

Trava de escrita, porque criar a coluna rearmou o sync. O script roda sozinho ao

abrir a tela de Pedidos; com a coluna criada, a query voltou a rodar e o UPDATE

voltou a ser alcancavel na primeira visita do ADMIN ao admin. Como o pedido foi

"modo leitura antes", o UPDATE passou a depender de $BK367_PODE_ESCREVER, hoje

false: o sync percorre tudo, calcula o que mudaria, registra no log com

ESCRITA BLOQUEADA e devolve escrita_liberada:false e bloqueados:N no JSON.

Para liberar, e trocar essa unica linha para true.

Resultado do dry-run (01/09, endpoint real ja travado):

{"success":true,"atualizados":0,"erros":0,"escrita_liberada":false,"bloqueados":0,"log":[]}

HTTP 200, contra os 500 de ontem. bloqueados:0 significa que **nenhum pedido

mudaria**: os 20 da janela ja estao com o mesmo status que o Mercado Pago reporta,

e as 20 consultas a API voltaram sem erro. Liberar a escrita hoje seria, na pratica,

um no-op — o que e o melhor cenario possivel para destravar.

Tambem instalado /root/rnt-tools/dry_sync_mp.php (atalho /root/p.sh), dry-run

independente do site, para reconferir quando quiser.


[2026-08-31] Protocolo ensinava a escapar SQL com funcao removida no PHP 7 (BK-368)

Fecha os dois pendentes que o BK-365 deixou explicitos em docs/00_PROTOCOLO.md.

1. mysql_real_escape_string() na secao de Seguranca. O protocolo e a leitura

numero 1 de quem chega no projeto, e mandava escapar SQL com uma funcao que a

extensao mysql_ levou embora no PHP 7. Substituida por

mysqli_real_escape_string($conexao, $valor) e prepared statement.

Correcao de um erro meu no registro anterior: o BK-365 afirmava que essa funcao

estava "hoje fatal em producao no 8.3". Fui medir antes de mexer e nao esta.

As 12 ocorrencias em .php sao cobertas pelo shim config/mysql_compat.php, que

recria as mysql_* sobre mysqli e entra por config/connect.php e pelos quatro

__CLASS.SQL.php. O log do 8.3 nao registra nenhum

Call to undefined function mysql_, e api/track-event.php (o caminho publico de

maior trafego entre eles) carrega connect.php na linha 13. O shim, alias, esta bem

feito: cai em addslashes() quando nao ha conexao, e evita o mysql_close() que no

5.6 derrubava o resto da pagina.

O risco real, entao, nao e um incendio ativo: e o protocolo ensinar codigo novo a

depender de uma ponte de migracao. Fora do alcance do shim, a chamada e fatal.

Licao do BK-364 incorporada: a secao agora proibe passar por anti_injection()

valor que nao vai para SQL (token, hash, JSON, assinatura). Foi essa corrupcao que

manteve a recuperacao de senha quebrada por 10 meses.

2. Mojibake. Cinco caracteres U+FFFD, concentrados no item 7.1 e na secao

"Validacao Estrutural de Delimitadores" - ou seja, justamente na parte que manda

checar mojibake. A instrucao dizia "buscar ?, ?", com os dois exemplos

ilegiveis, o que a tornava inaplicavel. Reescrita nomeando o caractere: procurar

U+FFFD, Ã, Â ou ? fora de lugar. Protocolo agora em v3.1, com 0 U+FFFD.


[2026-08-31] Secret do reCAPTCHA estava legivel na web (BK-366)

Achado ao conferir se os backups desta rodada eram servidos publicamente. Os

.bkp_original_ estao protegidos (404), mas */docs/ inteiro e legivel na

web**: docs/CHANGELOG.md, docs/BACKLOG.md, docs/LOCK.local.md,

docs/AGENTS.md, AGENTS.md e os docs/backlog/BK-*.md respondem 200.

So a listagem de diretorio esta bloqueada (403), o que nao ajuda: os nomes sao

previsiveis.

Dentro desse material publico havia o secret key do reCAPTCHA em claro,

documentado como Secret Key: <valor de 40 chars> em quatro arquivos

servidos:

  • docs/CHANGELOG.md
  • docs/CHANGELOG_bkp_20260316.md
  • docs/CHANGELOG_bkp_original_20260703-170234_whatsapp-produtor-falha.md
  • docs/CHANGELOG_bkp_original_20260703-163218_mensagem-aprovacao-produtor.md

Os _bkp_ antigos escapam do bloqueio do nginx porque terminam em .md; a regra

so pega o padrao arquivo.ext.bkp_original_.... Mais um motivo para o formato de

backup novo do BK-365.

Os quatro valores foram substituidos por <REDIGIDO-BK-366: ...>. O que continua

valendo, e nao muda com essa edicao:

  1. O secret vazou e precisa ser rotacionado no console do Google. Editar o

.md tira da vitrine, nao invalida a chave — ela esteve publica por meses e

segue no historico do Git. So o ADMIN acessa o console.

  1. Nao foi verificado se alguem leu. Nao ha como saber pelo log do nginx sem

varrer o historico inteiro de acessos a /docs/.

  1. Bloquear /docs/ no nginx ficou como proposta, nao aplicado, por ser

mudanca de configuracao de producao: location ^~ /docs/ { deny all; } —

regra de diretorio com ^~ funciona em qualquer posicao do arquivo, diferente

de regra por arquivo, que vira codigo morto depois do include enable-php.

Nao foram encontrados outros valores de segredo nos .md de docs/: a varredura

por token da Meta (EAA...), access token do Mercado Pago (APP_USR-...), chave

de API do Google (AIza...) e atribuicao de senha nao retornou nada real.

.git/config, .git/HEAD e .env respondem 404, e

painel/data/functions.php executa normalmente sem expor fonte (0 bytes).


[2026-08-31] Protocolo realinhado com a realidade da VPS (BK-365)

docs/00_PROTOCOLO.md estava parado em 01/12/2025, descrevendo um mundo que nao

existe mais, e se apresentava como tendo **"PRIORIDADE ABSOLUTA sobre qualquer

outra instrucao"**. O README.md e o docs/AGENTS.md apontam para ele como

leitura obrigatoria numero 1, entao era o primeiro documento que uma IA nova lia.

O que ele mandava, e que contradizia o AGENTS.md:

  • "NUNCA EXECUTAR COMANDOS GIT" — JAMAIS git add, JAMAIS git commit,

JAMAIS git push, "o USUARIO e quem executa os comandos git - SEMPRE".

O AGENTS.md (raiz do repo e /root) manda o contrario: editar o arquivo vivo,

validar, commitar e pushar direto na main — que e o que todo BK recente

fez de fato.

  • Caminho do projeto em H:\RIONOTEATRO\GITHUB\2026\... — maquina Windows

que nao e mais o ambiente de trabalho. Confirmado pelo ADMIN em 31/08/2026:

so se mexe direto na VPS.

  • Backup no formato arquivo_bkp_YYYYMMDD.php — formato perigoso, porque

termina em .php dentro do webroot e vira copia do sistema **servida

publicamente** com codigo velho. O padrao real e

arquivo.php.bkp_original_YYYYMMDD-HHMMSS_BK-NNN, que o nginx devolve como 404.

Nao e detalhe de documentacao: uma IA que obedecesse esse arquivo ao pe da letra

deixaria toda alteracao sem commit na VPS, exposta ao rsync externo do NBi7

que sobrescreve o webroot a cada ~11 min.

Corrigido:

  • Nota no topo dizendo onde o trabalho acontece hoje e que, havendo divergencia,

vale o AGENTS.md. Este protocolo passa a cobrir disciplina de seguranca e

validacao, nao fluxo de versionamento.

  • Passo 8 virou "commitar e pushar direto na main", com conferencia de arvore

limpa.

  • A secao de git virou permitido/proibido: liberado status/log/diff/add

nomeando arquivo/commit/push origin main; proibido git pull por

iniciativa propria, historico destrutivo (o guard bloqueia), checkout/

restore sobrescrevendo a main, branch e worktree.

  • Caminhos Windows trocados por /www/wwwroot/rionoteatro.com.br/.
  • Formato de backup corrigido, com o aviso de nunca terminar em .php.
  • Exemplo de fluxo e "ERRO 2" reescritos (o erro agora e git pull, nao commitar).
  • Rodape em v3.0.

Nao mexido de proposito: a exigencia de confirmacao explicita do ADMIN antes

de modificar (passos 1-5) continua valendo — a divergencia autorizada era sobre

git e ambiente, nao sobre isso.

Fica registrado como pendente, achado na mesma leitura e fora do escopo desta

correcao: a secao "Padroes de Codigo > Seguranca" ainda manda

usar mysql_real_escape_string() em queries, funcao removida no PHP 7 e que

hoje seria fatal em producao; e ha mojibake nos itens 7.1 e "Validacao Estrutural

de Delimitadores", provavelmente de uma gravacao em encoding errado.


[2026-08-31] Recuperacao de senha bloqueada pelo reCAPTCHA, e $title no produtor (BK-364)

1. O "esqueceu senha" recusava 100% das tentativas

Achado em cliente real, nao em sonda. Em 31/08 uma cliente cadastrada desde 2017

tentou comprar, bateu no "Ja existe um usuario com este CPF, E-MAIL ou RG" do

checkout, foi para o painel/esqueceu_senha.php e **as duas tentativas (13:56:42

e 13:56:46) foram recusadas pelo Google com invalid-input-response**.

clientes.pass_recover ficou vazio: o codigo nunca foi gerado e o e-mail nunca

saiu. Compra abandonada as 14:03.

O raio e o pior possivel: verify_captcha() e chamado em **um unico ponto do

site**, painel/action.php case remember. Login e checkout nao tem captcha.

Ou seja, o unico atingido e exatamente o cliente antigo que o checkout empurra

para a recuperacao de senha.

Nao e fallout do PHP 8.3. painel/action.php, painel/data/functions.php,

painel/esqueceu_senha.php e produtor/head.php estao **byte a byte identicos

a tag php56-baseline** — nada nesse caminho mudou na virada. O historico mostra

o reCAPTCHA sendo reescrito 10 vezes em 25/10/2025 (v1.2 ate v3.2, saindo do

Enterprise para o v3 classico) e os commits simplesmente pararem. A quebra e

dessa data, nao da migracao.

Hipoteses descartadas por medicao:

  • Par de chaves trocado — site key (painel/esqueceu_senha.php:17,

6Ldnb3AqAAAAADI_...) e secret (painel/data/functions.php:284,

6Ldnb3AqAAAAAORT...) compartilham o prefixo 6Ldnb3AqAAAAA, que e como o

Google emite o par do mesmo site. E o erro foi invalid-input-response, nao

invalid-input-secret: o secret foi reconhecido.

  • Tipo de chave errado (v2 dirigida por front-end v3) — o endpoint v2

(api2/anchor) recusa essa chave com "Invalid input" enquanto renderiza normal

a chave de teste v2 do Google, o que a caracteriza como v3. O front-end v3

(api.js?render= + grecaptcha.execute) casa com isso.

  • Dominio nao autorizado — a recusa e identica para rionoteatro.com.br,

www.rionoteatro.com.br e um dominio inexistente. O dominio nao discrimina.

Causa medida. painel/action.php passava o token por anti_injection()

antes de verificar. Essa funcao apaga -- (entre outros) da string, e o token do

reCAPTCHA e base64url, alfabeto que inclui -. Testado contra 10 tokens reais

emitidos pelo Google, de 100 caracteres: 1 saiu mutilado (perdeu 2 chars). O

token real do g-recaptcha-response e varias vezes maior, entao a taxa sobe

muito. Token mutilado nao decodifica e o siteverify responde exatamente

invalid-input-response.

Correcao. O token nao passa mais por sanitizador de SQL — ele vai para o

corpo de um POST HTTP ao Google via http_build_query, que ja escapa, e nunca

para o banco. Ficou trim() mais um teto de 8192 bytes, sem alterar o valor.

Os mesmos 10 tokens agora atravessam byte a byte intactos.

Foi adicionada instrumentacao temporaria que registra len_bruto, len_usado e

len_se_passasse_por_anti_injection. Na primeira recuperacao real, se os dois

primeiros forem iguais e o terceiro menor, a causa esta provada e a linha pode

sair.

Ressalva honesta: remover a corrupcao e necessario e comprovado, mas so a

proxima tentativa real diz se era suficiente. Se ainda vier

invalid-input-response com len_bruto == len_se_passasse_por_anti_injection,

a causa restante esta no console do Google (chave regenerada ou dominios do site),

que so o ADMIN acessa.

Recomendacao nao aplicada (decisao do ADMIN, muda postura de seguranca): hoje

qualquer falha de captcha deixa o cliente sem saida na tela. Vale um caminho

alternativo de recuperacao, ou pelo menos uma mensagem que ofereca o "Entrar com

Google" e o WhatsApp.

2. $title indefinido em produtor/head.php

Mesmo bug que o BK-354 corrigiu no admin/head.php e que ficou de fora do

produtor/. PHP Warning: Undefined variable $title em produtor/head.php:7.

Fallout legitimo do 8.3 (notice virou warning) num arquivo tambem identico ao

php56-baseline. Aplicada a mesma guarda do admin — !isset($title) || $title == "" —

preservando o texto padrao. Foi o primeiro sinal de trafego em /produtor/, que

a virada tinha deixado como ponto cego nao exercitado.

Validado: php -l limpo em 8.3 e em 5.6 (rollback continua possivel);

painel/esqueceu_senha.php em 200; POST real no endpoint devolvendo JSON valido

em 200, sem fatal, com o caminho completo exercitado ate o siteverify.

[2026-08-30] Peca em cartaz caia em 500 depois do corte de venda (BK-363)

Segundo fallout do PHP 8.3 (BK-348) no mesmo dia do BK-362, agora na pagina

publica. /cinderela-negra respondeu 500 duas vezes (15:57 e 15:59):

```

PHP Fatal error: Uncaught TypeError: in_array(): Argument #2 ($haystack)

must be of type array, null given in peca.php:1149

```

Causa. $seq_disp so recebia valor dentro do if (time() < $tempo_evento)

do laco de sessoes (peca.php:1032), isto e, so quando existia pelo menos uma

sessao ainda dentro da janela de venda. O laco de baixo, que monta os cards de

data, filtra por sec_data >= CURDATE() — condicao mais frouxa — e chegava em

in_array($n_data, $seq_disp) com a variavel nunca criada.

Conferido nos backups legados (peca.php.bkp, .bkp_original_2026-04-09-1744,

.bkp_original_20260501-1230_BK-304-date-funnel): $seq_disp nunca foi

inicializada em nenhuma versao. No PHP 5.6/7.4 in_array($x, null) era apenas

warning e devolvia false — nenhum card de data era montado e a pagina abria

normalmente. No 8.3 virou TypeError fatal. A correcao e $seq_disp = array(),

que reproduz exatamente o resultado do 5.6; nenhum comportamento novo foi criado.

Janela do bug. Cinderela Negra tinha uma unica sessao futura, 30/08 as 16:00,

com tempo_antes = "02:00, 00:59" — o menor valor manda, entao a venda fecha as

15:01. A partir das 15:01 nenhuma sessao passava no corte, $seq_disp ficava

indefinida e toda peca nessa situacao respondia 500, nao so esta.

Gemeo ja conhecido. E a mesma classe do $horarios = "" corrigido no BK-348

(peca.php:1169-1175): variavel que o PHP 5 criava sozinha em silencio. Aquele

foi corrigido; este passou porque o append e condicional, nao incondicional.

Validado: /cinderela-negra e /peca.php?id=1085 voltaram a 200, sem card de

data (identico ao 5.6) e sem fatal novo no rnt83-php-error.log.

Arquivo: peca.php.

Copias mortas com o mesmo bug. /root/rnt-tools/var_null_array.php (novo)

achou o mesmo padrao em peca_nova.php, peca_velhaa.php, peca_pagseguro.php

e duas copias sob geral/. peca_nova.php e peca_velhaa.php **respondem 200

por URL publica** e fatalavam igual (confirmado por sonda, log das 16:32) — o

corpo ja tinha sido descarregado antes do fatal, por isso 200 e nao 500, ou seja

o vigia enxerga, o status HTTP nao. Levaram a mesma inicializacao. peca_pagseguro.php

ja da 404 pelo nginx. **Continuam sendo codigo morto e devem sair na Fase 0 do

BK-348** (destravada em 31/08) — a correcao aqui e so para nao deixar fatal

acessivel enquanto os arquivos existirem.

Os outros 14 candidatos do scanner foram triados e sao falso positivo:

$info_faltante (action.php/action_pagseguro.php) so e lido dentro do mesmo

else que garante ao menos um append; $campos/$valores (admin pecas) sao

preenchidos incondicionalmente antes; $haystack (xlsxwriter) e $saida

(sympla_scraper) sao parametros por referencia, e os chamadores passam array.

[2026-08-30] Constante BASE_URL inexistente derrubava a tela de Pedidos (BK-362)

Primeiro fallout da virada para PHP 8.3 (BK-348) a chegar em usuario real. A tela

admin/modulos/pedidos_2/ respondia 200 com o corpo cortado na linha 465:

```

[PHP_EXCEPTION] Undefined constant "BASE_URL" - admin/modulos/pedidos_2/index.php:465

```

BASE_URL nunca foi definida em lugar nenhum do projeto — era usada em um

unico ponto, url: '<?= BASE_URL ?>atualizar_pedidos_pendentes.php?action=counter',

introduzido em 01a0c549b (01/11/2025). No PHP 5.6 constante indefinida virava a

string literal BASE_URL, ou seja, um AJAX 404 que falhava calado e deixava o

contador do PagSeguro eternamente em 1000/1000. No 8.3 isso e Error fatal.

O estrago nao era o contador, era o corte. A excecao interrompe a saida na

linha 465 de 800, entao todo o JS abaixo dela nunca chegava ao navegador —

incluindo #btn-enviar-wpp e .btn-wpp-modal (envio de WhatsApp pelo painel),

#btn-atualizar-pedidos, #btn-varredura-ps e .btn-conferir-ps. A pagina

"abria", so nao respondia a nenhum botao.

Por que so apareceu 5 dias depois da virada: o modulo nao foi aberto uma vez

sequer entre 26 e 29/08 (0 acessos no log do nginx nos quatro dias, 3.828 no

dia 25). O primeiro acesso pos-virada foi hoje, 14:30:13, e falhou na hora.

Correcao: a URL passou a apontar para o arquivo que existe de fato, na raiz do

site (/atualizar_pedidos_pendentes.php?action=counter). Nenhuma outra

ocorrencia de BASE_URL no codigo vivo.

Varredura de vizinhos virou ferramenta, nao inspecao manual:

/root/rnt-tools/const_undef.php procura constante usada e nunca definida

na arvore inteira, ignorando define(), const de topo e de classe, uso

protegido por defined() e vendor. Tem --selftest que reproduz este caso.

Rodar sempre com /www/server/php/83/bin/php — as constantes internas

mudam por versao, e sob o CLI 5.6 ele acusa IMAGETYPE_WEBP, que existe no 8.3.

A varredura no webroot inteiro achou um segundo fatal vivo, da mesma classe:

painel/modulos/clubernt/action.php:68 fazia $_SERVER[HTTP_HOST] sem aspas.

No 5.6 a constante indefinida virava a string HTTP_HOST, que por acaso e a

chave certa, entao funcionava; no 8.3 e Error fatal. Corrigido com aspas —

fidelidade exata ao que o 5.6 resolvia. O modulo e linkado de peca.php e

checkout_pix.php e tem 1 a 5 acessos por dia, ou seja, estava vivo.

O resto do que o scanner aponta foi triado e nao e acionavel: APP, CACHE,

CAKE, LIBS, LOG_ERROR sao CakePHP legado dentro de data/obeject.php

(incluido por action.php, mas em caminho de erro que nao roda); SELECT,

FROM, WHERE, INSERT, BOLETO estao so em action_backup_2025*.php, que o

nginx ja devolve 404; BASE em admin/modulos/report/Conn/Conn.class.php:31 so

e alcancavel na falha de conexao com o banco; e HTTP_HOST sobra apenas na

copia morta em painel_old/. Nao sobrou nenhum outro caso real em codigo alcancavel.

[2026-08-30] Cron confere se o WhatsApp de compra aprovada chegou (BK-361)

O conserto do BK-359 foi validado com --test-send para o gestor, mas isso prova

o transporte, nao o caminho real (webhook -> comprador de verdade). E o modo de

falha original era justamente o silencioso: ninguem percebeu por meses. Entao a

prova virou cron, admin/cron/verifica_wa_compra_aprovada.php, de 10 em 10

minutos por /etc/cron.d/rnt-verifica-wa-compra.

Para cada pedido pago ha mais de 10 minutos (carencia para o webhook rodar), ele

procura em whatsapp_mensagens uma mensagem com "Compra foi Aprovada" para o

telefone do cliente, posterior ao pedido.

Anti-ruido, que e o que decide se um cron desses sobrevive:

  • Piso de data (DESDE = 2026-08-29 21:00). Sem ele o cron abriria disparando

falso alarme dos pedidos #73905 e #73906, pagos ANTES do conserto, quando nao

receber mensagem era o comportamento esperado.

  • Fala uma vez e cala. Na primeira venda conferida avisa de qualquer jeito

(e a confirmacao pedida); depois so volta a falar em cima de falha.

  • sair=1 nao e falha. Cliente que pediu opt-out e bloqueado de proposito

pelo enviar_whatsapp(); o cron reporta como "nao e falha", nao como erro.

Cadastro sem celular, idem.

Fuso: date_default_timezone_set('America/Sao_Paulo') no topo. O php.ini desta

VPS tem date.timezone=PRC nas tres versoes, e o fuso ainda MUDAVA no meio da

execucao — algum include da cadeia de envio chama o date_default_timezone_set,

entao o log saia 11h adiantado antes do primeiro envio e correto depois dele.

Testado nos dois sentidos antes de entrar no cron, o que importa num detector:

--dry-run --desde=... alcancando #73905 e #73906 acusou saiu=NAO nos dois

(negativo verdadeiro, sem enviar nada), e a clausula de match foi conferida

contra a mensagem real de teste em whatsapp_mensagens (positivo verdadeiro).

Flags: --dry-run, --test-send (so para o numero fixo do gestor), --status

e --desde= para testar contra vendas antigas sem esperar a proxima.

[2026-08-29] Remove os 2 alertas mortos do PagSeguro classico (BK-360)

Fecha a ponta que o BK-359 deixou de proposito. action.php:881 e

action_pagseguro.php:564 montavam um WhatsApp "NOVO PEDIDO (PAGSEGURO

CLASSICO)" para todos os administradores e chamavam enviar_mensagem_whatsapp()

— a mesma funcao inexistente. Nunca rodaram.

Removidos em vez de religados, pela mesma razao do alerta de PIX no BK-359: e

aviso de admin a cada checkout iniciado, nao a cada venda, e a rota nao tem

trafego (site_config.checkout = mercadopago; 42/42 pedidos pagos nos ultimos

90 dias sao MERCADO_PAGO_*).

Cuidado que valeu a pena: o if ($config_checkout == 'pagseguro') /

== 'classic' em volta nao era morto — e ele que define a

$result['url'] do checkout. So a montagem da mensagem e o bloco

function_exists sairam; os dois ramos do if continuam intactos.

Varredura repetida depois da remocao: 1.190 arquivos PHP, 3.385 funcoes

definidas, 578 guardas function_exists() distintas. Zero orfas de codigo

proprio. As que sobram sao todas nativas dependentes de SAPI

(getallheaders, fastcgi_finish_request, apache_request_headers,

__autoload, imap_, mcrypt_), onde a guarda e legitima.

[2026-08-29] Cliente nunca recebeu o WhatsApp de compra aprovada (BK-359)

Os dois pontos que avisariam o comprador — webhook_mp_ingressos.php:171 e

notifications.php:239 — chamavam enviar_mensagem_whatsapp(), funcao que

nao existe em lugar nenhum do repositorio. Como as duas chamadas estavam

embrulhadas em if (function_exists('enviar_mensagem_whatsapp')), nunca deu

erro: a mensagem simplesmente nao saia, sem log e sem alerta.

Prova: a tabela whatsapp_mensagens tem 3.328 mensagens desde 20/02/2026 e

zero com "Compra foi Aprovada". E o webhook_mp_ingressos.php tambem nao

manda e-mail (a unica ocorrencia de $senderEmail e atribuicao, nunca envio).

Ou seja: quem compra pelo site nao recebia nenhuma confirmacao de aprovacao.

Foram 42 pedidos pagos so nos ultimos 90 dias, todos por Mercado Pago

(31 PIX + 11 cartao; site_config.checkout = mercadopago).

O que mudou:

  1. Cliente passa a ser avisado. As duas chamadas viraram enviar_whatsapp()

(includes/whatsapp_helper.php), que e a funcao real e ja aplica opt-out

(sair=1), rate limit de 24h e gravacao no historico — nada disso existia no

caminho antigo. O helper e carregado com require_once ANTES do bloco: no

webhook ele so era incluido mais abaixo, no alerta do dono.

  1. Texto em uma funcao so. rnt_msg_compra_aprovada_cliente($pedido, $nome)

monta a mensagem e nao envia nada. Serve para as duas rotas nao divergirem de

novo e para conferir o texto com --dry-run sem depender de venda real.

A data da sessao e lida por regex direto da string do banco, sem date() nem

DateTime, por causa do date.timezone=PRC do php.ini desta VPS; os

segundos sairam do texto (20:00:00 -> 29/08/2026 as 20h00).

  1. Alerta "NOVO PEDIDO (AGUARDANDO PIX)" removido (action.php:1181). Era

o mesmo codigo morto, mas por decisao do gestor nao foi religado: dispararia

a cada QR Code gerado, inclusive os abandonados, para todos os admins. O que

interessa ja e coberto pelo cron notifica_pedido_push.php (5 em 5 min,

pedido PAGO) e pelo lembrete_pedido_pendente.php do BK-343.

Validado com --dry-run e depois --test-send para o numero fixo do gestor

(5521999915554), antes de valer para qualquer cliente real — HTTP 200, enviado.

Varredura da classe do bug: 1.190 arquivos PHP, 3.385 funcoes definidas, todos

os function_exists('X') conferidos contra as definicoes reais. Fora as nativas

dependentes de SAPI (getallheaders, fastcgi_finish_request,

apache_request_headers, __autoload, imap_, mcrypt_), o unico orfao era

esse. Sobraram 2 usos de proposito, em action.php:891 e

action_pagseguro.php:574: sao o alerta de admin do PagSeguro classico, rota

sem trafego nenhum, e religa-los cairia na mesma decisao de ruido do item 3.

Licao: function_exists() em volta de funcao propria nao e defesa, e mordaca —

esconde para sempre um recurso que nunca funcionou. Vale so para funcao nativa

que pode faltar no SAPI.

[2026-08-29] Alerta de venda e receita de campanha usavam o valor unitario (BK-358)

pedidos.valor guarda o valor UNITARIO do ingresso — o total e `valor *

qtd. E assim que o sistema inteiro calcula: admin/modulos/report/index.php`,

admin/modulos/pedidos_2/exportar.php, o estorno em pedidos/action.php, o

efetua-pagamento_pagseguro.php e o cron notifica_pedido_push.php. Conferido

tambem na base: em 15 pedidos pagos com qtd > 1, valor bateu com

metadados.valor_unitario em 15 de 15, sem nenhuma divergencia.

O webhook_mp_ingressos.php era o unico ponto que imprimia a coluna crua, em

DOIS lugares:

  1. Alerta de nova venda ao gestor (linha 221): mostrava R$ 22,00 numa

venda de 2 ingressos de R$ 22,00 — metade do valor real. Subestimava toda

venda com mais de um ingresso. Agora imprime o total, no formato pt-BR

(number_format(..., 2, ',', '.')) usado no resto do admin.

  1. Webhook interno de campanhas (linha 247): o valor enviado vira receita

no sales_webhook.php, que calcula margem_10 = valor * 0.10 e dispara

ajuste de orcamento do Facebook Ads quando o acumulado passa de R$ 500. Com

o unitario, o gatilho demorava o dobro numa venda de 2 ingressos. Agora vai

o total, formatado com ponto porque o destino faz floatval().

Achado ao comparar o alerta do webhook com o push do

notifica_pedido_push.php para o mesmo pedido #73906: um dizia R$ 22,00 e o

outro R$ 44,00. Quando dois avisos do mesmo pedido discordam, um dos dois esta

errado — vale conferir qual, em vez de assumir que e ruido de formatacao.

[2026-08-29] HOTFIX: fatal derrubava a baixa do pagamento no webhook do MP (BK-357)

webhook_mp_ingressos.php:155 chamava rnt_checkout_wallet_finalize_order(),

mas essa funcao era declarada dentro do action.php — arquivo que o webhook

nao inclui (nem poderia: o action.php executa um switch de acao). Resultado:

Uncaught Error: Call to undefined function, HTTP 500, e o script morria no meio

do bloco de pos-pagamento. Veio do BK-248 (consolidacao da carteira do cliente),

que passou a chamar a funcao no webhook sem move-la para um include comum.

O que quebra e o que NAO quebra. O UPDATE pedidos roda ANTES da chamada,

entao o pedido fica corretamente baixado (status_transacao = 3) e o ingresso

vale na bilheteria. O que morre e tudo o que vem depois: consumo de saldo da

carteira, aviso de nova venda ao gestor (WhatsApp + push) e o webhook interno de

vendas das campanhas.

O erro se esconde no retry. O Mercado Pago reenvia a notificacao apos o 500,

mas na segunda passagem status_antigo == status_db e o bloco inteiro e pulado

— retorna 200 e o alerta some. So o primeiro POST aparece como 5xx, e nada e

reprocessado. Ocorrencia real: pedido #73906 (29/08, 17:58) ficou pago e

valido, mas sem o aviso de venda ao gestor.

Correcao: a funcao foi movida para config/functions_cliente_carteira.php

(com guarda function_exists), que ja e incluido pelo webhook, pelo

action.php, pelo checkout_pix.php e pelos paineis. A declaracao duplicada

saiu do action.php. Baseline do monitor do Mercado Pago re-selado.

O raio do estrago e menor do que parece — ha uma rede independente. O cron

/etc/cron.d/rnt-notifica-pedido-push (a cada 5min) manda push tipo=admin

"Pedido pago #<id>" com cliente, peca e valor, com dedup proprio em

bot_push_pedido_admin_log. Ele pegou o #73906 as 18:00:01, ~2min depois da

aprovacao. Ou seja: o gestor soube da venda; o que se perdeu foi o alerta rico

do webhook (metodo de pagamento e ingressos restantes da sessao) e o WhatsApp —

o cron e so push. Ao avaliar um 5xx nesse webhook, conferir esse log antes de

concluir que o gestor ficou no escuro.

Segundo achado, nao corrigido aqui: a linha "💳 R$ ..." do alerta de nova

venda imprime pedidos.valor cru, mas essa coluna guarda o valor unitario

— o total e valor * qtd, como o cron acima calcula. No #73906 (2 ingressos de

R$ 22,00) o alerta diria R$ 22,00 para uma venda de R$ 44,00. Subestima

toda venda com qtd > 1.

Achado colateral, nao corrigido aqui: a notificacao de aprovacao ao CLIENTE

neste webhook (e no notifications.php) esta atras de

function_exists('enviar_mensagem_whatsapp'), e essa funcao **nao existe em

nenhum lugar do repositorio** — o helper expoe enviar_whatsapp(). Ou seja, e

codigo morto desde antes deste bug: o cliente nunca recebe esse WhatsApp de

compra aprovada. Fica registrado para virar BK proprio.

[2026-08-26] Permissao insegura passa a ser corrigida sempre (BK-356)

Achado ao investigar por que uma linha somia do .env. O .env do RNT ficou em

**644 — legivel por qualquer usuario do sistema, com credencial de banco e do

Mercado Pago — das ~10:50 as 16:37, quase 6 horas.** O monitor de integridade

detectou o desvio o tempo todo e nao corrigiu.

A causa e uma condicao boa demais. O harden de permissoes so era chamado

quando mp_guard_diffs_are_metadata_only($diffs) — ou seja, quando todos os

desvios do ciclo eram de dono/grupo/permissao. Naquele dia havia junto um desvio

de HASH (action.php, alterado pelo BK-353 de manha sem o re-seal). Bastou esse

desvio de conteudo para o monitor deixar de fechar uma permissao insegura que

ele mesmo sabia consertar.

As duas coisas sao independentes: arquivo com permissao aberta tem que ser

fechado na hora, mesmo que outro arquivo tenha mudado de conteudo. Agora o

harden roda quando ha qualquer desvio de metadado

(mp_guard_has_metadata_diff()), e o log registra quantos foram normalizados.

Isto nao silencia nada. Desvio de hash continua gerando alerta normalmente —

o que muda e que ele para de arrastar junto um problema de permissao.

Testado com as funcoes carregadas isoladamente (sem o fluxo de alerta, para

nao disparar WhatsApp) e num sandbox com a mesma estrutura de diretorios: 8

verificacoes, incluindo o cenario exato do bug — .env degradado para 644 com

desvio de hash junto, e a permissao voltando a 640 apos o harden.

De onde vinha o chmod 644 (e o .env que "comia" linhas)

Auditoria (auditctl -w .env -p wa -k rntenv) identificou: um rsync --server

escreve no webroot vindo de sessoes SSH autenticadas com a chave teatro@NBi7,

que esta legitimamente em /root/.ssh/authorized_keys. Os IPs sao de blocos

Azure e rotativos (um IP diferente por conexao), o padrao existe **desde ao

menos 24/08** e cada ciclo faz rsync -> chown -R www:www . ->

chmod 644 ./.env. Nao e invasao: e credencial legitima do dono. Mas tem duas

consequencias que ninguem tinha notado — **alteracao feita no .env direto na

VPS nao persiste** (o arquivo volta byte a byte a versao de origem), e a

permissao dele e degradada para 644 a cada passagem.

Correcao de um engano registrado antes: cheguei a concluir que esse mesmo

processo re-selava o baseline do guard, o que anularia o monitor. **Estava

errado.** A correlacao correta (por ID de evento do auditd, e nao por

proximidade de linha) mostra que o --seal das 17:04 partiu da sessao local,

disparado por /root/cerebro/guard/postedit.sh — um hook da propria VPS que

re-sela, com backup, quando um arquivo monitorado e editado aqui. Nenhuma sessao

de rsync executou --seal.

[2026-08-26] Todo alerta ao gestor sai por WhatsApp E por push (BK-355)

Regra do ADMIN: os dois canais se completam — no WhatsApp da para copiar a

mensagem e trazer para analise depois; o push e o que se le rapido. Alerta que

sai so por um dos dois nao serve.

Auditoria dos 20 arquivos que chamam enviar_whatsapp(). A maioria nao e

alerta ao gestor e ficou de fora com motivo:

  • z_dispara_diario.php e disparaemail.php — o numero aparece como link de

contato dentro do e-mail ao cliente, nao ha envio ao gestor;

  • whatsapp_inbound_analyzer.php — responde ao cliente que escreveu;
  • admin/modulos/bot/whatsapp.php — so rotulos de UI, nao envia nada;
  • notificar_destaque_sem_banner.php — avisa o produtor, e **ja manda push

ao produtor** junto com o WhatsApp; o numero do gestor ali so entra no

--test-send. Mandar push de admin seria audiencia errada (ver a regra de que

push de admin nunca vai para cliente).

Dois pontos eram alerta de verdade e foram corrigidos:

  • monitor_mercadopago_integrity.php — o alerta de DRIFT do fluxo de

pagamento saia so por WhatsApp. Agora espelha em push e o guard.log

registra os dois canais (ALERTA WHATSAPP - enviado | PUSH - enviado).

  • webhook_mp_ingressos.php — o aviso de nova venda ao dono (com ingressos

restantes da sessao) tambem saia so por WhatsApp.

Os dois usam enviar_push_admin_from_whatsapp(), que ja existia e espelha o

mesmo texto (primeira linha vira titulo), com audiencia admin explicita.

As duas chamadas sao defensivas (file_exists + function_exists + @):

no monitor, falha de push nao pode derrubar o WhatsApp, que e o canal critico;

no webhook, nada de notificacao pode afetar a baixa do pedido.

Ja cumpriam a regra e nao precisaram de mudanca: vigia_erros_runtime.php e o

deploy.php do BK-354.

Validado com envio real: enviar_push_admin_from_whatsapp() chamado pelo

mesmo caminho dos dois arquivos, push entregue.

[2026-08-26] Faxina pos-virada e deploy do GitHub que nao come trabalho (BK-354)

Rodada nascida de uma varredura de log das primeiras 24h em PHP 8.3: **0 erros

5xx, 0 fatais, 0 excecoes** em 4.226 requisicoes. O que sobrou eram quatro

pontas soltas, todas corrigidas aqui.

1. termos-avaliacoes.php nao existia — o link "Termos e Condicoes" do

bloco PalcoFan (includes/inc_palcofan.php:156) apontava para uma pagina

inexistente, 7 requisicoes em 404. A pagina foi criada seguindo o padrao do

termos-uso.php (mesmo bootstrap, mesmos tokens de cor, fundo claro com cor de

texto fixada para nao sumir no tema escuro). **O conteudo descreve as regras

reais do codigo**, nao texto generico: exige login, nota de 1 a 5, comentario

de no minimo 10 caracteres, entrada como pendente com moderacao no admin,

media e listagem apenas sobre aprovado, as 10 mais recentes, reenvio

substituindo a anterior, exibir_nome opcional e limite de envio por periodo.

2. $title indefinido em admin/head.php — 186 warnings so no dia 26/08.

A investigacao derrubou a hipotese inicial: nao vinha de visitante nenhum.

Os carimbos sao de 5 em 5 minutos, no segundo 01-03, com fuso America/Recife

(que so admin/data/config.php define) — ou seja, e cron, e os crons de 5

minutos carregam o bootstrap do admin. Corrigido na origem, com isset(), no

proprio head.php: o 5.6 tratava $title == "" indefinido como verdadeiro e

caia no titulo padrao, e o novo codigo faz exatamente a mesma coisa. **Medido:

zero warnings em 33 minutos (6 ciclos de cron) apos a correcao.**

3. Carimbo de hora errado no log do vigia de erros — vig_log() usava

date() e herdava o date.timezone = PRC (+8) dos php.ini da VPS, porque pode

rodar antes do bootstrap do site. Uma linha datada "2026-08-26 06:07:54" era,

na verdade, 25/08 as 19:07 — 11 horas de diferenca em quem le o log de alerta

numa emergencia. Passou a usar DateTime com timezone explicito, que nao

altera o estado do processo.

4. Deploy do GitHub: reescrito para ABORTAR em vez de sobrescrever. O

GitHub vinha fazendo POST varias vezes ao dia em /webhook_deploy.php e

levando 404 — o arquivo virou .bkp em fevereiro e ninguem notou. Restaurar

como estava nao era opcao: ele fazia reset forcado para origin/main no

webroot, o que contraria a regra absoluta do AGENTS.md e apagaria em silencio

qualquer edicao viva nao commitada. Como nesta VPS se edita direto na main, a

VPS e a origem do codigo — um deploy que sobrescreve inverte isso e come

trabalho.

A versao nova separa as pecas:

  • webhook_deploy.php (webroot) so valida: metodo, HMAC SHA-256 com

hash_equals, evento ping, branch e anti-loop por autor. **Nao executa

git** e nao abre conexao com o banco antes de conferir a assinatura.

  • /www/rnt-deploy/deploy.php (fora do webroot) faz o trabalho, com duas

guardas que abortam sem tocar em nada: arvore suja (qualquer arquivo nao

commitado vale mais que o que veio do GitHub) e historico divergente (so

aplica fast-forward puro). Quando barra, avisa o gestor por WhatsApp e push.

Tem flock, dry-run por padrao e log proprio.

  • Leitura do GitHub por deploy key read-only: mesmo vazando, nao da push.
  • O segredo saiu do codigo para o .env e foi trocado — o antigo estava em

claro dentro de um .bkp no webroot desde fevereiro.

Validado: GET -> 405; POST sem assinatura -> 403; assinatura errada -> 403;

ping assinado -> 200 pong; push em outra branch -> ignorado; e a guarda de

arvore suja testada no cenario real, com 3 arquivos nao commitados no repo:

abortou com codigo 1 sem alterar nada.

Corrigido junto, em /root/rnt-tools/relatorio_acessos.php (fora do repo):

o digest de acessos resolvia hoje/ontem com date() sob o fuso PRC e, por

isso, depois das 13h locais pedia o dia seguinte e respondia "Nenhuma

requisicao registrada nesta data" — que se le como site sem trafego, nao como

erro de uso. Pior: era exatamente o comando que o vigia manda o gestor rodar no

alerta. Ganhou timezone fixo e um guardrail que recusa data no futuro dizendo

qual fuso o processo esta usando. O cron das 00:20 escapava por sorte.

Correcao do mesmo dia, depois de rodar d.sh: o segredo do webhook tinha

sido posto no .env, e isso estava errado por dois motivos. Primeiro, o .env

e arquivo vigiado pelo monitor_mercadopago_integrity.php: mexer nele

levanta DRIFT, alerta o gestor por WhatsApp e so silencia depois de um --seal

manual — um segredo de deploy nao pode custar um re-seal do baseline de

seguranca do pagamento cada vez que for trocado. Segundo, e mais estranho: a

chave sumiu do .env em poucos minutos, e o arquivo voltou byte a byte ao

estado anterior, sem autor conhecido. O segredo mudou para

/www/rnt-deploy/secret (fora do webroot, www:www, 600), e o webhook le de

la com fallback para o .env. Ficou um watch de auditoria (`auditctl -k

rntenv) no .env` para identificar quem o reescreve — **o watch e volatil e se

perde no reboot**.

Isso exigiu somar /www/rnt-deploy/ ao open_basedir do .user.ini — a mesma

armadilha que /www/rnt-reach/ ja tinha imposto: sem isso o PHP nao le nada

fora do webroot e o webhook responde 503. Editado com backup e o site validado

logo em seguida, porque este e o arquivo que carrega o auto_prepend_file, e

caminho ilegivel ali e fatal em toda pagina.

Drift do guard do Mercado Pago encerrado. A varredura encontrou o monitor

em DRIFT desde ~10:50 com duas alteracoes: action.php (mudanca legitima do

BK-353 daquela manha, que ficou sem o re-seal obrigatorio) e a permissao do

.env em 644 quando o esperado e 640. A permissao foi corrigida, o diff do

action.php conferido no Git (5 linhas somando share_click a uma whitelist

de tracking, sem relacao com pagamento) e o baseline re-selado com backup. O

monitor voltou a CHECK OK — o gestor estava recebendo alerta a cada 30 min.

Achado deixado em aberto: meta-analytics.php:529 passa null para

number_format() 643 vezes por dia, sozinho quase todo o ruido do log de

erro. O trecho esta dentro de um comentario HTML — que nao impede o PHP de

executar. E a mesma classe do fatal que derrubou o pedido.php em 25/08 e, no

PHP 9, deixa de ser Deprecated e vira erro fatal.

[2026-08-26] Light Mode consertado e contraste revisado (BK-352)

O tema claro estava quebrado por tres motivos independentes, e nenhum deles

gerava erro: console limpo, log limpo, pagina 200.

  • (1) O seletor tinha sumido. css/theme.css era 20 linhas de declaracoes sem nenhum seletor - zero {, um }. Bloco sem seletor e descartado inteiro pelo parser. Origem: e84917d8b (03/12/2025), que moveu o :root do dark para redesign_2026.css e levou junto o [data-theme="light"] { do bloco de baixo.
  • (2) Especificidade. Reabrir como [data-theme="light"] nao bastou - a pagina continuou escura. theme.css carrega ANTES de redesign_2026.css, e [data-theme="light"] empata em especificidade com :root (0,1,0), entao quem vem depois ganha. Virou :root[data-theme="light"] (0,2,0), que vence em qualquer ordem de carga.
  • (3) Contraste nunca revisado. Com os tokens finalmente valendo, o tema claro apareceu pela primeira vez. Sete pontos assumiam fundo escuro: amarelo da marca como texto (datas de sessao a 1.07:1, botao de compra dos cards), grades de teatro (1.50:1, 184 ocorrencias), vermelho do regulamento, estrelas do PalcoFan, breadcrumb branco-no-branco, o .rnt-container pintando uma caixa branca sobre o gradiente do CTA de teatros, e o botao do painel pedindo var(--white) - variavel que nao existe em nenhum CSS que a pagina carrega, entao a cor caia para herdada.
  • Regra seguida nas correcoes: nenhuma cor nova. Onde o amarelo nao serve entra o azul da marca; onde o texto precisa continuar claro entram os mesmos tons do tema escuro.

Bug pre-existente corrigido junto, afetava OS DOIS temas: o titulo da secao de cadastro em pecas.php e eventos.php e <h2 class="rnt-section-title" style="color: white">, e essa classe traz caixa amarela - branco sobre amarelo, 1.13:1, ilegivel tambem no escuro (medido). O index.php ja tinha o override que tira a caixa; a regra foi replicada nas outras duas.

Validacao: auditoria de contraste rodando dentro do navegador (CDP), tema claro ligado, em 11 paginas. Resultado final limpo, com tres casos analisados e descartados com prova: o hero da home tem fundo em <img>/<video> (o medidor nao enxerga), os dias cinza da agenda sao os dias sem evento, e o btn-success a 2.48:1 e o verde do Bootstrap, identico no tema escuro. Rodada tambem uma auditoria de regressao no tema escuro: nenhuma piora.

O botao de trocar tema tambem nao existia - e foi recolocado. Testado com clique real: #theme-toggle-floating nao existe no HTML; os dois ids aparecem uma unica vez cada na pagina, dentro do proprio JS que procura por eles. O commit a21a8d122 (mesmo 03/12/2025, ~3h depois) tirou o <button id="theme-toggle"> do header, renomeou o JS e nunca criou o botao flutuante. O tema claro quebrou duas vezes no mesmo dia, por caminhos independentes. O ADMIN escolheu a opcao (A), flutuante:

  • Desktop: botao criado em includes/inc_peca.php na posicao que o autor original tinha desenhado (topo-esquerda). Antes de manter essa posicao, foi medido com elementFromPoint em 6 paginas: nao cobre nada clicavel em nenhuma.
  • Celular: o flutuante fica escondido. Cinco posicoes foram medidas em seis paginas a 390px e nenhuma estava livre - todas cobriam link de card de venda, campo de formulario ou a barra de compra, que ja e fixa no rodape. Em vez de escolher o overlap menos ruim, o controle foi para dentro do menu, no grupo "Aparencia" - que e exatamente o que o updateIcons() esperava desde 12/2025 e nunca tinha sido criado. O padding-bottom do menu subiu para 150px para o grupo nao ficar debaixo do banner de notificacoes.
  • Cuidado registrado: updateIcons() faz sun.style.display sem checar null. Os dois <span> dentro de cada botao sao obrigatorios; remover um quebra o script inteiro do header. Enquanto os botoes nao existiam, o if(toggleBtn) mascarava isso.
  • Validado com clique real (nao simulacao de atributo): escuro -> claro -> gravado em localStorage -> sobrevive ao reload, zero erro de JS em 7 paginas.

Tambem corrigido a pedido do ADMIN: o <h1>Bem-vindo!</h1> do painel/login.php estava em #012444 sobre #121212 = 1.19:1 no tema escuro, quase invisivel para todo visitante. Passou a usar --text-primary. A causa de fundo: o card pede background: var(--white) e --white nao existe em nenhum CSS que a pagina carrega, entao a declaracao e invalida, o fundo escuro aparece e o titulo azul sumia.

Achado de brinde, anotado e nao mexido: painel/login.php carrega jQuery duas vezes (1.11.3 pelo LoadHeader, 1.11.1 depois pela propria pagina). A segunda carga troca o window.jQuery e apaga o plugin do owlCarousel registrado na primeira - e a origem do TypeError: $(...).owlCarousel is not a function no console. Nao ha carrossel nessa pagina, entao hoje e so ruido.

AGENTS.md: virou regra explicita que php sem versao no PATH e o 5.6.40 e sempre continuara sendo (para nao quebrar o rollback do v83.sh u), com os comandos para conferir o que cada cron esta usando. O arquivo ja citava o fato de passagem; agora tem a regra e o porque.

[2026-08-26] Compartilhamento: frase com desconto, bloco no evento e rastreio de clique (BK-353)

Tres pedidos do ADMIN em cima do BK-351, no mesmo dia.

  • Frase de recomendacao com a % de desconto (so peca nossa). A mensagem agora abre espelhando a faixa que o visitante ja ve na pagina: Olha essa ⚡ Promoção Exclusiva que achei! + 63% OFF + sem taxa de conveniência!. Copy centralizada em rnt_share_chamada_desconto(); sem oferta cadastrada cai para Olha essa peça que achei no Rio no Teatro!, sem promessa de preco.
  • A % anunciada e a PUBLICA, nao a do socio. peca.php guarda $peca_oferta_publica antes do bloco de associado, que sobrescreve $row['oferta'] com o desconto pessoal de quem esta logado. Sem isso, socio compartilhando "70% OFF" mandaria o amigo para uma pagina de 63%.
  • evento.php ganhou o mesmo bloco, SEM a frase. Evento de produtor tem venda fora do site: nao temos desconto nem valor proprio, e a propria pagina ja carimba "VENDA EXTERNA | SEM PROMOÇÃO RIONOTEATRO". Prometer preco ali seria mentira. Mensagem do evento fica so com nome, local e dias/horarios.
  • Clique no botao virou share_click em cliente_tracking. Evento novo liberado na allowlist de action.php (a allowlist existe desde os scanners de 12/01 e 11/04/2026; sem entrar nela o POST e recusado). A rede vai em utm_content, nao em utm_source — nesta tabela utm_source e a origem de ENTRADA do visitante, e misturar as duas coisas sujaria o funil. O envio usa sendBeacon (fallback fetch com keepalive) e esta em try/catch: tracking nunca pode quebrar o compartilhamento.

Validacao: php -l no 8.3 (e tambem no 5.6 para action.php); mensagem do WhatsApp conferida decodificada nas duas rotas; 7 botoes em peca em cartaz e em 4 paginas de evento, 0 em peca fora de cartaz e em evento de produtor vencido (meu-garoto); clique real em navegador via CDP gravou as duas linhas esperadas em cliente_tracking (whatsapp e copiar, com peca_id e utm_content corretos) — as 4 linhas de teste foram apagadas depois, a tabela voltou a ter 0 share_click; evento com nome invalido continua recusado (Evento de tracking invalido); nenhuma entrada nova em /www/wwwlogs/rnt83-php-error.log.

Nota do teste: no Chrome headless o navigator.clipboard falha por falta de foco e o botao cai no fallback, que mostra o link em texto para copia manual. Isso validou o caminho de erro; em navegador real com gesto do usuario o clipboard funciona.

[2026-08-26] Botoes de compartilhamento na pagina publica da peca (BK-351)

  • Pedido do ADMIN: as paginas de peca a venda no proprio site precisavam de botoes para o visitante compartilhar com amigos (WhatsApp, Instagram, X e as demais principais).
  • Entrega: bloco "Compartilhe com os amigos" no card do heroi, logo abaixo da faixa de promocao, com WhatsApp, Instagram, X, Facebook, Telegram, E-mail e Copiar link.
  • Componente reutilizavel: includes/rnt_share.php (rnt_share_render()), com CSS/JS impressos uma unica vez por request. peca.php so passa url/nome/local/resumo. Da para ligar em evento.php depois sem duplicar codigo.
  • Instagram nao tem intent de compartilhamento na web: no celular o botao abre a bandeja nativa (navigator.share, que lista o Instagram) e no desktop copia o link avisando para colar no story/direct. Copiar link usa navigator.clipboard com fallback de textarea + execCommand, e se os dois falharem mostra o link em texto para copia manual.
  • Preview do link: nao foi preciso mexer em nada — as meta tags OG/Twitter Card ja publicadas por meta-analytics.php sao o que o WhatsApp/Facebook/X leem (confirmado no HTML servido).
  • Atribuicao: cada botao carrega utm_source proprio (whatsapp, instagram, x, facebook, telegram, email, link-direto) com utm_medium=share&utm_campaign=compartilhar-peca. Como o save_tracking de view_item grava url_atual, a visita que chega por compartilhamento fica rastreavel sem tabela nova. O og:url continua limpo, sem UTM.
  • Cores: superficie, borda e texto saem dos tokens de css/redesign_2026.css. A unica cor crua e a cor de marca de cada rede, aplicada so no icone e na borda de hover, via a variavel inline --rnt-share-cor.
  • Escopo: so aparece com a peca em cartaz. Peca fora de cartaz e pagina de evento de produtor (evento.php) seguem sem o bloco.
  • Validacao: php -l no 8.3; HTTP 200 e 7 botoes em /lisa-lesa-e-louca e /cinderela-negra; 0 botoes em peca fora de cartaz (peca.php?id=922) e nas 4 paginas de evento testadas; URL compartilhada com UTM volta 200; screenshot em 1280px e em 390px (quebra em 3 linhas no mobile); nenhuma entrada nova em /www/wwwlogs/rnt83-php-error.log.

[2026-08-25] Total do checkout acima de R$ 1.000 aparecia dividido por mil (BK-350)

  • Sintoma: na tela em que o cliente confirma a compra, R$ 250,00 × 4 ingressos aparecia como "R$ 1,00" e R$ 4.600,00 × 1 como "R$ 4,60". Abaixo de mil coincidia, e foi por isso que passou anos sem ser notado.
  • Causa — dupla formatação: pedido.php:221 fazia $v_pagar = number_format($valor * $quantity, 2, ",", "."), ou seja já guardava a string formatada ("1.000,00"); a linha 284 formatava de novo. A conversão de volta para número para de ler no separador de milhar, então "1.000,00" virava 1.0.
  • Não era regressão da migração — o bug existia igual no PHP 5.6. O que a virada para 8.3 mudou foi transformá-lo em fatal (TypeError, porque number_format() passou a ter parâmetro tipado), corrigido antes em 029baaee1 com um cast que preservava a exibição errada.
  • Correção: a origem para de formatar cedo ($v_pagar = $valor * $quantity) e só a linha 284 formata. Aplicada nos dois arquivos que renderizam esse bloco: pedido.php e pedido_pagseguro.php.
  • Alcance medido: 13 sessões com sec_valor >= 1000 no banco (maior: 4.600,00); 4 ingressos de R$ 250 também chegam lá. Era só exibição — o valor cobrado nunca veio de $v_pagar, vem do action.php a partir de tab_sessoes.sec_valor.
  • Histórico da decisão, que vale registrar: a correção foi aplicada em 05c5e9679, revertida em e45a3c9ea durante a migração para manter fidelidade total ao 5.6, e restaurada aqui depois que o ADMIN confirmou que a intenção nunca foi manter o erro. A regra de fidelidade existe para que qualquer quebra pós-virada seja atribuível ao runtime — mas ela não deveria ter sido aplicada a um valor de dinheiro errado na tela de confirmar compra.
  • Validação: R$ 250 × 4 → R$ 1.000,00, R$ 4.600 × 1 → R$ 4.600,00, R$ 120 × 3 → R$ 360,00. Sintaxe ok nas duas versões do PHP.

[2026-08-24] Cópias de backup .php eram executáveis por URL, incluindo dois painéis de cliente antigos (BK-348)

  • Sintoma: /action_backup_20251022_2.php, /pedido_backup_20251022.php e outras 10 cópias de backup do handler de pagamento/pedido respondiam 200, executando código anterior às correções do BK-341. Pior: painel_old/ e painel_bkp_v1/ serviam login.php, altera_senha.php, cancelar_compra.php e connect.php — cópias antigas da área do cliente, fora do freio de força bruta e do bcrypt aplicados em painel/ em 23/08/2026, oferecendo caminho alternativo de autenticação sem as proteções novas.
  • Por que as regras do BK-341 não pegavam: location ~ \.(bak|bkp)([0-9_.-].)?$ exige ponto antes de bak/bkp; os arquivos usam _bkp_/_backup_/backup_. E painel antigo é diretório inteiro, que nenhum padrão de arquivo alcança.
  • Correção: quatro blocos deny no vhost — dois por padrão de arquivo, dois por diretório. Script idempotente em /root/f.sh, com backup bkp_original do conf, nginx -t antes do reload e rollback automático.
  • Armadilha de nginx que custou uma tentativa falha: a primeira aplicação funcionou para os diretórios mas não para os arquivos. O vhost tem include enable-php-56.conf; na linha 58, que define location ~ [^/]\.php(/|$); como o nginx usa a primeira regex que casa, as regras inseridas na linha ~237 nunca eram alcançadas. Regra de arquivo (location ~*) precisa vir antes do include do PHP; regra de diretório (location ^~) funciona em qualquer posição, porque prefixo com ^~ tem precedência sobre regex. O padrão em si estava correto desde o início — o erro era de posicionamento.
  • Verificação: dos 20 backups sensíveis testados, 0 continuam expostos (eram 12); painel_old/ e painel_bkp_v1/ retornam 404; amostra de 25 páginas vivas do log não teve nenhuma quebra; /, /peca.php, /eventos.php, /teatros.php = 200 e /painel/, /admin/ = 302 (login). O regex foi validado contra as 172 páginas vivas: zero falso positivo.
  • Não expostos (confirmado): .env e as cópias .env_bkp_original_* retornam 404, cobertos pelas regras de dotfile.
  • Achado durante a preparação do BK-348, ao validar empiricamente uma hipótese que estava escrita no próprio backlog.

[2026-08-02] Versão de conteúdo na home e no eventos.php: cartaz trocado deixa de sair do cache

  • Sintoma: a home continuava mostrando o cartaz antigo de "Quem Ama Cuida, Não Casa!". Causa: as listagens montavam a URL da imagem sem ?v=, e o nginx serve /arquivos/ com cache-control: max-age=2592000 — 30 dias. Arte corrigida mantendo o mesmo nome de arquivo continuava vindo do cache do navegador. O peca.php já estava coberto desde o BK-347; a home e o eventos.php não.
  • Versão agregada para listagem (rnt_listagem_conteudo_versao()): a página de peça tem um id; listagem não. O que envelhece ali é o conjunto — cartaz trocado, preço novo, peça que entrou ou saiu de cartaz, destaque ativado. O fingerprint cobre todas as peças publicáveis (status='A' AND fim_temporada >= CURDATE(), hoje 7) mais o filemtime de cada capa. Ao contrário da página de peça, *destaque_ entra no hash**: é justamente ele que decide o que aparece na home e na vitrine.
  • rnt_imagem_versionada(): põe ?v=<mtime> por arquivo, não por página. Com uma versão só, trocar o cartaz de uma peça rebaixaria o cache das imagens de todas — versionar por filemtime mantém o cache das que não mudaram.
  • Reuso, não cópia: o mesmo bloco includes/inc_conteudo_versao_js.php serve as quatro páginas. Ganhou $rnt_versao_chave — quando é numérica, o endpoint é ?id=<peca>; quando é home/eventos, é ?listagem=<chave>. Cookies: rnt_pv_<id>, rnt_pv_home, rnt_pv_eventos. O peca_versao.php passou a atender ?listagem=, com allowlist (home, eventos).
  • Validado em produção: home e eventos.php em 200, cookie gravado, imagens saindo com ?v=<mtime> (valores distintos por peça, como esperado), endpoint respondendo por listagem, zero warning de PHP no HTML. Teste do mecanismo ponta a ponta: alterei o mtime de um cartaz, a versão agregada mudou (3d75805675 → 0495b50de8), restaurei o mtime e a versão voltou ao valor original.

[2026-08-02] Galeria da peça de volta: múltiplas fotos, vídeo e lightbox com navegação

  • Por que tinha parado: a galeria estava comentada no peca.php desde que "deixou de funcionar". Eram dois defeitos somados, não um. (1) O upload do admin gravava sempre com o nome {slug}rionoteatro.{ext} e, se já existisse capa, apagava o arquivo e dava UPDATE na mesma linha — ou seja, uma foto por peça, nunca havia uma segunda imagem para a galeria mostrar. (2) O parser de vídeo (video_imagem2()/video_url()) só entendia youtube.com/watch?v=ID com o v na primeira posição da query e devolvia URL http://.
  • O parser de vídeo (includes/rnt_video.php, novo): rnt_video_info() reconhece watch?v=, youtu.be/ID, /shorts/, /embed/, /live/, m.youtube.com, com o v em qualquer posição da query, e Vimeo por vimeo.com/ID ou player.vimeo.com/video/ID. Tudo em https. Testado nos 11 formatos, incluindo os que voltavam error (youtu.be/ID?si=... é o que o botão compartilhar do YouTube gera hoje) e os que geravam id lixo (?si=x&v=ID virava =x). video_imagem2()/video_url() viraram invólucros dele, então peca_pagseguro.php e as páginas legadas também foram corrigidas. Thumb do Vimeo fica vazia de propósito: exigiria chamada de API no render da página pública; o CSS .rnt-thumb.video já desenha o badge de play.
  • Upload passa a somar, não substituir (admin/modulos/pecas/action.php): a primeira foto vira a capa e mantém o nome canônico (usado em og:image e no cartaz); as seguintes entram como {slug}-g{N}rionoteatro.{ext}, com fot_capa=0. Trocar a capa agora é "subir foto nova" + "Usar como capa" + "Excluir foto" — nada é sobrescrito sem o admin mandar. Efeito colateral bom: upload deixou de destruir o cartaz atual silenciosamente.
  • Miniatura de verdade: pecas_gerar_miniatura() (GD, 400px, preserva transparência de PNG/GIF/WebP, cai para cópia simples se o GD falhar) grava em /thumb/. Antes só existiam /large/ e /media/, ambos cópias do arquivo original — servir isso como miniatura numa galeria pesaria no 3G. Medido: 690KB → 388KB num PNG 600x1067 (JPEG reduz bem mais).
  • Galeria e lightbox (peca.php): itens montados em PHP (fotos ordenadas com a capa em primeiro + vídeos), tira de miniaturas com loading="lazy", e a galeria só aparece quando há mais de um item. O lightbox de imagem única virou lightbox de galeria: navegação circular, setas do teclado, Esc, arraste horizontal no celular, contador, legenda, e troca <img> por <iframe> quando o item é vídeo (com src limpo ao fechar, senão o áudio continua tocando). Sem biblioteca externa — o site já carrega jQuery 1.11 e não valia puxar mais um pacote de CDN.
  • config/Fotos.php: mesmo bug de coluna já corrigido no admin — lia fot_descricao, que não existe em tb_fotos (é fot_legenda). Por isso a legenda salva no gestor nunca chegava na galeria. getAllData() passou a devolver também capa, usado para ordenar.
  • Validado em produção com peça de teste descartável (1586): 3 itens renderizados (capa + foto com legenda + vídeo youtu.be/...?si=), thumbnail do YouTube em https respondendo 200, miniatura local gerada respondendo 200, zero warning de PHP no HTML. Dados de teste removidos em seguida e o hash de conteúdo voltou ao valor original, confirmando que não sobrou resíduo. Não validado: o upload pela tela do admin (área autenticada) e o toque real no celular.
  • Escopo: peca.php (peças com venda no site). O evento.php (eventos de produtor/bot) continua com imagem única — a correção do parser de vídeo vale para os dois, a galeria não.
  • Backups: config/functions.php.bkp_original_2026-08-02_14-52-32_galeria-video, config/Fotos.php.bkp_original_, peca.php.bkp_original__galeria-video, admin/modulos/pecas/action.php.bkp_original_*_galeria-multifoto.

[2026-08-02] Faxina em tb_fotos: 1332 linhas apontando para arquivo inexistente

  • O cron cleanup_old_pecas_images.php apaga o cartaz 7 dias após o fim da temporada, mas nunca removia a linha correspondente em tb_fotos. Resultado: 1332 de 1341 linhas do módulo pecas apontavam para arquivo que não existe mais em disco.
  • Critério da limpeza: linha sem nenhuma variação em disco (thumb/media/large/original) e peça já fora de temporada (1300 linhas) ou peça inexistente (32 linhas). Linha com arquivo em disco foi preservada (9). O diagnóstico apontou 0 casos de peça ainda em cartaz sem arquivo — ninguém em cartaz perdeu foto.
  • Backup completo em TSV antes do DELETE: /root/.rnt_security/tb_fotos_faxina_20260802_145016.tsv (modo 0600).
  • Detalhe que quase virou bug: o script rodou primeiro marcando 2026-08-03, porque o php.ini da VPS está com date.timezone=PRC (UTC+8). Sem date_default_timezone_set('America/Sao_Paulo') explícito, uma peça terminando hoje seria tratada como fora de temporada e perderia o registro da foto. Vale para qualquer script PHP CLI nesta VPS.

[2026-08-02] Cookie de versão de conteúdo na página de peça: informação corrigida chega ao visitante (BK-347)

  • Problema: ao corrigir uma informação errada (preço, horário, sinopse, endereço, cartaz) o visitante que já tinha aberto a página continuava lendo a versão antiga. O HTML já saía com no-store, mas isso só resolve quem passa pelo servidor — bfcache (botão voltar), aba adormecida no celular e webview do Instagram/Facebook mantêm o DOM antigo vivo, e o cartaz corrigido com o mesmo nome de arquivo vinha do cache do navegador.
  • Versão como fingerprint, não como bump manual (includes/rnt_conteudo_versao.php): rnt_peca_conteudo_versao() calcula um md5 curto sobre os dados que a página exibe — campos públicos de pecas, sessões (sec_data, sec_horario, valores), linha do teatros e as fotos (nome do arquivo + filemtime). Corrigiu no admin, a versão muda sozinha; não existe checkbox nem coluna para alguém esquecer de marcar. O filemtime cobre a troca de arte mantendo o mesmo nome de arquivo.
  • O que ficou de fora de propósito: sec_quantidade, sec_total e sec_mapa não entram no hash. São campos que mudam a cada venda — incluí-los faria a página de todo mundo se invalidar a cada ingresso vendido.
  • Cookie rnt_pv_<id> (180 dias, SameSite=Lax; Secure): guarda a última versão que aquele visitante viu. No render, peca.php compara e regrava. PHP 5.6 não aceita array de options em setcookie(), então o SameSite vai anexado ao path — mesmo hack já usado no cookie de sessão do BK-341.
  • Atualização forçada: o JS no rodapé de peca.php revalida a versão em peca_versao.php?id=<id> quando a página volta ao foco (pageshow com persisted, visibilitychange, focus), com throttle de 30s. Versão diferente ⇒ location.replace() para a mesma URL com ?rv=<versao> — URL inédita, que até webview teimoso busca do servidor. Trava anti-loop em sessionStorage (1 reload forçado por minuto) e o ?rv= é removido da barra de endereços com history.replaceState.
  • Decisão: não há checagem periódica com a página em primeiro plano. Recarregar só nos momentos de retorno evita derrubar quem está no meio da escolha de sessão/quantidade.
  • Cartaz versionado: ?v=<versao> nas URLs de large/ e media/ da capa em peca.php — cartaz corrigido aparece na hora, sem depender de refresh forçado do visitante.
  • Endpoint (peca_versao.php): aceita ?id= ou ?slug=, responde no-store. Erros saem com HTTP 200 e ok:false de propósito: com 4xx o servidor troca o corpo pelo ErrorDocument HTML e o JS não consegue ler o JSON.
  • Validado em produção: versão estável entre requisições (f92c5b5de0 em 3 chamadas seguidas); cookie defasado enviado à mão gerou RNT_CONTENT_VERSION ... atualizado=1 e regravação do cookie; alteração de campo na peça de teste (id 1343) mudou o hash e a reversão do dado devolveu o hash original; imagem com ?v= responde 200; ?rv= renderiza a página normalmente.
  • As duas rotas não são a mesma coisa: peca.php serve as peças com venda online no próprio site, cadastradas pelo admin (admin/modulos/pecas/index.php?status=ativas&origem=admin); evento.php serve os eventos cadastrados pelo próprio produtor/cliente ou pelo bot, sem venda interna, com compra de publicidade. Leem a mesma tabela pecas, mas exibem coisas diferentes — daí link_externo_ate ter entrado no fingerprint (no evento.php ele decide se o botão de compra externa aparece, e a publicidade expira por data), enquanto destaque_ e combo_ continuam fora: conferido no evento.php, nenhum deles é renderizado ali — mudam onde o evento aparece (home, vitrine), não a informação que o visitante lê.
  • Cobertura: as duas rotas públicas de página de peça/evento. O JS ficou em includes/inc_conteudo_versao_js.php, incluído no rodapé dos dois — sem duplicar o bloco. Em evento.php o ?v= entra só quando o cartaz é arquivo local (/arquivos/pecas/...); URL externa e logo padrão passam intactos.
  • OG/JSON-LD sem ?v= de propósito: og:image, twitter:image e o image do JSON-LD continuam com a URL limpa. São consumidos por crawler, que revisita por conta própria — versionar ali só geraria re-scrape a cada mudança de sessão.
  • Backups: peca.php.bkp_original_2026-08-02_13-20-50_cookie-versao-conteudo, evento.php.bkp_original_2026-08-02_13-34-18_cookie-versao-conteudo.

[2026-07-28] Fim do ruído de push para o admin: dedup, log auditável e alerta por transição (BK-346)

  • Diagnóstico: o dono recebia ~24 push/dia, quase todos do mesmo alerta. O push_dispatch.log não tinha timestamp nem título, então o histórico teve que ser reconstruído cruzando com admin/logs/debug.log.
  • Dedup no push de admin (includes/push_admin_helper.php): enviar_push_admin_from_whatsapp() é chamado no topo de enviar_whatsapp_admin(), antes do rate limit de 24h — alertas que no WhatsApp saíam 1x/dia iam de hora em hora no push. Agora há dedup próprio em admin/logs/push_admin_dedup.json (flock, fail-open, limpeza automática), janela padrão 86400s e 4º parâmetro $dedup_seconds em enviar_push_admin(). A chave normaliza data/hora antes do sha1: sem isso o dedup seria inútil, porque os alertas trazem Data: dd/mm/YYYY HH:ii:ss no corpo. O rate limit do WhatsApp escapa disso por acidente — usa md5($numero . substr($mensagem, -100)) e a data cai no meio da mensagem.
  • Falso positivo de 5 meses (admin/cron/monitor_story_roi.php): o check lia o caminho hardcoded /www/server/cron/ecef0b533e9b16166be606a34f900a72.log, congelado desde 05/03/2026 porque o cron do Facebook virou rnt_sync_facebook_planner/dispatcher. Ficou preso em error (force=true, throttle 900s) esse tempo todo. Agora usa lista de candidatos pelo mtime mais recente, em vez de caminho fixo.
  • Alerta por transição de estado: o monitor só notifica quando o status muda (piorou ou normalizou), com o último estado em admin/logs/monitor_story_roi_state.json. Estado estável = silêncio. O JSON completo continua indo pro log a cada run — nada deixa de ser observável, só para de ser empurrado. Sem isso, corrigir o caminho do log derrubaria de ~24 para ~1 push/dia, mas manteria um alerta diário sem nada acionável.
  • Dois checks que descreviam estado normal: roi_story_clicks_24h virou roi_story_clicks_7d e só alerta se houve story publicado no período (consulta social_post_queue); campaign_ads_story passou a alertar só na contaminação real (ads e orgânico dividindo o mesmo utm_campaign), não pela mera existência de campanha ADS ativa. Resultado: status geral saiu de error para ok, 9/9 checks OK.
  • Allowlist no tracking (action.php, case save_tracking): o nome do evento vinha do cliente e ia direto pro banco. Não era brecha de SQL (escapado; peca_id/valor com intval/floatval), mas scanners em 12/01 e 11/04/2026 gravaram 185 linhas com payloads e inflaram a tabela para 110 nomes de evento distintos contra ~9 reais. api/track-event.php já validava; action.php não. O DELETE das linhas de lixo ficou pendente (backup em /root/.rnt_security/cliente_tracking_lixo_20260729_074504.tsv).
  • Achado de fundo: o canal de story nunca decolou — 11 cliques em toda a série histórica, último em 10/04/2026, e o go.php está órfão (nada gera links pra ele). O gargalo real está em social_post_queue: 42 stories em pending_approval contra 3 publicados nos últimos 30 dias. Sem publicação não há clique para medir. Decisão pendente sobre reconectar ou aposentar o go.php.
  • Bug lateral: o php.ini da VPS está com date.timezone = PRC (UTC+8), 11h à frente do sistema. Os scripts do site escapam porque functions_new.php seta America/Sao_Paulo, mas o helper de push roda fora desse bootstrap — o log saiu com 07:31 do dia seguinte enquanto o sistema marcava 20:31. Corrigido com DateTime + timezone explícito, sem mexer no estado global do chamador. Vale para qualquer script PHP CLI nessa VPS.
  • action.php é monitorado pelo guardião de integridade do MP: --check acusou DRIFT, seguido de --seal e --check OK.
  • Backups: push_admin_helper.php.bkp_original_2026-07-28_20-28-27_dedup-log, monitor_story_roi.php.bkp_original_2026-07-28_20-28-27_fix-cronlog, monitor_story_roi.php.bkp_original__transicao-checks, action.php.bkp_original__allowlist-evento-tracking.

[2026-07-25] Auditoria de pagamentos: rate limit, cookie de sessão, headers e TLS (BK-341, itens 2/3/4/5/7)

  • Item 2 (rate limit no /admin/): o painel de maior privilégio estava menos protegido que o de produtores. Adicionado location = /admin/action.php com limit_req zone=login_rl burst=5 nodelay e limit_req_status 429, reusando a zona que já servia /produtor/. Validado: 6ª requisição consecutiva retorna 429 e admin/login.php continua 200. Lockout progressivo por usuário/IP na aplicação não foi implementado — segue como melhoria futura no backlog.
  • Item 3 (cookie de sessão): session.cookie_httponly=1 e session.cookie_secure=1 no .user.ini, com SameSite=Lax injetado via session.cookie_path="/; SameSite=Lax" (PHP 5.6 não tem session.cookie_samesite, que só existe desde 7.3). O valor precisa de aspas duplas: sem elas o parser de INI trata o ; como início de comentário e descarta o resto da linha — foi o que quebrou a primeira tentativa. Também foi necessário systemctl restart php-fpm-56, porque só o reload mantinha o .user.ini antigo em cache nos workers.

> Correção medida em 24/08/2026 (BK-348): a parte do restart acima não se confirmou. Ao instalar o logger de runtime foram feitas três alterações no mesmo .user.ini (acrescentar open_basedir, acrescentar auto_prepend_file, e o rollback removendo tudo), cada uma seguida só de /etc/init.d/php-fpm-56 reload — as três valeram em menos de 3 s, confirmadas por requisição HTTP real, não por suposição. O reload do FPM recicla os workers, e worker novo relê o .user.ini. O que de fato engana é editar e testar sem recarregar nada: aí o user_ini.cache_ttl (300 s, default) mantém o valor antigo por até 5 minutos. A hipótese mais provável para o episódio de 25/07 é que o problema real era o ; sem aspas, e o restart levou o crédito por ter vindo junto com a correção da aspas. A lição que continua valendo é a outra: validar sempre no HTTP real. Validado: Set-Cookie da home passou a vir path=/; SameSite=Lax; secure; HttpOnly.

  • Item 4 (headers de segurança): X-Frame-Options: SAMEORIGIN, X-Content-Type-Options: nosniff e Referrer-Policy: same-origin via add_header no vhost, somados ao Strict-Transport-Security que já existia. Validado na resposta da home, sem regressão.
  • Item 5 (TLS 1.1): ssl_protocols passou de TLSv1.1 TLSv1.2 TLSv1.3 para TLSv1.2 TLSv1.3 — TLS 1.1 está abaixo do mínimo do PCI-DSS e o site processa cartão via Mercado Pago. Validado: handshake forçando --tls-max 1.1 falha; --tlsv1.2 continua 200.
  • Item 7 (rate limit do webhook MP): nova zona limit_req_zone ... zone=mp_webhook_rl:10m rate=120r/m no nginx.conf e location = /webhook_mp_ingressos.php com burst=20 nodelay. Limite mais generoso que login_rl de propósito, porque em pico de venda o Mercado Pago manda várias notificações legítimas em sequência. Validado: webhook continua 200 em requisição isolada e o guard de integridade do MP não acusou drift.
  • Itens ainda abertos: item 6 (PHP 5.6 EOL) e item 8 (.env dentro do webroot), ambos por decisão consciente de escopo — motivo detalhado em docs/backlog/BK-341-auditoria-seguranca-pagamentos-mp.md e resumido no docs/BACKLOG.md.
  • Backups anteriores a cada mudança: rionoteatro.com.br.conf.bkp_original_20260725-0944_BK-341-itens-2-4-5-7, nginx.conf.bkp_original_20260725-0944_BK-341-item7-zone e .user.ini.bkp_original_20260725-0944_BK-341-item3-cookie-flags.

[2026-07-25] Lembrete automático de pedido pendente — redação/formatação final e regra de valor promocional (BK-343)

  • Texto do lembrete reescrito a pedido do dono: Oi <NOME>! Notamos que você não concluiu a compra para <PEÇA> _(dd/mm às HH:MM)_ de ~R$<CHEIO>~ por somente R$<PAGO> !!, com linha em branco entre os três parágrafos e o link isolado no último (Para facilitar segue link 👉 <URL>). Peça em negrito, data/hora em itálico, valor cheio riscado; preço redondo sai sem centavos (R$33, não R$33,00).
  • Nova regra: se a sessão não tiver valor promocional válido (tab_sessoes.sec_valor_de ausente ou menor/igual ao valor pago), o lembrete não é enviado — isso é sintoma de cadastro errado da sessão, e o log registra PULADO ... corrigir cadastro da sessao. O pedido não é gravado em bot_lembrete_pagamento_log, então volta a ser elegível assim que a sessão for corrigida.
  • Montagem da mensagem extraída para montar_mensagem_lembrete() (retorna null no caso acima), usada pelo envio real e pelos modos de teste — sem texto duplicado.
  • Novo modo --preview: imprime no terminal a mensagem já montada com o pedido pendente mais recente do banco, sem enviar nada. Necessário porque o anti-spam do whatsapp_helper.php deduplica por md5(telefone + últimos 100 caracteres) em janela de 24h, ou seja, ajustar só o começo do texto não libera reenvio de teste para o mesmo número.
  • --test-send sem candidato elegível agora renderiza a mensagem com dados reais do pedido pendente mais recente em vez de um texto genérico (envio continua indo só para o número fixo do gestor, sem gravar dedup).

[2026-07-24] Correção: código-fonte de checkout/pagamento baixável via backups .bak/.bkp (BK-341)

  • Auditoria de segurança confirmou que a regex de bloqueio do Nginx (\.(log|sql|bak|backup|old|ini|sh)$) só cobria nomes terminados exatamente em .bak, deixando de fora o padrão real de backup do projeto (.bkp_original_*, .bak_YYYYMMDD, .bak2, etc.).
  • Mais de 150 arquivos, incluindo checkout_pix.php.bkp_, efetua-pagamento.php.bak_, webhook_mp_ingressos.php.bkp_ (webhook do Mercado Pago) e action.php.bkp_ (login do admin), estavam baixáveis publicamente como texto puro.
  • Adicionada nova location no vhost Nginx (/www/server/panel/vhost/nginx/rionoteatro.com.br.conf) cobrindo .bak/.bkp em qualquer posição do nome. Backups não foram movidos (ficaram no lugar, preservando o workflow de versionamento local).
  • Backup do .conf antes da mudança: rionoteatro.com.br.conf.bkp_original_20260724-1749_BK-341-fix-bak-exposure.
  • Validado: arquivos antes com HTTP 200 agora retornam 404; home e assets reais continuam 200 sem regressão (inclusive dentro de /docs/, que tinha o mesmo problema).
  • Detalhe completo e demais itens (ainda pendentes) em docs/backlog/BK-341-auditoria-seguranca-pagamentos-mp.md.

[2026-07-11] Notificações Web Push: opt-in no site e disparo por peça no admin (BK-345)

  • Canal de notificação próprio, independente de WhatsApp: banner de opt-in em todas as páginas (injetado por includes/inc_peca.php), service worker em sw.js, manifest PWA e ícones dedicados.
  • push_action.php grava/atualiza/remove inscrição na tabela nova rnt_push_subscriptions usando mysqli preparado. Dá pra se inscrever sem login; se o visitante logar depois, o vínculo com clientes é preservado. Opt-out disponível no perfil do cliente, logo acima de Zonas de Interesse.
  • admin/modulos/pecas/push.php (botão de sino na listagem de peças) dispara por peça com prefill de título/texto/link, estimativa de alcance, segmentação por zona e três variantes de botão para teste A/B/C — marcadas com utm_term=plano_a|b|c para comparar cliques no Analytics.
  • Precedência de alcance, igual no admin e no sender: zonas da própria inscrição > clientes.zonas_interesse do perfil > sem preferência recebe tudo.
  • O envio real roda num sender PHP 8.3 fora do webroot (/www/rnt-push-sender/send.php, com vendor/ próprio e VAPID em vapid.json com permissão 640), chamado por shell_exec com todos os argumentos passando por escapeshellarg(). Motivo: o site é PHP 5.6, incompatível com a lib de Web Push. A chave pública VAPID fica no JS (é pública por design); a privada nunca entra no repositório.
  • Inscrição que responde 404/410 é marcada ativo=0 automaticamente, então a base não acumula endpoint morto. Log de cada disparo em admin/logs/push_dispatch.log.
  • Documentado retroativamente em 25/07/2026 em docs/backlog/BK-345-web-push-notificacoes.md, com as pendências (disparo automático e relatório de entrega/clique).

[2026-07-03] Hotfix: mensagem e falha de WhatsApp na aprovação de produtor

  • Corrigida a mensagem WhatsApp enviada quando o cadastro de produtor passa para ativo.
  • Texto agora usa acentuação correta, produtor(a), Você já, espetáculos e estamos à disposição.
  • Admin passa a avisar quando o cadastro foi salvo, mas a confirmação por WhatsApp não foi entregue.
  • Corrigido o lock anti-spam do helper para liberar nova tentativa quando a API retorna erro permanente, como Numero sem conta WhatsApp ativa (No LID).
  • Evidência do produtor #792: cadastro aprovado em 2026-07-03 16:33:16, envio chamado, resposta WhatsApp HTTP 422 / No LID; checagem posterior /check-number retornou exists=true.
  • Validações: php -l admin/modulos/produtores/action.php, php -l includes/whatsapp_helper.php.
  • Changelog detalhado: docs/changelog/2026/CL-2026-07-03-hotfix-mensagem-aprovacao-produtor.md.

[2026-06-16] Helper WhatsApp compartilhado com Amor Eterno

  • bot/whatsapp/server.js passou a reconhecer mensagens com tag #AE como tráfego do projeto Amor Eterno.
  • Mensagens recebidas com #AE são roteadas para o webhook local do Amor Eterno e deixam de cair no webhook/histórico normal do Rio no Teatro.
  • Envios feitos via API com project=AE são marcados para não poluir o histórico do Rio no Teatro.
  • Adicionado suporte a envio de mídia por media_path restrito a /opt/amoreterno/media.
  • Service rnt-whatsapp reiniciado e validado conectado em 127.0.0.1:3033.

[2026-05-20] Teste real WhatsApp: Enfermeira Zilda no Miguel Falabella

  • Criado shortlink https://rionoteatro.com.br/4pie para Enfermeira Zilda com UTM da campanha de teste admin.
  • Importada campanha de teste #10 (enfermeira_zilda_mf_20260520_test_admin) com apenas o numero admin 5521999915554, janela 00:00-17:30 por ser disparo no dia da sessao das 19:30.
  • Enviado teste real pela fila profissional WhatsApp (queue_id=9741): sent=1, failed=0, blocked=0; kill switch global religado apos o envio.
  • Ajustado segundo teste admin (#11, queue_id=9742) com "unica apresentacao da comedia", CTA em frase unica ate 18:30 e rodape PT-BR com opt-out acentuado + atualizacao de zonas de interesse; envio real: sent=1, failed=0, blocked=0.
  • Confirmada viabilidade de base: 1256 compradores historicos do Teatro Miguel Falabella com celular 5521* e 3674 linhas de fila da base Zona Norte sem status sent.

[2026-05-20] Hotfix: lista digital do produtor abre token gerado pelo disparo diario

  • Corrigido config/listaopen_token_helper.php para gerar tokens da lista digital com segredo estavel baseado nas variaveis DB_* do ambiente.
  • Mantida compatibilidade de leitura com tokens legados gerados antes da correcao, incluindo o link ja enviado para Enfermeira Zilda - 20/05/2026 19:30:00.
  • Causa confirmada: z_dispara_diario.php gerava o token sem constantes DB_ definidas, enquanto /produtor/listaopen.php validava com constantes DB_, fazendo o token resolver para 0 no produtor e mostrar "Sessão não encontrada.".
  • Validacoes: php -l config/listaopen_token_helper.php, resolucao do token antigo em contexto raiz e /produtor, resolucao de token novo em /produtor e curl publico sem "Sessão não encontrada.".

[2026-04-29] BK-280: documentação da política de shortlinks

  • Criado docs/SHORTLINKS.md como referência local da política de retenção.
  • Adicionado comentário em config/shortlink_helper.php apontando para essa documentação.
  • Reforçado que campanhas usam expires_at=NULL por padrão e que expiração é apenas opt-in/manual.
  • Changelog detalhado: docs/changelog/2026/CL-2026-04-29-BK-280-doc-politica-shortlinks.md.

[2026-04-29] BK-279: roadmap de shortlink automático em Campanhas

  • Documentado roadmap para implementar shortlink automático no fluxo admin/modulos/campanhas/incluir.php / action.php.
  • Definida política de retenção: shortlinks de campanha ficam permanentes por padrão; expires_at passa a ser recurso opcional, não expiração automática.
  • Registrada estratégia de execução para amanhã com revisões externas menores e paralelas por pacote.
  • Atualizados os links atuais 1a2b e 9t66 para expires_at=NULL, mantendo histórico e redirect ativos por tempo indeterminado.
  • Smoke de revisão externa pequena: Gemini CLI encerrou por timeout em 90s; OpenCode falhou por tools[*].eager_input_streaming.
  • Backlog vivo: docs/backlog/BK-279-shortlink-automatico-campanhas.md.

[2026-04-29] BK-278: shortlinks rastreáveis para WhatsApp

  • Criado short.php e config/shortlink_helper.php para gerar, resolver e rastrear links curtos de campanha.
  • Adicionada regra de shortlink no .htaccess e no rewrite efetivo do Nginx da VPS.
  • Gerados os links https://rionoteatro.com.br/1a2b e https://rionoteatro.com.br/9t66 preservando UTMs completas.
  • Regenerados os CSVs da campanha com copy em português BR, acentuação correta e link curto.
  • Canceladas as filas antigas importadas antes da copy final e importadas as filas v2 #3 e #4, ainda bloqueadas por global_kill_switch=true, dry_run_only=1 e sem aprovação.
  • Validações: php -l, nginx -t, reload do Nginx, curl -L dos shortlinks, dedupe dos CSVs e processamento bloqueado por kill switch.
  • Backlog vivo: docs/backlog/BK-278-shortlink-whatsapp-campanha.md.
  • Changelog detalhado: docs/changelog/2026/CL-2026-04-29-BK-278-shortlink-whatsapp-campanha.md.

[2026-04-29] BK-277: dry-run WhatsApp Zona Norte com allowlist

  • Criado admin/cron/whatsapp_campaign_dry_run_zona_norte.php para gerar dry-run da campanha de As Loucas do Meier sem disparo.
  • A campanha segmenta compradores historicos de Teatro Miguel Falabella e Imperator, nao todos os teatros da Zona Norte; por padrao a query de destinatarios exige allowlist fixa 552199915551.
  • Adicionados guardrails:
  • script CLI-only;
  • flags --send, --execute, --dispatch e --enviar abortam;
  • nenhum helper/fila/webhook de WhatsApp e chamado;
  • relatorios com destinatarios ficam em admin/runtime/campaigns/, ignorado pelo Git.
  • Apos autorizacao do admin, o script ganhou --full-csv para gerar CSV completo sem allowlist, ainda sem qualquer rota de envio.
  • O CSV final passou a deduplicar por WhatsApp normalizado, preservando o cadastro com compra historica mais recente.
  • A mensagem no CSV passou a usar \n literal para manter uma linha por destinatario.
  • Adicionado --expanded-csv para gerar CSV posterior de Zona Norte + Baixada, excluindo por WhatsApp normalizado quem ja entrou na base Miguel Falabella + Imperator.
  • O CSV extra foi validado com 2503 WhatsApps unicos, 0 duplicados internos e 0 sobreposicao com a base dos 2 locais.
  • Instalada fila profissional WhatsApp com kill switch global, dry-run por padrao, aprovacao nominal, ciclos randomicos de envio, footer obrigatorio de opt-out e opt-out inbound automatico.
  • Auditoria final do commit 8d70565f5 confirmou global_kill_switch=true, campanhas v2 #3/#4 sem aprovacao real, sent_count=0, process --execute bloqueado por kill switch e smokes de footer/opt-out sem envio.
  • Por autorizacao humana posterior, a campanha #3 foi aprovada para envio real e o global_kill_switch foi desligado, sem rodar process --execute; a campanha #4 segue bloqueada.
  • Disparo real monitorado da campanha #3 enviou 15 mensagens, bloqueou 4 numeros sem WhatsApp ativo, pausou automaticamente por auto_failure_rate_21 e teve o global_kill_switch religado.
  • Apos identificar telefones fora do DDD 21 no disparo, o fluxo foi corrigido para aceitar somente 5521 + 9 digitos, preferindo clientes.celular e usando clientes.telefone apenas como fallback valido; a fila tambem passou a prechecar /check-number antes de enviar.
  • Backlog vivo: docs/backlog/BK-277-whatsapp-zona-norte-dry-run.md.
  • Changelog detalhado: docs/changelog/2026/CL-2026-04-29-BK-277-whatsapp-zona-norte-dry-run.md.

[2026-04-28] BK-276: lock operacional local fora do GitHub

  • docs/LOCK.md deixou de carregar estado vivo de locks e passou a ser apenas protocolo/template versionado.
  • O estado operacional diario passa a ficar em docs/LOCK.local.md, ignorado pelo Git.
  • Criado docs/LOCK.local.example.md como modelo versionado.
  • Atualizados AGENTS.md, docs/AGENTS.md e docs/GIT_WORKFLOW.md para apontar o fluxo diario ao lock local.
  • Changelog detalhado: docs/changelog/2026/CL-2026-04-28-BK-276-lock-local-fora-github.md.

[2026-04-28] BK-275: hotfix 502 no Gerar com IA do Texto para Audio

  • Corrigido admin/modulos/campanhas/redes_sociais/texto_for_audio.php para o botao Gerar com IA nao aguardar provedores de IA dentro da requisicao web.
  • Causa confirmada: o POST sincrono podia ficar preso em chamadas Gemini/Kimi/bridge por 75-120s e estourar o gateway.
  • O botao principal e o Refazer com sugestao agora criam um job pendente em social_audio_jobs, redirecionam o admin e processam a IA em background.
  • O fluxo Gerar TUDO (background) passou a reaproveitar o mesmo executor de job pendente.
  • Apos teste real do job #13, o processamento passou a marcar processando antes de chamar IA e o timeout do bridge Gemini/Kimi foi reduzido para cair em fallback mais rapido quando o provedor travar.
  • A tela do job ativo passou a mostrar alerta de status e auto-refresh enquanto o job estiver pendente ou processando.
  • Apos o job #14, erros terminais do Gemini direto, como quota/billing/rate-limit, deixam de tentar o bridge e caem em fallback mais rapido com aviso mais fiel.
  • Por decisao operacional posterior, o provider gemini do Texto para Audio deixou de usar chave/API Google e passou a chamar sempre o Gemini CLI logado na VPS.
  • Validacao CLI: chamada PHP isolada retornou provider=gemini, model=gemini_cli_default e 3 versoes geradas.
  • Validacao: php -l admin/modulos/campanhas/redes_sociais/texto_for_audio.php.
  • Changelog detalhado: docs/changelog/2026/CL-2026-04-28-BK-275-hotfix-audio-copy-timeout-502.md.

[2026-04-27] Hotfix: feed pecas.xml inclui sessoes no ultimo dia da temporada

  • Corrigido atualizar_feed.php para comparar DATE(sec_data) com fim_temporada ao montar os feeds Meta/Google.
  • Causa confirmada: tab_sessoes.sec_data e datetime, enquanto pecas.fim_temporada e date; sessoes as 20h no proprio ultimo dia eram removidas do XML.
  • Impacto confirmado: pecas.php listava 3 pecas ativas, mas pecas.xml publicava apenas Fanáticos; As Loucas do Méier e É Tudo Culpa Da Mãe ficaram fora por terem sessao unica no dia final da temporada.
  • Feeds regenerados no servidor: pecas.xml, pecas_inteira.xml, ofertas.csv e offer.csv.
  • Validacoes: php -l atualizar_feed.php; consulta SQL comparativa retornando 3 pecas; php atualizar_feed.php com itens=3; leitura local e via URL publica de https://rionoteatro.com.br/pecas.xml confirmando IDs 1509, 1493 e 1536.
  • Changelog detalhado: docs/changelog/2026/CL-2026-04-27-hotfix-feed-pecas-ultimo-dia.md.

[2026-04-27] Hotfix crítico: valor unitário no Mercado Pago

  • Corrigido o fluxo de rascunho do pedido para gravar pedidos.valor como valor unitário, não como total do pedido.
  • Blindado o cálculo enviado ao Mercado Pago para detectar rascunhos antigos com total salvo em valor e cobrar somente valor_unitario * qtd.
  • Ajustada a tela de checkout para exibir e inicializar PIX/cartão com o total protegido.
  • Corrigidos rascunhos pendentes contaminados para evitar retomada de checkout com cobrança duplicada.
  • Auditoria de impacto confirmou cobrança a maior nos pedidos #73731 e #73767; os estornos da diferença foram executados manualmente pelo admin.
  • Validações: php -l em config/functions_new.php, pedido.php, pedido_pagseguro.php, action.php e checkout_pix.php; git diff --check; simulação local de cálculo pós-hotfix para #73731 e #73767.
  • Changelog detalhado: docs/changelog/2026/CL-2026-04-27-hotfix-mercadopago-valor-unitario.md.

[2026-04-13] BK-271: restauração de agenda e carrossel compacto unificado

  • eventos.php
  • Restaurada a versão funcional da agenda (bkp_original_20260413-0354).
  • Adicionado botão 'Voltar ao topo' com ícone oficial.
  • Sincronizado o visual compacto do carrossel (Nome | Local | Classificação em uma linha).
  • Sincronizado o comportamento de scroll direcional (some ao descer, aparece ao subir significativo).
  • evento.php
  • Alterada a frase do badge de venda externa para "VENDA EXTERNA | SEM PROMOÇÃO RIONOTEATRO".
  • Unificado o visual compacto do carrossel e removido o botão grande de oferta.
  • Implementada a lógica de scroll direcional, removendo a trava de visibilidade anterior.
  • Estabilizado o offset fixo em 60px para ambos os arquivos.

[2026-04-12] BK-267: memória persistente de preço captado x corrigido

  • admin/modulos/eventos/action.php passou a registrar overrides de valor do BOT em bot_valor_overrides quando o admin corrige um preço captado errado.
  • o registro guarda, no mínimo:
  • sugestão
  • evento
  • sessão/data
  • valor captado
  • valor corrigido
  • bot/handlers/event_matcher.php passou a consultar essa memória antes de contar valor_alterado.
  • resultado prático:
  • se o BOT capturar novamente o mesmo valor errado já corrigido para aquela sessão, isso não volta como Alteração Detectada.

[2026-04-12] BK-267: hardening de unificação e nome editado do captado

  • admin/modulos/eventos/action.php
  • Unificar passou a forçar o evento destino para status = 'A'
  • o merge agora também propaga para o destino o nome/sinopse/ficha/local editados pelo admin na sugestão captada
  • o slug canônico do destino continua preservado

[2026-04-12] BK-267: conciliação preserva produtor/origem

  • admin/modulos/eventos/action.php
  • conciliar_vincular, conciliar_unificar, conciliar_aplicar_update e conciliar_reativar passaram a preservar origem e id_produtor atuais do evento destino
  • admin/modulos/eventos/conciliacao.php
  • a UI deixou de oferecer opções de migrar/assumir produtor nesta tela
  • a tela passou a deixar explícito que a conciliação só atualiza conteúdo

[2026-03-25] Auditoria da pasta órfã rionoteatro (BK-161)

Fechado como: DONE (25/03/2026) · migrado do bloco CONCLUIDOS (historico) do BACKLOG.md em 10/09/2026, onde era a unica copia.

  • A coexistência de /www/wwwroot/rionoteatro e /www/wwwroot/rionoteatro.com.br foi verificada.
  • A pasta curta estava vazia e foi removida com segurança após confirmação humana.
  • O diretório ativo do projeto segue somente em /www/wwwroot/rionoteatro.com.br.

[2026-03-11] Branch vps/BK-109-story-video-priority - story com video por peca

Fechado como: DONE (11/03/2026) · migrado do bloco CONCLUIDOS (historico) do BACKLOG.md em 10/09/2026, onde era a unica copia.

  • Diagnostico confirmado na social_post_queue: falhas de facebook_feed (Missing or invalid image file) e instagram_feed (Only photo or video can be accepted as media type.) em 11/03/2026.
  • Cron social ajustado para normalizar a capa da peca em JPG compativel com Meta antes do envio para feed.
  • Stories passaram a priorizar somente video em /arquivos/pecas/{id}/video; sem video, o fallback agora vai direto para a capa da peca.
  • Gestor de pecas ganhou aba Storie para upload/listagem/remoção de assets em /arquivos/pecas/{id}/video.
  • Agenda social ganhou atalho para abrir o gestor da peca direto na aba de story.
  • Pos-ajuste operacional em 11/03/2026: upload de .mp4 na aba Storie estabilizado com XMLHttpRequest + FormData(form), barra de progresso e diagnostico ampliado no debug.log.
  • Causa raiz confirmada em producao: admin/.user.ini limitava post_max_size a 16M, fazendo uploads grandes chegarem ao PHP com multipart/form-data mas $_POST e $_FILES vazios.
  • Evidencia real do fix: upload da peca 1342 salvo com sucesso em /arquivos/pecas/1342/video/lisa-lesa-e-louca-story.mp4 em 11/03/2026 11:41.
  • Observacao de governanca: o numero BK-109 ja existia no backlog/changelog historico para outra entrega (WhatsApp); por isso este fechamento foi registrado pelo nome da branch operacional, sem reutilizar o ID numerado no backlog.

[2026-03-06] Expandir debug_log para APIs, formularios, painel, crons e uploads (BK-89)

Fechado como: DONE (06/03/2026) · migrado do bloco CONCLUIDOS (historico) do BACKLOG.md em 10/09/2026, onde era a unica copia.

  • Cobertura de tratamento de erros aplicada em módulos admin/painel, APIs, crons e uploads.

[2026-03-06] Endpoint de IA para Copy (integrado ao Video IA) (BK-93)

Fechado como: DONE (06/03/2026) · migrado do bloco CONCLUIDOS (historico) do BACKLOG.md em 10/09/2026, onde era a unica copia.

  • Geração de legendas e roteiros automatizada via Gemini integrada ao módulo de Redes Sociais.

[2026-03-06] Sistema de Notificacoes por e-mail (BK-88)

Fechado como: DONE (06/03/2026) · migrado do bloco CONCLUIDOS (historico) do BACKLOG.md em 10/09/2026, onde era a unica copia.

  • Descontinuado em favor do WhatsApp (prioridade definida pelo usuario).

[2026-03-06] Executar bateria de testes de integracao ponta a ponta (BK-90)

Fechado como: DONE (06/03/2026) · migrado do bloco CONCLUIDOS (historico) do BACKLOG.md em 10/09/2026, onde era a unica copia.

  • Adicionado smoke test em tests/bk90-smoke-integration.sh e validado retorno de 15/15 checks em produção.
  • Evidência registrada em docs/BK-90-RELATORIO-TESTES-INTEGRACAO.md.

BK-246 · Clarificação da janela real de lock em docs/LOCK.md

Data: 9 de Abril de 2026

Contexto operacional:

  • a regra local de lock ainda deixava margem para manter o proprio docs/LOCK.md em LOCKED mesmo sem escrita ativa no arquivo.
  • isso criava atrito desnecessario porque o quadro canonico precisava continuar disponivel para outros programadores registrarem seus arquivos.
  • o alinhamento humano da rodada foi explicitar que espera por teste, autorizacao ou qualquer validacao externa nao justifica bloquear o quadro em si.

Ajustes aplicados:

  • AGENTS.md
  • a seção de lock por arquivo passou a declarar que docs/LOCK.md so fica LOCKED durante a janela real de edição do proprio arquivo;
  • o fluxo obrigatorio ganhou a mesma regra nos passos de fechamento.
  • docs/LOCK.md
  • a seção Como usar passou a declarar expressamente a exceção operacional do proprio quadro;
  • a seção Estados passou a registrar que o caso especial de docs/LOCK.md exige lock curto e funcional, não prolongado.

Validações:

  • conferência textual dos trechos alterados em AGENTS.md
  • conferência textual dos trechos alterados em docs/LOCK.md
  • aplicação imediata da regra nas linhas abertas do BK-245

Referências:

BK-245 · Hotfix de ultimo dia em peca.php

Data: 9 de Abril de 2026

Contexto operacional:

  • a página pública de venda em peca.php considerava a peça encerrada logo após 00:00 do próprio fim_temporada quando o campo vinha salvo apenas como YYYY-MM-DD.
  • isso fazia a vitrine cair cedo no layout enxuto de fora de cartaz mesmo durante o último dia válido da temporada.
  • a validação funcional humana da rodada confirmou que esse era o comportamento observado em produção.

Ajustes aplicados:

  • peca.php
  • a regra de em_cartaz deixou de comparar strtotime($dtFim) diretamente com time();
  • o arquivo passou a calcular dtFimTimestamp de forma defensiva:
  • se fim_temporada vier como YYYY-MM-DD, o fim passa a ser interpretado como 23:59:59 desse dia;
  • se no futuro o campo vier com horário real, o horário existente continua valendo.

Validações:

  • php -l peca.php
  • revisão do diff final isolado
  • validação funcional humana no ar com confirmação de funcionamento

Referências:

BK-244 · Hotfix de performance no painel de pedidos

Data: 9 de Abril de 2026

Contexto operacional:

  • a tela painel/modulos/pedidos/index.php estava lenta no fluxo autenticado do cliente.
  • a análise confirmou N+1 queries em pecas e tab_sessoes, além de leitura repetida de disponibilidade dentro da renderização.
  • na mesma rodada, após o hotfix principal, o botão Carregar mais e os botões renderizados dinamicamente precisaram ser ajustados para voltar a funcionar e respeitar o tema dark atual.

Ajustes aplicados:

  • painel/modulos/pedidos/index.php
  • criados helpers locais read-only para validade e disponibilidade com cache por request;
  • a listagem passou a pré-carregar peças, sessões, última sessão por evento, sessão atual por id_evento + data, totais comprados e configs mínimas antes do loop principal;
  • removidas consultas repetidas a pecas e tab_sessoes dentro da renderização da listagem;
  • o payload JS passou a usar status_label limpo para os cards carregados dinamicamente;
  • o botão Carregar mais foi corrigido e o container da seção Todos foi fechado corretamente;
  • os botões dinâmicos receberam ajuste visual local para respeitar o tema dark da tela atual.

Validações:

  • php -l painel/modulos/pedidos/index.php
  • revisão do diff final do arquivo
  • validação funcional humana na própria sessão:
  • abertura da tela mais rápida
  • Carregar mais funcionando
  • botões/cores coerentes com o tema dark

Referências:

BK-243 · Ajuste local da regra DONE vs UNLOCKED no LOCK

Data: 9 de Abril de 2026

Contexto operacional:

  • a regra local ainda deixava margem para encerrar a linha criada pelo próprio BK como UNLOCKED.
  • o alinhamento humano da rodada foi deixar DONE como padrão para a linha criada pelo BK atual e usar UNLOCKED apenas quando uma linha reaproveitada estiver sendo devolvida ao estado livre.

Ajustes aplicados:

  • docs/LOCK.md
  • a semântica de DONE e UNLOCKED foi esclarecida;
  • a limpeza automática/manual de linhas DONE passou de 5 para 7 dias sem uso;
  • as linhas abertas pelo BK-242 foram convertidas de UNLOCKED para DONE.
  • AGENTS.md
  • o protocolo do projeto foi alinhado para repetir a mesma regra prática em texto canônico.

Validações:

  • conferência textual das regras em AGENTS.md e docs/LOCK.md
  • conversão das linhas do BK-242 para o estado final correto da trilha

Referências:

BK-242 · Inativação automática de peças vencidas no admin

Data: 9 de Abril de 2026

Contexto operacional:

  • a listagem admin/modulos/pecas/index.php tratava a aba Ativas apenas por status = 'A'.
  • por isso, peças com fim_temporada já encerrado continuavam aparecendo no admin quando o status não era limpo a tempo.
  • o caso real da rodada foi a peça Bee Gees, Abba e Carpenters - Nos embalos de SÁBADO a noite (id=333), que ainda estava ativa mesmo tendo encerrado em 25/03/2026.

Ajustes aplicados:

  • admin/cron/expired_pecas_status_helper.php
  • criado helper compartilhado para localizar e inativar peças com status = 'A' e fim_temporada < CURDATE();
  • adicionada guarda por lock e throttle curto para evitar reexecução excessiva.
  • admin/modulos/pecas/index.php
  • a rotina de saneamento passou a rodar antes da montagem da listagem do admin.
  • admin/cron/sync_facebook_events.php
  • a mesma rotina passou a rodar antes da sincronização com Facebook, com log quando houver atualização.

Validações:

  • php -l admin/cron/expired_pecas_status_helper.php
  • php -l admin/modulos/pecas/index.php
  • php -l admin/cron/sync_facebook_events.php
  • execução real da rotina no banco com 79 peças inativadas
  • conferência posterior retornando 0 peças com status='A' e fim_temporada < CURDATE()

Referências:

BK-241 · Hotfix da Central de Cancelamento para peças online

Data: 9 de Abril de 2026

Contexto operacional:

  • a Central de Cancelamento de Sessão em admin/modulos/pecas/index.php havia voltado a divergir da vitrine pública.
  • o select Espetáculo estava listando todos os eventos ativos, em vez de apenas as peças elegíveis de pecas.php.
  • o select Sessão ainda incluía sessões antigas.
  • a rodada também exigiu refinamento iterativo dos textos padrão enviados por WhatsApp.

Ajustes aplicados:

  • admin/modulos/pecas/session_cancel_console_helper.php
  • a elegibilidade pública foi centralizada em session_cancel_console_public_catalog_where();
  • o select Espetáculo passou a espelhar a mesma regra comercial de pecas.php;
  • a validação do event_id no backend passou a usar a mesma regra;
  • o select Sessão passou a listar apenas sessões de hoje em diante;
  • o envio passou a substituir {primeiro nome} pelo primeiro nome real do destinatário;
  • as mensagens padrão do console foram alinhadas à copy final validada na rodada.
  • admin/modulos/pecas/index.php
  • comentário explícito deixado ao lado do select Espetáculo para evitar nova regressão por troca da fonte.
  • docs/modules/pecas/KNOWN_TRAPS.md
  • criado para registrar traps datados do módulo;
  • incluída a regra de sempre comparar arquivo atual + bkp_original mais novo + backup antigo útil antes de reaproveitar trecho antigo.

Validações:

  • php -l admin/modulos/pecas/session_cancel_console_helper.php
  • php -l admin/modulos/pecas/index.php
  • validação funcional incremental no admin com feedback humano durante a rodada

Referências:

BK-240 · Upload rápido do admin com suporte a imagens

Data: 8 de Abril de 2026

Contexto operacional:

  • a área admin/index.php de Upload Rápido de Arquivo aceitava apenas planilhas, documentos e formatos estruturados.
  • a operação precisava subir também imagens jpg, jpeg e png sem quebrar a whitelist já existente.

Ajustes aplicados:

  • admin/index.php
  • o input de upload passou a aceitar jpg, jpeg e png;
  • o texto de ajuda foi atualizado para refletir a nova whitelist.
  • admin/upload_dashboard_arquivo.php
  • a whitelist do backend passou a aceitar jpg, jpeg e png;
  • a mensagem de erro foi alinhada à nova lista de formatos.
  • imgs/20260408-224728-b1904ad8-logomarca-atelie-vo-neinha.png
  • a imagem enviada no upload real da rodada foi movida de /arquivos/download/ para um caminho estável em imgs/.

Validações:

  • php -l admin/index.php
  • php -l admin/upload_dashboard_arquivo.php
  • validação funcional com upload real de imagem no admin

Referências:

BK-239 · Unificacao documental de docs com AGENTS como entrada principal

Data: 8 de Abril de 2026

Contexto operacional:

Ajustes aplicados:

  • docs/AGENTS.md
  • consolidado como ponto de entrada principal de /docs;
  • passou a concentrar onboarding, fontes de verdade, mapa rapido e regras de manutencao da camada documental.
  • docs/README.md
  • reduzido a stub minimo de redirecionamento para docs/AGENTS.md e docs/00_SITEMAP_DOCUMENTACAO.md.
  • docs/00_SITEMAP_DOCUMENTACAO.md
  • reescrito para descrever apenas o estado real atual de docs/;
  • deixou de tratar como referencia atual arquivos removidos, ausentes ou historicos.

Validações:

  • conferência local de existencia dos links principais citados no novo AGENTS e no novo SITEMAP
  • revisão da direção proposta com segunda opinião externa trazida via OpenCode pelo usuário, filtrando os itens realmente stale dos falsos positivos

Referências:

BK-236 · Fechamento do produtor sem seletor de período e sem RG

Data: 8 de Abril de 2026

Contexto operacional:

  • a tela produtor/fechamento.php ainda permitia ao produtor trocar manualmente o período do relatório e exibia o RG dos compradores na planilha.
  • na mesma rodada, a ação de WhatsApp precisava ser simplificada para um texto curto de aprovação do fechamento, em vez de abrir uma mensagem longa com todo o resumo novamente.

Ajustes aplicados:

  • produtor/fechamento.php
  • o seletor de período foi removido e a página passou a exibir apenas o mês vindo pela query m=YYYY-MM;
  • o RG saiu da consulta SQL e da tabela renderizada para o produtor;
  • o botão foi renomeado para Aprovar via WhatsApp;
  • a mensagem do WhatsApp passou a abrir em formato curto:
  • Fechamento da Peça {nome} aprovado!
  • Valor Repasse (80%): R$ {valor}
  • Segue PIX:
  • Nome da conta:
  • o e-mail financeiro@rionoteatro.com.br foi removido do rodapé da página.

Validações:

  • php -l produtor/fechamento.php

Referências:

BK-227 · Checkout de créditos com PIX, cartão MP e planos alinhados ao painel

Data: 8 de Abril de 2026

Contexto operacional:

  • o módulo de painel/modulos/creditos/index.php ainda expunha um fluxo centrado só em PIX, com pacote teste de R$ 1, sem detalhar claramente o que cada valor entregava e sem aceitar cartão Mercado Pago dentro da própria tela.
  • a operação também precisava alinhar os pacotes aos recursos premium reais do painel de eventos e exigir os mesmos dados de faturamento já usados no checkout principal para o cartão.

Ajustes aplicados:

  • painel/modulos/creditos/index.php
  • painel/modulos/creditos/action.php
  • painel/modulos/creditos/callback_mp.php
  • o pacote teste de R$ 1 foi ocultado e a tela passou a trabalhar com os planos reais 50, 200, 300 e 500;
  • ao clicar no plano, a tela agora mostra o que aquele valor engloba, reaproveitando a lógica/copy dos recursos premium do painel de eventos;
  • o usuário escolhe entre PIX e Cartão dentro da própria página;
  • o PIX passou a abrir o QR Code em modal;
  • o Cartão passou a usar o Brick oficial do Mercado Pago;
  • foram adicionados os campos de faturamento exigidos pelo Mercado Pago para cartão: CEP, Telefone/Celular, Logradouro, Nº, Bairro, Cidade e UF;
  • o backend de créditos passou a processar cartão Mercado Pago, consultar status da compra e reconciliar callback/webhook para PIX e Cartão;
  • os textos dos pacotes e observações comerciais foram refinados conforme a regra operacional validada na sessão.

Validações:

  • php -l painel/modulos/creditos/index.php
  • php -l painel/modulos/creditos/action.php
  • php -l painel/modulos/creditos/callback_mp.php
  • node --check do JS inline sanitizado da tela de créditos

Referências:

BK-225 · Ordem da vitrine de eventos separada por admin, produtor e bot

Data: 8 de Abril de 2026

Contexto operacional:

  • a vitrine pública de eventos.php ainda agrupava tudo em apenas dois blocos: venda online/admin primeiro e o restante depois.
  • a operação precisava destacar primeiro os eventos sob gestão do admin ou em parceria_online=1, depois os eventos aprovados de produtor e, por último, os eventos aprovados vindos do bot.

Ajustes aplicados:

  • eventos.php
  • o ORDER BY da listagem principal passou a priorizar em três níveis:
  • admin e parceria_online=1
  • produtor
  • bot
  • os filtros existentes de status, aprovação e janela de temporada foram preservados.

Validações:

  • leitura da query principal em eventos.php
  • php -l eventos.php
  • consulta real na base confirmando os primeiros blocos na sequência admin/parceria -> produtor -> bot

Referências:

BK-224 · Scroll horizontal mobile na lista de eventos do painel

Data: 7 de Abril de 2026

Contexto operacional:

Ajustes aplicados:

  • painel/modulos/eventos/index.php
  • a tabela passou a ficar dentro de um wrapper com overflow-x: auto no mobile;
  • a largura mínima da tabela foi preservada para evitar colapso das colunas.

Validações:

  • leitura da estrutura da tabela no painel
  • php -l painel/modulos/eventos/index.php

Referências:

BK-223 · Hotfixes do fluxo produtor/admin de eventos e peças

Data: 7 de Abril de 2026

Contexto operacional:

  • o painel do produtor ainda limitava upload de foto a 5MB, mantinha imagens em pending_eventos sem promover para o caminho canônico em cenários de parceria online e permitia estados inconsistentes entre status, editado_produtor, origem e parceria_online.
  • no admin, a fila de eventos precisava exibir melhor a mídia pendente, o dashboard precisava mostrar solicitações de exclusão, o save/approve precisava bloquear conflito de slug ativo e o módulo de peças precisava voltar a abrir previews válidos e permitir remover a gestão Admin quando apropriado.
  • a VPS também estava sem agendamento ativo para o cleanup de imagens antigas, apesar do script já existir no repositório.

Ajustes aplicados:

  • painel/modulos/eventos/incluir.php
  • painel/modulos/eventos/editar.php
  • painel/modulos/eventos/action.php
  • upload de foto do produtor elevado para 10MB, mantendo crop/resize antes de persistir;
  • edição do produtor bloqueada quando parceria_online=1, com aviso explícito e link de WhatsApp do admin;
  • slug passou a ser validado contra peças/eventos ativos antes de cadastrar ou editar;
  • exclusão lógica do produtor passou a apagar mídia local imediatamente;
  • quando o evento entra em parceria_online=1, a foto pendente é promovida para /arquivos/pecas/<id>/... no próprio fluxo do painel.
  • admin/modulos/eventos/index.php
  • admin/modulos/eventos/editar.php
  • admin/modulos/eventos/action.php
  • admin/index.php
  • dashboard admin ganhou alerta para Solicitação de Exclusão;
  • fila admin passou a priorizar foto pendente, depois foto publicada e por fim imagem externa;
  • campo slug foi exposto para revisão/edição do admin;
  • aprovação, criação e edição no admin passaram a barrar conflito com slug já ativo.
  • admin/modulos/pecas/index.php
  • admin/modulos/pecas/action.php
  • previews inativos voltaram a abrir rota pública válida;
  • ativar peça/evento em contexto admin agora zera editado_produtor;
  • badge Admin passou a permitir remover a gestão admin e devolver o item ao produtor com parceria_online=0 e status=I.
  • admin/cron/cleanup_old_pecas_images.php
  • cleanup endurecido para reconciliar tb_fotos durante execução real.

Validações:

  • php -l em todos os PHPs alterados
  • validação humana do botão salvar no painel produtor
  • validação humana do link real da coluna Nome no admin de peças
  • execução real do cleanup:
  • 241 arquivos removidos
  • 9.37 MB liberados
  • 242 registros removidos de tb_fotos
  • segundo dry-run com 0 candidatos restantes

Referências:

BK-222 · Link publico real na coluna Nome das pecas ativas

Data: 7 de Abril de 2026

Contexto operacional:

  • na listagem de admin/modulos/pecas/index.php, a coluna Nome das peças/eventos ativos ainda abria o preview interno do admin com evento.php?id=...&preview=1.
  • para operação e conferência rápida, o clique precisava abrir a URL pública real do evento no site, como https://rionoteatro.com.br/<slug>.

Ajustes aplicados:

  • admin/modulos/pecas/index.php
  • a coluna Nome passou a usar a URL pública canônica https://rionoteatro.com.br/<slug> quando o item está com status = A;
  • quando o slug estiver vazio, o link faz fallback seguro para https://rionoteatro.com.br/evento.php?id=<id> ou https://rionoteatro.com.br/peca.php?id=<id>;
  • itens fora do ar continuam abrindo o preview legado com &preview=1.

Validações:

  • leitura do trecho de montagem da coluna Nome em admin/modulos/pecas/index.php
  • conferência da regra pública já usada em admin/modulos/eventos/index.php
  • php -l admin/modulos/pecas/index.php
  • validação funcional no admin confirmando a abertura do link público real

Referências:

BK-183 · Correção da validação de datas de temporada no painel

Data: 7 de Abril de 2026

Contexto operacional:

  • a edição de evento no painel do cliente-produtor estava bloqueando o salvamento com a mensagem Datas de temporada invalidas. Use DD/MM/AAAA. mesmo com datas válidas preenchidas.
  • a comparação com painel/modulos/eventos/incluir.php mostrou que o formulário de edição estava sem os IDs esperados pela validação JavaScript.

Ajustes aplicados:

  • painel/modulos/eventos/editar.php
  • adicionados apenas id="inicio_temporada" e id="fim_temporada" nos campos de data;
  • patch reduzido ao mínimo necessário para alinhar o formulário de edição ao padrão de incluir.php.

Validações:

  • leitura comparativa de painel/modulos/eventos/incluir.php e painel/modulos/eventos/editar.php
  • php -l painel/modulos/eventos/editar.php
  • teste real no navegador confirmando o salvamento sem o erro anterior

Referências:

BK-201 · Automação social da peça nova + agenda operacional

Data: 3 de Abril de 2026

Contexto operacional:

  • o fluxo de nova peça ainda estava parcial, com approvals inconsistentes, stories pesados, WhatsApp misturado com alertas e agenda sem edição operacional suficiente.
  • a operação precisava validar o ciclo real: gerar approvals, aprovar, reagendar, editar e reagendar postagem na plataforma.

Ajustes aplicados:

  • a primeira rodada automática da peça nova passou a abrir a trilha de facebook_feed, instagram_feed, threads_feed, facebook_story e instagram_story
  • o número de approval de postagem foi segregado dos alertas gerais no WhatsApp
  • o pipeline de copy ganhou bridge/pool local e a aprovação humana seguiu como gate obrigatório
  • a agenda passou a mostrar queue_id, gatilho humano + código técnico, horário planejado completo e texto aprovado
  • a agenda ganhou edição operacional de texto, mídia e agendamento para itens editáveis
  • o modal da agenda passou a abrir o approval quando o item estiver em pending_approval e houver artifact
  • o charset do fluxo foi alinhado para utf8mb4, corrigindo degradação de emojis na agenda
  • facebook_feed em scheduled ganhou fluxo de Editar, cancelando o agendamento remoto e reabrindo o item para edição
  • o fluxo de mídia passou a priorizar vídeo nos canais compatíveis e a alternar vídeo/capa por peça+canal na semana
  • facebook_feed com vídeo do acervo local passou a subir o arquivo por upload binário, evitando dependência do file_url remoto para a Graph API
  • a publicação no Instagram passou a aguardar o container de vídeo ficar pronto antes do media_publish, reduzindo o erro 9007
  • instagram_feed passou a montar payload de REELS quando houver vídeo
  • threads_feed passou a montar payload com video_url, ainda dependente de validação real porque o token do canal segue inválido

Validações:

  • php -l em:
  • admin/cron/sync_facebook_events.php
  • admin/cron/ai_copy_helper.php
  • admin/classes/FacebookEventService.php
  • admin/modulos/campanhas/action_squad.php
  • admin/modulos/campanhas/aprovar_squad.php
  • admin/modulos/facebook/agendamento.php
  • admin/modulos/facebook/agendamento-events.php
  • admin/modulos/facebook/agendamento-update-status.php
  • config/connect.php
  • admin/data/__CLASS.SQL.php
  • validação real de approval e fila para Fanáticos
  • validação real de Editar em facebook_feed scheduled, com novo rascunho agendado na plataforma
  • rerun real do queue_id 1024 (Fanáticos / facebook_feed) saindo do erro de fetch remoto do vídeo para publicação bem-sucedida
  • rerun real dos instagram_story com vídeo (queue_id 1028 e 1029) saindo do 9007 Media ID is not available para publicação bem-sucedida

Referências:

BK-195 · Análise de mídia e tração 2024 x 2025 x 2026

Data: 1 de Abril de 2026

Contexto operacional:

  • o projeto já tem snapshots históricos de Meta Ads e snapshots recentes de GA4, permitindo uma leitura inicial da perda de tração de mídia.

Ajustes aplicados:

Validações:

  • leitura dos snapshots persistidos em campanhas_metricas_importadas
  • comparação do histórico retroativo do Meta com o snapshot recente de GA4

BK-194 · Add Peça com IA no admin de Peças

Data: 1 de Abril de 2026

Contexto operacional:

  • o fluxo atual depende de texto recebido no WhatsApp + consulta manual do Sympla + preenchimento manual em pecas/detalhe.php.
  • isso é útil, mas lento e repetitivo.
  • o follow-up do mesmo dia fechou a tentativa interrompida de embutir a nova tela direto na aba de Peças.

Ajustes aplicados:

  • criada a nova aba Add Peça com IA em index.php
  • criada a tela dedicada add_ia.php
  • a primeira fase já permite:
  • colar o texto do WhatsApp
  • extrair campos principais
  • detectar o link do Sympla
  • abrir rapidamente a conciliação e o link da venda oficial
  • o embed da aba passou a:
  • respeitar #tab-add-ia no carregamento
  • manter o POST interno em modo embed
  • abrir detalhe.php na janela principal quando o rascunho é promovido
  • ajustar a altura do iframe no carregamento
  • o fluxo de peça admin/parceria foi endurecido para:
  • persistir origem='admin' no save do módulo de peças;
  • exibir endereço + mapa em peca.php;
  • exibir CTA operacional de WhatsApp em evento.php quando faltar contato cadastrado;
  • gerar meta descriptions corretas mesmo quando a sinopse antiga vier com entidades HTML;
  • fazer novos salvamentos do editor em UTF-8 normal, sem &oacute;/&ccedil;
  • publicado CL-194-embed-fix-add-ia.md

Validações:

  • leitura do fluxo atual em painel/modulos/eventos/incluir.php
  • leitura da conciliação em admin/modulos/eventos/conciliacao.php
  • leitura da listagem e do formulário de peças em admin/modulos/pecas/index.php e detalhe.php
  • php -l admin/modulos/pecas/index.php
  • php -l admin/modulos/pecas/add_ia.php
  • php -l admin/modulos/pecas/action.php
  • php -l peca.php
  • php -l evento.php
  • php -l meta-analytics.php

BK-188 · Mapa operacional de GTM/GA4/Ads/Meta

Data: 31 de Março de 2026

Contexto operacional:

  • O repositório acumulava tracking atual e legado em paralelo, o que dificultava entender com clareza quais tags e eventos ainda são canônicos.

Ajustes aplicados:

  • criado MAPA_TRACKING_GTM_GA4.md
  • o documento separa:
  • containers/tags identificados
  • eventos identificados
  • arquivos centrais
  • camada atual vs legado
  • recomendações de manutenção

Validações:

  • leitura do HTML público da home
  • busca em código por gtag, dataLayer, fbq, send_to, GTM, GA4, UA e Ads

BK-189 · Google Ads em rascunho para aprovação humana

Data: 31 de Março de 2026

Contexto operacional:

  • o tracking atual do RNT já permite orientar melhor campanhas de mídia paga.
  • o operador quer ajuda das IAs na criação de anúncios, mas sem publicação automática e sem risco de surpresa na fatura.

Ajustes aplicados:

  • criado PLANO_GOOGLE_ADS_RASCUNHO_APROVACAO.md
  • o documento formaliza:
  • uso do tracking atual como base de sugestão;
  • campanhas e anúncios em modo rascunho;
  • aprovação humana antes de qualquer publicação;
  • proibição explícita de automação cega de gasto/orçamento.

Validações:

  • leitura do mapa atual de tracking do RNT
  • alinhamento do fluxo operacional com a preferência de aprovação humana antes de mídia paga

BK-190 · Padronização de ARCHITECTURE.md e GUIDELINES.md

Data: 31 de Março de 2026

Contexto operacional:

  • o projeto já tinha material útil em .agent/ e .context, mas faltava uma dupla curta e explícita na raiz para reduzir custo de contexto.

Ajustes aplicados:

Validações:

  • leitura da estrutura real do projeto
  • alinhamento com o mapa atual de tracking, admin e cron

BK-191 · Rascunho inicial de Google Ads e Meta Ads

Data: 31 de Março de 2026

Contexto operacional:

  • o RNT já tem tracking suficiente para apoiar uma primeira rodada de mídia assistida.
  • a operação continua exigindo aprovação humana antes de qualquer publicação ou gasto.

Ajustes aplicados:

  • criado RASCUNHO_INICIAL_GOOGLE_META_ADS.md
  • o documento consolida:
  • rascunho inicial de Google Ads
  • rascunho inicial de Meta Ads
  • sinais de tracking usados
  • públicos, copies e guardrails

Validações:

  • leitura do mapa atual de tracking
  • leitura do plano de Google Ads em rascunho
  • leitura do sistema de tracking/ROI já documentado no projeto
  • alinhamento da regra operacional: toda peça com venda online entra em mídia; ROI governa reforço/manutenção/pausa

BK-192 · Modernizar Analytics para GA4 e Meta Ads API read-only

Data: 31 de Março de 2026

Contexto operacional:

  • o projeto já tem base de campanhas, ROI, tracking UTM e integrações iniciais, mas a leitura de Analytics ainda está presa ao modelo antigo.
  • para chegar a ROI real com liberdade operacional assistida por IA, é necessário ler GA4 e Ads de forma moderna e segura.

Ajustes aplicados:

  • criado PLANO_GA4_META_ADS_API_READONLY.md
  • o documento consolida:
  • estado atual do projeto
  • lacunas da trilha antiga
  • arquitetura recomendada para GA4, Google Ads e Meta Ads
  • guardrails e fases de implantação

Validações:

  • leitura dos módulos atuais de Analytics antigo
  • leitura dos pontos de entrada de Google Ads e Facebook Ads no projeto
  • leitura do sistema de ROI e tracking UTM já existente
  • alinhamento da regra operacional de elegibilidade por peça com venda online

BK-193 · Hardening de token Meta e preparação de Meta Ads read-only

Data: 31 de Março de 2026

Contexto operacional:

  • a frente de Meta Ads ainda não está bloqueada só por “falta de integração”; existe também um problema de segurança/hardening, com token Meta hardcoded no código.
  • o fluxo admin atual atende página/Instagram, mas não fecha sozinho a camada de Ads Insights.

Ajustes aplicados:

  • criado BK-193-hardening-meta-token-e-ads-readonly.md
  • o BK separa explicitamente:
  • remoção do token hardcoded de notifications.php
  • provisão de META_ADS_ACCESS_TOKEN
  • provisão de META_ADS_AD_ACCOUNT_ID
  • preparação do collector isolado meta_ads

Validações:

  • leitura do wrapper facebook_ads.php
  • leitura do fluxo admin de token em admin/modulos/facebook/configure.php e admin/modulos/facebook/token_exchange.php
  • varredura segura por credenciais Meta e por indícios de ad_account_id

BK-187 · Governanca global em /root + no-pull automatico na VPS

Data: 30 de Março de 2026

Contexto operacional:

  • O repositório local ainda induzia git pull automatico na VPS como preflight de governanca.
  • A nova camada global em /root passou a coordenar a VPS, mas sem substituir a fonte da verdade local do RNT.

Ajustes aplicados:

  • AGENTS.md
  • passou a reconhecer explicitamente a camada global em /root;
  • passou a exigir conferencia local de branch/base com git checkout main, sem git pull automatico;
  • passou a descrever merge sobre o estado local mais atual validado na VPS.
  • docs/GIT_WORKFLOW.md
  • removeu git pull do preflight;
  • passou a declarar que a VPS deve ser tratada como potencialmente mais atual que o remoto;
  • ajustou exemplos de fluxo e worktree para nao induzir pull cego.

Validações:

  • leitura integral e diff local de AGENTS.md
  • leitura integral e diff local de docs/GIT_WORKFLOW.md
  • git status -sb conferido para manter os itens do BK-186 fora deste recorte

BK-182 · Auditoria e reconciliacao de branches antigas + stash

Data: 27 de Março de 2026

Contexto operacional:

  • O repositório local acumulava branches antigas, um worktree documental e um stash com funcionalidades úteis de campanhas ainda fora da main.
  • A limpeza exigia auditoria por trecho para não perder conteúdo útil misturado a histórico antigo.

Ajustes aplicados:

  • integrados trechos úteis do stash bk170-campanhas-pendentes no fluxo social/admin;
  • integrados trechos úteis do BK-167 em scraper Sympla, runtime do WhatsApp e conciliação de eventos/teatros;
  • incorporado o backlog vivo do BK-156 ao repositório principal;
  • removidas ou preparadas para remoção as referências locais obsoletas após reconciliar o conteúdo.

Validações:

  • php -l nos PHP reconciliados
  • node --check bot/whatsapp/server.js
  • git diff --check

BK-181 · Feed pecas_inteira.xml separado do feed principal

Data: 27 de Março de 2026

Contexto operacional:

  • O pecas.xml passou a servir bem ao catálogo principal com price + sale_price.
  • Para testes/catálogos alternativos com base no preço cheio, ficou necessário um segundo feed de produtos sem sale_price.

Ajustes aplicados:

  • atualizar_feed.php
  • passou a gerar pecas_inteira.xml;
  • manteve pecas.xml como está;
  • o novo feed publica apenas g:price com valor cheio.
  • admin/modulos/pecas/action.php
  • passou a retornar a URL e o timestamp do pecas_inteira.xml.
  • .gitignore
  • passou a ignorar pecas_inteira.xml.

Validações:

  • php -l atualizar_feed.php
  • php -l admin/modulos/pecas/action.php
  • php atualizar_feed.php
  • inspeção direta de pecas_inteira.xml

BK-180 · Feed de ofertas Meta adiciona min_quantity = 1

Data: 27 de Março de 2026

Contexto operacional:

  • O Meta rejeitou AUTOMATIC_AT_CHECKOUT sem pré-requisito.
  • A própria documentação oficial exige min_quantity ou min_subtotal para esse tipo de oferta.

Ajustes aplicados:

  • atualizar_feed.php
  • passou a emitir a coluna min_quantity;
  • passou a preencher min_quantity = 1 no ofertas.csv e no offer.csv.

Validações:

  • php -l atualizar_feed.php
  • php atualizar_feed.php
  • inspeção direta de ofertas.csv

BK-179 · Feed de ofertas Meta usa AUTOMATIC_AT_CHECKOUT

Data: 27 de Março de 2026

Contexto operacional:

  • O ofertas.csv estava emitindo application_type = SALE, o que modelava a oferta como desconto direto sobre o preço do item.
  • Para testar a categoria “Promoção” do Commerce Manager de forma mais fiel, o feed precisava usar aplicação automática no checkout.

Ajustes aplicados:

  • atualizar_feed.php
  • passou a emitir application_type = AUTOMATIC_AT_CHECKOUT no ofertas.csv e no offer.csv;
  • manteve exclude_sale_priced_products = YES.

Validações:

  • php -l atualizar_feed.php
  • php atualizar_feed.php
  • inspeção direta de ofertas.csv

BK-178 · Feed de ofertas Meta exclui produtos com sale_price

Data: 27 de Março de 2026

Contexto operacional:

  • O feed de ofertas continuava modelado como desconto percentual por item.
  • Para alinhar com a opção do Commerce Manager de não aplicar oferta sobre produtos já com sale_price, o arquivo precisava declarar isso explicitamente.

Ajustes aplicados:

  • atualizar_feed.php
  • passou a emitir a coluna exclude_sale_priced_products;
  • passou a preencher essa coluna com YES no ofertas.csv e no offer.csv.

Validações:

  • php -l atualizar_feed.php
  • php atualizar_feed.php
  • inspeção direta de ofertas.csv
  • inspeção direta de offer.csv

BK-174 · Feed Meta com price cheio e sale_price promocional

Data: 26 de Março de 2026

Contexto operacional:

  • O pecas.xml estava enviando apenas o valor promocional em price.
  • Quando o feed de ofertas do Meta também entrava, o Commerce Manager aplicava desconto adicional sobre um valor já reduzido.

Ajustes aplicados:

  • atualizar_feed.php
  • passou a calcular price a partir do valor cheio (sec_valor_de quando maior que sec_valor);
  • passou a enviar sale_price com o valor promocional real (sec_valor);
  • mantém apenas price quando não houver diferença entre inteiro e promocional.

Validações:

  • php -l atualizar_feed.php
  • php atualizar_feed.php
  • conferência direta do pecas.xml regenerado
  • g:price e g:sale_price validados nos itens ativos

BK-172 · Feed de ofertas Meta separado do catálogo

Data: 26 de Março de 2026

Contexto operacional:

  • O pecas.xml carregava corretamente como feed de produtos, mas falhava quando era reutilizado no Commerce Manager como feed de ofertas/promoções.

Ajustes aplicados:

  • atualizar_feed.php
  • manteve a geração do pecas.xml;
  • passou a gerar também ofertas.csv e offer.csv com schema de promoções baseado em pecas.oferta.
  • passou a incluir target_type = line_item, exigido pelo Meta para o subtipo de desconto de produto.
  • meta-analytics.php, pedido.php, confirm_pagamento.php
  • os eventos do pixel RNT CLICKS passaram a usar um helper explícito com trackSingle para fixar ViewContent, AddToCart, InitiateCheckout e Purchase no pixel 897210417852424.
  • admin/modulos/pecas/action.php
  • passou a confirmar também a geração dos feeds de ofertas;
  • passou a retornar as URLs públicas dos arquivos promocionais.

URLs operacionais:

  • https://rionoteatro.com.br/pecas.xml
  • https://rionoteatro.com.br/ofertas.csv
  • https://rionoteatro.com.br/offer.csv

Validações:

  • php -l atualizar_feed.php
  • php -l admin/modulos/pecas/action.php
  • php atualizar_feed.php
  • conferência direta de ofertas.csv
  • validação da action regenerar_feed_xml

BK-171 · Feed XML respeita status e fim de temporada

Data: 26 de Março de 2026

Contexto operacional:

  • O botão manual de regeneração do pecas.xml estava executando o gerador corretamente, mas o feed ainda precisava de uma regra mais rígida para impedir peças inativas, vencidas ou com sessão fora da temporada de entrarem no XML.

Ajustes aplicados:

  • atualizar_feed.php
  • o feed passou a exigir status = 'A';
  • passou a exigir fim_temporada válido ou não vencido;
  • passou a desconsiderar sessões futuras que ultrapassem fim_temporada.

Validações:

  • php -l atualizar_feed.php
  • php atualizar_feed.php
  • validação do endpoint admin/modulos/pecas/action.php?act=regenerar_feed_xml
  • conferência direta no pecas.xml regenerado

BK-165 · Feed XML no path correto + PalcoFan visível + conciliação Sympla alinhada

Data: 25 de Março de 2026

Contexto operacional:

  • O pecas.xml do catálogo Meta estava sendo gravado com caminho relativo e podia cair na raiz da VPS dependendo do diretório de trabalho do cron.
  • O bloco público do PalcoFan ainda mantinha os botões de login/cadastro com contraste ruim.
  • O conciliacao.php do Sympla tinha perdido parte do fluxo manual que já existia no conciliacao_eventim.php, especialmente em vínculo manual e separação por fonte.

Ajustes aplicados:

  • atualizar_feed.php
  • a gravação do feed passou a usar caminho absoluto com base em __DIR__, garantindo pecas.xml na raiz do projeto.
  • includes/inc_palcofan.php
  • os botões Fazer Login e Cadastre-se grátis ganharam cores explícitas e contraste seguro sobre o bloco branco.
  • admin/modulos/eventos/conciliacao.php
  • passou a filtrar por fonte = 'sympla', alinhando a tela ao particionamento já existente no Eventim;
  • recuperou formulários de vínculo manual em abas onde o Sympla tinha perdido esse fallback;
  • voltou a expor, quando o item já existe como admin, a opção de manter como Admin ou migrar para BOT.

Auditoria complementar:

  • O backend atual de conciliação ainda só persiste edições de sessões já existentes em bot_sessoes_sugestoes.
  • Não há hoje suporte completo no action.php para criar uma sessão nova capturada do zero via UI, então o botão de “nova sessão” exige um follow-up próprio e não foi improvisado parcialmente nesta entrega.

Validações:

  • php -l atualizar_feed.php
  • php -l includes/inc_palcofan.php
  • php -l admin/modulos/eventos/conciliacao.php
  • git diff --check

BK-164 · PalcoFan no dashboard/peça + auditoria do pecas.xml

Data: 25 de Março de 2026

Contexto operacional:

  • O fluxo do PalcoFan já existia, mas faltavam dois pontos práticos:
  • o admin dashboard não sinalizava comentários pendentes de moderação;
  • a página da peça não mostrava um minicard de avaliações aprovado no grid principal.
  • Em paralelo, foi pedida uma auditoria do gerador do feed pecas.xml, sem alterar o script nesta rodada.

Ajustes aplicados:

  • admin/index.php
  • passou a consultar tb_avaliacoes com status = 'pendente';
  • ganhou card/alerta com link direto para admin/modulos/comentarios/index.php.
  • peca.php
  • passou a consultar média/quantidade de avaliações aprovadas da peça;
  • ganhou minicard Avaliações no grid superior quando houver pelo menos 1 comentário aprovado;
  • o minicard aponta para a âncora da seção completa do PalcoFan.
  • includes/inc_palcofan.php
  • ganhou âncora explícita #palcofan-avaliacoes para receber a rolagem do minicard.

Auditoria executada sem alteração de código:

  • atualizar_feed.php
  • confirmado como o arquivo que gera o pecas.xml;
  • a gravação continua usando caminho relativo em file_put_contents('pecas.xml', $xml).
  • /www/server/cron/f1fb970e7ca0b68f38510abb76a86aa0
  • confirmado como o wrapper do cron que dispara php -q /www/wwwroot/rionoteatro.com.br/atualizar_feed.php.

Resultado técnico observado:

  • O dashboard do admin agora está pronto para acender quando houver comentário PalcoFan realmente em pendente.
  • O minicard de avaliações aparece no topo de peca.php quando a peça tiver comentários aprovados e leva o cliente para a seção detalhada.
  • Na auditoria atual do banco, a tabela tb_avaliacoes estava com 0 pendentes e 1 avaliação já aprovada para a peça 23, então o alerta do dashboard não acenderia nesse instante porque não há pendência real.

Validações:

  • php -l admin/index.php
  • php -l peca.php
  • php -l includes/inc_palcofan.php
  • render local de peca.php confirmando minicard + âncora
  • leitura integral de atualizar_feed.php e do wrapper /www/server/cron/f1fb970e7ca0b68f38510abb76a86aa0

BK-160 · Carrossel semanal cover por peça

Data: 25 de Março de 2026

Contexto operacional:

  • O feed semanal que sai do cron sync_facebook_events.php vinha empacotando até nove peças em um único mosaico, o que diluía a identidade visual de cada capa e obrigava o Instagram a replicar o mesmo grid pouco legível.

Ajustes aplicados:

  • admin/cron/sync_facebook_events.php
  • reduziu a seleção a 10 peças por semana e reaproveitou image_mtime para gerar cache key por peça.
  • substituiu buildWeeklyCarouselSlides por um compositor que gera um JPEG 1080x1350 por peça e usa a capa como fundo em cover.
  • manteve items[] compatível com createFeedCarouselPost e createInstagramCarousel, preservando a legenda e os UTM já existentes.
  • passou a espelhar a vitrine de pecas.php na elegibilidade comercial da semana-alvo, sem exigir sessão cadastrada para a peça continuar no carrossel se a temporada ainda estiver válida.
  • passou a aceitar weekly comercial com apenas 1 peça e usa PROMOÇÃO DA SEMANA! no singular nesse caso.
  • separou a cadência operacional em dois fluxos:
  • domingo -> approval comercial da segunda;
  • segunda -> approval editorial da quarta.
  • ganhou um segundo tipo de fila semanal para o carrossel editorial dos eventos mais acessados do site, sem misturar esse fluxo com o weekly comercial.
  • passou a reaproveitar o payload visual do weekly carousel no approval, preenchendo preview real do primeiro slide e hash visual para evitar recriação cega do mesmo approval.
  • deixou de promover automaticamente o weekly carousel para queued quando o item retorna de erro, preservando a aprovação humana como gate antes do publish.
  • admin/modulos/campanhas/aprovar_squad.php
  • passou a renderizar uma galeria com todos os slides quando o approval contém carousel_items, mantendo o preview simples para posts comuns.
  • admin/modulos/campanhas/action_squad.php
  • passou a respeitar headline singular/plural no refresh do weekly e a aceitar também o tipo semanal editorial.
  • includes/inc_palcofan.php
  • endurecido para exibir estrelas unicode clicáveis e CTA visível de avaliação, sem depender visualmente do Font Awesome nem de JS mais moderno.

Resultado técnico observado:

  • O cron agora freelanca slides individuais do carrossel com rolagem lateral nativa, removendo o card 3x3 e dando destaque à arte oficial de cada peça.
  • O payload segue aceito pelas APIs da Meta porque continua entregando items com imagem_url (até 10 itens) e o cache evita rebuilds desnecessários quando a capa não muda.
  • O link de approval do weekly carousel deixa de ser cego: o operador passa a enxergar o preview do primeiro slide e a galeria completa antes de aprovar.
  • A elegibilidade comercial da próxima segunda deixa de cair no falso negativo de sessão ausente e passa a refletir o catálogo comercial ativo de pecas.php.
  • O PalcoFan volta a mostrar estrelas e CTA de envio de forma explícita mesmo em browser/mobile mais sensível.

Validações:

  • php -l admin/cron/sync_facebook_events.php
  • php -l admin/modulos/campanhas/aprovar_squad.php
  • php -l admin/modulos/campanhas/action_squad.php
  • php -l includes/inc_palcofan.php
  • git diff --check

BK-159 · Correção de Layout Mobile na Lista de Compradores

Data: 21 de Março de 2026

Contexto operacional:

  • O formulário de visualização da lista de compradores (produtor/listaopen.php) apresentava problemas graves de responsividade no mobile.
  • A coluna de RG/Documento ficava oculta ou inacessível em telas pequenas.

Ajustes aplicados:

  • produtor/listaopen.php
  • Implementado container de rolagem lateral (overflow-x: auto) para a tabela.
  • Adicionado aviso visual em vermelho ("Role a tabela para o lado ->") exclusivo para mobile.
  • Forçado display: table-cell !important para a coluna de RG via CSS responsivo.
  • Otimizado formulário de envio de e-mail e botões para touch.
  • Adicionado format-detection="telephone=no" para evitar links automáticos em documentos.

BK-151 · Brand profile bootstrap do RNT

Data: 20 de Março de 2026

Contexto operacional:

  • O Squad ainda trabalhava com contexto disperso de marca, campanha e CTA.
  • O primeiro corte da entrega mostrava o resumo do profile na UI, mas o helper JSON ainda não garantia brandProfile no payload da run.

Ajustes aplicados:

  • admin/modulos/campanhas/social_brand_profile.php
  • criado como artefato canônico PHP 5.6-safe do perfil inicial de marca.
  • admin/modulos/campanhas/social_squad_helper.php
  • passou a incluir o artefato e injetar brandProfile no piece_context.
  • admin/modulos/campanhas/redes_sociais_ui_helper.php
  • passou a renderizar um resumo do profile ativo no workspace do Squad.

Resultado técnico observado:

  • o payload do Squad passa a carregar um profile de marca explícito e reutilizável;
  • a UI local e o helper JSON passaram a usar a mesma fonte de verdade;
  • o profile fica pronto para reutilização nos próximos BKs sem crawler nem banco novo.

BK-150 · Loop de metricas do Squad com social_post_metrics_history

Data: 19 de Março de 2026

Contexto operacional:

  • O RNT já tinha coleta de métricas sociais, mas o approval e a próxima run do Squad ainda não aproveitavam esse histórico.
  • O primeiro corte do BK-150 ainda ancorava o resumo no queue_id atual, o que não resolvia o caso real de desempenho anterior antes da aprovação.

Ajustes aplicados:

  • admin/modulos/campanhas/action_squad.php
  • passou a anexar metrics_summary com base na última performance anterior útil por peca_id + canal compatível, excluindo o queue_id atual;
  • separou current_queue_metrics para o caso em que o próprio item já possua histórico.
  • admin/modulos/campanhas/aprovar_squad.php
  • passou a exibir painel de desempenho anterior útil antes da aprovação.
  • admin/modulos/campanhas/social_squad_helper.php
  • ganhou helpers de consulta/resumo de métricas;
  • passou a injetar lastPerformance no contexto da próxima run;
  • ficou compatível com uso embedded sem responder JSON bruto.

Resultado técnico observado:

  • o approval passa a mostrar performance anterior realmente útil;
  • o artefato final preserva esse resumo em approved_payload;
  • a próxima run do Squad recebe contexto resumido de performance sem tabela nova e sem cron novo.

BK-149 · Hardening de permissoes e arquivos runtime do social

Data: 19 de Março de 2026

Contexto operacional:

  • O fluxo social ainda carregava dívida objetiva de permissões frágeis (0777/0666) em paths de runtime.
  • O endurecimento cego para 0755/0644 quebraria parte do contrato multi-worker entre cron CLI e painel web.

Ajustes aplicados:

  • admin/cron/sync_facebook_events.php
  • passou a diferenciar arquivos compartilhados (lock, state) de artefatos read-only;
  • diretórios compartilhados passaram a tentar 02775 com fallback explícito para 0777;
  • arquivos compartilhados passaram a tentar 0664 com fallback explícito para 0666.
  • admin/cron/sync_facebook_page_events.php
  • alinhado à mesma política de diretórios compartilhados;
  • queue/sample do fallback browser passaram a receber permissão de escrita compartilhada porque o painel de approval também regrava esses arquivos.
  • docs/AI_CEREBRO_RELAY.md
  • política canônica de permissões reescrita com checklist operacional e rollback.

Resultado técnico observado:

  • o fluxo social ficou menos frágil sem romper o contrato de escrita cruzada root + www-data;
  • falhas de permissão deixam rastro explícito em log;
  • o relay deixou de recomendar 777 como política base.

BK-148 · Modularizacao segura de redes_sociais.php (fase 1)

Data: 19 de Março de 2026

Contexto operacional:

  • admin/modulos/campanhas/redes_sociais.php concentrava blocos grandes demais de preparo/render, aumentando risco de manutenção.
  • A fase 1 precisava extrair apenas os blocos de menor risco, sem reescrever a tela inteira.

Ajustes aplicados:

  • admin/modulos/campanhas/redes_sociais.php
  • passou a delegar leitura/render de blocos específicos para helper dedicado.
  • admin/modulos/campanhas/redes_sociais_ui_helper.php
  • criado para absorver preparo do estado da ponte social;
  • preparo do fallback browser de FB Events;
  • render do painel de fallback browser;
  • render do Radar de Recomendacao;
  • render do conteúdo superior da aba Squad Social.

Resultado técnico observado:

  • a tela única da Central de Redes Sociais foi preservada;
  • o módulo principal ficou menor e mais legível;
  • a base ficou pronta para as próximas fases sem big bang rewrite.

BK-147 · Visibilidade operacional da ponte social na Central de Redes Sociais

Data: 19 de Março de 2026

Contexto operacional:

  • A Central de Redes Sociais não mostrava claramente quando a ponte de copy com o Cérebro estava saudável, degradada ou indisponível.
  • O caso de failover para bridge local podia ser lido como sucesso remoto, escondendo degradação real da operação.

Ajustes aplicados:

  • admin/cron/ai_copy_helper.php
  • passou a persistir telemetria mínima da ponte em admin/logs/ai_bridge_status.json;
  • anota provider = remote|local e propaga ai_reviewed_local quando a resposta útil vier da bridge local.
  • admin/modulos/campanhas/redes_sociais.php
  • passou a ler o snapshot da ponte no topo da tela;
  • exibe badges de estado SAUDÁVEL, DEGRADADA, INDISPONÍVEL e sinais separados para FALLBACK BRIDGE e FALLBACK HARDCODED.

Resultado técnico observado:

  • o operador ganhou visibilidade direta do estado da ponte social sem runtime paralelo novo;
  • o fallback local deixou de aparecer falsamente como operação saudável;
  • a trilha documental canônica do BK-147 ficou regularizada no RNT.

BK-158 · Saneamento P0 de credenciais expostas

Data: 19 de Março de 2026

Contexto operacional:

  • O repositório mantinha na raiz um relatório versionado com segredos literais reais.
  • O .gitignore local precisava endurecimento mínimo para reduzir risco de versionamento acidental de variantes do .env.

Ajustes aplicados:

  • CREDENCIAIS_EXPOSTAS_RELATORIO.md
  • removido da raiz do projeto.
  • docs/seguranca/credenciais-audit-20260213.md
  • criado como versão sanitizada e canônica do relatório, com valores literais ofuscados.
  • .gitignore
  • endurecido para cobrir .env, .env.local, .env..local e */.env.

Resultado técnico observado:

  • nenhum relatório explícito de credenciais permaneceu solto na raiz do repositório;
  • o conteúdo útil foi preservado em trilha documental segura;
  • a proteção contra commit acidental de arquivos de ambiente ficou mais explícita.

BK-132 · Endurecimento do fluxo de prova de Story manual/notificado

Data: 19 de Março de 2026

Contexto operacional:

  • O fluxo de prova de Story manual dependia exclusivamente do draft_url para ser considerado sucesso. Falhas na extração do link pela automação de browser resultavam em perda de sinal operacional, mesmo quando o screenshot ou artefato útil existia.

Ajustes aplicados:

  • Esquema de Banco:
  • Adicionadas colunas profile_id, draft_url, notification_sent_at e notification_result à tabela social_post_queue para suporte nativo a metadados de prova.
  • admin/cron/sync_facebook_events.php:
  • Função rntExecuteBrowserStoryDraftProof agora aceita sucessos parciais (existência de screenshot/artefato) e sincroniza automaticamente os dados para as novas colunas diretas da fila.
  • admin/modulos/facebook/agendamento-events.php:
  • Consulta SQL atualizada para carregar os novos campos de prova.
  • Função rnt_social_queue_extract_draft_proof refatorada para preferir dados estruturados das colunas e oferecer fallback visual detalhado no calendário.

Resultado técnico observado:

  • Itens em modo notified agora exibem prova operacional útil na agenda admin, seja via link direto para rascunho ou via sinalização de screenshot disponível, reduzindo a necessidade de conferência manual cega.

BK-138 · Correção de Mídias Invisíveis e Datas no Squad Approval

Data: 18 de Marco de 2026

Contexto operacional:

  • o painel de Aprovação de Squad Social (aprovar_squad.php) acusava a ausência de mídias/criativos para peças mesmo quando os links deveriam estar disponíveis, e as datas de agendamento estavam travadas na hora contínua do disparo da rotina, confundindo a operação.

Ajustes aplicados:

  • admin/cron/sync_facebook_events.php
  • alteração em rntResolvePecaPrimaryImageAsset para retornar um array estritamente vazio (array()) caso não encontre nenhuma capa física válida via @is_file, permitindo grace-degradation no visualizador do Squad.
  • resgate do metadado scheduled_for nos geradores de cron planSiteEntryFeedQueue, planWeeklyCarouselQueue, e gatilhos de stories, passando avante essa data exata para reescrever o campo date do json de Aprovação com $meta['scheduled_for'].

Resultado técnico observado:

  • próximas peças submetidas pelo Cerebro na fila do Approval possuirão links físicos estritamente confirmados (caso preenchido) ou omitidos (caso em falta), e exibirão os verdadeiros horários projetados para as plataformas ao invés dos horários de processamento.

BK-137 · Fluxo social reconciliado com gate criativo, WhatsApp e prova de rascunho

Data: 18 de Marco de 2026

Contexto operacional:

  • o fluxo social ja tinha pecas importantes implantadas, mas seguia com dois problemas reais:
  • a copy de approval caia em fallback generico porque nao havia endpoint criativo funcional;
  • a tela de aprovacao do admin ainda nao mostrava bem o contexto comercial, a midia prevista e o parecer interno do criativo.
  • ao mesmo tempo, o contrato do produto foi finalmente fechado: criativo -> review interno -> WhatsApp -> agenda -> draft/post -> conferencia final.

Ajustes aplicados:

  • admin/cron/ai_copy_helper.php
  • passou a apontar por padrao para http://127.0.0.1:9903/api/social-copy;
  • ganhou fallback local e enrich de contexto com capa + pasta /arquivos/pecas/{id}/video;
  • passou a preservar review, selected_media, selected_media_reason, creative_direction e commercial_summary.
  • admin/cron/sync_facebook_events.php
  • approval payload ficou mais rico;
  • midia escolhida pelo Cérebro passou a ser preservada no approval e reaproveitada na fila;
  • o fluxo de prova de rascunho/manual draft ficou refletido na agenda.
  • admin/modulos/campanhas/aprovar_squad.php
  • a tela de aprovacao passou a mostrar canal, origem, CTA, resumo comercial, midia prevista e revisao interna.
  • admin/modulos/campanhas/action_squad.php
  • passou a persistir os novos metadados do approval aprovado em approved_payload.
  • admin/modulos/facebook/agendamento.php e admin/modulos/facebook/agendamento-events.php
  • seguiram exibindo a prova de rascunho e o estado real da fila.

Resultado tecnico observado:

  • o approval do WhatsApp deixou de depender de endpoint vazio.
  • o admin passou a aprovar com muito mais contexto.
  • o proximo passo ativo volta a ser operacional, nao documental: fechar notificacao pos-publicacao e endurecer o story browser flow.

Validacoes executadas:

  • php -l admin/cron/ai_copy_helper.php
  • php -l admin/cron/sync_facebook_events.php
  • php -l admin/modulos/campanhas/aprovar_squad.php
  • php -l admin/modulos/campanhas/action_squad.php
  • php -l admin/modulos/facebook/agendamento.php
  • php -l admin/modulos/facebook/agendamento-events.php
  • git diff --check

BK-136 · Governanca compartilhada de auto-fechamento e backlog->changelog

Data: 18 de Marco de 2026

Contexto operacional:

  • depois de formalizar no Cérebro a regra de auto-fechamento de BK concluido e a semantica correta de backlog vivo versus changelog historico, o Rio no Teatro precisava receber o mesmo protocolo para eliminar divergencia entre os dois repositorios.
  • a auditoria local mostrou duas lacunas principais:
  • ainda havia texto permitindo hotfix direto na main em alguns trechos, apesar da regra da VPS ja exigir branch;
  • ainda faltava explicitar aqui que backlog vivo e changelog detalhado devem caminhar juntos no fechamento do BK.

Ajustes aplicados:

  • AGENTS.md
  • passou a declarar que hotfix na VPS continua em branch obrigatoria;
  • passou a exigir backlog vivo em docs/backlog/ e changelog detalhado em docs/changelog/;
  • passou a mandar comparar /www/wwwroot/rionoteatro.com.br com /root/cerebro antes de alterar regra compartilhada;
  • passou a exigir que BK concluido, auditado e validado siga ate merge/push/unlock no mesmo atendimento.
  • docs/GIT_WORKFLOW.md
  • ganhou o fluxo BK->CL no passo a passo do branch workflow;
  • endureceu o fechamento automatico apos reconciliacao e auditoria de conteudo aprovadas;
  • esclareceu que deploy direto fica restrito a fora da VPS.
  • docs/BACKLOG.md
  • passou a deixar explicita a semantica de backlog vivo e changelog historico;
  • recebeu o fechamento do BK-136 como regra operacional permanente.
  • docs/LOCK.md
  • ganhou locks do BK-136 e passou a explicitar o significado operacional de UNLOCKED.

Resultado tecnico observado:

  • Cérebro e Rio no Teatro ficam alinhados na mesma governanca para:
  • branch obrigatoria na VPS;
  • auditoria por hunk;
  • backlog vivo + changelog detalhado;
  • fechamento completo do BK no mesmo atendimento.
  • isso reduz drift documental e evita retrabalho quando a CLI começa em um repo e depois migra para o outro.

Validacoes executadas:

  • leitura integral e reconciliacao textual de AGENTS.md, docs/GIT_WORKFLOW.md, docs/BACKLOG.md, docs/CHANGELOG.md e docs/LOCK.md
  • comparacao de governanca com /root/cerebro
  • git diff --check

BK-135 · Protocolo canonico de auditoria por hunk antes de limpeza Git

Data: 18 de Marco de 2026

Contexto operacional:

  • auditorias recentes de branch/worktree/stash mostraram o mesmo risco recorrente: um unico arquivo pode misturar, ao mesmo tempo, trecho novo e util, regressao, backup operacional e ruido de modo/ambiente.
  • o erro de processo que precisava ser eliminado era decidir "descartar o arquivo" em vez de decidir por trecho de codigo.
  • como a CLI costuma iniciar por este repositório e o AGENTS.md e a referencia primaria das IAs, a regra foi endurecida aqui de forma explicita.

Ajustes aplicados:

  • AGENTS.md
  • passou a declarar como proibicao absoluta descartar arquivo inteiro apenas porque a branch/worktree/stash parece antiga;
  • formalizou que a unidade de decisao e o hunk, nao o arquivo;
  • exigiu classificacao de cada diferenca em util e atual, util mas precisa adaptacao, regressao ou lixo/backup/legado;
  • passou a exigir conclusao explicita de que nao sobrou nenhum hunk util fora da main antes de qualquer limpeza.
  • docs/GIT_WORKFLOW.md
  • ganhou a secao canonica Auditoria de Conteudo Antes de Merge ou Limpeza;
  • passou a listar comandos minimos para auditar branch/worktree/stash contra a main atual e contra a base comum (merge-base);
  • passou a instruir a mescla manual apenas dos hunks uteis na versao mais nova do arquivo.
  • docs/BACKLOG.md
  • recebeu o fechamento do BK-135 como regra operacional permanente.

Resultado tecnico observado:

  • o protocolo deixa de permitir a simplificacao errada de "arquivo antigo => apagar tudo".
  • futuras limpezas de branch, worktree, stash, tag de archive ou patch temporario passam a exigir triagem por trecho de conteudo.
  • quando um arquivo misturar antigo + novo, a acao correta passa a ser montar uma nova versao com o melhor dos dois lados e so entao limpar o restante.

Validacoes executadas:

  • leitura integral e reconciliacao textual de AGENTS.md, docs/GIT_WORKFLOW.md, docs/BACKLOG.md e docs/LOCK.md
  • git diff --check

BK-104 · Fallback browser operacional para Facebook Page Events

Data: 17 de Marco de 2026

Contexto operacional:

  • a RCA do Graph API permaneceu a mesma: POST /{page_id}/events segue bloqueado com code 100 / subcode 33.
  • o desbloqueio real passou a depender do BK-129 no Cérebro, que agora ja possui:
  • storage state autenticado fora do repo;
  • probe autenticado validado;
  • create-flow em modo seguro sem publicacao.

Ajustes aplicados:

  • admin/cron/sync_facebook_page_events.php
  • quando o probe do Graph continuar bloqueado, o cron agora monta a fila operacional do fallback browser em admin/runtime/facebook_page_events_browser_queue.json;
  • o mesmo fluxo tambem grava um payload sample em admin/runtime/facebook_page_events_browser_sample_input.json;
  • o snapshot inclui:
  • profile_id;
  • storage_state_path;
  • output_root;
  • lista de pecas elegiveis com event_title, event_url, start_iso, description, venue_name e cover_image_url;
  • comandos prontos para probe e create-flow no Cérebro.
  • admin/modulos/campanhas/redes_sociais.php
  • a Central de Redes Sociais passou a exibir o status do fallback browser quando a fila runtime existir;
  • a tela agora mostra:
  • quantidade de itens preparados;
  • motivo do bloqueio do probe;
  • caminhos do snapshot/payload sample;
  • comandos exatos para rodar o browser control autenticado no Cérebro.

Resultado tecnico observado:

  • o BK-104 deixa de parar no diagnostico do Graph e passa a entregar um handoff operacional real para browser automation.
  • o admin continua sem publicar nada automaticamente na Meta por este caminho.
  • o create-flow fica seguro para endurecimento iterativo: primeiro draft/evidencia, publish apenas quando houver gate humano decidido.

Validacoes executadas:

  • php -l admin/cron/sync_facebook_page_events.php
  • php -l admin/modulos/campanhas/redes_sociais.php
  • php admin/cron/sync_facebook_page_events.php
  • confirmacao do snapshot runtime gerado em admin/runtime/facebook_page_events_browser_queue.json
  • git diff --check

BK-104 · Probe e hardening do cron de Facebook Page Events

Data: 17 de Marco de 2026

Contexto operacional:

  • o fluxo legado de Page Events ja tinha evidenciado leitura ok em GET /{page_id}/events, mas seguia falhando na criacao em POST /{page_id}/events com GraphMethodException code 100 / subcode 33.
  • a verificacao de runtime foi repetida no ambiente atual com a propria configuracao da pagina:
  • GET /{page_id}/events continua retornando eventos reais;
  • um POST /{page_id}/events propositalmente vazio, portanto incapaz de criar qualquer evento, devolve o mesmo Unsupported post request ... code 100 / subcode 33.
  • isso fecha a RCA do caminho Graph API atual: o bloqueio nao e apenas payload faltando, e sim operacao nao suportada/perdida para este contexto de pagina/token.

Ajustes aplicados:

  • admin/classes/FacebookEventService.php
  • ganhou probePageEventsCreateSupport() para testar o POST /{page_id}/events de forma segura, sem payload minimo de criacao;
  • o service agora classifica o retorno entre:
  • unsupported_endpoint;
  • permission_denied;
  • validation_error_only;
  • unknown_error.
  • admin/cron/sync_facebook_page_events.php
  • passou a rodar o probe antes de qualquer cleanup/create/update;
  • ganhou o modo probe_only=1;
  • quando o endpoint continua bloqueado, o cron aborta cedo com diagnostico explicito, sem fingir sucesso operacional e sem tentar mutacoes na pagina.
  • admin/modulos/campanhas/redes_sociais.php
  • o botao operacional foi reposicionado para:
  • FB Events (probe);
  • FB Events (sync);
  • a tela agora explica que o cron faz probe seguro antes de qualquer tentativa real.

Resultado tecnico observado:

  • o sistema deixa de tratar FB Events (real) como se o problema fosse apenas de payload.
  • o operador passa a ver um diagnostico honesto:
  • se o Graph API estiver morto, o cron para antes;
  • se algum dia a Meta voltar a aceitar o endpoint, o sync pode seguir sem remover esse hardening.
  • a conclusao pratica deste BK mudou:
  • desbloqueio via Graph API nao esta acontecendo neste caminho;
  • o proximo passo de automacao real para Page Events precisa migrar para browser automation/orquestracao pelo Cerebro.

Validacoes executadas:

  • php -l admin/classes/FacebookEventService.php
  • php -l admin/cron/sync_facebook_page_events.php
  • php -l admin/modulos/campanhas/redes_sociais.php
  • php admin/cron/sync_facebook_page_events.php probe_only=1
  • php admin/cron/sync_facebook_page_events.php
  • curl GET /{page_id}/events com token da pagina retornando eventos reais
  • curl POST /{page_id}/events sem payload minimo retornando GraphMethodException code 100 / subcode 33
  • git diff --check

BK-103 · TikTok no orquestrador social em modo manual/notificado

Data: 17 de Marco de 2026

Contexto operacional:

  • com a trilha mysql_* do admin adiada durante a venda ativa, o proximo passo operacional foi liberar o TikTok dentro do orquestrador social sem depender de publicacao automatica por API.
  • o objetivo deste BK foi encaixar TikTok na mesma fila social ja usada por Facebook/Instagram/Threads, mas com regra explicita de operacao manual/notificada.

Ajustes aplicados:

  • admin/modulos/facebook/agendamento.php
  • a agenda manual ganhou os canais TikTok Feed e TikTok Story;
  • o modal passou a forcar modo = notified para TikTok;
  • a UI passou a esconder o fluxo de preset story para TikTok, mantendo esse recurso apenas para stories Meta.
  • admin/modulos/facebook/agendamento-create.php
  • o endpoint passou a aceitar tiktok_feed e tiktok_story;
  • canais TikTok agora gravam a fila como manual_notified, mesmo que o operador tente auto.
  • admin/modulos/facebook/agendamento-events.php
  • calendario/filtros/labels/icones passaram a reconhecer TikTok.
  • admin/modulos/facebook/agendamento-update-status.php
  • itens TikTok em status = notified podem ser concluídos como posted_manual pela mesma ação da agenda.
  • admin/cron/sync_facebook_events.php
  • o dispatcher passou a ler itens due de tiktok_feed e tiktok_story;
  • quando chega o horario, o item vira notified e fica aguardando postagem manual;
  • o resumo final do cron passou a contabilizar TikTok notificados (manual).
  • admin/modulos/campanhas/redes_sociais.php
  • a Central de Redes Sociais passou a refletir TikTok em labels, resumos e na acao de trazer stories manuais para agora.

Resultado tecnico observado:

  • TikTok passa a existir como canal operacional da social_post_queue, sem introduzir automacao de publicacao fora do que o ambiente atual suporta.
  • o fluxo padrao deste BK fica:
  • agendar item TikTok na agenda manual;
  • dispatcher promover para notified no horario;
  • operador publicar no app e marcar como posted_manual.

Validacoes executadas:

  • php -l admin/modulos/facebook/agendamento.php
  • php -l admin/modulos/facebook/agendamento-create.php
  • php -l admin/modulos/facebook/agendamento-update-status.php
  • php -l admin/modulos/facebook/agendamento-events.php
  • php -l admin/cron/sync_facebook_events.php
  • php -l admin/modulos/campanhas/redes_sociais.php
  • extracao do JS inline de admin/modulos/facebook/agendamento.php + node --check
  • git diff --check
  • teste seguro em transacao do banco para o ciclo queued -> notified -> posted_manual com tiktok_feed, seguido de rollback

BK-102 · Complemento de tracking publico com GTM/dataLayer

Data: 17 de Marco de 2026

Contexto operacional:

  • com o BK-87 adiado durante venda ativa, a frente menos invasiva escolhida foi o BK-102, restrito ao tracking publico e sem tocar na migracao mysql_* remanescente do admin.
  • o objetivo foi complementar os eventos faltantes e padronizar a camada de dados para GTM/GA4 sem desmontar os fallbacks ja existentes de gtag.
  • por definicao operacional do projeto:
  • a margem real de referencia do negocio e 15% do bruto;
  • o valor enviado para Google/Facebook continua em 10% do bruto como sinal conservador de otimizacao de midia/ROI.

Ajustes aplicados:

  • meta-analytics.php
  • ganhou o helper canonico window.rntTrackGtmEvent;
  • o helper passa a:
  • clonar o payload recebido;
  • montar ecommerce automaticamente quando houver currency, value, items, transaction_id e coupon;
  • publicar no dataLayer com event = rnt_track_event e event_name separado, evitando acoplamento direto do GTM ao nome cru do evento;
  • manter fallback para window.gtag('event', ...).
  • peca.php
  • view_item foi migrado para o helper canônico, preservando o fallback de gtag.
  • pedido.php
  • begin_checkout publico foi migrado para o helper canônico, preservando o fallback de gtag.
  • evento.php
  • o clique de compra externa passou a usar o helper canônico no onclick, com fallback para gtag.
  • confirm_pagamento.php
  • begin_checkout de pagamento pendente e purchase de pagamento confirmado passaram a usar o helper canônico;
  • a regra de 10% ficou documentada como decisao deliberada de otimizacao de midia, e nao como margem operacional real.

Resultado tecnico observado:

  • o site passa a expor uma camada de dados publica mais previsivel para GTM, sem remover o comportamento atual de GA4/Ads.
  • os principais eventos de funil publico (view_item, begin_checkout, purchase) ficam padronizados no mesmo helper.
  • a nomenclatura deixa explicito no codigo que:
  • 10% e sinal conservador para a plataforma de midia;
  • 15% permanece como referencia financeira interna do negocio.

Validacoes executadas:

  • php -l meta-analytics.php
  • php -l peca.php
  • php -l pedido.php
  • php -l evento.php
  • php -l confirm_pagamento.php
  • node --check /tmp/bk102_tracking_inline_check.js
  • git diff --check
  • curl em HTML publico confirmando o helper rntTrackGtmEvent renderizado

BK-134 · RCA e hardening do fluxo pos-compra com cupom, assento e cancelamento

Data: 16 de Marco de 2026

Contexto operacional:

  • o incidente nasceu de uma compra real de teste com cupom de 100%, que expôs varios sintomas no mesmo fluxo:
  • pedido sem leitura clara de voucher/pago no admin;
  • cliente redirecionado para remarcar assento mesmo ja tendo assento salvo;
  • assento inicial permanecendo bloqueado apos solicitacao de cancelamento;
  • cancelamento pouco visivel no painel do cliente;
  • parceiro de cupom ainda preso ao hardcode KM de Vantagens.
  • a RCA confirmou que o desvio principal era de regra de negocio/estado entre modulos, nao um efeito primario da migracao mysql_* para mysqli.
  • por exigencia operacional do BK, backups versionados _bkp_20260316. foram criados antes das edicoes manuais dos arquivos centrais.

Ajustes aplicados:

  • config/functions_new.php
  • novos helpers para:
  • localizar pedido rascunho reaproveitavel no contexto do voucher;
  • localizar cupom por pedido;
  • reativar cupom dentro da validade;
  • liberar assentos de rascunhos duplicados;
  • calcular janela recente de reserva considerando o legado misto de timestamps locais/UTC.
  • action.php
  • register_voucher passou a:
  • validar melhor o cliente logado;
  • reaproveitar o pedido rascunho correto quando o cupom 100% vira compra gratis;
  • absorver assentos do rascunho duplicado para o pedido pago quando necessario;
  • preservar metadados do pedido/cupom;
  • devolver ao front a response.url correta para o proximo passo.
  • painel/modulos/pedidos/action.php
  • cancelamento de pedido VOUCHER passou a:
  • refletir cancelamento imediato;
  • limpar assentos;
  • reativar o cupom quando ainda estiver valido;
  • preservar metadados e marcar o foco de retorno para o painel.
  • painel/modulos/pedidos/index.php, painel/modulos/pedidos/assento.php, painel/modulos/pedidos/detalhe.php
  • o painel do cliente passou a refletir melhor cancelamento/foco;
  • a tela de assento usa a janela de reserva endurecida para nao tratar indevidamente o proprio pedido como nova selecao;
  • o detalhe do pedido passou a exibir Celular no lugar do telefone legado.
  • admin/modulos/pedidos_2/index.php e admin/modulos/pedidos_2/detalhe.php
  • o admin passou a exibir com mais clareza:
  • compra por Voucher;
  • cupom associado;
  • status efetivo de Solicitado Cancelamento ou Cancelada.
  • js/voucher.js
  • o fluxo free passou a respeitar a response.url decidida pelo backend, em vez de sempre mandar o cliente para o painel generico.
  • admin/modulos/cupons/index.php, admin/modulos/cupons/action.php, admin/js/cupom.js
  • o hardcode KM de Vantagens foi trocado para RIONOTEATRO como parceiro padrao (001);
  • o modulo ganhou criacao/exclusao de parceiros dinamicos;
  • o backend deixou de mutilar delete_partner por causa de anti_injection(), usando sanitizacao propria para act/id;
  • o front passou a tratar erro AJAX sem deixar o modal travado em Excluindo Parceiro.

Resultado funcional observado:

  • o pedido pago por voucher deixa de competir com rascunho paralelo por assento.
  • o fluxo de cancelamento de voucher passa a:
  • liberar assento;
  • refletir melhor o estado no painel/admin;
  • devolver o cupom dentro da validade, conforme regra definida na RCA.
  • o modulo de cupons deixa de depender de parceiro hardcoded e volta a responder corretamente no ciclo criar -> excluir parceiro.
  • saneamento do incidente real validado no banco:
  • pedidos 73616 e 73617 ficaram cancelados e sem assentos presos;
  • cupom 12899 voltou para status=1.

Validacoes executadas:

  • php -l action.php
  • php -l config/functions_new.php
  • php -l painel/modulos/pedidos/action.php
  • php -l painel/modulos/pedidos/index.php
  • php -l painel/modulos/pedidos/assento.php
  • php -l painel/modulos/pedidos/detalhe.php
  • php -l admin/modulos/pedidos_2/index.php
  • php -l admin/modulos/pedidos_2/detalhe.php
  • php -l admin/modulos/cupons/index.php
  • php -l admin/modulos/cupons/action.php
  • node --check js/voucher.js
  • node --check admin/js/cupom.js
  • git diff --check
  • reproducao controlada do endpoint de parceiros com sessao admin sintetica:
  • create_partner retornando JSON de sucesso;
  • delete_partner retornando JSON de sucesso apos a correcao do act.

BK-101 · Radar operacional de recomendacao social

Data: 16 de Marco de 2026

Contexto operacional:

  • depois do fechamento do BK-95, a referencia canonica registrada no backlog com base no protocolo do AGENTS.md apontava BK-92 -> BK-95 -> BK-101 como sequencia imediata de retomada.
  • a operacao social ja possuia historico tecnico em social_post_metrics_history, mas ainda faltava transformar esse volume em leitura pratica dentro da propria Central de Redes Sociais.
  • o objetivo do BK-101 foi entregar um MVP de recomendacao orientado por evidencias reais de performance, sem criar cron novo nem depender de camada externa de BI.

Ajustes aplicados:

  • admin/modulos/campanhas/redes_sociais.php
  • ganhou o estado $social_recommendation_snapshot e helpers para:
  • inferir perfil/tipo da peca por generos e fallback em categoria;
  • normalizar labels de canal, formato, dia da semana e janela de postagem;
  • calcular score operacional combinando alcance, engajamento e interacoes relevantes do snapshot.
  • ganhou o builder rnt_social_build_recommendation_snapshot(...), que:
  • verifica a existencia de social_post_metrics_history;
  • puxa o ultimo snapshot conhecido por item de fila/post;
  • consolida amostras por peca/perfil;
  • ranqueia melhor canal, melhor janela e melhor formato com base na media de score;
  • produz linhas de recomendacao por peca com indicador de confianca.
  • a aba Operacao Social passou a renderizar o painel BK-101 · Radar de Recomendacao, com:
  • cards de canal lider, janela lider, formato lider e cobertura da amostra;
  • tabela por peca/perfil contendo fonte da recomendacao, melhor horario e nivel de confianca;
  • fallback textual quando ainda nao existir historico suficiente.

Resultado funcional observado:

  • a Central de Redes Sociais passa a exibir um resumo operacional reutilizavel a partir do historico real ja salvo pelo BK-100, sem obrigar a operacao a consultar banco manualmente.
  • o painel destaca qual canal/janela/formato estao performando melhor no recorte conhecido e ainda preserva leitura por peca/perfil para orientar publicacao assistida.
  • quando a base historica ainda nao for suficiente, o modulo nao quebra: exibe aviso explicando a falta de cobertura.

Validacoes executadas:

  • php -l admin/modulos/campanhas/redes_sociais.php
  • git diff --check
  • inspecao estrutural do modulo apos o merge para confirmar:
  • presenca do painel BK-101 · Radar de Recomendacao;
  • uso do ultimo snapshot conhecido por post em social_post_metrics_history
  • tentativa de smoke de runtime via bootstrap/CLI do modulo:
  • bloqueada por autenticacao MySQL do bootstrap do admin neste contexto de terminal, entao a validacao de runtime ficou parcial

BK-95 · Healthcheck WhatsApp em 5 min com cron canônico reconciliado

Data: 16 de Marco de 2026

Contexto operacional:

  • apos o fechamento do BK-92, a priorizacao do dia foi reaplicada no backlog com base no protocolo de triagem/execucao do AGENTS.md, puxando o BK-95 como proxima frente de operacao/venda.
  • a auditoria do agendamento mostrou que o healthcheck real nao estava mais no desenho documentado:
  • o job canonico do aaPanel (crontab.id=24, wrapper 86f580c4ec3c598ee051b7783bacf704) rodava de hora em hora, com bash;
  • havia uma linha manual duplicada no root para 0 /2 /www/wwwroot/rionoteatro.com.br/bot/whatsapp/healthcheck.sh, mas ela falhava com Permission denied;
  • o watchdog_systemd.sh ja estava agendado a cada 5 minutos, porem tambem falhava com Permission denied por falta de interpretador explicito na linha manual.
  • o efeito pratico era drift operacional: a documentacao falava em 2h, o runtime executava 1h pelo aaPanel e os jobs manuais geravam ruido de erro sem entregar o comportamento esperado.

Ajustes aplicados:

  • runtime aaPanel / cron
  • o job WhatsApp Healthcheck do aaPanel (id=24) foi reconciliado para minute-n com cadencia de 5 minutos, preservando o wrapper oficial do painel e a execucao via bash;
  • a linha manual duplicada de 2h para healthcheck.sh foi removida do crontab do root;
  • a linha manual do watchdog_systemd.sh foi mantida em */5, mas passou a chamar bash /www/wwwroot/rionoteatro.com.br/bot/whatsapp/watchdog_systemd.sh para parar o Permission denied.
  • docs/BACKLOG.md
  • ganhou nota explicita de repriorizacao do BK-95, referenciando o gate de triagem do AGENTS.md.
  • bot/whatsapp/SETUP_RESILIENCIA.md
  • passou a documentar healthcheck canonico a cada 5 minutos via aaPanel com bash, incluindo a recomendacao de nao manter jobs paralelos duplicados;
  • passou a registrar o watchdog_systemd.sh tambem com invocacao via bash.
  • docs/documentacao_tecnica/WHATSAPP_HELPER.md
  • foi atualizado para refletir o runtime atual via systemd, o papel do healthcheck como job de 5 minutos e o disparo sob demanda feito pelo helper PHP.

Resultado funcional observado:

  • o healthcheck passou a ter uma unica fonte canonica persistente no aaPanel para a cadencia de 5 minutos;
  • o cron manual deixou de carregar a linha redundante quebrada de 2h;
  • o watchdog de 5 minutos deixou de depender do bit de execucao do arquivo para rodar no cron manual.

Validacoes executadas:

  • sqlite3 /www/server/panel/data/default.db "select id,name,type,where1,where_hour,where_minute from crontab where id=24;"
  • crontab -l | rg "86f580c4ec3c598ee051b7783bacf704|healthcheck\\.sh|watchdog_systemd\\.sh"
  • bash /www/server/cron/86f580c4ec3c598ee051b7783bacf704
  • bash /www/wwwroot/rionoteatro.com.br/bot/whatsapp/watchdog_systemd.sh
  • tail -n 20 /www/wwwroot/rionoteatro.com.br/bot/whatsapp/healthcheck.log
  • tail -n 20 /www/server/cron/rnt_whatsapp_watchdog.log
  • git diff --stat

BK-92 · Hardening operacional do stack WhatsApp e notify_state

Data: 16 de Marco de 2026

Contexto operacional:

  • A priorizacao do dia foi registrada no backlog conforme o protocolo de triagem/execucao do AGENTS.md, com foco imediato em operacao/venda no item BK-92.
  • A evidencia coletada antes da alteracao mostrou que o servico rnt-whatsapp estava ativo e enviando, porem com housekeeping incompleto:
  • fila zerada;
  • 151 locks anti-spam com mais de 24h em bot/whatsapp/locks/;
  • 4 arquivos ativos em admin/cron/logs/notify_state/, sem excesso no momento.
  • O bot/whatsapp/healthcheck.sh dependia de uma API_KEY hardcoded, criando acoplamento fragil com o runtime real do microservico.

Ajustes aplicados:

  • docs/BACKLOG.md
  • ganhou nota explicita de priorizacao do BK-92, referenciando o fluxo orientado por AGENTS.md.
  • includes/whatsapp_helper.php
  • ganhou limpeza defensiva de locks anti-spam expirados, com cooldown para nao escanear o diretorio a cada envio.
  • bot/whatsapp/healthcheck.sh
  • passou a carregar BOT_WHATSAPP_API_KEY/WPP_API_KEY do ambiente e/ou .env, removendo dependencia de chave hardcoded no shell;
  • passou a limpar locks anti-spam expirados e arquivos temporarios antigos de fila antes do processamento.
  • admin/cron/cron_whatsapp_notify.php
  • passou a executar auto-limpeza defensiva de notify_state no proprio fluxo de debounce, reduzindo dependencia exclusiva do cron noturno.
  • admin/cron/limpar_notify_state.php
  • foi endurecido com configuracao por ambiente, guarda de realpath, ignorando symlinks e reportando melhor o que foi preservado/removido.

Resultado funcional observado:

  • o healthcheck passou a autenticar usando a chave vigente do runtime, sem depender de segredo hardcoded em shell versionado;
  • a execucao manual segura do healthcheck removeu 149 locks anti-spam expirados de uma vez, reduzindo o diretorio para 12 locks totais (10 recentes e 2 ainda dentro da margem de seguranca de 25h);
  • o notify_state ficou sob duas camadas de limpeza: auto-higiene no helper e cron dedicado como retaguarda, sem remover os 4 arquivos ainda dentro da janela valida.

Validacoes executadas:

  • git status -sb
  • systemctl status rnt-whatsapp --no-pager -n 40
  • contagem objetiva de runtime:
  • find bot/whatsapp/locks ... -> 151 locks com mais de 24h antes do hardening
  • find admin/cron/logs/notify_state ... -> 4 arquivos ativos no recorte inicial
  • php -l includes/whatsapp_helper.php
  • php -l admin/cron/cron_whatsapp_notify.php
  • php -l admin/cron/limpar_notify_state.php
  • bash -n bot/whatsapp/healthcheck.sh
  • execucao manual segura:
  • bash bot/whatsapp/healthcheck.sh
  • php admin/cron/limpar_notify_state.php
  • php -r 'require "admin/cron/cron_whatsapp_notify.php"; ... cronNotifyCleanupState(...)'

BK-133 · Auditoria do archive/20260209 e plano de resgate

Data: 15 de Marco de 2026

Contexto operacional:

  • Durante a limpeza de branches antigas do projeto, foi preservado no GitHub o snapshot archive/20260209-stash-fix-url-ajax-teatros, originado de um stash antigo que ainda nao tinha sido auditado contra a main.
  • O protocolo novo passou a exigir auditoria de conteudo arquivo a arquivo antes de apagar branch, worktree ou stash, entao o snapshot foi tratado como material potencialmente valioso, nao como lixo operacional.
  • Na sequencia, o mesmo protocolo foi aplicado tambem ao snapshot archive/20260206-stash-ci-caminho-remoto-deploy, para permitir limpeza final das branches remotas sem perda de historico.

Evidencia objetiva coletada:

  • branch auditada: archive/20260209-stash-fix-url-ajax-teatros
  • commit preservado: 764f0849c303bc1c4519d60da2e5ffbfe0e61430
  • data original do commit arquivado: 2026-02-09 08:16:02 +0000
  • push de preservacao no GitHub: 2026-03-15 19:26:15 -0300
  • comparacao contra a main atual:
  • 34.766 caminhos tocados pelo snapshot;
  • 1 caminho igual na main atual (admin/data/setup.php);
  • 14.386 caminhos com conteudo/modo diferente da main;
  • 20.379 caminhos existentes apenas no snapshot arquivado.

Blocos candidatos a resgate dirigido:

  • Roteamento publico / AJAX / admin-data: action.php, index.php, admin/data/Fotos.php, admin/data/Login.php, admin/data/__CLASS.SQL.php, admin/data/config.php, admin/data/functions.php, admin/data/urlAmigavel.php
  • Bot captador / Sympla: bot/cron_captador.php, bot/scrapers/sympla_scraper.php
  • Referencias auxiliares do snapshot: README.md, action.php.bak, index.php_bkp

Blocos explicitamente bloqueados para merge cego:

  • vendor/ (20.152 caminhos, todos apenas no snapshot)
  • admin/outros/ (9.910 caminhos, quase todos divergentes da main)
  • painel/, img/, apps/, produtor/, PagSeguroLibrary/, phpmailer/ e legado amplo do front/admin
  • arquivos de infra/estaticos (.well-known/, paginas de erro, modos de arquivo, backups velhos) sem incidente atual associado

Decisao operacional:

  • o snapshot continua preservado em branch propria no GitHub e localmente;
  • foi aberto o BK-133 para resgate dirigido, com escopo inicial restrito aos arquivos de maior sinal funcional;
  • qualquer recuperacao de codigo a partir desse snapshot deve acontecer por revisao manual de diff, arquivo a arquivo, nunca por merge amplo do snapshot.

Resultado final da auditoria:

  • no archive/20260209, o bloco action.php/index.php/admin/data/* auditado nao trouxe correcao funcional reaproveitavel; o que apareceu ali foi majoritariamente drift historico e mudanca de modo.
  • as mudancas funcionais encontradas em bot/cron_captador.php e bot/scrapers/sympla_scraper.php nao foram portadas para a main, porque o diff contra a main atual mostrou regressao para uma versao mais antiga do captador/scraper.
  • no archive/20260206, a auditoria apontou um snapshot muito amplo e historico, sem recorte seguro para merge automatico.
  • como destino final, ambos os snapshots foram preservados por tags Git:
  • archive-20260209-stash-fix-url-ajax-teatros
  • archive-20260206-stash-ci-caminho-remoto-deploy
  • com isso, as branches remotas archive/... ficaram aptas a delecao sem perda de rastreabilidade.

Documento de apoio:

  • ver docs/BK-133-RELATORIO-RESGATE-ARCHIVE-20260209.md

Validacoes executadas:

  • git show -s --format='%H%n%ci%n%cn%n%s' archive/20260209-stash-fix-url-ajax-teatros
  • git reflog show --date=iso refs/remotes/origin/archive/20260209-stash-fix-url-ajax-teatros
  • git rev-list --left-right --count main...archive/20260209-stash-fix-url-ajax-teatros
  • auditoria por arvore Git (blobs/modos) comparando os 34.766 caminhos tocados pelo snapshot contra a main
  • git diff --stat main archive/20260209-stash-fix-url-ajax-teatros -- README.md action.php admin/data/Fotos.php admin/data/Login.php admin/data/__CLASS.SQL.php admin/data/config.php admin/data/functions.php admin/data/urlAmigavel.php admin/data/setup.php bot/cron_captador.php bot/scrapers/sympla_scraper.php index.php
  • git show -s --format='%H%n%ci%n%cn%n%s' archive/20260206-stash-ci-caminho-remoto-deploy
  • git reflog show --date=iso refs/remotes/origin/archive/20260206-stash-ci-caminho-remoto-deploy
  • auditoria por arvore Git comparando os 37.638 caminhos do snapshot 20260206 contra a main
  • git tag -a archive-20260209-stash-fix-url-ajax-teatros ...
  • git tag -a archive-20260206-stash-ci-caminho-remoto-deploy ...

BK-132 · Console de cancelamento por sessao em Peças

Data: 14 de Marco de 2026

Contexto operacional:

  • O usuario pediu uma ferramenta no admin de Peças para cancelar apenas uma sessao especifica, sem mexer no espetaculo inteiro e sem disparos automaticos fora de confirmacao explicita.
  • O fluxo precisava separar dois grupos no momento do aviso:
  • compradores com status 3/4, com mensagem editavel antes do envio;
  • pedidos sem pagamento concluido, com mensagem padrao.
  • O ponto mais importante desta entrega foi governanca:
  • nao executar nada sozinho;
  • exigir preview antes de liberar o envio final.

Ajustes aplicados:

  • admin/modulos/pecas/index.php
  • nova area Central de Cancelamento de Sessão dentro da propria tela de Peças.
  • fluxo com:
  • seletor de espetaculo;
  • seletor de sessao;
  • textarea editavel para compradores 3/4;
  • textarea padrao para pedidos nao concluidos;
  • resumo visual com contagem de pedidos travaveis, clientes por grupo e avisos de celular ausente.
  • o botao final fica desabilitado ate existir uma pre-visualizacao valida.
  • admin/modulos/pecas/action.php
  • novos acts:
  • cancelamento_list_sessions
  • cancelamento_preview
  • cancelamento_execute
  • o envio/travamento ficou encapsulado em endpoint proprio, separado dos fluxos antigos do modulo.
  • admin/modulos/pecas/session_cancel_console_helper.php
  • novo helper para:
  • listar pecas/sessoes elegiveis;
  • consultar pedidos por sessao;
  • agrupar destinatarios por cliente;
  • separar paid, unpaid e ignored;
  • aplicar status_manual + status_transacao=2 somente quando o operador confirmar.
  • query de sessoes endurecida para nao inflar abertos em sessoes sem pedido.
  • texto default dos compradores ficou neutro/editavel, sem sugestao automatica de desfecho.

Resultado funcional:

  • O admin passa a conseguir preparar cancelamento por sessao sem sair de admin/modulos/pecas/index.php.
  • O operador ve antes do envio:
  • quantos pedidos serao travados;
  • quantos clientes estao em 3/4;
  • quantos sao pedidos nao concluidos;
  • quantos estao sem celular;
  • quem ficou fora da regra.
  • O envio final continua manual e confirmado.

Validacoes executadas:

  • php -l admin/modulos/pecas/session_cancel_console_helper.php
  • php -l admin/modulos/pecas/action.php
  • php -l admin/modulos/pecas/index.php
  • extracao do JS inline de admin/modulos/pecas/index.php + node --check
  • teste funcional seguro do preview:
  • listagem real de sessoes por event_id
  • chamada real do act=cancelamento_preview para a sessao 2026-03-14 19:00:00
  • sem disparo de mensagens nem update de pedidos nesta validacao

BK-131 · Agenda social story-first + resiliencia do Rodar Squad

Data: 14 de Marco de 2026

Contexto operacional:

  • A operacao relatou dois desvios no mesmo fluxo social:
  • o botao Rodar Squad na aba #tab-squad parecia nao responder quando a tela ainda estava conectando/bootstrapando;
  • o planner social seguia empilhando feed recorrente por sessao, mesmo com a regra operacional atual pedindo Story como padrao e feed apenas como excecao.
  • Na agenda manual, a criacao de nova postagem ainda abria em facebook_feed, empurrando a operacao para o canal errado.
  • Na sequencia, a operacao acrescentou mais duas exigencias do mesmo eixo:
  • o gatilho last_tickets precisava avisar o admin no WhatsApp a cada novo marco de 10 ingressos vendidos para a proxima sessao;
  • a segunda-feira precisava ganhar um carrossel semanal multi-peca em feed, sem depender de montagem manual fora do sistema.

Ajustes aplicados:

  • admin/modulos/campanhas/redes_sociais_squad.js
  • a intencao de start passou a ficar pendente enquanto bootstrap/WebSocket nao estao prontos.
  • ao concluir a conexao, a run e enviada automaticamente sem exigir novo clique do operador.
  • erros de run_ack limpam o estado pendente para evitar reenvio fantasma.
  • admin/modulos/campanhas/redes_sociais.php
  • cache-buster do asset redes_sociais_squad.js atualizado para forcar o admin a carregar a versao corrigida.
  • admin/modulos/facebook/agendamento.php
  • o modal Nova postagem agora abre em instagram_story com modo auto.
  • os canais de Story foram movidos para o topo do seletor.
  • o texto operacional passou a reforcar que Story automatico e o padrao; notified fica reservado para casos de sticker/link manual.
  • admin/cron/sync_facebook_events.php
  • o planner entrou em politica story-first.
  • a fila legada planner_session de feed recorrente passou a ser cancelada automaticamente para nao continuar gerando tentativas desalinhadas.
  • foi criado o planejamento planner_site_entry, que preserva apenas um feed automatico inicial por peca/canal quando ainda nao existe registro valido (queued, scheduled ou posted).
  • o restante da pressao operacional continua nos gatilhos automaticos de Story que ja existiam no cron.
  • admin/cron/sync_facebook_events.php
  • o gatilho last_tickets passou a levar milestone, sold e horario de sessao no payload.
  • foi criado maybeNotifyLastTicketsMilestone() para enviar alerta ao admin via WhatsApp helper quando a proxima sessao atinge novo marco de 10+ ingressos vendidos.
  • foi criado o tipo de fila planner_weekly_carousel, com agendamento automatico para segunda-feira 09:05.
  • o dispatcher passou a montar e publicar o carrossel semanal diretamente da fila, em facebook_feed e instagram_feed.
  • os slides do carrossel passaram a ser compostos em JPG pelo proprio cron, agrupando varias pecas por pagina para caber toda a semana no limite das plataformas.
  • admin/classes/FacebookEventService.php
  • ganhou createFeedCarouselPost() para publicar album/carrossel de varias imagens no Facebook Page Feed.
  • ganhou createInstagramCarousel() para criar container CAROUSEL no Instagram e publicar depois via media_publish.
  • admin/modulos/facebook/agendamento-events.php
  • a agenda passou a renderizar o item semanal com nome amigavel (Carrossel semanal) e imagem institucional, em vez de exibir Peca #0.
  • docs/BACKLOG.md
  • foi aberto o BK-110 para a camada editorial/manual do carrossel semanal, cobrindo override de curadoria, ordem e slide de abertura.

Resultado funcional:

  • O Rodar Squad deixa de depender do timing exato entre clique e conexao do WebSocket.
  • A agenda manual passa a nascer no fluxo esperado pela operacao social atual.
  • O cron deixa de insistir em feed recorrente por sessao e reduz o ruido que estava alimentando erro em fila para um comportamento mais proximo da regra de negocio pedida.
  • Quando a proxima sessao cruza um novo patamar de venda (10, 20, 30...), o admin recebe alerta para preparar o Story de urgencia.
  • A segunda-feira passa a ter carrossel semanal multi-peca planejado e publicado automaticamente no feed, com asset composto pelo backend e rastreio na mesma social_post_queue.

Validacoes executadas:

  • php -l admin/classes/FacebookEventService.php
  • php -l admin/cron/sync_facebook_events.php
  • php -l admin/modulos/facebook/agendamento.php
  • php -l admin/modulos/facebook/agendamento-events.php
  • php -l admin/modulos/campanhas/redes_sociais.php
  • node --check admin/modulos/campanhas/redes_sociais_squad.js
  • extracao do JS inline de admin/modulos/facebook/agendamento.php + node --check

BK-100 · Metricas por postagem social com historico por item de fila

Data: 13 de Marco de 2026

Contexto operacional:

  • A fila social (social_post_queue) ja persistia platform_post_id, mas nao existia historico de snapshots por item nem qualquer leitura de metricas por postagem no admin.
  • A agenda social mostrava apenas status operacional e Post ID, o que resolvia publicacao/triagem, mas nao permitia comparar alcance, engajamento ou resposta real por canal.
  • Havia ainda uma aresta importante no fluxo manual/notificado:
  • quando o operador conclui sem informar ID real, a referencia continua como manual:*, o que precisa ser tratado como nao mensuravel em vez de quebrar o coletor.

Ajustes aplicados:

  • admin/cron/sync_social_metrics.php
  • novo cron dedicado para coletar metricas sociais sem inflar ainda mais o sync_facebook_events.php.
  • cria/garante social_post_metrics_history e grava snapshot diario por queue_id.
  • processa posted, posted_manual e scheduled ja vencidos, com janela de seguranca para itens agendados na plataforma.
  • trata referencias manual:* e URLs soltas como skipped, preservando rastreabilidade sem gerar erro falso.
  • admin/classes/FacebookEventService.php
  • novos leitores de metricas por canal para Facebook, Instagram e Threads.
  • leitura por metrica individual para tolerar respostas parciais da API sem derrubar o snapshot inteiro.
  • payload bruto das plataformas passou a ser preservado junto do resumo normalizado.
  • admin/modulos/facebook/agendamento-events.php
  • agenda passa a anexar o ultimo snapshot de social_post_metrics_history por item da fila.
  • tooltip/modal agora mostram resumo das metricas disponiveis e usam permalink real da plataforma quando retornado.
  • admin/modulos/facebook/agendamento.php
  • CTA do modal foi generalizado para Ver na plataforma.
  • prompt de conclusao manual passou a deixar explicito que o ID real ajuda a manter o item mensuravel.
  • sql_updates/create_social_post_metrics_history.sql
  • script documental da nova tabela de historico de metricas.

Resultado funcional:

  • Cada item elegivel da fila social pode acumular snapshots diarios de desempenho, em vez de sobrescrever um unico estado final.
  • O operador passa a ver, na propria agenda social, se o item ja tem alcance/engajamento/resposta e quando a ultima coleta aconteceu.
  • O coletor continua robusto em cenarios incompletos:
  • ok quando houve retorno normal;
  • partial quando parte das metricas volta e outra parte falha;
  • skipped quando a referencia nao e mensuravel (manual:* ou URL sem resolucao);
  • error quando a API nao entrega retorno utilizavel.

Validacoes executadas:

  • php -l admin/classes/FacebookEventService.php
  • php -l admin/cron/sync_social_metrics.php
  • php -l admin/modulos/facebook/agendamento-events.php
  • php -l admin/modulos/facebook/agendamento.php
  • extracao do JS inline de admin/modulos/facebook/agendamento.php + node --check
  • execucao controlada do novo cron via injecao do .env da arvore viva:
  • php admin/cron/sync_social_metrics.php --limit=5
  • resultado do primeiro run: sem candidatos elegiveis dentro do limite consultado

BK-129 · Fundo do carrossel + flutuacao mobile em evento

Data: 13 de Marco de 2026

Contexto operacional:

  • O ajuste visual anterior do carrossel em evento.php melhorou contraste e CTA, mas o usuario percebeu que a faixa escura ainda parecia curta por tras do bloco.
  • Na mesma rodada, tambem foi pedido para o carrossel continuar acompanhando a rolagem na versao mobile da pagina de evento.

Ajustes aplicados:

  • evento.php
  • .rnt-event-carousel-container recebeu box-sizing: border-box, min-height: 170px e padding maior para a faixa escura cobrir melhor a altura visual do carrossel.
  • o ajuste responsivo em max-width: 991px passou a manter a faixa com min-height: 150px e padding proprio, em vez de apenas resetar o wrapper.
  • .rnt-event-carousel-inner passou a ocupar width: 100%, com respiro lateral proprio tambem no mobile.
  • a funcao atualizarEstadoFlutuanteCarrosselEvento deixou de desligar o modo flutuante em telas <= 991px.

Resultado funcional:

  • A faixa escura do carrossel fica mais consistente por tras dos cards, evitando a sensacao de fundo "curto".
  • O carrossel de ofertas da pagina de evento continua acompanhando o scroll tambem no mobile, seguindo a mesma logica de placeholder e offset do header.

Validacoes executadas:

  • php -l evento.php
  • extracao do trecho JS de sincronizarCarrosselEvento + node --check
  • confirmacao por diff da remocao da guarda window.innerWidth <= 991

BK-105 · Hardening do fluxo manual Story na agenda social

Data: 13 de Marco de 2026

Contexto operacional:

  • O fluxo de Story com sticker/link ja passava por notified, mas a conclusao manual ainda gravava posted, misturando no historico itens manuais com publicacoes feitas por API/agendamento.
  • O endpoint agendamento-update-status.php aceitava mark_posted_manual apenas com queue_id, sem validar no servidor se o item ainda estava em status=notified.

Ajustes aplicados:

  • admin/modulos/facebook/agendamento-update-status.php
  • passou a validar canal de Story + tipo manual/notificado + status = 'notified' antes de concluir a publicacao manual.
  • status final passou de posted para posted_manual.
  • last_error e limpo na conclusao manual bem-sucedida.
  • admin/modulos/facebook/agendamento-events.php
  • agenda ganhou o status/filtro posted_manual como classe propria do historico.
  • admin/modulos/facebook/agendamento.php
  • filtro visual passou a expor Publicado manualmente.
  • docs/BACKLOG.md
  • BK-97 foi removido da lista ativa porque ja estava fechado no historico/codigo.
  • a subtarefa remanescente do BK-105 foi marcada como concluida com o fluxo oficial queued -> notified -> posted_manual.

Resultado funcional:

  • Stories manuais deixam de aparecer como posted generico na fila social.
  • A trilha operacional fica mais auditavel:
  • queued para fila;
  • notified para acao manual aguardando operador;
  • posted_manual somente depois da confirmacao humana.
  • O bypass direto de queued para conclusao manual deixa de ser aceito pelo endpoint.

Validacoes executadas:

  • php -l admin/modulos/facebook/agendamento-update-status.php
  • php -l admin/modulos/facebook/agendamento-events.php
  • php -l admin/modulos/facebook/agendamento.php
  • validacao do JS inline de admin/modulos/facebook/agendamento.php por extracao do bloco <script> + node --check

BK-128 · Workspace Squad Social na Central de Redes Sociais

Data: 13 de Marco de 2026

Contexto operacional:

  • O runtime multiagente do Cerebro ja estava pronto para squads, mas o operador do RNT ainda precisava sair do admin para iniciar e revisar runs.
  • A necessidade desta fase era abrir um workspace proprio dentro de redes_sociais.php para:
  • briefing humano;
  • escolha de template/modo;
  • historico de runs;
  • decisao humana (approve / reject / rerun) sem sair da Central de Redes Sociais.
  • O escopo foi mantido pragmatico:
  • publish continua assistido;
  • esta entrega nao automatiza a publicacao final dentro do RNT.

Ajustes aplicados:

  • admin/modulos/campanhas/redes_sociais.php
  • nova aba Squad Social adicionada ao lado de Operacao Social, Videos IA e Texto Audio Story.
  • novo workspace com:
  • seletor de peca elegivel;
  • seletor de template do squad;
  • modos economico e alta_performance;
  • engines Codex, Gemini e Kimi;
  • briefing persistido em localStorage;
  • historico de runs da workspace;
  • console com resumo da run, etapas, artefatos e timeline;
  • acoes manuais approve, reject e rerun.
  • admin/modulos/campanhas/social_squad_helper.php
  • novo helper JSON para o front da aba.
  • expõe:
  • bootstrap do Squad (wsUrl, defaults, namespace de sessao, dashboardUrl);
  • contexto leve da peca selecionada (local, bairro, proxima sessao, preco/oferta e briefing base).
  • faz fallback do endpoint WS para wss://cerebro.seuimovel.rio.br/api/gateway quando nenhum env especifico estiver definido.

Resultado funcional:

  • O operador passa a conseguir iniciar uma run do Squad Social sem sair do admin do RNT.
  • A mesma tela agora mostra:
  • templates disponiveis do projeto rionoteatro;
  • runs recentes da workspace atual;
  • console por etapa com timeline e artefatos;
  • decisao humana de aprovacao ou devolucao para rework.
  • O fluxo continua seguro para esta fase porque o publish segue assistido, sem automacao cega de postagem final.

Validacoes executadas:

  • php -l admin/modulos/campanhas/redes_sociais.php
  • php -l admin/modulos/campanhas/social_squad_helper.php
  • node --check admin/modulos/campanhas/redes_sociais_squad.js

BK-127 · Ajuste visual do carrossel em evento

Data: 13 de Marco de 2026

Contexto operacional:

  • Depois da correcao funcional do carrossel flutuante em evento.php, o usuario pediu apenas refinamento visual.
  • Os dois pedidos foram objetivos:
  • aplicar fundo escuro com transparencia na div do carrossel;
  • reduzir a tipografia do CTA QUERO ESTA OFERTA.

Ajustes aplicados:

  • evento.php
  • .rnt-event-carousel-container passou a usar background: rgba(8, 16, 28, 0.86).
  • foi adicionado backdrop-filter: blur(10px) para reforcar a leitura do bloco enquanto ele flutua sobre a pagina.
  • o seletor .rnt-event-carousel-container .btn.btn-success passou a aplicar font-size: 11px.

Resultado funcional:

  • O carrossel continua acompanhando o header/menu, mas agora com contraste visual mais claro como faixa destacada.
  • O CTA interno fica menos pesado visualmente e ocupa menos area horizontal.

Validacoes executadas:

  • php -l evento.php
  • validacao HTTP real da URL https://rionoteatro.com.br/se-der-corda
  • confirmada a presenca de:
  • background: rgba(8, 16, 28, 0.86)
  • font-size: 11px

BK-126 · Correcao do carrossel flutuante em evento

Data: 13 de Marco de 2026

Contexto operacional:

  • A URL https://rionoteatro.com.br/se-der-corda foi usada como validacao real apos o merge do BK-125.
  • O HTML mostrava o wrapper novo do carrossel, mas no navegador ele ainda nao acompanhava o menu durante o scroll.

Causa raiz confirmada:

  • evento.php
  • o wrapper do carrossel estava em position: sticky;
  • mas, na pratica, ele ficava limitado ao #carousel-placeholder, que nao oferecia area de rolagem suficiente para sustentar o efeito ao longo da pagina.
  • Resultado:
  • o carrossel parecia normal/estatico mesmo com as classes e scripts presentes.

Ajustes aplicados:

  • evento.php
  • o modo flutuante passou a usar position: fixed quando o topo do placeholder e ultrapassado.
  • o #carousel-placeholder passou a reservar dinamicamente a altura do carrossel (minHeight) para evitar salto de layout.
  • a funcao window.sincronizarCarrosselEvento foi exposta e continua recalculando:
  • altura do header;
  • offset do topo;
  • estado flutuante;
  • altura reservada.
  • no mobile o comportamento continua estatico.

Resultado funcional:

  • O carrossel de evento.php deixa de depender do limite estrutural do placeholder.
  • Em desktop, ele passa a acompanhar de fato o header/menu durante a rolagem.
  • O conteudo abaixo nao "sobe" quando o bloco entra em modo flutuante.

Validacoes executadas:

  • php -l evento.php
  • validacao HTTP real da URL https://rionoteatro.com.br/se-der-corda
  • confirmada no HTML a presenca de:
  • window.sincronizarCarrosselEvento
  • placeholder.style.minHeight
  • position: fixed no estado .is-floating

BK-125 · Carrossel flutuante na pagina de evento

Data: 13 de Marco de 2026

Contexto operacional:

  • O usuario pediu explicitamente para mexer apenas em evento.php.
  • O comportamento desejado era manter o carrossel acompanhando o menu/header quando a pagina de evento fosse rolada para baixo.
  • O carrossel ja existia no topo da pagina, mas era apenas injetado no placeholder e desaparecia completamente durante a navegacao pelo restante do conteudo.

Ajustes aplicados:

  • evento.php
  • criado um wrapper dedicado do carrossel (#rnt-event-carousel-shell) exclusivo da pagina de evento.
  • o wrapper passou a usar position: sticky somente em desktop.
  • o top nao ficou fixo em valor magico: um script passa a recalcular --rnt-event-carousel-offset a partir da altura real do .rnt-header.
  • listeners de load, resize e scroll mantem o offset sincronizado com o estado do header, inclusive quando ele encolhe no modo scrolled.
  • o carrossel ganha sombra leve ao entrar em estado flutuante para reforcar visualmente que esta acompanhando o topo.
  • em mobile (max-width: 991px) o comportamento volta para estatico para evitar um bloco grande ocupando a viewport.

Resultado funcional:

  • Em desktop, o carrossel da pagina de evento passa a acompanhar o header/menu durante a rolagem.
  • O offset acompanha a altura real do header para evitar colisao visual.
  • Em mobile, a experiencia permanece conservadora e sem sobrecarga de sticky.

Validacoes executadas:

  • php -l evento.php
  • validacao estrutural do arquivo renderizado para confirmar:
  • #rnt-event-carousel-shell
  • --rnt-event-carousel-offset
  • listeners de load, resize e scroll

BK-124 · Hardening de fora de cartaz em metadados + comentarios operacionais

Data: 13 de Marco de 2026

Contexto operacional:

  • O usuario reportou a URL https://rionoteatro.com.br/gonzaguinha-eterno-aprendiz como se ainda estivesse com cara de peca atual.
  • A verificacao real mostrou um quadro misto:
  • o corpo da pagina ja estava no layout enxuto de fora de cartaz;
  • o <head> ainda anunciava a peca como evento ativo, com oferta antiga, imagem antiga e JSON-LD de evento agendado.
  • Esse desalinhamento explicava muito bem a percepcao de pagina "atual" em cache, preview social ou snippet, mesmo quando o layout visivel ja estava correto em aba anonima.

Causa raiz confirmada:

  • meta-analytics.php
  • o arquivo gerava OG/Twitter/JSON-LD sempre como evento ativo para as paginas de peca.php.
  • isso incluia:
  • og:title com sufixo de oferta antiga;
  • og:image com a capa historica da peca;
  • event:start_time / event:end_time;
  • JSON-LD TheaterEvent com EventScheduled.
  • evento.php
  • o bloco proprio de OG/Twitter tambem continuava tratando eventos fora de cartaz como event, reaproveitando imagem e descricao historicas.
  • peca.php e evento.php
  • o layout enxuto de fora de cartaz existia, mas a regra operacional de limpeza de assets apos 7 dias nao estava comentada no proprio ponto de decisao da pagina.

Ajustes aplicados:

  • meta-analytics.php
  • passou a detectar quando a peca publica esta fora de cartaz.
  • nesse caso:
  • og:type passa a website;
  • og:title / twitter:title deixam de carregar oferta antiga;
  • meta description informa que o espetaculo nao esta em cartaz no momento;
  • og:image / twitter:image passam a usar img/logo_2023.png;
  • event:start_time / event:end_time deixam de ser publicados;
  • o JSON-LD troca TheaterEvent/EventScheduled por WebPage.
  • evento.php
  • o bloco OG/Twitter proprio dos eventos de produtor foi alinhado com a mesma regra de fora de cartaz:
  • og:type=website;
  • descricao neutra;
  • imagem institucional em vez da capa antiga.
  • peca.php e evento.php
  • comentarios operacionais adicionados para deixar explicito que:
  • 7 dias apos o fim da temporada, fotos/videos antigos devem ser removidos pelo cleanup;
  • o layout canonico de fora de cartaz deve esconder foto e dados da temporada passada e manter apenas o aviso de indisponibilidade com outras pecas abaixo.

Resultado funcional:

  • A URL historica deixa de se vender como evento agendado quando ja esta fora de cartaz.
  • O corpo e o <head> passam a contar a mesma historia:
  • pagina indisponivel no momento;
  • sem oferta antiga;
  • sem imagem antiga;
  • sem structured data de evento ativo.

Validacoes executadas:

  • php -l peca.php
  • php -l evento.php
  • php -l meta-analytics.php
  • Validacao HTTP real da URL https://rionoteatro.com.br/gonzaguinha-eterno-aprendiz:
  • corpo com Nao temos informacoes deste espetaculo no momento
  • og:type=website
  • og:title=Gonzaguinha - o Eterno Aprendiz | Rio no Teatro
  • og:image=https://rionoteatro.com.br/img/logo_2023.png
  • ausencia de event:start_time / event:end_time
  • JSON-LD com @type=WebPage

BK-123 · Hardening do login admin com fallback sem JSON cru

Data: 13 de Marco de 2026

Contexto operacional:

  • O endpoint admin/action.php do login foi desenhado para responder JSON.
  • Quando o submit do formulario caia como POST tradicional, sem o JavaScript interceptar a resposta, o navegador exibia diretamente:
  • {"status":"success","url":"...","message":"Direcionando ..."}
  • O sintoma era intermitente: em tentativas seguintes o login voltava a funcionar, reforcando que a autenticacao em si nao estava quebrada; o problema estava no acoplamento entre o form e o JS.

Causa raiz confirmada:

  • admin/login.php
  • o formulario aponta direto para admin/action.php.
  • admin/action.php
  • o case 'login' respondia JSON tanto para sucesso quanto para erro, sem fallback para submit tradicional.
  • admin/js/parsley-validate-form.js
  • o redirect real dependia da interceptacao AJAX no front-end.
  • Com isso, qualquer falha transitora de carregamento/cache/execucao do JS fazia o navegador receber o JSON cru do endpoint.

Ajustes aplicados:

  • admin/action.php
  • adicionada deteccao de requisicao AJAX por HTTP_X_REQUESTED_WITH.
  • no case 'login':
  • AJAX continua recebendo JSON;
  • submit tradicional recebe 302 para index.php quando autenticado;
  • submit tradicional recebe 302 para login.php?login_error=1 quando invalido.
  • as respostas JSON do endpoint passaram a declarar Content-Type: application/json; charset=utf-8.
  • admin/login.php
  • adicionado helper local com filemtime() para versionar:
  • js/jquery.js
  • js/bootstrap.min.js
  • assets/toastr-master/toastr.js
  • js/plugins/parsley/parsley.js
  • js/parsley-validate-form.js
  • adicionado alerta visual simples quando a tela voltar com login_error=1.

Resultado funcional:

  • O login admin passa a ter dois caminhos seguros:
  • caminho principal: AJAX + redirect no front-end, como antes;
  • caminho de contingencia: redirect HTTP nativo sem exibir JSON cru no navegador.
  • O cache-buster reduz a chance de recorrencia por asset JS stale depois de deploy/ajuste.

Validacoes executadas:

  • php -l admin/login.php
  • php -l admin/action.php
  • Validacao HTTP real do fallback:
  • POST tradicional invalido para admin/action.php retorna 302 Location: /admin/login.php?login_error=1
  • POST com header X-Requested-With: XMLHttpRequest continua retornando JSON (status=error)
  • GET admin/login.php renderiza os scripts com sufixo ?v=...

Riscos residuais:

  • O fallback por header() depende de nao haver output prematuro em includes futuros de admin/action.php.
  • Este BK cobre apenas o fluxo login; remember e recovery continuam dependentes do fluxo AJAX legado.

BK-122 · Copy IA multi-formato na Central de Redes Sociais

Data: 12 de Marco de 2026

Contexto operacional:

  • A Central de Redes Sociais já tinha fluxo semimanual forte para vídeo, mas o texto para locução ainda estava em modo local/template.
  • A operação precisava escalar para copies reutilizáveis por IA, com foco em venda: teatro, bairro, dia, horário, preço e desconto.
  • O usuário também definiu um recorte funcional claro para esta fase:
  • 2 stories por IA;
  • 2 reels por IA;
  • story mais direto/comercial;
  • reels podendo falar um pouco mais do atrativo do espetáculo.

Ajustes aplicados:

  • admin/modulos/campanhas/redes_sociais.php
  • nova aba Texto Audio Story incorporada ao módulo.
  • regra de elegibilidade de peça alinhada com a vitrine pública (pecas.php) para o seletor interno.
  • contexto da peça enriquecido com:
  • nome limpo da landing;
  • teatro;
  • bairro/região;
  • próxima sessão;
  • preço promocional;
  • desconto percentual.
  • nova tabela social_audio_jobs criada em runtime para persistir jobs de copy.
  • geração por IA passou a suportar:
  • Gemini via CLI na politica atual da VPS;
  • OpenAI (OPENAI_API_KEY).
  • para cada IA configurada, o clique em Gerar com IA passa a criar:
  • 2 jobs story;
  • 2 jobs reels.
  • prompt endurecido para:
  • priorizar local, bairro, dia, horário, preço e desconto;
  • destacar quando o desconto supera a meia;
  • evitar frases longas e inventar informações como sem taxa.
  • listagem de jobs passou a mostrar:
  • provider;
  • modelo;
  • formato;
  • versões geradas;
  • seleção manual da versão ativa.

Resultado funcional:

  • A tela deixou de depender apenas de texto determinístico local.
  • Quando as chaves estão configuradas, a operação passa a ter múltiplas versões de copy por IA e por formato.
  • Quando as chaves não estão configuradas, o fluxo ainda salva job com fallback local para não interromper a rotina.

Validacoes executadas:

  • php -l admin/modulos/campanhas/redes_sociais.php
  • validacao do JS inline equivalente por extracao do bloco <script> + node

BK-119 · Reabrir sugestões aprovadas na conciliação do bot

Data: 11 de Marco de 2026

Contexto operacional:

  • O fluxo de conciliação de bot perdeu funcionalidade após merge anterior: itens aprovados não tinham mais atalho de retorno para revisão.
  • O usuário reportou que aprovados sumiam da lista de ação sem como reabrir para ajuste.

Ajustes aplicados:

  • admin/modulos/eventos/action.php
  • adicionado o case 'conciliar_reabrir'.
  • valida que o usuário é admin.
  • carrega a sugestão por id, valida existência e confirma status = 'aprovado'.
  • atualiza a sugestão para:
  • status = 'pendente'
  • data_decisao = NULL
  • retorna sucesso com mensagem e URL de retorno.
  • admin/modulos/eventos/conciliacao.php e admin/modulos/eventos/conciliacao_eventim.php
  • filtro getSugestoes() passou a aceitar aprovados com WHERE status = 'aprovado'.
  • a ordenação da aba aprovados passou a usar ORDER BY COALESCE(data_decisao, data_captacao) DESC.
  • nova aba Aprovados com cards de revisão e botão Reabrir para Revisão para cada item.

Resultado funcional:

  • Aprovação continua registrada normalmente.
  • Itens aprovados podem ser devolvidos ao fluxo de conciliação com um clique, sem perder histórico.

Validações executadas:

  • php -l admin/modulos/eventos/action.php
  • php -l admin/modulos/eventos/conciliacao.php
  • php -l admin/modulos/eventos/conciliacao_eventim.php

BK-116 · Aba Aguardando Aprovação em Peças restrita a produtores reais

Data: 11 de Marco de 2026

Contexto operacional:

  • A tela admin/modulos/pecas/index.php?filtro=aprovacao estava misturando dois fluxos diferentes:
  • peças reais enviadas por produtores (pecas.editado_produtor = 1);
  • sugestões do bot vindas de bot_pecas_sugestoes.
  • Isso inflava o badge de aprovação, gerava linhas BOT-* dentro do módulo de Peças e confundia a curadoria de cadastro humano com o fluxo de conciliação automática do bot.

Ajustes aplicados:

  • admin/modulos/pecas/index.php
  • contador da aba Aguardando Aprovação passou a considerar apenas pecas.editado_produtor = 1.
  • bloco que buscava bot_pecas_sugestoes WHERE status = 'pendente' foi removido da listagem.
  • comentário operacional adicionado para deixar explícito que sugestões do bot não pertencem a essa aba.

Resultado funcional:

  • Peças > Aguardando Aprovação agora lista somente eventos enviados por produtores reais.
  • Sugestões do bot continuam restritas ao fluxo correto de Eventos/Conciliação.

Validacoes executadas:

  • php -l admin/modulos/pecas/index.php
  • verificacao estrutural do arquivo para confirmar remocao de:
  • contador do bot na aba de aprovação;
  • query em bot_pecas_sugestoes;
  • linhas BOT-* renderizadas na tabela de Peças

BK-115 · Painel WhatsApp com fallback de historico e contraste por status

Data: 11 de Marco de 2026

Contexto operacional:

  • Depois do redesenho visual do BK-114, a area Ultimos envios continuava sem utilidade pratica quando bot_whatsapp_stats estivesse vazia ou com poucos registros.
  • A verificacao real confirmou:
  • SELECT COUNT(*) FROM bot_whatsapp_stats retornava 0 no momento da analise inicial;
  • whatsapp_mensagens seguia com historico recente e confiavel de envios processados;
  • o helper de WhatsApp precisava garantir compatibilidade imediata do schema para voltar a alimentar a telemetria dedicada.

Ajustes aplicados:

  • includes/whatsapp_helper.php
  • _whatsapp_obter_schema_stats(...) deixou de apenas inspecionar e passou a autocorrigir o schema de bot_whatsapp_stats para o formato esperado pelo helper atual:
  • status ENUM('sucesso','erro','bloqueado')
  • coluna mensagem
  • coluna message_id
  • indice idx_numero_data
  • admin/modulos/bot/stats.php
  • a tela agora dispara a autocorrecao de schema logo na abertura, via helper.
  • Ultimos envios passou a funcionar em tres modos:
  • telemetria pura;
  • fallback total com whatsapp_mensagens;
  • modo misto (telemetria + fallback) quando a telemetria ainda esta retomando.
  • refinamento de UX adicional:
  • chips de contagem por sucesso, bloqueado e erro;
  • origem da grade visivel (telemetria, fallback total ou telemetria + fallback);
  • destaque visual por linha para cada status;
  • preview da mensagem nos sucessos;
  • copy operacional explicando quando o fallback esta em uso.

Evidencia operacional confirmada em 11/03/2026:

  • schema apos autocorrecao:
  • bot_whatsapp_stats passou a aceitar mensagem, message_id e status='bloqueado'
  • teste controlado do helper:
  • insert sucesso com message_id voltou a gravar corretamente
  • registro de teste removido em seguida
  • composicao final da grade:
  • telemetry=1
  • merged=50
  • fallback_added=49
  • topo da listagem mostrou primeiro o evento real bloqueado e, na sequencia, o historico recente vindo de whatsapp_mensagens

Validacoes executadas:

  • php -l includes/whatsapp_helper.php
  • php -l admin/modulos/bot/stats.php
  • validacao do JS inline equivalente via extracao do bloco <script> + node --check
  • teste controlado do helper com persistencia e limpeza do registro de teste
  • validacao da composicao telemetria + fallback com consulta real ao banco

BK-118 · RCA WhatsApp: ACK pendente, restart agressivo e telemetria restaurada

Data: 11 de Marco de 2026

Contexto operacional:

  • Incidente analisado a partir do envio da lista de compradores do evento pecas.id=333 / sessão 17592 em 11/03/2026.
  • O banco (whatsapp_mensagens) mostrava 4 saídas processadas, mas apenas o número 21999915554 exibia a mensagem na conversa.
  • O painel de stats também estava cego: o helper chamava _whatsapp_gravar_telemetria(), porém a tabela real bot_whatsapp_stats não tinha o mesmo schema esperado pelo código.

Causa raiz confirmada:

  • includes/whatsapp_helper.php
  • após cada HTTP 200 do /send, o helper executava um restart preventivo imediato do rnt-whatsapp.
  • no journalctl -u rnt-whatsapp, os 4 envios do incidente apareceram em sequência, cada um seguido por SIGTERM e restart do serviço antes do próximo destinatário.
  • bot/whatsapp/server.js
  • os messageId reais puderam ser consultados em /message-status, confirmando o estado atual:
  • 5521979346876 → ack=0 (ACK_PENDING)
  • 5521996868181 → ack=0 (ACK_PENDING)
  • 5521999915554 → ack=3 (ACK_READ)
  • 5521967194720 → ack=0 (ACK_PENDING)
  • cliente_id = NULL não era causa de bloqueio:
  • o vínculo com cliente era usado apenas para histórico/CRM; o envio já havia acontecido antes dessa associação.
  • bot_whatsapp_stats
  • a tabela aceitava apenas sucesso|erro e não possuía colunas mensagem/message_id, enquanto o helper tentava gravar bloqueado e mensagem, falhando silenciosamente.

Ajustes aplicados:

  • includes/whatsapp_helper.php
  • removido o restart preventivo após envio bem-sucedido para não encerrar a sessão antes do ciclo de ACK do WhatsApp.
  • logging de sucesso passou a registrar message_id retornado pelo microserviço.
  • telemetria passou a detectar o schema real de bot_whatsapp_stats, normalizar status aceitos e inserir apenas colunas existentes.
  • bot/whatsapp/server.js
  • adicionado cache leve de contexto por messageId para correlacionar ACKs de mensagens enviadas.
  • novo log message_ack com rótulos legíveis (ACK_PENDING, ACK_SERVER, ACK_DEVICE, ACK_READ, ACK_PLAYED).
  • endpoint POST /message-status passou a retornar também ackLabel.
  • setup_whatsapp_table.php
  • schema de referência atualizado para refletir a telemetria usada em produção (bloqueado, mensagem, message_id, índice por número/data).
  • Banco de dados
  • bot_whatsapp_stats migrada para incluir:
  • status ENUM('sucesso','erro','bloqueado')
  • mensagem TEXT
  • message_id VARCHAR(120)
  • índice idx_numero_data (numero, data_envio)

Validações executadas:

  • php -l includes/whatsapp_helper.php
  • node --check bot/whatsapp/server.js
  • php -l setup_whatsapp_table.php
  • systemctl restart rnt-whatsapp
  • consulta real em journalctl -u rnt-whatsapp para os 4 envios do incidente
  • chamadas reais ao endpoint local POST /message-status com os messageId do incidente
  • teste controlado de _whatsapp_gravar_telemetria() confirmando persistência de sucesso e bloqueado com message_id

BK-114 · Reorganizacao visual do painel WhatsApp (admin/modulos/bot/stats.php)

Data: 11 de Marco de 2026

Contexto operacional:

  • A tela admin/modulos/bot/stats.php estava funcional, mas visualmente achatada e com baixa hierarquia de leitura para operacao diaria.
  • O layout antigo espalhava status do servico, acao da fila, cards numericos, grafico e tabelas sem um bloco principal de contexto.
  • Como esta e a tela mais direta para ver saude do WhatsApp, fila pendente e historico de envio, a leitura precisava ficar mais imediata e mais proxima do padrao visual das telas recentes do admin.

Ajustes aplicados:

  • admin/modulos/bot/stats.php
  • novo hero operacional com status do servico, fila atual, protecao 24h, ultimo registro e acoes rapidas.
  • cards de KPI refeitos com icones, texto de apoio e destaque para fila atual.
  • grafico de envios por hora ganhou container proprio, refinamento visual e resumo de pico do dia.
  • resumo operacional lateral adicionado com taxa de sucesso, retentativas, item mais antigo na fila e leitura do helper/status.
  • tabelas de historico e fila foram reorganizadas em cards responsivos com estados vazios mais claros.
  • ajuste funcional acoplado ao layout: limparHistoricoEnvios(...) passou a aceitar o botao clicado diretamente, evitando dependencia do event.currentTarget global.
  • docs/BACKLOG.md
  • BK-114 registrado como melhoria de layout/operacao desta tela.

Validacoes executadas:

  • php -l admin/modulos/bot/stats.php
  • validacao do JS inline equivalente com extracao do bloco <script> e node --check apos substituir o placeholder PHP <?=$json_grafico?> por um array estatico

BK-113 · Restauracao de common-scripts.js em telas do admin

Data: 11 de Marco de 2026

Contexto operacional:

  • Depois do fechamento do BK-111 e do BK-112, ficou evidente que a regressao do submenu lateral do admin nao estava isolada.
  • Outras telas ainda encerravam a pagina sem common-scripts.js, ou com um rodape JS paralelo ao stack oficial do painel, o que deixava o submenu lateral e comportamentos base do admin inconsistentes entre modulos.
  • A revisao foi aberta especificamente para as telas:
  • admin/modulos/facebook/configure.php
  • admin/modulos/facebook/agendamento.php
  • admin/modulos/campanhas/index.php
  • admin/modulos/campanhas/redes_sociais.php
  • admin/modulos/bot/stats.php

Ajustes aplicados:

  • admin/modulos/facebook/configure.php
  • bloco final do admin reforcado com common-scripts.js apos inc-js.php.
  • admin/modulos/facebook/agendamento.php
  • common-scripts.js restaurado antes do JS especifico do FullCalendar e da agenda social.
  • admin/modulos/bot/stats.php
  • common-scripts.js restaurado antes do bloco inline de acoes do dashboard WhatsApp.
  • admin/modulos/campanhas/index.php
  • rodape JS paralelo por CDN removido do papel de stack principal.
  • pagina voltou a usar inc-js.php + common-scripts.js, mantendo o JS proprio da listagem logo em seguida.
  • admin/modulos/campanhas/redes_sociais.php
  • rodape JS paralelo por CDN substituido pelo stack padrao do admin (inc-js.php + common-scripts.js), sem remover o JS operacional da tela.
  • docs/BACKLOG.md
  • BK-113 registrado como fechamento separado para rastrear a restauracao transversal do submenu.

Validacoes executadas:

  • php -l admin/modulos/facebook/configure.php
  • php -l admin/modulos/facebook/agendamento.php
  • php -l admin/modulos/campanhas/index.php
  • php -l admin/modulos/campanhas/redes_sociais.php
  • php -l admin/modulos/bot/stats.php
  • verificacao estrutural com busca por inc-js.php e common-scripts.js nas cinco telas ajustadas

BK-112 · Eventim via API publica + conciliacao admin versionada

Data: 11 de Marco de 2026

Contexto operacional:

  • O backlog ainda tratava Eventim como incidente de bloqueio HTTP2 no HTML, com proposta aberta de stealth browser ou investigacao adicional.
  • A evidencia real deste ambiente mostrou outro desenho operacional:
  • a API publica public-api.eventim.com responde normalmente;
  • o host HTML www.eventim.com.br continua expirando por timeout na VPS;
  • o scraper Eventim ja estava implementado para priorizar a API publica;
  • a tela admin conciliacao_eventim.php e os testes CLI existiam no servidor, mas nao estavam versionados no Git.

Ajustes aplicados:

  • admin/modulos/eventos/conciliacao_eventim.php
  • tela de conciliacao Eventim passou a ficar versionada no repositorio.
  • isso fecha a lacuna entre o Git e o codigo ja referenciado no admin por:
  • admin/modulos/eventos/index.php
  • admin/modulos/eventos/conciliacao.php
  • admin/modulos/eventos/action.php
  • admin/modulos/eventos/conciliacao_eventim.php
  • bloco final do JS padrao do admin foi reforcado com common-scripts.js, evitando regressao do submenu lateral.
  • admin/modulos/eventos/conciliacao.php
  • o mesmo bloco common-scripts.js foi restaurado para manter consistencia com a conciliacao principal do bot.
  • bot/tests/test_eventim_api.php
  • versionado teste de diagnostico da API publica.
  • bot/tests/test_eventim_scraper.php
  • versionado teste integrado do EventimScraper.
  • bot/tests/test_eventim_site.php
  • versionado teste de timeout/challenge do host HTML principal.
  • bot/tests/test_eventim_validator.php
  • versionado teste isolado da regra de validacao para diferenciar teatro real de curso/oferta indevida.
  • bot/scrapers/eventim_scraper.php
  • comentario operacional adicionado para registrar que HTML fica apenas como contingencia/diagnostico e que a fonte primaria neste ambiente deve ser a API publica.
  • docs/BACKLOG.md
  • item aberto de Eventim foi encerrado como BK-112 com evidencia concreta.

Evidencia operacional confirmada em 11/03/2026:

  • php bot/tests/test_eventim_api.php
  • HTTP 200
  • 49 produtos/sessoes retornados para Rio de Janeiro
  • php bot/tests/test_eventim_scraper.php
  • 5 espetaculos unicos consolidados a partir da API publica
  • php bot/tests/test_eventim_validator.php
  • caso eventim_falso_negativo validado como teatro real
  • caso eventim_curso_real rejeitado com erro Contém termo de exclusão: 'curso'
  • php bot/tests/test_eventim_site.php
  • timeout em ipv6 e default para https://www.eventim.com.br/city/rio-de-janeiro-1672/
  • conclusao: o HTML principal segue inadequado como fonte primaria neste ambiente

Validacoes executadas:

  • php -l admin/modulos/eventos/conciliacao_eventim.php
  • php -l admin/modulos/eventos/conciliacao.php
  • php -l bot/tests/test_eventim_api.php
  • php -l bot/tests/test_eventim_scraper.php
  • php -l bot/tests/test_eventim_site.php
  • php -l bot/tests/test_eventim_validator.php
  • php -l bot/scrapers/eventim_scraper.php
  • execucao real dos 4 testes CLI acima

BK-111 · Correcao arquitetural do bootstrap do admin PalcoFan

Data: 11 de Marco de 2026

Contexto operacional:

  • A tela admin/modulos/comentarios/index.php voltou a abrir sem CSS/JS porque estava subindo apenas com ../../data/setup.php.
  • Nesse modo, APP_ROOT, MODULO_SLUG e MODULO_ACTIVE_MENU caiam nos fallbacks vazios de setup.php, quebrando os caminhos de asset do admin em head.php.
  • O mesmo modulo executa queries mysqli_* com $conexao, mas setup.php sozinho nao inicializa a conexao mysqli.
  • Havia um admin/modulos/comentarios/bootstrap.php solto no worktree, porem nao versionado e ainda incompleto para o uso atual do modulo.

Ajustes aplicados:

  • admin/modulos/comentarios/bootstrap.php
  • arquivo passou a ser a entrada obrigatoria do modulo.
  • comentario de regra adicionado para deixar explicito que este modulo nao pode voltar a usar ../../data/setup.php direto.
  • bootstrap endurecido com caminhos absolutos via __DIR__, incluindo config/connect.php para disponibilizar $conexao as queries mysqli_*.
  • admin/modulos/comentarios/index.php
  • topo trocado de require("../../data/setup.php"); para o bootstrap.php local.
  • includes compartilhados (head.php, inc-topo.php, inc-menu.php, inc-footer.php) endurecidos com __DIR__ + APP_ROOT.
  • bloco final de JS do admin restaurado com inc-js.php + common-scripts.js, reativando o submenu lateral e o comportamento padrao do painel.
  • comentario operacional adicionado para registrar a dependencia de APP_ROOT/MODULO_* e $conexao.
  • docs/BACKLOG.md
  • BK-111 reaberto para rastrear a correcao arquitetural do modulo admin PalcoFan.
  • BK-75 ajustado para nao seguir afirmando que a consolidacao do layout/bootstrapping do admin ja estava entregue.
  • docs/CHANGELOG.md
  • historico do BK-75 corrigido para refletir que a consolidacao do bootstrap/layout do modulo admin ficou como correcao posterior.

Validacoes executadas:

  • php -l admin/modulos/comentarios/bootstrap.php
  • php -l admin/modulos/comentarios/index.php
  • renderizacao local via CLI com HTTP_HOST=localhost para confirmar:
  • asset_ok;
  • accordion_ok;
  • common_ok;
  • menu_ok.
  • comparacao estrutural com modulos estaveis do admin para confirmar a presenca do bloco inc-js.php + common-scripts.js no fechamento da pagina.

Aba Storie · estabilizacao do upload de video .mp4 no admin

Data: 11 de Marco de 2026

Contexto operacional:

  • A nova aba Storie estava listando assets corretamente, mas o upload de video grande continuava falhando no admin.
  • Evidencia real coletada no debug.log:
  • request chegava como multipart/form-data;
  • CONTENT_LENGTH vinha preenchido (~37 MB);
  • $_POST e $_FILES chegavam vazios.
  • Causa raiz confirmada: override local em admin/.user.ini com post_max_size = 16M, enquanto o restante do site aceitava uploads maiores.

Ajustes aplicados:

  • admin/.user.ini
  • post_max_size elevado para 500M.
  • upload_max_filesize elevado para 500M.
  • max_input_time elevado para 1800.
  • admin/js/gestor-story.js
  • upload trocado para XMLHttpRequest puro com FormData(form).
  • adicionada barra de progresso real.
  • mantido envio dedicado para evitar interferencia do jQuery legado do admin em multipart grande.
  • admin/inc-gestor.php
  • adicionada UI de progresso do upload na aba Storie.
  • admin/modulos/pecas/action.php
  • logging expandido com CONTENT_TYPE, CONTENT_LENGTH, post_max_size e upload_max_filesize efetivos.
  • tratamento explicito para multipart descartado antes de popular $_POST/$_FILES.
  • admin/modulos/pecas/index.php
  • cache-buster do JS atualizado para forcar carregamento da versao corrigida.

Evidencia operacional confirmada:

  • Upload real da peca 1342 concluido com sucesso em 11/03/2026 11:41.
  • Arquivo salvo em:
  • /arquivos/pecas/1342/video/lisa-lesa-e-louca-story.mp4
  • Evidencia de log:
  • ini_post_max=500M ini_upload_max=500M
  • files_keys=story_file
  • OK item=0 salvo=/www/wwwroot/rionoteatro.com.br/arquivos/pecas/1342/video/lisa-lesa-e-louca-story.mp4

Validacoes executadas:

  • php -l admin/modulos/pecas/action.php
  • php -l admin/inc-gestor.php
  • php -l admin/modulos/pecas/index.php
  • node --check admin/js/gestor-story.js
  • confirmacao em disco via ls -lah arquivos/pecas/1342/video

Fechamento operacional da branch vps/BK-109-story-video-priority · prioridade de video story por peca

Data: 11 de Marco de 2026

Contexto operacional:

  • Em 11/03/2026, a fila social_post_queue registrou falhas reais de midia nos canais de feed:
  • facebook_feed: Missing or invalid image file
  • instagram_feed: Only photo or video can be accepted as media type.
  • Casos confirmados no momento da analise:
  • peca 333 (Bee Gees, Abba e Carpenters - Nos embalos de SABADO a noite)
  • peca 1341 (E O Otimismo Que Faz O Besouro Voar!)
  • A peca 1341 ja possuia mp4 em /arquivos/pecas/1341/video, enquanto outras pecas afetadas dependiam apenas da capa.

Ajustes aplicados:

  • admin/cron/sync_facebook_events.php
  • adicionado resolvedor de capa principal da peca e normalizacao para JPG compativel com Meta em uploads/social_meta_assets.
  • stories passaram a procurar somente video em /arquivos/pecas/{id}/video.
  • fallback de story ajustado para:
  • usar video quando existir;
  • cair direto para a capa da peca quando nao houver video.
  • overlay automatico de story continua sendo aplicado apenas em fluxo por imagem.
  • admin/classes/FacebookEventService.php
  • createInstagramMedia(...) passou a aceitar video_url em stories.
  • createFacebookStory(...) passou a tentar video_stories primeiro e, se necessario, cair para photo_stories.
  • admin/inc-gestor.php
  • nova aba Storie no gestor de pecas.
  • admin/modulos/pecas/action.php
  • upload, listagem e exclusao de assets de story em /arquivos/pecas/{id}/video.
  • admin/js/gestor-story.js
  • fluxo AJAX da aba Storie e deep-link para abrir o gestor da peca ja na aba correta.
  • admin/modulos/facebook/agendamento.php
  • atalho operacional para abrir o gestor da peca diretamente no contexto de story.
  • admin/modulos/pecas/index.php
  • inclusao do JS do gestor de story.

Validacoes executadas:

  • php -l admin/classes/FacebookEventService.php
  • php -l admin/cron/sync_facebook_events.php
  • php -l admin/modulos/facebook/agendamento.php
  • php -l admin/inc-gestor.php
  • php -l admin/modulos/pecas/action.php
  • php -l admin/modulos/pecas/index.php
  • parse do JS novo admin/js/gestor-story.js via node -e
  • smoke HTTP apos push em main:
  • https://rionoteatro.com.br/ -> 200
  • https://rionoteatro.com.br/admin/ -> 302
  • https://rionoteatro.com.br/painel/modulos/eventos/ -> 302

Git / deploy:

  • Commit funcional do codigo: 8d62d4bf3 (fix(social): prioriza video story por peca)
  • Merge em main realizado na VPS com fast-forward.
  • git push origin main executado com sucesso, disparando o workflow de deploy definido em .github/workflows/deploy.yml.
  • Branch local vps/BK-109-story-video-priority removida apos merge.
  • Consulta a origin confirmou que nao existia branch remota vps/BK-109-story-video-priority para exclusao.

Observacao de governanca:

  • O identificador BK-109 ja estava em uso no historico do projeto para outra entrega (Corrigir erro de renderizacao da aba WhatsApp no detalhe de cliente).
  • Para nao duplicar numeracao no backlog/changelog, este fechamento foi registrado como encerramento operacional da branch vps/BK-109-story-video-priority.

BK-75 · Correcao documental do modulo PalcoFan + inclusao publica em peca.php e evento.php

Data: 10 de Marco de 2026

Contexto:

  • O historico do BK-75 estava inconsistente com o que realmente foi entregue para o modulo de avaliacoes PalcoFan.
  • A analise do codigo em producao confirmou que o nucleo do modulo existia, mas a inclusao publica estava efetivamente presente apenas em teatro.php.

Ajustes aplicados:

  • peca.php
  • inclusao do bloco includes/inc_palcofan.php no frontend da pagina da peca.
  • evento.php
  • inclusao do bloco includes/inc_palcofan.php no frontend da pagina do evento.
  • admin/modulos/comentarios/index.php
  • modulo administrativo versionado e acessivel pelo submenu nativo Peças > Comentários.
  • docs/BACKLOG.md
  • registro consolidado do escopo real entregue pelo BK-75.

Escopo real confirmado do PalcoFan:

  • Endpoint AJAX: includes/ajax_palcofan.php
  • aceita apenas POST,
  • exige cliente logado,
  • valida tipo_item, nota e comentario,
  • reenvia avaliacao atualizada para pendente.
  • Moderacao admin: admin/modulos/comentarios/index.php
  • filtros pendente, aprovado, rejeitado e todos,
  • acoes de aprovar, rejeitar e excluir.
  • Bloco publico: includes/inc_palcofan.php
  • resumo de media,
  • comentarios aprovados,
  • formulario para cliente logado.
  • SEO/Schema: meta-analytics.php
  • aggregateRating dinamico para pecas com base em tb_avaliacoes.

Observacoes importantes:

  • A tabela tb_avaliacoes existe em producao, com indices por item/status, mas a restricao de nota 1..5 esta implementada no PHP e nao como CHECK no schema MySQL atual.
  • O link de contexto do item avaliado foi ajustado para tratar tipo_item='evento' pelo fluxo real da tabela pecas, com fallback legado para eventos.
  • A consolidacao do bootstrap/layout padrao do admin para admin/modulos/comentarios/ nao estava de fato versionada neste momento; essa correcao arquitetural ficou registrada separadamente no BK-111.
  • Este registro nao apaga o historico anterior do BK-75; ele corrige documentalmente o escopo efetivamente identificado no codigo/ambiente.

BK-108 · Reordenacao de CTA de compra e padronizacao AdSense no evento.php

Data: 06 de Marco de 2026

Objetivo operacional:

  • Melhorar a experiência do usuário (UX) e o fluxo de conversão em eventos de produtores externos/bot, movendo o botão de compra para após a seção de localização.

Ajustes aplicados:

  • evento.php:
  • Botão de compra externa (CTA) removido do topo da página.
  • Removida lógica condicional baseada na origem do evento.
  • Inclusão de novo bloco de CTA fixo posicionado imediatamente após a seção de LOCAL.
  • Padronização da chamada renderAdsenseResponsivoPorDispositivo() entre as seções:
  • Após Sinopse.
  • Após Ficha Técnica.
  • Após Botão de Compra/Local.
  • Após Dúvidas.
  • Resultado:
  • Layout mais limpo e informativo antes da decisão de compra.
  • Melhor distribuição de anúncios ao longo da página.

BK-96 · Migracao de enderecos e bairros estruturados dos teatros

Data: 06 de Marco de 2026

Objetivo operacional:

  • Estruturar os dados geográficos da tabela teatros, movendo informações misturadas no campo local para os campos dedicados endereco e bairro.

Ajustes aplicados:

  • Criacao do script de migracao: admin/cron/migrar_enderecos_teatros.php:
  • Extração inteligente de endereços de 118 registros via regex (Rua, Av, Pr, Estr).
  • Limpeza automatica de HTML, iframes do Google Maps e avisos redundantes do campo local.
  • Criacao do script de refinamento: admin/cron/extrair_bairros_do_endereco.php:
  • Mapeamento de 86 bairros comuns e específicos do Rio de Janeiro e Niterói.
  • Lógica de detecção por nome do teatro e sufixos no endereço.
  • Resultado Final:
  • 148 teatros ativos com endereco e bairro preenchidos (100% da base ativa).
  • Campo local preservado apenas com informações complementares (capacidade, etc).
  • Estrutura pronta para integração com mapas dinâmicos e SEO local.

BK-109 · Corrigir erro de renderizacao da aba WhatsApp no detalhe de cliente

Data: 06 de Marco de 2026

Objetivo operacional:

  • Corrigir falha crítica no carregamento do histórico da aba #whatsapp-tab para clientes com histórico de conversa.

Ajustes aplicados:

  • admin/modulos/clientes/detalhe.php:
  • corrigido reset de cursor de resultado de $obj_sql->data_seek($q_wa, 0) para mysql_data_seek($q_wa, 0).
  • mantida a lógica de fallback de busca por cliente_id, celular e telefone.
  • Resultado observado:
  • links como ...detalhe.php?id=50877#whatsapp-tab e ...detalhe.php?id=775 voltam a renderizar corretamente.

BK-94 · Validação E2E de quebra de linha e fila WhatsApp

Data: 06 de Marco de 2026

Objetivo operacional:

  • Validar que o fluxo de envio via fila:
  • aceita mensagens com quebra de linha,
  • envia com payload JSON seguro,
  • remove o item processado (fila zerada) após sucesso.

Ajustes aplicados:

  • tests/bk94-whatsapp-e2e.sh (novo)
  • verifica endpoints de detalhe de cliente (IDs 50877 e 775).
  • executa validação offline de fila com mock de /status e /send.
  • valida payload com linha 1\nlinha 2 e remoção do JSON da fila no final.

Validações executadas:

  • bash tests/bk94-whatsapp-e2e.sh
  • Resultado esperado: envio com \\n aceito e fila de teste finalizada sem arquivos remanescentes.

BK-107 · Gemini agora + arquivos por peca no modulo de videos

Data: 06 de Marco de 2026

Objetivo operacional:

  • Tirar dependencia de uploads/video_examples, centralizar tudo em /arquivos/pecas/{id}/video e permitir disparo direto do gerador Gemini por job.

Ajustes aplicados:

  • admin/modulos/campanhas/redes_sociais.php
  • fallback operacional para quota da API:
  • novo botao por job: "Gerar Prompt (Gemini App)",
  • gera prompt manual completo (tom de voz + assets + link da peca + formato/duracao) para colar no app Gemini com upload manual dos arquivos,
  • bloco copiavel na tela com botao "Copiar prompt".
  • prompts manuais de video refinados para qualidade criativa:
  • duracao obrigatoria entre 25 e 30 segundos (evita retorno curto de 8s),
  • foco em transicoes e audio final,
  • regra explicita para nao deformar textos/imagens e limitar animacao de texto ao nome da peca + CTA curto.
  • novo endpoint AJAX interno (ajax=video_piece_files) para listar dinamicamente os arquivos da pasta da peca selecionada.
  • painel lateral alterado de "Referencias em uploads/video_examples" para "Arquivos do Video (da peca selecionada)".
  • painel "Arquivos do Video" ganhou upload rapido direto para a pasta da peca selecionada.
  • seletor de peca passou a manter selecao apos postback e atualizar a listagem da pasta automaticamente.
  • tabela "Jobs de Video Recentes" agora filtra pela mesma peca selecionada no topo (com botao para limpar filtro).
  • card do job reforcado com bloco explicito para adicionar mais imagens/videos sem recriar job.
  • lista de assets no card ganhou exclusao individual com botao X por arquivo (remove do job e tenta remover do disco com seguranca de referencia).
  • adicionado botao por job: "Gerar com Gemini agora".
  • adicionada opcao de excluir job (com escolha de remover tambem os arquivos daquele job).
  • fluxo de formato no job:
  • criacao/edicao com escolha entre Story (alvo 30s) e Reels (alvo 60s),
  • campos persistidos no job (formato, duracao_alvo_sec) com migracao automatica para bases antigas.
  • novo fluxo action=video_generate_now:
  • consulta endpoint de video por variavel de ambiente (CEREBRO_VIDEO_ENDPOINT/RNT_AI_VIDEO_ENDPOINT),
  • fallback direto por API Gemini foi removido em 2026-04-29 por politica operacional: sempre CLI/servico interno, nunca chave direta,
  • leitura de env ampliada para fallback central da VPS (/root/cerebro/.env e /root/.env) para evitar duplicacao de segredos por projeto,
  • ajuste de robustez para open_basedir (leitura de paths externos com checagem silenciosa, sem warning em tela),
  • envia payload com job + peca + assets,
  • tenta salvar retorno em /arquivos/pecas/{id}/video (path local, download por URL ou base64),
  • atualiza video_final_path, transcricao_audio, status e assets_json.
  • aplica regra operacional de duracao para Story: tenta forcar saida final em 30s com ajuste automatico quando vier menor.
  • tratamento de erro normalizado no fallback direto/endpoint para nunca retornar mensagem generica Array (agora mostra texto real da API e HTTP quando existir).
  • payload direto do Gemini ajustado para compatibilidade com o modelo atual (durationSeconds numerico e remocao de campos nao suportados).
  • chamada direta agora usa duracao por formato do job (Story=30s, Reels=60s) com fallback tecnico para 8s apenas quando o modelo/API recusar o parametro.
  • comentarios de regra mantidos para evitar regressao de caminho e de provedor.
  • Operacao de arquivos (VPS):
  • conteudo de uploads/video_examples foi transferido para /arquivos/pecas/1341/video.

BK-106 · Ajustes de operacao (retencao de video + presets de voz)

Data: 06 de Marco de 2026

Objetivo operacional:

  • Evitar acumo de lixo de video na VPS e acelerar a criacao de roteiro/voz com presets reutilizaveis.

Ajustes aplicados:

  • admin/cron/cleanup_old_pecas_images.php
  • limpeza passou a considerar tambem a subpasta video por padrao.
  • regra de retencao mantida em --days=7 (1 semana apos fim da temporada).
  • comentarios de protecao adicionados (REGRA DE NEGOCIO (NAO REMOVER)).
  • admin/modulos/campanhas/redes_sociais.php
  • adicionada tabela de presets de voz (social_video_voice_presets) com seed inicial:
  • preset comercial padrao,
  • preset Sambarilove (Malandro Carioca).
  • formulario de criacao de video ganhou:
  • seletor de preset salvo,
  • textarea de tom de voz (texto longo),
  • painel para salvar/atualizar novos presets.
  • compatibilidade de schema no job de video:
  • tom_voz ajustado para TEXT (bases antigas com VARCHAR(180) sao migradas automaticamente).
  • correcao operacional de upload/job:
  • criacao de novo job passou a reaproveitar rascunho existente da mesma peca por padrao (evita duplicacao acidental de jobs),
  • adicionado bloco Anexar assets/video ao job no card para complementar uploads sem recriar job,
  • feedback de upload passou a mostrar Arquivos recebidos no POST,
  • suporte de assets ampliado para extensoes heic/heif.
  • mensagem de tela reforcada para evitar ambiguidade:
  • etapa semimanual atual organiza insumos e ainda nao renderiza video automaticamente.

BK-106 · MVP semimanual de Video IA na Central de Redes Sociais

Data: 06 de Marco de 2026

Objetivo operacional:

  • Viabilizar criacao de videos por fluxo semimanual (sem cron) diretamente no admin, usando Gemini como provedor padrao na VPS.

Ajustes aplicados:

  • admin/modulos/campanhas/redes_sociais.php
  • adicionada aba Videos IA (Gemini) na central.
  • criado fluxo de job semimanual com:
  • selecao de peca elegivel (somente venda online/Admin),
  • upload de assets (imagem/video/audio),
  • campo de roteiro base,
  • campo de transcricao do audio editavel.
  • criado controle de status do job (rascunho, pronto_revisao, aprovado, publicado, arquivado) com edicao posterior.
  • criado anexo opcional de video final no proprio job.
  • tabela social_video_jobs passa a ser garantida via CREATE TABLE IF NOT EXISTS (padrao do modulo).
  • adicionadas regras comentadas no codigo (NAO REMOVER) para evitar regressao:
  • provedor fixo gemini nesse fluxo,
  • filtro de pecas somente catalogo online/Admin.
  • listagem de referencia dos arquivos em uploads/video_examples dentro da propria aba.

BK-105/BK-106 · Kickoff de automacao assistida Story + pipeline de video IA

Data: 06 de Marco de 2026

Objetivo operacional:

  • Formalizar os proximos passos para:
  • automacao assistida de Story com sticker/link (fluxo de risco controlado),
  • pipeline de video IA para divulgacao de pecas/eventos (Gemini como motor principal).

Ajustes aplicados:

  • docs/BACKLOG.md
  • BK-105 e BK-106 foram detalhados com:
  • responsavel inicial,
  • tarefas executaveis,
  • criterios de operacao/fallback.
  • docs/documentacao_tecnica/campanha_track/VIDEO_IA_PIPELINE_NOTAS_2026-03.md (novo)
  • documento tecnico de referencia para BK-106 com:
  • contrato de entrada (MVP),
  • etapas do pipeline,
  • modelo de fila de render,
  • regras de qualidade e mitigacao de risco.

BK-99 · Fila social recorrente (social_post_queue) + agenda por fila

Data: 05 de Marco de 2026

Objetivo operacional:

  • Encerrar dependencia do ciclo fixo de 21 dias no cron social.
  • Permitir programacao recorrente robusta (varios agendamentos por peca/semana) sem duplicar postagens.
  • Tornar a tela de agenda aderente ao que realmente esta na fila.

Ajustes aplicados:

  • admin/cron/sync_facebook_events.php
  • criada/garantida tabela social_post_queue via bootstrap automatico no cron (CREATE TABLE IF NOT EXISTS).
  • hardening anti-duplicacao:
  • garantia de UNIQUE em dedupe_key (com limpeza defensiva de legado antes de criar o indice).
  • removido bloco legado de "evergreen 21 dias".
  • adicionado planner por sessoes futuras:
  • janela configuravel (SYNC_SOCIAL_PLANNER_DAYS, default 14),
  • limite por semana/peca (SYNC_SOCIAL_MAX_FEED_PER_WEEK, default 3),
  • deduplicacao por dedupe_key (canal:peca:session_date).
  • separacao de modos no mesmo cron:
  • mode=planner (planeja fila, sem enviar),
  • mode=dispatcher (envia fila/gatilhos),
  • mode=full (padrao; planner + dispatcher).
  • feed FB/IG passou a usar itens da fila:
  • FB: envia imediato ou com scheduled_publish_time para futuro (status scheduled na fila).
  • IG: processa itens due (scheduled_for <= NOW()), mantendo os futuros em queued.
  • reset (?reset=1) agora limpa tambem a social_post_queue.
  • comentarios REGRA (NAO REMOVER) adicionados para evitar retorno ao fluxo antigo.
  • ajuste adicional: fallback silencioso de fonte TTF com @file_exists para evitar warning de open_basedir.
  • admin/modulos/facebook/agendamento-events.php
  • agenda passou a ler social_post_queue como fonte principal.
  • exibe um evento por item de fila (canal + status + post_id/erro).
  • imagem do modal passou a usar pasta large (corrige falha de thumb ausente em alguns cards).
  • payload dos eventos agora inclui icon_class e tooltip_html para hover enriquecido no calendario.
  • fallback legado mantido para pecas.facebook_scheduled_at se a fila nao existir/estiver vazia.
  • admin/modulos/facebook/agendamento.php
  • texto da tela atualizado para explicitar que a agenda usa a fila social.
  • adicionados filtros de status e canal no calendario (com refetch no endpoint).
  • calendario localizado em portugues (meses/dias/botoes).
  • novos recursos de UX:
  • icone por canal no card do calendario (FB/IG),
  • tooltip com resumo da postagem no hover.
  • criacao manual de postagem diretamente na agenda (modal "Nova postagem").
  • duplicacao de postagem direto do modal de detalhes (botao "Duplicar agendamento" com pre-preenchimento).
  • comentario de protecao inserido no HTML (NAO REMOVER) para evitar regressao.
  • admin/modulos/facebook/agendamento-create.php
  • novo endpoint para inserir/agendar itens manuais na social_post_queue (feed e story).
  • admin/cron/sync_facebook_events.php
  • dispatcher passou a processar tambem itens manuais de facebook_story e instagram_story da fila (due por horario), alem dos gatilhos automáticos.
  • sql_updates/create_social_post_queue.sql
  • script versionado para criacao da fila social em ambientes externos.

Atualizacao complementar (05/03/2026 - noite):

  • admin/classes/FacebookEventService.php
  • adicionados metodos de Threads API:
  • getThreadsUserInfo()
  • createThreadsMedia()
  • publishThreadsMedia()
  • suporte por variaveis THREADS_ACCESS_TOKEN e THREADS_USER_ID.
  • admin/cron/sync_facebook_events.php
  • planner passou a incluir canal threads_feed quando Threads estiver configurado.
  • dispatcher passou a processar threads_feed da fila.
  • REGRA COMENTADA NO CODIGO (NAO REMOVER): container do Threads expira em ~24h, portanto o container eh criado/publicado apenas perto do horario due.
  • resumo final de execucao passou a incluir Threads Feed ok/erro.
  • admin/modulos/facebook/agendamento.php
  • filtros e modal manual com opcao Threads Feed.
  • novo fluxo de Story notificado/manual na agenda:
  • status notified,
  • botoes operacionais: salvar preset, aplicar preset, clonar em massa e marcar publicado manual.
  • admin/modulos/facebook/agendamento-events.php
  • canal threads_feed adicionado no endpoint de agenda (label + icone).
  • status notified incluido no payload/filtros.
  • admin/modulos/facebook/agendamento-create.php
  • endpoint manual aceita threads_feed.
  • modo de publicacao (auto/notified) para stories.
  • admin/modulos/facebook/agendamento-preset-save.php
  • salva preset por peca/canal de story.
  • admin/modulos/facebook/agendamento-preset-apply.php
  • aplica preset para gerar agenda semanal em lote.
  • admin/modulos/facebook/agendamento-bulk-clone.php
  • clona agendamento base em massa (N semanas).
  • admin/modulos/facebook/agendamento-update-status.php
  • marca item notificado como publicado manualmente (com post_id opcional).
  • docs/documentacao_tecnica/campanha_track/THREADS_E_STORY_LINKS_NOTAS_2026-03.md
  • documentacao operacional das limitacoes de Story link sticker (API vs notificado/manual) e regra de 24h do Threads.
  • docs/BACKLOG.md
  • adicionados BK-100, BK-101, BK-102 para inteligencia de horarios/posts e complemento de tracking via GTM.

Operacao aplicada na VPS (05/03/2026 18:51 - America/Sao_Paulo):

  • cron legado horario do sync_facebook_events.php foi substituido por:
  • 0 6 * -> planner (mode=planner)
  • /5 * -> dispatcher (mode=dispatcher)
  • wrappers criados em /www/server/cron/:
  • rnt_sync_facebook_planner
  • rnt_sync_facebook_dispatcher

Atualizacao complementar (05/03/2026 - noite, operacao social + tracking/admin):

  • admin/modulos/facebook/agendamento.php
  • REGRA COMENTADA (NAO REMOVER): modal "Nova Postagem" passou a listar somente pecas elegiveis de venda online com contexto/produtor Admin.
  • select de peca passou a exibir nome do produtor junto ao titulo.
  • campo Data da sessao (opcional) recebeu texto explicando uso em fila/gatilhos/analise por sessao.
  • admin/modulos/facebook/agendamento-create.php
  • validacao server-side da mesma regra de elegibilidade (evita bypass via POST manual).
  • admin/inc-menu.php
  • submenu legado Feed Facebook Ads foi roteado para central de redes sociais no modulo Campanhas.
  • comentario operacional adicionado para evitar regressao de menu/link.
  • admin/modulos/campanhas/redes_sociais.php (novo)
  • central operacional de redes sociais com botoes para:
  • sync_facebook_events.php (planner/dispatcher/full/reset),
  • agenda social,
  • feed catalogo CSV (feed_facebook_catalogo.php).
  • resumo da social_post_queue por status/canal.
  • lista de stories em operacao (queued/notified/error).
  • acao manual para adiantar stories notificados para "agora" (uso operacional com sticker/link manual).
  • admin/modulos/campanhas/index.php
  • novo atalho "Redes Sociais" no cabecalho do modulo.
  • admin/modulos/dashboard/tracking.php
  • correcao de exibicao de horario para America/Sao_Paulo a partir de cliente_tracking.data_hora (UTC).
  • tabela de eventos recentes ganhou coluna de links operacionais:
  • origem do acesso,
  • pagina canonica do evento/peca,
  • pedido relacionado (quando vinculavel por cliente+peca).
  • view_item e begin_checkout agora mostram nota de contexto de valor para evitar leitura como compra efetivada.
  • docs/BACKLOG.md
  • adicionado item BK-103 para integracao TikTok no orquestrador social.

Validacao operacional executada (05/03/2026 20:24 BRT):

  • Execucao manual CLI:
  • php admin/cron/sync_facebook_events.php mode=dispatcher
  • cron executou em modo dispatcher e confirmou processamento de fila/gatilhos sem erro de acesso.
  • observacao: stories manual_notified so mudam para notified quando scheduled_for <= NOW().

Atualizacao complementar (05/03/2026 - catalogo Meta + Facebook Events):

  • atualizar_feed.php
  • feed pecas.xml passou a refletir automaticamente pecas de venda online do site:
  • status='A'
  • (id_produtor IS NULL OR parceria_online=1)
  • exclui Teste
  • somente sessoes futuras com sec_valor > 0 (remove vencidas automaticamente).
  • saida em CLI ficou enxuta (OK pecas.xml atualizado | itens=N) para evitar log gigante no cron.
  • admin/cron/feed_facebook_catalogo.php
  • corrigida query (remocao de coluna inexistente p.foto_home que deixava CSV sem linhas).
  • mesmo filtro de elegibilidade do catalogo online.
  • ajuste de preco:
  • price usa valor cheio quando existir;
  • sale_price usa valor final de venda (sec_valor) quando menor que o cheio.
  • admin/classes/FacebookEventService.php
  • updateEvent() ampliado para aceitar start_time, end_time, location e cover_url.
  • admin/cron/sync_facebook_page_events.php (novo)
  • cron para criar/atualizar/remover eventos de pagina FB por peca elegivel.
  • inclui dry_run=1 para validacao sem publicar.
  • cria coluna pecas.facebook_page_event_id automaticamente quando ausente.
  • admin/modulos/campanhas/redes_sociais.php
  • adicionados botoes operacionais:
  • FB Events (dry-run)
  • FB Events (real).
  • Operacao de cron (VPS):
  • feed pecas.xml passou de diario para /30 * (automatico mais rapido para catalogo).
  • evidencia: /www/server/cron/f1fb970e7ca0b68f38510abb76a86aa0 executando com retorno OK pecas.xml atualizado | itens=3.
  • Diagnostico de plataforma (ambiente atual):
  • leitura de eventos da pagina funciona (GET /{page_id}/events).
  • criacao via API falha (POST /{page_id}/events) com GraphMethodException code 100 / subcode 33.
  • item aberto no backlog como BK-104 para desbloqueio definitivo.

BK-98 · Hardening do webhook WhatsApp para CRM externo (LegalizaRJ)

Data: 04 de Marco de 2026

Ajustes aplicados no helper (bot/whatsapp/server.js):

  • Webhook passou a aceitar e enviar apenas eventos de chat privado valido (@c.us ou @s.whatsapp.net).
  • Eventos de grupo/canal/newsletter/ID nao telefonico passam a ser ignorados.
  • Inclusao de assinatura HMAC SHA-256 no webhook:
  • X-Webhook-Timestamp
  • X-Webhook-Signature (sha256=<hash>)
  • Nova variavel obrigatoria no service:
  • WPP_WEBHOOK_SECRET

Objetivo de seguranca:

  • Evitar ingestao de mensagens indevidas no CRM externo.
  • Reduzir risco de spoof de webhook com payload sem assinatura valida.

Operacao:

  • Service reiniciado com novas variaveis:
  • WPP_WEBHOOK_TOKEN (rotacionado)
  • WPP_WEBHOOK_SECRET (novo)

BK-91 · Token Facebook/IG bloqueado por instabilidade externa (Meta/JSSDK)

Data: 03 de Marco de 2026

Diagnostico executado:

  • sync_facebook_events.php?reset=1:
  • Encontradas 3 pecas elegiveis para sincronizacao.
  • erro de token: Session has expired on Monday, 02-Mar-26 20:00:00 PST.
  • php admin/cron/monitor_story_roi.php:
  • facebook_token_health=error
  • expires_at=2026-03-03 01:00:00
  • instagram_binding_live=warn por token expirado.
  • Fluxo do painel em admin/modulos/facebook/configure.php:
  • tentativa de Obter Token Automaticamente bloqueada com mensagem de plataforma:
  • A opção JSSDK não está ativada.

Conclusao operacional:

  • Bloqueio externo confirmado (Meta/Facebook instavel + dependencia de OAuth/JSSDK).
  • BK-91 permanece aberto e pausado ate estabilizacao do fluxo de login/token no app Meta.

Retomada planejada (quando estabilizar):

  • Renovar token da pagina no painel.
  • Revalidar:
  • php admin/cron/monitor_story_roi.php com status ok;
  • https://rionoteatro.com.br/admin/cron/sync_facebook_events.php?reset=1 sem erro de token.

BK-97 · Governanca do pecas.json (runtime) + filtro da peca Teste no cron social

Data: 03 de Marco de 2026

Contexto validado:

  • O endpoint sync_facebook_events.php?reset=1 nao regenera api/eventos/pecas.json; ele apenas limpa IDs sociais na tabela pecas.
  • Consulta real de elegibilidade retornava 4 pecas, incluindo homologacao:
  • 1343 | Teste | parceria=1.

Ajustes aplicados:

  • admin/cron/sync_facebook_events.php
  • selecao de pecas elegiveis passou a excluir nome = 'Teste'.
  • Governanca de artefato gerado (api/eventos/pecas.json):
  • removido do versionamento Git (git rm --cached);
  • adicionado no .gitignore;
  • criado api/eventos/pecas.example.json como base versionada.
  • api/eventos/README.md
  • documentacao atualizada para diferenciar pecas.example.json (versionado) de pecas.json (runtime).

Token Facebook/IG (evidencia operacional):

  • Execucao em 03/03/2026 18:08:10 (PST) retornou:
  • Session has expired on Monday, 02-Mar-26 20:00:00 PST.
  • Backlog BK-91 atualizado para exigir renovacao e revalidacao do reset.

BK-86 · SQL/Collation do Robo ROI corrigidos + cron id=19 reativado

Data: 03 de Marco de 2026

Problemas corrigidos:

  • Truncated incorrect DOUBLE value: 'pago' em sp_calcular_roi_campanhas.
  • Illegal mix of collations for operation 'concat' em sp_gerar_alertas_roi.
  • Retorno inconsistente ROI calculado para -1 registros.

Ajustes aplicados:

  • Criado script versionado: SQL/bk86_fix_roi_procedures.sql.
  • Recriadas procedures:
  • sp_calcular_roi_campanhas
  • ajuste de status de pagamento para p.status_transacao IN (3, 4);
  • cast/sanitizacao de pedidos.valor (VARCHAR -> DECIMAL) em todos os somatorios;
  • contagem explicita de linhas inseridas para retorno correto do total.
  • sp_gerar_alertas_roi
  • cast robusto de thresholds em robot_config.valor;
  • padronizacao de collation em CONCAT/comparacoes com utf8mb4_unicode_ci.

Validacoes executadas:

  • CALL sp_calcular_roi_campanhas(CURDATE() - INTERVAL 7 DAY, CURDATE())
  • resultado: ROI calculado para 3 registros.
  • CALL sp_gerar_alertas_roi()
  • resultado: alertas_gerados = 0.
  • php admin/cron/analisar_roi_campanhas.php
  • execucao completa sem erros SQL/collation.

Operacao / Cron:

  • Cron aaPanel id=19 (Robo ROI Rio no Teatro) reativado.
  • Evidencia operacional: wrapper do cron executado manualmente com status final de sucesso.

BK-85 · Alerta WhatsApp para erro critico via debug_log (debounce 30min)

Data: 03 de Marco de 2026

Ajustes aplicados:

  • admin/logs/debug_helper.php
  • debug_log() passou a avaliar erros criticos e acionar notificacao para admin via WhatsApp.
  • Adicionadas funcoes internas para:
  • detectar gatilhos de erro (ERRO, FATAL, EXCEPTION, CRITICO);
  • evitar loop de alerta para modulo WHATSAPP/DEBUG_LOG_ALERT;
  • aplicar debounce global de 30 minutos (admin/logs/debug_whatsapp_alert_last_ts.lock);
  • enviar mensagem resumida com link direto do painel de Debug Log.

Comportamento esperado:

  • Ao registrar erro critico com debug_log('MODULO', '...ERRO...'), o sistema tenta enviar:
  • ALERTA ERRO no site: [MODULO] ...
  • Debug Log: https://www.rionoteatro.com.br/admin/modulos/debug_log/
  • Novos alertas ficam bloqueados por 30 minutos para evitar flood.

Validacoes executadas:

  • php -l admin/logs/debug_helper.php sem erros de sintaxe.
  • Revisao de diff para confirmar anti-loop, debounce e integracao via enviar_whatsapp_admin().

BK-83 · Fila WhatsApp: correção de JSON com quebra de linha + higiene de cache

Data: 03 de Março de 2026

Causa raiz confirmada (pendência travada na fila mesmo com WhatsApp online):

  • O bot/whatsapp/healthcheck.sh montava o body JSON do /send por interpolação de string.
  • Mensagens com quebra de linha (\n) geravam payload inválido e a API retornava:
  • JSON invalido: Bad control character in string literal ...
  • Resultado: item permanecia na fila com tentativas incrementando.

Ajustes aplicados:

  • bot/whatsapp/healthcheck.sh
  • payload do /send passou a ser gerado com jq -cn (escaping seguro de caracteres especiais/quebras de linha).
  • .gitignore
  • adicionada regra bot/whatsapp/.wwebjs_cache/ (cache runtime do whatsapp-web.js fora do versionamento).

Validações executadas:

  • bash -n bot/whatsapp/healthcheck.sh sem erros.
  • Execução manual do healthcheck.sh com serviço conectado:
  • envio da mensagem pendente concluído com sucesso.
  • pasta bot/whatsapp/queue/ ficou vazia após processamento.

BK-82 · QR Code web no admin para reconexao WhatsApp + correcao JSON

Data: 03 de Março de 2026

Causa raiz confirmada (nao exibia QR no navegador):

  • O backend Node mostrava QR apenas no terminal (journalctl) e nao expunha endpoint web para o painel.
  • O endpoint de restart do admin podia retornar warnings PHP (open_basedir em file_exists('/bin/bash')), quebrando JSON do AJAX e impedindo o fluxo de exibir QR.

Ajustes aplicados:

  • bot/whatsapp/server.js
  • novo endpoint GET /qr com qr_image_data_url (PNG base64), qr_pending e qr_updated_at.
  • cache do ultimo QR em memoria para entrega ao painel admin.
  • bot/whatsapp/package.json
  • adicionada dependencia qrcode.
  • admin/whatsapp_qr.php (novo)
  • endpoint autenticado para o admin consultar status + QR do microservico.
  • admin/index.php
  • bloco visual para exibir QR quando offline.
  • polling de whatsapp_qr.php a cada 8s com cache: false.
  • fluxo do botao reiniciar trata requires_qr sem reload forcado.
  • admin/whatsapp_restart.php
  • resposta JSON padronizada com requires_qr e qr_pending.
  • blindagem de output (ob_clean) para evitar lixo antes do JSON.
  • includes/whatsapp_helper.php
  • _whatsapp_tentar_restart() passou a usar bash via PATH (remove warnings de open_basedir ao testar /bin/bash).
  • bot/whatsapp/README.md
  • documentado endpoint GET /qr.

Validações executadas:

  • node --check bot/whatsapp/server.js sem erros.
  • php -l em admin/index.php, admin/whatsapp_restart.php, admin/whatsapp_qr.php, includes/whatsapp_helper.php.
  • Teste direto da API local:
  • GET /status => disconnected com qr_pending=true.
  • GET /qr => qr_image_data_url preenchido.
  • Teste CLI do endpoint admin:
  • REQUEST_METHOD=POST HTTP_HOST=localhost php admin/whatsapp_restart.php retornando JSON valido com requires_qr=true.

BK-80 · RCA WhatsApp Offline + auto-recuperacao systemd

Data: 03 de Março de 2026

Causa raiz confirmada (incidente):

  • healthcheck.sh tentava reiniciar via PM2 (pm2 restart whatsapp-bot), mas o bot roda via systemd (rnt-whatsapp).
  • cleanup.sh limpava apenas parte dos locks e ainda fazia chown para www-data, divergente do user do service (www), causando falhas recorrentes de lock/sessao.
  • Timeout curto no AJAX do painel (30s) gerava falso erro de comunicacao em reinicios mais lentos.

Ajustes aplicados:

  • bot/whatsapp/healthcheck.sh
  • troca de PM2 para systemctl com fallback sudo -n.
  • reinicio apenas quando API local esta indisponivel; disconnected nao força restart.
  • lock file com trap para limpeza garantida.
  • bot/whatsapp/cleanup.sh
  • limpeza ampliada para auth/session (Singleton + DevToolsActivePort).
  • kill de processos Chrome/Chromium relacionados ao userDataDir do bot.
  • ownership alinhado dinamicamente ao usuario/grupo do service.
  • bot/whatsapp/restart.sh
  • restart via systemctl com verificacao em loop.
  • rotacao simples de restart.log para evitar crescimento infinito.
  • includes/whatsapp_helper.php
  • _whatsapp_tentar_restart() passou a aguardar ate 45s por reconexao.
  • admin/index.php
  • timeout AJAX do botao de restart aumentado para 90s.
  • bot/whatsapp/server.js
  • removido webVersionCache remoto quebrado (URL 404).
  • headless alterado para true (estabilidade com Chrome atual).
  • client.initialize() com catch explicito e exit(1) para recovery por systemd.
  • handler de SIGTERM para encerramento limpo (elimina timeout de stop).
  • /etc/systemd/system/rnt-whatsapp.service
  • adicionado PermissionsStartOnly=true para permitir ExecStartPre com privilegio de limpeza.

Validações executadas:

  • bash -n: healthcheck.sh, cleanup.sh, restart.sh.
  • node --check: bot/whatsapp/server.js.
  • php -l: includes/whatsapp_helper.php, admin/index.php.
  • systemctl restart rnt-whatsapp concluindo sem timeout (elapsed=1s).
  • curl http://127.0.0.1:3033/status respondendo com JSON e service active.
  • healthcheck.sh manual nao forca restart quando status esta disconnected.

BK-79 · Teatros canônicos sem /teatro + mapa e slugs normalizados

Data: 03 de Março de 2026

Ajustes:

  • Teatros passaram a usar URL canônica pública em https://rionoteatro.com.br/<slug_do_teatro>.
  • Rotas legadas /teatro/<slug|id> agora redirecionam com 301 para o slug direto.
  • Página de teatro (teatro.php) passou a gerar/persistir slug automaticamente quando ausente (incluindo casos antigos como /teatro/118).
  • teatros.php, lista-teatros.php e card de “outros teatros” foram ajustados para links diretos sem /teatro/.
  • Roteamento de slug direto foi ampliado em peca.php: quando o slug não é peça, mas é teatro, a página do teatro é exibida.
  • Mapa em teatro.php deixou de usar maps/embed/v1/place?key=... e passou a usar o mesmo padrão dos eventos (google.com/maps?output=embed&q=...), eliminando erro de API key inválida.
  • Geração de slug em eventos/teatros foi reforçada no server-side (admin + painel) para substituir acentuação corretamente (ç -> c, ê -> e, etc.) mesmo com slug digitado manualmente.

Arquivos modificados:

  • teatro.php
  • teatros.php
  • lista-teatros.php
  • peca.php
  • admin/modulos/teatros/action.php
  • admin/modulos/eventos/action.php
  • painel/modulos/eventos/action.php

Validações executadas:

  • php -l em todos os arquivos PHP alterados: sem erros de sintaxe.
  • HTTP: /teatro/118 retornando 301 para /acaso-cultural.
  • HTTP: /teatro/grupo-teatros-da-barra retornando 301 para /grupo-teatros-da-barra.
  • HTTP: /grupo-teatros-da-barra retornando 200.
  • Conferência de HTML: sem links públicos restantes com /teatro/ em teatros.php e lista-teatros.php.

BK-78 · Canonical sem /evento/ em todo fluxo público

Data: 03 de Março de 2026

Ajustes:

  • URLs públicas de eventos padronizadas para https://rionoteatro.com.br/<slug> nas listagens de eventos.php e teatro.php.
  • Rota legada /evento/<slug> passou a responder com redirecionamento 301 para o slug canônico (preservando query string útil).
  • go.php deixou de gerar destino /evento/<slug> (inclusive quando dest=evento) e agora direciona sempre para /<slug>.
  • Metadados SEO em evento.php atualizados com rel="canonical" e og:url apontando para o slug canônico.
  • Link de visualização pública no admin (admin/modulos/eventos/index.php) atualizado para o padrão sem /evento/.

Arquivos modificados:

  • eventos.php
  • teatro.php
  • evento.php
  • go.php
  • admin/modulos/eventos/index.php

Validações executadas:

  • php -l em todos os arquivos PHP alterados: sem erros de sintaxe.
  • Teste HTTP: /evento/sarav-a-com-dia e /evento/os-homens-querem-casar retornando 301 para /<slug>.
  • Teste HTTP: /go/<slug>?dest=evento retornando destino final sem /evento/.

BK-74 · UX WhatsApp + Lista diária também por WhatsApp

Data: 03 de Março de 2026

Ajustes:

  • CTA de parceria no cadastro de eventos atualizado para "Fechei a Parceria Gratuita + Venda!".
  • Texto pré-preenchido da mensagem WhatsApp do fluxo de parceria atualizado para a nova frase.
  • Envio da lista digital diária de compradores passou a usar canal duplo:
  • e-mail (fluxo existente preservado)
  • WhatsApp para o celular do produtor (clientes.celular) quando disponível.

Observação de auditoria:

  • painel/modulos/eventos/editar.php não possui atualmente botão WhatsApp equivalente ao fluxo de parceria do incluir.php.

Arquivos modificados:

  • painel/modulos/eventos/incluir.php
  • disparaemail.php
  • docs/BACKLOG.md

BK-70 · Ajuste de frequência do alerta de token Facebook

Data: 03 de Março de 2026

Ajuste:

  • O alerta cron_sync_facebook_token_error foi calibrado para repetição de 1 em 1 hora (min_seconds=3600, force=false).
  • Objetivo: manter visibilidade do incidente sem voltar ao spam de intervalos curtos.

Arquivo modificado:

  • admin/cron/sync_facebook_events.php

BK-69 · Debounce de alerta crítico do token Facebook + backlog rápido

Data: 03 de Março de 2026

Ajustes:

  • Reduzido spam de WhatsApp no erro de token inválido/expirado do cron social.
  • Alterado cron_sync_facebook_token_error para respeitar debounce real:
  • min_seconds: 21600 (6h)
  • force: false
  • Mantido o exit(1) no cron quando token está inválido (comportamento operacional preservado).

Backlog documentado nesta entrega:

  • Ajuste textual do CTA WhatsApp em painel/modulos/eventos/incluir.php e painel/modulos/eventos/editar.php.
  • Envio complementar por WhatsApp do link diário da lista de compradores para o produtor.

Arquivos modificados:

  • admin/cron/sync_facebook_events.php
  • docs/BACKLOG.md

BK-66 · Parceria Premium (Admin x Produtor) + Visibilidade em Home/Peças

Data: 03 de Março de 2026

Funcionalidades:

  • Admin ganhou ação dedicada para ativar Parceria Premium direto na edição do evento.
  • Tela de eventos no admin recebeu botão + modal de confirmação para o fluxo de ativação.
  • Módulo de peças do admin recebeu ação para desfazer Parceria Premium e botão de atalho.
  • Listagem de peças/home pública passou a aceitar também eventos com parceria_online = 1.
  • Fluxo do produtor foi ajustado para manter semântica de pendência (status/editado_produtor) no modo parceria.

Arquivos modificados:

  • admin/modulos/eventos/action.php
  • admin/modulos/eventos/editar.php
  • admin/modulos/pecas/action.php
  • admin/modulos/pecas/index.php
  • painel/modulos/eventos/action.php
  • index.php
  • pecas.php

Validações executadas:

  • php -l em todos os arquivos alterados: sem erros de sintaxe.
  • Verificação de diff hygiene (git diff --check): sem trailing whitespace.

Dias da Semana e Horarios nos Eventos + Bot Captador

Data: 12 de Fevereiro de 2026

Funcionalidades:

  • Novos campos dias_semana e horarios na tabela pecas (eventos)
  • Painel do produtor: campos para informar dias da semana e horários do evento
  • Bot capturador Sympla: extrai automaticamente dias e horários da agenda
  • Admin: ao aprovar evento do bot, campos dias_semana e horários são salvos automaticamente
  • Busca em eventos.php: agora busca também por dias da semana e horários
  • Fallback na busca: se não encontrar sessões cadastradas, verifica se a data está dentro do período da temporada

Banco de Dados:

  • Script SQL: update_pecas_schema.sql - Adiciona colunas dias_semana e horarios
  • Script PHP: add_columns.php - Adiciona colunas via PHP (alternativa ao SQL)

Arquivos criados:

  • update_pecas_schema.sql - Script SQL para adicionar colunas
  • add_columns.php - Script PHP para adicionar colunas via código
  • check_pecas_schema.php - Verifica estrutura da tabela pecas
  • check_schema_temp.php - Script temporário de verificação

Arquivos modificados:

  • eventos.php - Busca agora inclui dias_semana e horarios nos filtros
  • painel/modulos/eventos/incluir.php - Campos dias_semana e horarios no formulário
  • painel/modulos/eventos/editar.php - Campos dias_semana e horarios na edição
  • painel/modulos/eventos/action.php - Salva dias_semana e horarios no cadastro
  • admin/modulos/eventos/action.php - Salva dias_semana e horarios ao aprovar evento do bot
  • bot/scrapers/sympla_scraper.php - Extrai dias e horários da agenda do Sympla

Busca Inteligente em Eventos

Data: 12 de Fevereiro de 2026

Funcionalidades:

  • Campo de busca na pagina eventos.php (igual ao de teatros.php)
  • Busca por: nome do evento, nome do teatro, bairro
  • Busca por dia da semana (ex: "sabado", "segunda-feira")
  • Busca por data especifica (ex: "23/04", "15/03/2025")
  • Busca por data por extenso (ex: "23 de abril", "15 de março")
  • Busca por dia do mes (ex: "15" - encontra eventos no dia 15 de qualquer mes)
  • Filtro client-side instantaneo sem recarregar pagina
  • Contador de resultados em tempo real

Estilo Visual:

  • Implementado modo dark igual ao teatros.php
  • Fundo escuro (var(--dark-card)) com borda sutil (var(--dark-border))
  • Texto claro (var(--text-primary)) com placeholder em tom medio (var(--text-secondary))
  • Efeito glow verde ao focar (var(--accent-green))
  • Remove div rnt-container extra para layout mais limpo

Arquivos modificados:

  • eventos.php - Adicionado campo de busca com filtros inteligentes e estilo dark mode

Arquivos de backup criados:

  • eventos_backup_20260212.php

Notificacoes WhatsApp para Admin/Produtores + Resiliencia

Data: 09 de Fevereiro de 2026

Funcionalidades:

  • Notificacoes WhatsApp para admin: novo produtor cadastrado, novo evento criado, interesse em creditos
  • Notificacoes WhatsApp para produtores: cadastro aprovado/rejeitado, evento aprovado/correcao, conta bloqueada
  • Sistema de resiliencia com auto-recuperacao: healthcheck antes de enviar, restart automatico se offline, fila de mensagens
  • Restart preventivo apos envio bem-sucedido (limpa Chrome zumbis e locks)
  • Nenhuma mensagem se perde: toda falha (cURL, HTTP, offline) enfileira automaticamente
  • Dashboard admin com status WhatsApp em tempo real (verde=online, vermelho=offline) + contador de fila
  • Cron de backup (a cada 2h) para processar fila em horarios sem movimento

Arquivos criados:

  • includes/whatsapp_helper.php - Helper com funcoes reutilizaveis: enviar_whatsapp(), enviar_whatsapp_admin(), fila, healthcheck, restart
  • bot/whatsapp/cleanup.sh - Mata zumbis Chrome + remove locks Puppeteer
  • bot/whatsapp/restart.sh - Restart seguro do servico (cleanup + systemctl restart)
  • bot/whatsapp/healthcheck.sh - Script cron para verificar saude e processar fila
  • bot/whatsapp/SETUP_RESILIENCIA.md - Documentacao completa do setup no servidor

Arquivos modificados:

  • action.php (root) - Notificacao WhatsApp ao admin quando novo produtor se cadastra
  • painel/modulos/eventos/action.php - Notificacao WhatsApp ao admin quando novo evento e criado
  • admin/modulos/produtores/action.php - WhatsApp para produtor (aprovacao/rejeicao/bloqueio)
  • admin/modulos/eventos/action.php - WhatsApp para produtor (aprovacao/correcao de evento)
  • admin/modulos/eventos/index.php - Fix: badge de eventos pendentes mostrava 0
  • admin/modulos/eventos/editar.php - Fix: alerta de exclusao aparecia indevidamente (editado_produtor==4)
  • admin/index.php - Dashboard com status WhatsApp em tempo real (verde/vermelho)
  • painel/modulos/creditos/action.php - Notificacao WhatsApp quando produtor clica pacote creditos
  • painel/modulos/creditos/index.php - AJAX para notificar + CSS redesign_2026

Setup servidor (manual):

  • systemd: ExecStartPre=/bin/bash cleanup.sh (limpeza antes de cada start)
  • Sudoers: www-data pode executar systemctl restart/is-active sem senha
  • Cron aaPanel: healthcheck.sh a cada 2 horas
  • Permissoes: restart.log com ownership www-data

Microservico WhatsApp + Scraping SPA + Alertas Bot Captador

Data: 08-09 de Fevereiro de 2026

Funcionalidades:

  • Microservico Node.js para envio de mensagens WhatsApp via whatsapp-web.js (Puppeteer + Chrome headless)
  • Endpoint POST /scrape para scraping de paginas SPA (Sympla) com scroll automatico e lazy loading
  • Sistema de alertas integrado ao Bot Captador (WhatsApp primario, email fallback)
  • Modulo debug_log no admin para monitoramento em tempo real
  • Filtro "Somente BOT" no admin de eventos

Arquivos criados:

  • bot/whatsapp/server.js - Servidor principal (API REST + WhatsApp + Scrape)
  • bot/whatsapp/package.json - Dependencias npm (whatsapp-web.js, qrcode-terminal)
  • bot/whatsapp/setup.sh - Script de instalacao para VPS com systemd
  • bot/whatsapp/README.md - Documentacao completa do microservico
  • bot/handlers/alert_handler.php - Handler de alertas (WhatsApp + email)

Arquivos modificados:

  • config/bot_config.php - Configuracoes WhatsApp (URL, API key, numero alerta)
  • bot/cron_captador.php - Integracao com AlertHandler pos-execucao
  • admin/modulos/eventos/index.php - Filtro "Somente BOT" nas abas

Detalhes tecnicos:

  • Engine: whatsapp-web.js v1.26+ com webVersionCache remoto
  • Chrome: Google Chrome Stable (nao snap) em servidor headless
  • Porta: 3033 (localhost only, protegida por API key)
  • Servico: systemd rnt-whatsapp (User=www-data, Restart=on-failure)
  • Sessao persistente via LocalAuth em bot/whatsapp/auth/

SQL executado:

  • SQL/bot_melhorias_20260208.sql - Migracao InnoDB + tabela bot_alertas_log

2025

Sistema de Produtores - Historico Completo

FASES CONCLUIDAS

FASE 18: Integração de Recursos Premium no Cadastro de Eventos

Data: 02 de Dezembro de 2025

Funcionalidade:

  • Integração completa da contratação de recursos premium diretamente no formulário de inclusão de eventos (incluir.php).
  • Objetivo: Aumentar a conversão de upgrades no momento de maior interesse do produtor (criação do evento).

Implementação:

  • [x] 18.1 - Seção de Recursos Premium no Formulário
  • Adicionada seção visual "Recursos Premium" ao final do formulário de cadastro.
  • Exibição em tempo real do saldo de créditos do produtor.
  • Cards informativos com 3 grupos de ofertas:
  1. Pacotes Completos: Combo Premium (500 créditos).
  2. Destaques Individuais: Home (300 créditos) e Topo (200 créditos).
  3. Recursos Adicionais: Link Externo (50 créditos).
  • [x] 18.2 - Fluxo de Contratação Inteligente
  • Auto-save: Ao clicar em ativar um recurso, o sistema salva automaticamente o evento primeiro (rascunho) para garantir que o ID exista.
  • Modals de Confirmação: Detalham custos, benefícios e saldo final antes da confirmação.
  • Validação de Saldo: Bloqueia ativação se saldo insuficiente e sugere compra de créditos.
  • Feedback Visual: Botões com estados de carregamento e mensagens de sucesso/erro via Toastr.

Arquivos Modificados:

  • painel/modulos/eventos/incluir.php
  • painel/modulos/eventos/js/incluir.js (lógica de auto-save e ativação)

Impacto:

  • Simplifica a jornada do produtor, permitindo destacar o evento imediatamente após o cadastro sem navegar por múltiplos menus.

FASE 17: Correção de Scroll no Painel 🐛

Data: 02 de Dezembro de 2025

Problema Identificado:

  • A página painel/index.php não rolava para baixo apesar de ter conteúdo extenso.
  • O problema era causado pela biblioteca jquery.nicescroll.js que conflita com renderizações modernas no elemento html.

Correções Aplicadas:

  • [x] 17.1 - Desativação Local do NiceScroll
  • Adicionado script em painel/index.php para remover o nicescroll especificamente nesta página.
  • Mantido o funcionamento global do nicescroll para outras páginas (sidebar, etc) para evitar regressões visuais.
  • Restaurado overflow: auto no elemento html.

Arquivos Modificados:

  • painel/index.php

Backups Criados:

  • painel/index_bkp_20251202.php
  • painel/js/common-scripts_bkp_20251202.js (criado durante tentativa anterior, mantido por segurança)

Impacto:

  • Restaura a usabilidade da Home do Painel permitindo acesso a todo o conteúdo.

FASE 16: Correção de Mensagens de Erro no Checkout 🐛

Data: 12 de Novembro de 2025

Problema Identificado:

  • Mensagens de erro (warning/info) não apareciam ao tentar cadastrar email/CPF/RG duplicado
  • JavaScript buscava apenas .alert-danger mas action.php retornava status "warning" ou "info"
  • Usuários não recebiam feedback visual, levando ao abandono do processo de compra
  • Possível causa da queda de vendas desde 02/11/2025

Correções Aplicadas:

  • [x] 16.1 - Refatoração do Sistema de Alertas no pedido.php
  • JavaScript agora busca alerta dinamicamente: .alert-{status} (warning, info, danger, success)
  • Adiciona classe show necessária para exibir alertas (CSS usa display: none !important)
  • Implementado scroll suave até o alerta para melhor UX
  • Esconde alertas anteriores antes de mostrar o novo
  • Aplicado em ambos os fluxos:
  • Botão "Continuar para Pagamento" (usuário NÃO logado) - linhas 947-966
  • Botão "Entendi, Prosseguir" (usuário logado) - linhas 1013-1032
  • [x] 16.2 - Testes Completos via CURL
  • ✅ Cadastro com CPF/Email/RG duplicado → Retorna status: "warning" com mensagem
  • ✅ Cadastro com CPF inválido → Retorna status: "info" com mensagem
  • ✅ Cadastro válido (novo usuário) → Retorna status: "success" + URL PagSeguro
  • ✅ Validação de redirecionamento PagSeguro → Funcionando corretamente
  • ✅ Validação de datas indisponíveis → Sistema valida corretamente

Arquivos Modificados:

  • pedido.php - Linhas 947-966 (fluxo novo cadastro) e 1013-1032 (fluxo usuário logado)

Backups Criados:

  • pedido_bkp_12112025.php - Backup antes das alterações

Impacto:

  • Melhora significativa na UX do checkout
  • Usuários agora recebem feedback claro sobre erros
  • Redução de abandono de carrinho por falta de feedback
  • Possível recuperação das vendas que caíram desde 02/11

Deploy:

  • Commit: 722f38af
  • Branch: main
  • Deploy automático: ~1 minuto após push
  • Status: ✅ EM PRODUÇÃO

FASE 15: Campo Status Admin + Exclusão de Fotos 🎛️

Data: 10 de Novembro de 2025

  • [x] 15.1 - Exclusão de Fotos pelo Produtor
  • Implementado botão "Excluir Foto" em painel/modulos/eventos/editar.php
  • Função JavaScript deletarFoto() com confirmação
  • Case deletar_foto em painel/modulos/eventos/action.php
  • Deleta arquivos físicos de /large/ e /media/
  • Remove registro do banco (tb_fotos)
  • Validação de permissões (produtor só deleta suas fotos)
  • Logs detalhados para debugging
  • [x] 15.2 - Correção JSON no Admin
  • Corrigido erro "SyntaxError: Unexpected token '<'" em admin/modulos/eventos/action.php
  • Movido processamento de formulário PARA DENTRO dos cases
  • Removido suporte a AVIF (PHP 5.6)
  • Adicionados 4 cases especiais: aprovar, informar_correcao, rejeitar_definitivo, limpar_historico
  • Restaurado Cropper.js no admin
  • [x] 15.3 - Campo Status no Admin ⭐
  • Adicionado campo <select name="status"> em admin/modulos/eventos/editar.php
  • Dropdown com 2 opções: 'A' (Ativo) e 'I' (Inativo)
  • Admin pode controlar visibilidade do evento manualmente
  • Processamento em admin/modulos/eventos/action.php:
  • Case 'cadastrar': Admin escolhe status inicial
  • Case 'editar': Admin altera status manualmente
  • Produtor continua sem acesso (status sempre 'I')
  • Campo obrigatório com validação
  • Help text explicativo

Arquivos Criados:

  • docs/FUNCIONALIDADE_EXCLUSAO_FOTOS.md - Documentação de exclusão de fotos
  • docs/CORRECAO_ACTION_ADMIN.md - Correção do action.php do admin
  • docs/RESTAURACAO_CROP_ADMIN.md - Restauração do crop
  • docs/CAMPO_STATUS_ADMIN.md - Documentação do campo status

Arquivos Modificados:

  • painel/modulos/eventos/editar.php - Botão de exclusão de foto
  • painel/modulos/eventos/action.php - Refatoração completa (case deletar_foto)
  • admin/modulos/eventos/editar.php - Campo status + restauração do crop
  • admin/modulos/eventos/action.php - Refatoração completa + processamento de status

Backups Criados:

  • painel/modulos/eventos/editar_bkp_20251110.php
  • painel/modulos/eventos/action_bkp_20251110.php
  • painel/modulos/eventos/action_bkp_20251110_v2.php
  • admin/modulos/eventos/editar_bkp_20251110_simplificado.php
  • admin/modulos/eventos/editar_bkp_20251110_v2.php
  • admin/modulos/eventos/editar_bkp_20251110_v3.php
  • admin/modulos/eventos/action_bkp_20251110_v2.php
  • admin/modulos/eventos/action_bkp_20251110_v3.php

Problema Resolvido:

  • Código de processamento executava ANTES do switch, causando PHP Notices quando $_POST não tinha os campos esperados
  • Solução: Movido TODO o processamento para DENTRO de cada case específico

Compatibilidade:

  • PHP 5.6+ (sem AVIF)
  • WebP com verificação function_exists()
  • jQuery (usando jQuery ao invés de $ para compatibilidade global)

FASE 14: Sistema Completo de Tracking de ROI 📊

Data: 04 de Novembro de 2025

  • [x] 14.1 - Estrutura de Banco de Dados
  • Criadas 4 novas tabelas: campanhas, campanhas_roi, campanhas_alertas, robot_config
  • Adicionados 5 campos UTM em cliente_tracking: utm_source, utm_medium, utm_campaign, utm_content, utm_term
  • Criadas 2 VIEWs: v_campanhas_performance, v_ranking_fontes
  • Criadas 2 Stored Procedures: sp_calcular_roi_campanhas, sp_gerar_alertas_roi
  • Índices otimizados para performance em todas as tabelas
  • [x] 14.2 - Tracking Frontend Automático
  • Criado js/utm-tracker.js para captura automática de UTM parameters
  • Sistema de cookies (7 dias) e sessão PHP
  • Tracking de 3 eventos: view_item, begin_checkout, purchase
  • First touch e last touch attribution
  • APIs backend: api/track-utm.php, api/track-event.php
  • [x] 14.3 - Módulo Admin Completo de Campanhas
  • 8 arquivos PHP em admin/modulos/campanhas/:
  • index.php - Listagem com 4 cards de métricas
  • incluir.php - Criar campanha com preview de link UTM
  • editar.php - Editar com métricas ao vivo
  • detalhes.php - Visão completa + gráfico de evolução
  • dashboard-roi.php - Dashboard com Chart.js (3 gráficos)
  • alertas.php - Sistema de alertas inteligentes
  • action.php - Backend com 8 actions (criar, editar, deletar, etc.)
  • bootstrap.php - Configuração do módulo
  • [x] 14.4 - Dashboard Analítico com Gráficos
  • Chart.js 3.9.1 para visualizações
  • Gráfico 1: Evolução do ROI (linha temporal)
  • Gráfico 2: Receita por Fonte (pizza)
  • Gráfico 3: Performance por Fonte (barras)
  • Top 5 melhores e piores campanhas
  • Filtros por período (data início/fim)
  • KPIs em tempo real
  • [x] 14.5 - Sistema de Alertas Automáticos
  • 5 tipos de alertas: roi_positivo, roi_negativo, baixa_conversao, alto_custo, meta_atingida
  • 4 níveis de prioridade: urgente, alta, media, baixa
  • 4 status: novo, lido, resolvido, ignorado
  • Ações sugeridas automáticas
  • Interface completa com filtros e ações rápidas
  • [x] 14.6 - Robô de Análise Automática
  • Script admin/cron/analisar_roi_campanhas.php
  • 7 passos de execução automática:
  1. Carrega configurações do robô
  2. Calcula ROI de campanhas ativas
  3. Gera alertas inteligentes
  4. Busca alertas para notificação
  5. Envia email HTML com resumo
  6. Limpa alertas antigos (>30 dias)
  7. Gera estatísticas finais
  • Sistema de logs detalhados em admin/cron/logs/
  • Compatível com CLI e execução WEB
  • Pronto para cron job diário
  • [x] 14.7 - Métricas Calculadas
  • ROI % e Valor (lucro líquido)
  • CPA (Custo Por Aquisição)
  • Taxa de Conversão
  • Ticket Médio
  • Receita Bruta
  • Visualizações, Checkouts, Vendas
  • [x] 14.8 - Documentação Completa
  • docs/SISTEMA_TRACKING_ROI.md (1000+ linhas) - Manual completo
  • docs/GUIA_TESTES_ROI.md - 34 casos de teste em 7 fases
  • docs/RESUMO_IMPLEMENTACAO_ROI.md - Resumo executivo
  • docs/CHECKLIST_POS_UPLOAD.md - Validação pós-deploy
  • admin/cron/README.md - Configuração do cron job

Arquivos Criados:

  • SQL: EXECUTAR_ESTE.sql, EXECUTAR_FALTANTE.sql, adicionar_menu_campanhas.sql
  • JS: js/utm-tracker.js
  • APIs: api/track-utm.php, api/track-event.php
  • Admin: 8 arquivos em admin/modulos/campanhas/
  • Cron: admin/cron/analisar_roi_campanhas.php
  • Docs: 4 arquivos em docs/

Arquivos Modificados:

  • includes/inc_peca.php → includes/inc_peca_2026.php (adicionado utm-tracker.js na linha 451)
  • meta-analytics.php (adicionadas variáveis globais JavaScript nas linhas 250-260)

Banco de Dados:

  • 4 novas tabelas
  • 5 campos adicionados em cliente_tracking
  • 2 VIEWs
  • 2 Stored Procedures
  • 8 configurações do robô

Tecnologias:

  • PHP 5.6+ (mysql_* functions)
  • MySQL 5.7+ (VIEWs, Stored Procedures)
  • JavaScript ES5 (compatibilidade)
  • Chart.js 3.9.1
  • Bootstrap 3.x
  • Parsley.js (validação)

Total: 24 arquivos criados/modificados | ~8.000 linhas de código

Status: ✅ Testado e funcionando em produção

Documentação: Ver SISTEMA_TRACKING_ROI.md (SISTEMA_TRACKING_ROI.md) para manual completo


FASE 13: Democratização do Acesso aos Eventos 🎭

Data: 03 de Novembro de 2025

  • [x] 13.1 - Documento opcional no cadastro de produtor
  • Removida obrigatoriedade de anexar documento em cadastro-produtor.php
  • Campo "Documento Comprobatório" agora é opcional
  • Backend action.php adaptado para aceitar envio vazio
  • Objetivo: Facilitar inclusão de novos produtores
  • [x] 13.2 - Link "Divulgue seu Evento" dinâmico
  • Link do menu público agora muda baseado no estado de login
  • NÃO logado → /cadastro-produtor.php
  • Logado → /painel/modulos/eventos/
  • Implementado em desktop e mobile
  • [x] 13.3 - Correção de layout da página de login
  • Removido padding-top: 100px do body
  • Menu agora encosta no topo da página
  • Login wrapper centralizado com flexbox
  • Adicionado margin: 0 auto para centralização horizontal
  • [x] 13.4 - Sistema de redirect inteligente para login
  • Login redireciona para pecas.php por padrão
  • Exceção: Se estava em peca.php, volta para o evento específico
  • Cadastro e compra mantêm comportamento original (qualquer página)
  • Aplicado em login AJAX e Google OAuth
  • [x] 13.5 - Menu "Divulgar Eventos" visível para todos
  • Removida verificação de is_produtor no painel/inc-menu.php
  • Nome alterado: "Meus Eventos" → "Divulgar Eventos"
  • Link agora SEMPRE visível para todos os clientes
  • Código simplificado (removida query com JOIN)
  • [x] 13.6 - Documentação completa atualizada
  • Nova seção "Menu do Painel do Cliente" em menu.md
  • Resumo de todas as 5 alterações documentado
  • Histórico de versões atualizado para 3.2
  • Comparação ANTES/DEPOIS dos fluxos

Arquivos Modificados:

  • cadastro-produtor.php (../cadastro-produtor.php) (linha 82-85)
  • action.php (../action.php) (linhas 145-166, 221-243, 966-999)
  • includes/inc_peca.php (../includes/inc_peca.php) (linhas 483-487, 531, 541)
  • painel/login.php (../painel/login.php) (linhas 46-71)
  • painel/inc-menu.php (../painel/inc-menu.php) (linhas 17-23)
  • docs/menu.md (../docs/menu.md) (versão 3.2)

Status: ✅ Funcionando em produção


FASE 12: Implementação de Toastr em Formulários 🔔

Data: 31 de Outubro de 2025

  • [x] 12.1 - Correção do sistema de alertas em cadastre-se.php
  • Substituído sistema legado de alertas HTML por notificações Toastr
  • Adicionada biblioteca Toastr (CSS e JS) no cadastre-se.php
  • Configuração centralizada de Toastr com barra de progresso
  • Posicionamento: topo centralizado (toast-top-center)
  • [x] 12.2 - Atualização de js/cadastre-se.js (v1.0.1 → v1.0.2)
  • Reescrita completa do handler de sucesso AJAX
  • Implementada lógica especial para emails duplicados
  • Adicionado link clicável para "Recuperar Senha" em erros de duplicidade
  • Timeout estendido (15s) para mensagens de erro com ações
  • Removido sistema antigo de scroll e manipulação de elementos HTML
  • [x] 12.3 - Padronização de notificações
  • Formato consistente: toastrstatus (message)
  • Suporte a HTML em mensagens (links, botões, ícones)
  • Tratamento de erros genéricos (falha de comunicação)
  • Redirecionamento automático após 3 segundos em caso de sucesso
  • [x] 12.4 - Testes completos via cURL
  • ✅ Teste 1: Cadastro novo - PASSOU
  • ✅ Teste 2: Email duplicado - PASSOU
  • ✅ Teste 3: CPF inválido - PASSOU
  • ✅ Teste 4: Segundo cadastro novo - PASSOU
  • ✅ Teste 5: Cadastro produtor (email duplicado) - PASSOU
  • ✅ Teste 6: Cadastro produtor (sem documento) - PASSOU
  • [x] 12.5 - Documentação
  • Criado formularios.md (formularios.md) com documentação completa
  • Documentados todos os formulários do sistema
  • Incluídos exemplos de uso, troubleshooting e testes
  • Adicionado roadmap de melhorias futuras

Arquivos Modificados:

  • cadastre-se.php (../cadastre-se.php) (linhas 13, 308)
  • js/cadastre-se.js (../js/cadastre-se.js) (reescrita)

Status: ✅ Funcionando em produção


FASE 11: Simplificação do Upload de Imagens 🎨

  • [x] 11.1 - Refatoração para input file nativo HTML5
  • Removido sistema drag-drop complexo do gestor de imagens
  • Implementado input file nativo com atributo accept para validação
  • Interface mais simples e intuitiva para produtores
  • [x] 11.2 - Preview instantâneo da imagem
  • JavaScript com FileReader API para preview antes do upload
  • Validação client-side de formato (JPG, JPEG, PNG)
  • Validação de tamanho (máximo 5MB)
  • Mensagens de erro claras e específicas
  • [x] 11.3 - Unificação da interface
  • Campo de foto adicionado diretamente no formulário de incluir.php
  • Campo de foto adicionado diretamente no formulário de editar.php
  • Removido alerta "Importante: Após preencher os dados..."
  • Removida seção separada "Gestor de Foto do Evento"
  • [x] 11.4 - Limpeza de código
  • Removidos scripts não utilizados (uploader-eventos.js)
  • Removidas bibliotecas de drag-drop não necessárias
  • Código JavaScript inline simplificado
  • Redução de dependências externas

FASE 10: Upload de Imagens para Eventos 📸

  • [x] 10.1 - Integração do Gestor de Imagens no fluxo de eventos
  • Redirect automático para editar.php após cadastro (com ID do evento)
  • Mensagem incentiva: "Agora adicione uma foto para deixar seu evento mais atrativo!"
  • [x] 10.2 - Gestor de Fotos no editar.php
  • Seção "Foto do Evento" totalmente funcional
  • Upload de APENAS 1 foto (limite configurado)
  • Sem vídeos, sem tabs (simplificado para produtores)
  • Script customizado: painel/modulos/eventos/js/uploader-eventos.js (../painel/modulos/eventos/js/uploader-eventos.js)
  • [x] 10.3 - Preview visual no incluir.php
  • Seção de foto visível mas desabilitada (opacity: 0.5; pointer-events: none)
  • Alerta amarelo: "Salve o evento primeiro!"
  • UX clara: produtor já vê onde vai adicionar a foto
  • [x] 10.4 - Configurações de upload
  • Formatos aceitos: JPG, JPEG, PNG
  • Limite: 1 foto por evento
  • Mensagem de erro: "Máximo de 1 foto permitida. Delete a foto atual para enviar outra."
  • Admin mantém funcionalidades completas (múltiplas fotos e vídeos)

FASE 9: Estrutura de Relacionamento Produtor-Peça 🔗

  • [x] 9.1 - Análise completa da estrutura atual vs. ideal
  • Documento: ANALISE_ESTRUTURA_PECAS.md (ANALISE_ESTRUTURA_PECAS.md)
  • Identificado conflito: código tentava usar id_cliente, SQL planejado usava id_produtor
  • Decisão: implementar id_produtor (semântica correta + performance)
  • [x] 9.2 - Alteração do banco de dados
  • Script: /SQL/SQL_ADD_ID_PRODUTOR_PECAS.sql (../SQL/SQL_ADD_ID_PRODUTOR_PECAS.sql)
  • Executado: ALTER TABLE pecas ADD COLUMN id_produtor INT NULL
  • Criado índice: idx_id_produtor para performance
  • Coluna editado_produtor já existia (não foi necessário criar)
  • [x] 9.3 - Correção do código do painel
  • painel/modulos/eventos/action.php:84 (../painel/modulos/eventos/action.php#L84) - INSERT com id_produtor
  • painel/modulos/eventos/index.php:77 (../painel/modulos/eventos/index.php#L77) - Listagem filtrando por id_produtor
  • painel/modulos/eventos/editar.php:14 (../painel/modulos/eventos/editar.php#L14) - Verificação de propriedade
  • painel/modulos/eventos/sessoes.php:15 (../painel/modulos/eventos/sessoes.php#L15) - Verificação de propriedade
  • Todas queries de segurança atualizadas
  • [x] 9.4 - Otimização do código do admin
  • admin/modulos/pecas/index.php:111-115 (../admin/modulos/pecas/index.php#L111-L115) - Query otimizada com LEFT JOIN
  • Eliminado lookup ineficiente por produtor_email
  • Contador de pendentes usando editado_produtor = 1
  • Performance melhorada significativamente

FASE 8: Correções Críticas e Debug 🐛

  • [x] 8.1 - Criação de DEBUG.md (DEBUG.md) com processo de exposição de erros SQL
  • [x] 8.2 - Correção inicial de nomenclatura (descobriu necessidade da Fase 9)
  • [x] 8.3 - Implementação de editado_produtor=1 ao criar/editar peças
  • Nova peça: marca editado_produtor=1 (aguarda aprovação admin)
  • Edição de peça: marca editado_produtor=1 e status=I (aguarda nova aprovação)
  • Admin aprova: altera editado_produtor=0 e status=A (peça vai ao ar)

FASE 7: Módulo Admin de Produtores 🎭

  • [x] 7.1 - Criação completa do módulo /admin/modulos/produtores/
  • [x] 7.2 - Sistema de listagem com JOIN à tabela clientes
  • [x] 7.3 - Formulário de aprovação/rejeição/bloqueio (detalhe.php)
  • [x] 7.4 - Campos Email e Celular editáveis no admin
  • [x] 7.5 - Visualização de documentos anexados (PDF e imagens)
  • [x] 7.6 - Sistema de status com dropdown (pendente, ativo, rejeitado, bloqueado)
  • [x] 7.7 - Campos de motivo de rejeição e bloqueio
  • [x] 7.8 - Correção de roteamento AJAX com data-action-url
  • [x] 7.9 - Refatoração de SQL UPDATE para melhor tratamento de erros
  • [x] 7.10 - Exibição de status e motivo de rejeição no painel do cliente
  • [x] 7.11 - Sistema de resubmissão após rejeição (máx 3 tentativas)
  • [x] 7.12 - Atualização automática de clientes.is_produtor ao aprovar

FASE 6: Backend de Solicitação de Produtor 🚀

  • [x] 6.1 - Implementar case "solicitar_produtor" em produtor/action.php
  • [x] 6.2 - Sistema completo de upload de documentos
  • [x] 6.3 - Validações de segurança (tipo, tamanho, tentativas)
  • [x] 6.4 - Integração com tabela tb_produtores
  • [x] 6.5 - Controle de status e tentativas (máx 3)
  • [x] 6.6 - Correção do frontend para enviar para produtor/action.php
  • [x] 6.7 - Implementação de div para exibir motivo de rejeição
  • [x] 6.8 - Backup de segurança do arquivo produtor/action.php

FASE 1: Preparação e Backup ⚠️

  • [x] 1.1 - Backup de todos os arquivos que serão modificados
  • [x] 1.2 - Análise da estrutura atual do banco de dados
  • [x] 1.3 - Documentação das modificações necessárias

FASE 2: Estrutura de Banco de Dados 🗄️

  • [x] 2.1 - Adicionar campo use_cpf na tabela tab_users
  • [x] 2.2 - Adicionar campo produtor_email na tabela pecas
  • [x] 2.3 - Adicionar campo status_aprovacao na tabela pecas
  • [x] 2.4 - Criar índices necessários for performance

FASE 3: Sistema de Cadastro Público 📝

  • [x] 3.1 - Criar página /cadastro-produtor.php (formulário público)
  • [x] 3.2 - Criar validação de CPF e e-mail únicos
  • [x] 3.3 - Sistema de confirmação por e-mail (no-reply@rionoteatro.com.br)
  • [x] 3.4 - Integrar com design atual do site

FASE 4: Modificações no Admin 👨💼

  • [x] 4.1 - Modificar /admin/modulos/administradores/index.php (adicionar CPF)
  • [x] 4.2 - Modificar /admin/modulos/pecas/index.php (aba aprovação + filtros)
  • [x] 4.3 - Sistema de controle via Status (Ativo/Inativo) + campo editado_produtor
  • [x] 4.4 - Sistema de notificação por e-mail para produtores

📊 IMPLEMENTAÇÕES RECENTES (Outubro 2025)

🎨 Simplificação do Upload de Imagens para Eventos ✅

Data: 26/10/2025

  • FUNCIONALIDADE IMPLEMENTADA: Refatoração completa do sistema de upload de fotos para interface nativa HTML5
  • MOTIVAÇÃO:
  • Sistema drag-drop anterior era complexo demais para apenas 1 foto
  • Dependências externas desnecessárias (uploader-eventos.js, jquery.uploadfile.min.js)
  • Interface confusa com seções separadas e alertas múltiplos
  • MUDANÇAS IMPLEMENTADAS:
  • Input File Nativo:
  • Substituído sistema drag-drop por <input type="file">
  • Atributo accept="image/jpeg,image/jpg,image/png" para validação nativa
  • Compatível com todos navegadores modernos e mobile
  • Preview Instantâneo:
  • JavaScript puro com FileReader API
  • Preview da imagem antes do upload (sem necessidade de salvar)
  • Imagem exibida em tamanho otimizado (max-width: 300px, max-height: 200px)
  • Validações Client-Side:
  • Tamanho máximo: 5MB (alerta antes do upload)
  • Formatos aceitos: JPG, JPEG, PNG (validação dupla: HTML5 + JS)
  • Mensagens de erro específicas e claras
  • Interface Unificada:
  • Campo de foto integrado diretamente no formulário
  • Mesmo comportamento em incluir.php e editar.php
  • Removidos alertas informativos redundantes
  • Removida seção separada "Gestor de Foto do Evento"
  • Limpeza de Código:
  • Removidos 140+ linhas de código desnecessário
  • Eliminadas 5 dependências de bibliotecas externas
  • JavaScript simplificado (30 linhas vs. 92 anteriores)
  • ARQUIVOS MODIFICADOS:
  • painel/modulos/eventos/incluir.php
  • Removido alerta informativo (linhas 76-79)
  • Removida seção "Gestor de Foto do Evento" (linhas 177-194)
  • Adicionado input file com preview no formulário principal
  • Adicionado JavaScript de validação e preview (30 linhas)
  • painel/modulos/eventos/editar.php
  • Removida seção "Gestor de Foto do Evento" (linhas 201-216)
  • Adicionado input file com preview no formulário principal
  • Removidos scripts de upload antigos (linhas 337-344)
  • Adicionado JavaScript de validação e preview (30 linhas)
  • BENEFÍCIOS:
  • ✅ Interface mais simples e intuitiva
  • ✅ Menos dependências externas (melhor performance)
  • ✅ Preview instantâneo melhora UX
  • ✅ Compatibilidade mobile melhorada
  • ✅ Código mais limpo e manutenível
  • ✅ Validações mais claras para o usuário
  • IMPACTO:
  • Redução de ~60% no código relacionado a upload
  • Interface unificada entre incluir e editar
  • Experiência do usuário mais fluida
  • OBSERVAÇÃO:
  • ⚠️ Backend (action.php) precisará ser ajustado para processar upload via formulário
  • 📝 Documentação atualizada em README.md e RESUMO_FASE_11.md

🔒 reCAPTCHA v3 Invisível - Esqueceu Senha ✅

Data: 25/10/2025

  • FUNCIONALIDADE IMPLEMENTADA: Sistema de proteção anti-bot invisível no formulário de recuperação de senha
  • ARQUIVOS MODIFICADOS:
  • painel/esqueceu_senha.php - Frontend com reCAPTCHA v3
  • painel/data/functions.php - Função verify_captcha() atualizada
  • CHAVES reCAPTCHA v3:
  • Site Key: 6Ldnb3AqAAAAADI_kdUeqIbA26axUeeKMom3rKOp
  • Secret Key: 6Ldnb3AqAAAAA<REDIGIDO-BK-366: secret do reCAPTCHA, ver painel/data/functions.php>
  • IMPLEMENTAÇÃO:
  • Script carregado: https://www.google.com/recaptcha/api.js?render=[SITE_KEY]
  • Execução invisível com grecaptcha.execute() na ação 'submit'
  • Sem checkbox visível (100% invisível para o usuário)
  • Validação backend baseada em score (0.0 a 1.0)
  • Score mínimo aceito: 0.5 (humano provável)
  • FLUXO DE VALIDAÇÃO:
  1. Usuário preenche email e clica "Enviar"
  2. reCAPTCHA v3 executa invisível em background
  3. Token gerado automaticamente
  4. Backend valida token via API do Google
  5. Score >= 0.5 = aprovado, email enviado
  6. Score < 0.5 = rejeitado como bot
  • PROTEÇÃO CONTRA:
  • Ataques de força bruta
  • Spam de emails
  • Enumeração de usuários
  • DoS (Denial of Service)
  • LOGS DE DEBUG:
  • Logs detalhados em error_log() para troubleshooting
  • Registro de token, score e motivos de rejeição
  • BENEFÍCIOS:
  • Experiência do usuário sem fricção (sem captcha visível)
  • Proteção robusta contra bots automatizados
  • Gratuito (até 1 milhão de requisições/mês)

🎨 Simplificação e Ajustes do Formulário de Eventos ✅

Data: 25/10/2025

  • FUNCIONALIDADE IMPLEMENTADA: Simplificação do formulário de eventos e correções de validação
  • MÓDULO ATUALIZADO: /painel/modulos/eventos/
  • MUDANÇAS IMPLEMENTADAS:
  • Campo Contato (WhatsApp):
  • Máscara automática: (21) 99999-9999
  • Validação de 11 dígitos obrigatórios (DDD + 9 dígitos)
  • Salva no banco apenas números limpos (sem formatação)
  • Mensagem de ajuda: "Este número precisa ser de WhatsApp pois será o contato dos interessados"
  • Campo Classificação:
  • Alterado de input text para dropdown
  • Opções: LIVRE, 10 anos, 12 anos, 14 anos, 16 anos, 18 anos
  • Campo Teatro:
  • Agora obrigatório (*)
  • Dropdown populado da tabela teatros
  • Campo Ficha Técnica:
  • Agora é opcional (removido asterisco)
  • CKEditor Simplificado:
  • Toolbar reduzida: Negrito, Itálico, Sublinhado, Remover Formatação
  • Alinhamento: Esquerda, Centro, Direita, Justificado
  • Formato: Somente Parágrafo Normal (p) e Título (h1)
  • Altura fixada em 200px
  • Campos Removidos:
  • Quantidade Máxima de Ingressos
  • Oferta (Desconto em %)
  • Local e Contatos
  • Regras
  • Contatos Vendas Interno
  • E-mail lista de compradores
  • Tempo do envio antecipado
  • Correção do Action:
  • Adicionado data-action-url="action.php" nos formulários
  • Corrige envio para o action.php correto do módulo
  • Validação JavaScript:
  • Máscara dinâmica no campo contato
  • Validação de 11 dígitos antes de enviar
  • Limpeza automática do número (remove formatação)
  • Confirmação:
  • Eventos são inseridos na tabela pecas (corrigido de 'tb_pecas')
  • Campo id_produtor relaciona evento ao cliente produtor
  • Correção de Nome de Tabela:
  • Corrigido TB_ATUAL de 'tb_pecas' para 'pecas' no bootstrap.php
  • Tabela correta do banco de dados é 'pecas' (sem prefixo 'tb_')

🎭 Módulo Admin de Produtores Completo ✅

Data: 25/10/2025

  • FUNCIONALIDADE IMPLEMENTADA: Sistema completo de gestão de solicitações de produtores no painel admin
  • MÓDULO CRIADO: /admin/modulos/produtores/ com todos os arquivos necessários
  • MUDANÇAS IMPLEMENTADAS:
  • Estrutura do Módulo:
  • bootstrap.php - Configuração do módulo
  • index.php - Listagem de solicitações com JOIN à tabela clientes
  • detalhe.php - Formulário de aprovação/rejeição/bloqueio
  • action.php - Processamento de edições e exclusões
  • Listagem (index.php):
  • Query com JOIN entre tb_produtores e clientes
  • Exibição de: Nome, Email, Empresa, Status, Data Solicitação
  • Labels coloridos por status (warning=pendente, success=ativo, danger=rejeitado, info=bloqueado)
  • Botões de ação: Editar, Deletar
  • Formulário de Aprovação (detalhe.php):
  • Campos editáveis: Email, Celular (requisito do usuário)
  • Campo Empresa (pode ser NULL)
  • Dropdown de Status (pendente, ativo, rejeitado, bloqueado)
  • Textarea "Motivo da Rejeição" (condicional, só aparece quando status=rejeitado)
  • Textarea "Motivo do Bloqueio" (condicional, só aparece quando status=bloqueado)
  • Textarea "Observações" (sempre visível)
  • Visualização de documento anexado com preview para imagens e link para PDFs
  • Atributo data-action-url="action.php" para roteamento correto
  • Processamento (action.php):
  • Case 'editar': Atualiza tb_produtores e clientes (email e celular)
  • Case 'deletar': Remove registro de solicitação
  • Incremento de tentativas ao rejeitar
  • Atualização de clientes.is_produtor = 1 ao aprovar
  • Refatoração para usar UPDATE direto ao invés de $obj_sql->update()
  • Tratamento dinâmico de campos NULL (empresa, motivo_rejeicao, motivo_bloqueio)
  • Logging de erros MySQL na resposta JSON
  • Correções de Roteamento:
  • Modificado admin/js/parsley-validate-form.js linha 40-44
  • Agora verifica data-action-url antes de usar APP_ROOT + 'action.php'
  • Permite que formulários em submódulos postem para seu próprio action.php
  • Exibição de Status no Painel Cliente:
  • painel/modulos/profile/index.php agora faz query de status do produtor
  • Alertas coloridos por status (info=pendente, success=ativo, danger=rejeitado, warning=bloqueado)
  • Exibição de motivo de rejeição quando aplicável
  • Contador de tentativas (1/3, 2/3, 3/3)
  • Sistema permite resubmissão se status=rejeitado e tentativas < 3
  • Correções de Upload:
  • Upload agora usa $_SERVER['DOCUMENT_ROOT'] para path absoluto
  • Salva path como /uploads/produtores/filename.pdf no banco
  • Evita conflitos com regras nginx rewrite
  • BENEFÍCIOS:
  • ✅ Admin pode revisar todas as solicitações de produtores
  • ✅ Aprovação/rejeição com feedback ao cliente
  • ✅ Email e celular editáveis para corrigir dados
  • ✅ Preview de documentos anexados
  • ✅ Cliente vê status e motivo de rejeição em tempo real
  • ✅ Sistema de resubmissão controlado (máx 3 tentativas)
  • ARQUIVOS CRIADOS:
  • admin/modulos/produtores/bootstrap.php
  • admin/modulos/produtores/index.php
  • admin/modulos/produtores/detalhe.php
  • admin/modulos/produtores/action.php
  • ARQUIVOS MODIFICADOS:
  • admin/js/parsley-validate-form.js
  • painel/modulos/profile/index.php
  • produtor/action.php (correções de upload)
  • SQL EXECUTADO:
  • INSERT em tb_menus para criar submenu "Produtores"

🔧 Correção de Caminhos Absolutos para Login/Logout ✅

Data: 24/10/2025

  • PROBLEMA RESOLVIDO: Login/logout falhavam após alterações de rewrite nginx - caminhos relativos causavam redirecionamento para peca.php
  • CAUSA RAIZ:
  • JavaScript usava caminhos relativos ('action.php') que eram capturados pelo rewrite
  • Link de logout apontava para arquivo inexistente (/admin/logout, /produtor/logout)
  • Produtor usava HTTP ao invés de HTTPS
  • Faltava definição de APP_ROOT no produtor
  • MUDANÇAS IMPLEMENTADAS:
  • Variável JS Global (11 arquivos HEAD):
  • admin/head.php - Adicionado <script>var APP_ROOT = '<?=APP_ROOT?>';</script>
  • produtor/head.php - Adicionado <script>var APP_ROOT = '<?=APP_ROOT?>';</script>
  • painel/head.php - Adicionado <script>var APP_ROOT = '<?=APP_ROOT?>';</script>
  • Links de Logout (3 arquivos):
  • admin/inc-topo.php - Alterado de <?=APP_ROOT?>logout para ?logout=1
  • produtor/inc-topo.php - Alterado de <?=APP_ROOT?>logout para ?logout=1
  • painel/inc-topo.php - JÁ ESTAVA CORRETO (index.php?logout=1)
  • JavaScript Parsley (3 arquivos):
  • admin/js/parsley-validate-form.js - 3x alterações: 'action.php' → APP_ROOT + 'action.php'
  • produtor/js/parsley-validate-form.js - 3x alterações: 'action.php' → APP_ROOT + 'action.php'
  • painel/js/parsley-validate-form.js - 3x alterações: 'action.php' → APP_ROOT + 'action.php'
  • Configurações do Produtor:
  • produtor/login.php - APP_ROOT de '' para '/produtor/'
  • produtor/data/config.php - http:// → https:// (3 constantes)
  • produtor/data/config.php - Adicionado define("APP_ROOT", URL_CLIENTE);
  • BENEFÍCIOS:
  • ✅ Login funciona independente de rewrites nginx
  • ✅ Logout usa sistema GET correto (?logout=1)
  • ✅ Caminhos absolutos evitam conflitos com regras de rewrite
  • ✅ HTTPS forçado em todo sistema produtor
  • ✅ Consistência entre admin, produtor e painel
  • BACKUPS CRIADOS:
  • admin/head_backup_20251024.php
  • admin/inc-topo_backup_20251024.php
  • admin/js/parsley-validate-form_backup_20251024.js
  • produtor/head_backup_20251024.php
  • produtor/inc-topo_backup_20251024.php
  • produtor/js/parsley-validate-form_backup_20251024.js
  • produtor/login_backup_20251024.php
  • produtor/data/config_backup_20251024.php
  • painel/head_backup_20251024.php
  • painel/inc-topo_backup_20251024.php
  • painel/js/parsley-validate-form_backup_20251024.js

🔒 Tratamento de Erro Google OAuth (access_denied) ✅

Data: 23/10/2025

  • PROBLEMA RESOLVIDO: Quando usuário cancelava autorização do Google OAuth, sistema retornava erro confuso "E-mail não encontrado"
  • MUDANÇAS IMPLEMENTADAS:
  • painel/action.php (linhas 20-41):
  • Validação de $_GET['error'] antes de processar login
  • Detecta access_denied (usuário cancelou) e outros erros OAuth
  • Validação dupla: garante dados POST OU código do Google
  • Previne tentativas de login sem informações válidas
  • Redireciona para login.php?google_error=cancelled|oauth_failed
  • painel/login.php (linhas 680-692):
  • Tratamento visual de erros do Google OAuth
  • Mensagem para cancelamento: "Você cancelou o acesso com Google. Use e-mail/CPF e senha ou tente novamente."
  • Mensagem para falhas: "Falha na autenticação com Google. Tente novamente ou use e-mail/CPF e senha."
  • Integração com Toastr (warning para cancelamento, error para falhas)
  • Limpeza automática de parâmetros da URL
  • BENEFÍCIOS:
  • ✅ Mensagens claras e orientativas para o usuário
  • ✅ Previne logs de erros falsos no sistema
  • ✅ Melhora UX em caso de cancelamento
  • ✅ Segurança adicional contra acesso indevido
  • ✅ Zero impacto em fluxos de login existentes
  • BACKUPS CRIADOS:
  • painel/action_backup_20251023.php
  • painel/login_backup_20251023.php

🎨 Separação de JavaScript em Módulos Externos ✅

Data: 23/10/2025

  • MUDANÇA: JavaScript modularizado para melhor manutenção e performance
  • ARQUIVOS CRIADOS:
  • js/cadastre-se.js - Lógica do formulário de cadastro público
  • Sistema de validação Parsley + AJAX
  • Redirecionamento inteligente pós-cadastro
  • Scroll suave para alertas
  • Tratamento de mensagens de sucesso/erro
  • js/pedido.js - Lógica do formulário de cadastro no checkout
  • Máscaras de telefone (00) 0 0000-0000
  • Email autocomplete com domínios populares
  • Sugestão inteligente de correção de email (mailcheck)
  • Inicialização de Parsley no formulário #form-cadastro-pedido
  • IMPACTO:
  • Código mais organizado e reutilizável
  • Melhor separação de responsabilidades
  • Cache de arquivos JS pelo navegador
  • Facilita manutenção e testes
  • ARQUIVOS MODIFICADOS:
  • cadastre-se.php - Importa js/cadastre-se.js?v1.0.1
  • pedido.php - Importa js/pedido.js?v=1.0.2

📋 Sistema de Cadastro: Seleção UF/Cidade/Zonas ✅

Data: 23/10/2025

  • MUDANÇA: Sistema dinâmico de seleção geográfica implementado
  • FUNCIONALIDADES:
  • UF: Seleção de estado com opção para estrangeiros
  • Cidade: Exibido apenas se UF = RJ (24 cidades do Rio de Janeiro)
  • Zonas de Interesse: Exibido apenas se Cidade = Rio de Janeiro
  • Centro, Zona Sul, Zona Norte, Zona Oeste, Zona Sudoeste, Baixada, Todo o RJ
  • Função toggleCidadeBairro() - Controla exibição de cidade
  • Função toggleZonas() - Controla exibição de zonas
  • Alertas informativos sobre opcional/obrigatório
  • IMPACTO:
  • Melhor segmentação de público
  • Ofertas personalizadas por região
  • UX otimizada (campos condicionais)
  • ARQUIVOS MODIFICADOS:
  • cadastre-se.php (linhas 168-239)

🎯 Campo "Como conheceu o Rio no Teatro?" ✅

Data: 23/10/2025

  • MUDANÇA: Campo de origem de tráfego atualizado
  • OPÇÕES ADICIONADAS:
  • TikTok (nova opção)
  • "Outro" com campo texto livre
  • OPÇÕES EXISTENTES:
  • Google, Facebook, Instagram, Newsletter, Amigo
  • IMPACTO: Rastreamento de canais de aquisição mais completo
  • ARQUIVOS MODIFICADOS:
  • cadastre-se.php (linhas 240-283)

💬 Mensagem Especial para Visitantes do Google ✅

Data: 23/10/2025

  • MUDANÇA: Detecção de referer do Google e exibição de mensagem de boas-vindas
  • FUNCIONALIDADE:
  • Verifica HTTP_REFERER = www.google.com
  • Exibe alert box amigável com emoji 👋
  • Mensagem: "Olá! Que bom que você nos encontrou no Google..."
  • IMPACTO: Melhora conversão de visitantes orgânicos
  • ARQUIVOS MODIFICADOS:
  • cadastre-se.php (linhas 25-32)

🔄 Redirecionamento Inteligente Pós-Cadastro ✅

Data: 23/10/2025

  • MUDANÇA: Sistema que preserva página de origem para redirecionamento
  • FUNCIONALIDADES:
  • Captura HTTP_REFERER ao acessar cadastre-se.php
  • Armazena em $_SESSION['original_page']
  • Exclui loops (não salva se referer = cadastre-se.php ou login)
  • Redireciona usuário para página original após login/cadastro
  • IMPACTO: UX melhorada - usuário retorna de onde veio
  • ARQUIVOS MODIFICADOS:
  • cadastre-se.php (linhas 19-23)
  • js/cadastre-se.js (redirecionamento via data.url)

Feat: Imagem responsiva na página da peça

Data: 23/10/2025

  • MUDANÇA: A imagem principal do evento em peca.php foi atualizada para usar a tag <picture> do HTML5.
  • IMPACTO: Otimização de performance. Dispositivos móveis agora carregam uma imagem de resolução média, enquanto desktops carregam a imagem de alta resolução, resultando em um carregamento mais rápido em conexões móveis.
  • ARQUIVOS MODIFICADOS:
  • peca.php

Layout: Reordena seções em peca.php

Data: 23/10/2025

  • MUDANÇA: A seção "Regras e Instruções" na página de detalhes da peça foi movida para ser a última seção, posicionada após "Dúvidas".
  • IMPACTO: Melhora o fluxo de leitura da página, deixando informações de contato e ajuda mais acessíveis antes das regras detalhadas.
  • ARQUIVOS MODIFICADOS:
  • peca.php

Fix: Salva "Zonas de Interesse" no perfil do usuário ✅

Data: 23/10/2025

  • PROBLEMA: O campo "Zonas de Interesse" no perfil do usuário não estava sendo salvo no banco de dados após a edição.
  • CAUSA RAIZ: O arquivo painel/modulos/profile/action.php não processava o campo zonas[] do formulário para incluí-lo na query de atualização do banco de dados.
  • SOLUÇÃO IMPLEMENTADA:
  • Adicionada uma linha em painel/modulos/profile/action.php para converter o array $_POST['zonas'] em um texto separado por vírgulas e salvá-lo no campo zonas_interesse da tabela clientes.
  • O campo zonas_interesse foi previamente adicionado à tabela clientes.
  • ARQUIVOS MODIFICADOS:
  • painel/modulos/profile/action.php
  • STATUS: ✅ Corrigido. As zonas de interesse agora são salvas corretamente.

🛒 Sistema de Checkout: Formulário de Cadastro no Pedido ✅

Data: 23/10/2025

  • MUDANÇA: Formulário de cadastro completo durante o checkout para usuários não logados
  • FUNCIONALIDADES:
  • Campos implementados:
  • Nome, Email, Confirmar Email
  • Senha (apenas para não logados)
  • Celular com máscara (00) 0 0000-0000
  • CPF obrigatório - Identificador único
  • Documento Oficial com Foto obrigatório - Para retirada na bilheteria
  • Validação:
  • Parsley validation no formulário #form-cadastro-pedido
  • Email autocomplete e correção inteligente (mailcheck)
  • Validação customizada via js/pedido.js
  • Alertas informativos:
  • Alerta sobre documentos obrigatórios com foto
  • Função alertDocumento() com informações sobre RG, OAB, Passaporte, CREA, CPF com foto, etc.
  • Campos hidden:
  • Quantidade, data, ID da peça, assentos, nome do espetáculo
  • Action pedido para processamento
  • IMPACTO:
  • Conversão de venda direta sem redirecionamentos
  • Dados completos capturados no momento da compra
  • Conformidade com regras de retirada de ingressos
  • ARQUIVOS MODIFICADOS:
  • pedido.php (linhas 439-611 - formulário completo)
  • js/pedido.js (máscaras e validações)

Data: 23/10/2025

  • MUDANÇA: Modal educativo sobre solicitação de endereço pelo PagSeguro
  • FUNCIONALIDADES:
  • Modal ID: #modalEnderecoPagSeguro
  • Trigger: Botão "Confirmar Pedido" (#btnConfirmarPedido)
  • Conteúdo:
  • Explicação: PagSeguro solicita endereço como medida de segurança
  • ℹ️ NÃO utilizamos o endereço informado
  • ℹ️ NÃO enviamos ingressos por correio
  • ℹ️ Ingressos SEMPRE retirados na bilheteria
  • ℹ️ Política de cancelamento (até 3h antes)
  • 🎭 Instruções de retirada com documento
  • Botão de confirmação: "Entendi, Prosseguir para Pagamento" (#btnEntendidoProsseguir)
  • Comportamento:
  • Modal não fecha com ESC ou clique fora (data-backdrop="static")
  • Após confirmação, processa pedido via AJAX e redireciona para PagSeguro
  • IMPACTO:
  • Reduz dúvidas sobre solicitação de endereço
  • Aumenta confiança do usuário
  • Diminui abandono no checkout
  • ARQUIVOS MODIFICADOS:
  • pedido.php (linhas 701-737 - HTML do modal)
  • pedido.php (linhas 912-1018 - JavaScript inline com handlers)

🔄 Fluxo de Duas Etapas: Cadastro → Confirmação ✅

Data: 23/10/2025

  • MUDANÇA: Sistema de cadastro em duas etapas durante o checkout
  • FLUXO PARA USUÁRIOS NÃO LOGADOS:
  1. Etapa 1: Preencher formulário de cadastro
  2. Clicar "Continuar para Pagamento" (#btnContinuarCadastro)
  3. Sistema valida com Parsley
  4. AJAX envia dados para action.php com act=pedido
  5. Sistema cadastra usuário e cria sessão
  6. Página recarrega - Usuário agora está logado
  7. Etapa 2: Tela de confirmação com resumo do pedido
  8. Clicar "Confirmar Pedido" (#btnConfirmarPedido)
  9. Modal informativo é exibido
  10. Clicar "Entendi, Prosseguir para Pagamento"
  11. AJAX processa pedido (act=efetuacompra)
  12. Redireciona para PagSeguro
  • FLUXO PARA USUÁRIOS LOGADOS:
  1. Visualiza resumo do pedido direto
  2. Clicar "Confirmar Pedido"
  3. Modal informativo é exibido
  4. Clicar "Entendi, Prosseguir para Pagamento"
  5. AJAX processa pedido
  6. Redireciona para PagSeguro
  • IMPACTO:
  • Separação clara entre cadastro e confirmação
  • Usuário tem tempo de ler informações importantes
  • Reduz erros de cadastro apressado
  • ARQUIVOS MODIFICADOS:
  • pedido.php (JavaScript inline - linhas 880-1018)
  • js/pedido.js (apenas helpers - máscaras e autocomplete)

📦 Exibição de Assentos Selecionados ✅

Data: 23/10/2025

  • MUDANÇA: Campo de assentos exibido quando aplicável
  • FUNCIONALIDADE:
  • Verifica se $assentos não está vazio
  • Exibe linha com "Assentos: [lista de assentos]"
  • Presente no resumo do pedido (logado e não logado)
  • IMPACTO: Transparência sobre assentos escolhidos
  • ARQUIVOS MODIFICADOS:
  • pedido.php (linhas 281-283, 643-645)

🐞 Correção: Fluxo de Cadastro em pedido.php ✅

Data: 23/10/2025

  • PROBLEMA: Ao se cadastrar durante a compra em pedido.php, o modal de endereço do PagSeguro era exibido prematuramente (ou piscava na tela), antes da etapa de confirmação do pedido.
  • IMPACTO: Interrompia o fluxo de duas etapas (1. Cadastro, 2. Confirmação) e causava confusão visual.
  • CAUSA RAIZ: Lógica de event handler duplicada. O comportamento correto foi implementado em um script inline em pedido.php, mas uma versão antiga e conflitante da mesma lógica ainda existia no arquivo externo js/pedido.js.
  • SOLUÇÃO IMPLEMENTADA:
  • 1ª Parte (pedido.php): O script inline foi corrigido para que o botão de cadastro (#btnContinuarCadastro) submetesse o formulário via AJAX e recarregasse a página, em vez de abrir o modal.
  • 2ª Parte (js/pedido.js): O bloco de código duplicado e conflitante, que continha os event handlers antigos para os botões de cadastro e confirmação, foi completamente removido de js/pedido.js.
  • ARQUIVOS MODIFICADOS:
  • pedido.php (bloco de script no final do arquivo)
  • js/pedido.js
  • STATUS: ✅ Corrigido. O conflito foi eliminado e o fluxo de cadastro para novos usuários foi restaurado.

✨ UX: Modal Informativo no Checkout (pedido.php) ✅

Data: 22/10/2025

  • PROBLEMA: Modal com informações importantes sobre PagSeguro abria e redirecionava imediatamente
  • IMPACTO: Usuários não tinham tempo de ler informações sobre endereço e retirada de ingressos
  • SOLUÇÃO IMPLEMENTADA:
  • Botão "Confirmar Pedido" agora apenas abre o modal (não redireciona)
  • Redirecionamento para PagSeguro movido para botão "Entendi, Prosseguir para Pagamento" dentro do modal
  • Implementado sistema AJAX manual que replica comportamento do Parsley
  • Handler específico para usuários logados (sem validação) e não-logados (com validação)
  • ARQUIVOS MODIFICADOS:
  • pedido.php (linhas 370, 915-989)
  • Botão alterado para type="button" com ID btnConfirmarPedido
  • JavaScript: Handler de click abre modal direto
  • JavaScript: Botão do modal executa AJAX e redireciona
  • FLUXO ATUAL:
  1. Usuário clica "Confirmar Pedido"
  2. Modal abre com informações sobre endereço PagSeguro
  3. Usuário lê com calma (sem redirecionamento automático)
  4. Usuário clica "Entendi, Prosseguir para Pagamento"
  5. AJAX processa pedido e redireciona para PagSeguro
  • STATUS: ✅ Implementado e funcionando
  • PENDENTE: Verificar comportamento do history.go(-1) no alert de validação de data

📚 Protocolo de Desenvolvimento Atualizado ✅

Data: 22/10/2025

  • ATUALIZAÇÃO CRÍTICA no README.md:
  • Seção específica sobre NUNCA EXECUTAR COMANDOS GIT
  • Clarificado que assistente apenas PROPÕE comandos
  • USUÁRIO é quem executa git add, git commit, git push
  • Exemplos de erros comuns adicionados
  • Fluxo correto documentado com exemplos práticos
  • STATUS: ✅ Documentação atualizada

📊 IMPLEMENTAÇÕES ANTERIORES (Dezembro 2024)

  • FOOTER ADICIONADO: Links legais em inc_peca.php e inc_peca_2026.php
  • PÁGINAS CRIADAS: politica-privacidade.php (LGPD) e termos-uso.php
  • MIGRAÇÃO HOMEPAGE: index.php usa inc_peca.php (original atualizado)
  • CONFORMIDADE: Footer com Política de Privacidade, Termos de Uso e Contato
  • STATUS: ✅ Ambos arquivos de header têm footer consistente

🔧 Google OAuth Login - REDIRECT LOOP CORRIGIDO ✅

  • PROBLEMA: Usuários redirecionados para myaccount.google.com após login Google
  • CAUSA: Sistema salvando URLs externas em original_page e redirecionando para elas
  • SOLUÇÃO: Filtro de URLs internas - apenas rionoteatro.com.br ou / aceitas
  • MIGRAÇÃO: login.php usa action.php (não mais action_2026.php)
  • STATUS: ✅ Google OAuth funcionando corretamente - problema resolvido

🔄 Migração Crítica de Arquivos _2026.php ✅

  • Google OAuth Fix: ✅ RESOLVIDO - Filtro de URLs externas implementado
  • Login System Migration: Migrado painel/login_2026.php → painel/login.php com design moderno
  • Action Handler Migration: Migrado painel/action_2026.php → painel/action.php com Google OAuth
  • Header Migration: Migrado includes/inc_peca_2026.php → includes/inc_peca.php com design responsivo
  • Logout Fix: Corrigido painel/data/setup.php para redirecionar para login.php ao invés de login_2026.php
  • Security Enhancement: URLs externas bloqueadas - apenas internas aceitas para redirect

🔐 Sistema de Login/Logout Consolidado ✅

  • Autenticação Unificada: CPF e email em um único sistema
  • Google OAuth Integrado: Login social funcionando corretamente
  • Redirecionamento Inteligente: Usuários retornam à página original após login
  • Session Management: Sistema de sessão consolidado e funcional
  • Password Recovery: Sistema "Lembrar Senha" com case remember_2026 implementado

📁 Arquivos Migrados e Corrigidos:

  • painel/login.php - ✅ Migrado com design 2026 e Google OAuth
  • painel/action.php - ✅ Migrado com handlers completos
  • includes/inc_peca.php - ✅ Migrado com header moderno e menu responsivo
  • painel/data/setup.php - ✅ Corrigido logout redirect
  • Backups criados: _backup_20251221.php para todos os arquivos modificados

💾 BACKUPS CRIADOS

  • produtor/modulos/administradores/index_20251021.php
  • produtor/modulos/administradores/detalhe_20251021.php
  • admin/modulos/pecas/index_20251021.php

📊 ARQUIVOS IMPLEMENTADOS

  • sql_updates/fase2_adicionar_campos.sql - Scripts SQL para banco
  • cadastro-produtor.php - Formulário público de cadastro
  • processar-cadastro-produtor.php - Backend de processamento
  • confirmar-produtor.php - Confirmação de e-mail
  • admin/modulos/pecas/notificacao-produtor.php - Sistema de notificações
  • produtor/completar-cadastro.php - Completar cadastro existente

🎯 MELHORIAS IMPLEMENTADAS

  • Documento Único: Campo RG aceita qualquer documento oficial com foto
  • Sistema de E-mails: Notificações automáticas para aprovação/reprovação
  • Aba de Aprovação: Interface administrativa com filtros e badges
  • Validação CPF: Sistema completo de validação e unicidade
  • UX Melhorado: Sistema de redirecionamento inteligente pós-login/cadastro
  • Formulários Otimizados: Removidos campos desnecessários (telefone, sexo)
  • Sistema UF/Cidade/Zonas: Seleção dinâmica com zonas do Rio de Janeiro
  • Menu Reorganizado: Nova ordem priorizando funcionalidades principais

🔄 MIGRAÇÃO COMPLETA DOS ARQUIVOS ORIGINAIS (Dezembro 2024)

✅ ARQUIVOS ORIGINAIS 100% MIGRADOS E FUNCIONAIS

Backend/Actions:

  • painel/action.php ✅ MIGRADO COMPLETO
  • ❌ REMOVIDO: case remember_2026 duplicado
  • ✅ MIGRADO: case remember ← funcionalidades do remember_2026
  • ✅ VALIDAÇÃO SEQUENCIAL: Email primeiro, depois reCAPTCHA
  • ✅ LOGS DETALHADOS: Sistema de debug completo
  • ✅ REDIRECIONAMENTOS: Todos apontam para arquivos originais

Configurações e Setup:

  • painel/data/setup.php ✅ MIGRADO COMPLETO
  • ✅ INCLUDES: Usa functions.php (não functions_2026.php)
  • ✅ LOGOUT: Redireciona para login.php (não login_2026.php)
  • ✅ FUNÇÃO: checkSession_2026() mantida para compatibilidade
  • painel/data/functions.php ✅ MIGRADO COMPLETO
  • ✅ checkSession() ← checkSession_2026() (IDÊNTICAS)
  • ✅ reCAPTCHA Enterprise: Sistema atualizado
  • ✅ GLOBAL $login: Corrigido
  • ✅ isset($_GET['r']): Adicionado
  • ✅ exit(): Adicionado

Interface de Login e Recuperação:

  • painel/login.php ✅ MIGRADO COMPLETO
  • ✅ INCLUDES: Usa data/setup.php (correto)
  • ✅ LINKS: Aponta para esqueceu_senha.php (correto)
  • ✅ DESIGN: Mantém design 2026
  • painel/esqueceu_senha.php ✅ MIGRADO COMPLETO
  • ✅ DESIGN 2026: CSS moderno completo
  • ✅ ACTION: Usa remember (não remember_2026)
  • ✅ INCLUDES: Usa data/setup.php (não setup_2026.php)
  • ✅ LINKS: Aponta para login.php e action.php
  • ✅ FUNCIONALIDADE: Validação sequencial idêntica ao _2026
  • painel/index.php ✅ MIGRADO COMPLETO
  • ✅ FUNÇÃO: Usa checkSession() (não checkSession_2026())
  • ✅ INCLUDES: Todos corretos

Arquivos de Interface:

  • painel/inc-topo.php ✅ SEM INCLUDES PROBLEMÁTICOS
  • painel/inc-menu.php ✅ SEM INCLUDES PROBLEMÁTICOS

📋 MAPEAMENTO DE FUNCIONALIDADES MIGRADAS:

| Funcionalidade | Arquivo Original | Status | Observações |

|---|---|---|---|

| Login CPF/Email | action.php case login | ✅ MIGRADO | Detecta CPF automaticamente |

| Recuperação Senha | action.php case remember | ✅ MIGRADO | Validação sequencial ← remember_2026 |

| Verificação Sessão | functions.php checkSession() | ✅ MIGRADO | Idêntica ao checkSession_2026() |

| reCAPTCHA Enterprise | functions.php verify_captcha() | ✅ MIGRADO | Sistema atualizado |

| Design Moderno | esqueceu_senha.php | ✅ MIGRADO | CSS 2026 completo |

| Redirecionamento Inteligente | action.php case login | ✅ MIGRADO | Página original preservada |

🎯 RESULTADO FINAL:

  • ✅ 100% dos arquivos originais possuem TODAS as funcionalidades das versões _2026
  • ✅ 0 dependências de arquivos _2026.php
  • ✅ Sistema completo e funcional
  • ✅ Compatibilidade total mantida
  • ✅ Performance otimizada com validação sequencial

⚠️ IMPORTANTE:

Os arquivos _2026.php podem permanecer para referência, mas NÃO são mais necessários para o funcionamento do sistema. Todos os arquivos originais estão 100% atualizados e funcionais.

🔄 REGRA OFICIAL DE MIGRAÇÃO:

SEMPRE que encontrar remember_2026 pode alterar para remember

  • ✅ CONFIRMADO 100%: remember possui TODAS as funcionalidades do remember_2026
  • ✅ Validação sequencial: Email primeiro, depois reCAPTCHA
  • ✅ Logs detalhados: Sistema de debug completo
  • ✅ Mensagens específicas: Tratamento de erros idêntico
  • ✅ Compatibilidade total: Funciona com todos os formulários

Exemplo de migração:

```javascript

// ANTES (versão 2026)

data: { act: 'remember_2026', email: email }

// DEPOIS (versão consolidada)

data: { act: 'remember', email: email }

```

🐛 PROBLEMA GOOGLE LOGIN RESOLVIDO:

  • CAUSA: Links JavaScript hardcoded para _2026.php
  • CORREÇÃO: Todos os links atualizados para arquivos originais
  • STATUS: ✅ Login Google funcionando corretamente

📋 STATUS DOS ARQUIVOS _2026.php:

| Arquivo Original | Arquivo _2026 | Status Migração | Funcionalidades |

|---|---|---|---|

| login.php | login_2026.php | ✅ EQUIVALENTE | Mesmas funcionalidades, links diferentes |

| action.php | action_2026.php | ✅ EQUIVALENTE | remember = remember_2026 |

| esqueceu_senha.php | esqueceu_senha_2026.php | ✅ EQUIVALENTE | Design 2026 + action remember |

| functions.php | functions_2026.php | ✅ IDÊNTICOS | Arquivos 100% iguais |

| setup.php | setup_2026.php | ✅ EQUIVALENTE | Mesmas funcionalidades, includes diferentes |


🔓 Arquivos Autorizados para Modificação (Versão 2026)

Arquivos já migrados (versão 2026 ativa):

  • peca.php - Página de detalhes da peça ✅
  • pecas.php - Listagem de peças ✅ (migrado de pecas_2026.php)
  • cadastre-se.php - Página de cadastro ✅
  • index.php - Homepage Broadway com sistema de backgrounds dinâmicos ✅ (migrado de index_2026.php)

Módulos Admin Implementados:

  • admin/modulos/indexfotos/ - Sistema completo de legendas para backgrounds ✅
  • admin/modulos/slider/ - Módulo de sliders integrado ao menu via banco ⚠️ (upload de imagens com problema de permissões)

✅ Arquivos Migrados (_2026.php → originais):

  • painel/login.php - ✅ MIGRADO (era login_2026.php)
  • painel/action.php - ✅ MIGRADO (era action_2026.php)
  • includes/inc_peca.php - ✅ MIGRADO (era inc_peca_2026.php) + Footer Legal
  • index.php - ✅ MIGRADO para usar inc_peca.php original

📁 Arquivos _2026.php (STATUS DETALHADO):

  • painel/index_2026.php - Painel do cliente
  • painel/esqueceu_senha_2026.php - Recuperação de senha (pronto para migrar)
  • painel/data/setup_2026.php - Configurações e setup
  • painel/inc-topo_2026.php - Topo do painel
  • painel/inc-menu_2026.php - Menu lateral do painel
  • css/redesign_2026.css - CSS do novo design 2026

Funcionalidades Implementadas na Homepage 2026:

  • Design Broadway - Fullscreen hero com imagens rotativas
  • Sistema de backgrounds dinâmico - Detecção automática de arquivos
  • Lazy loading - Carregamento otimizado das imagens
  • Legendas preparadas - Sistema pronto para admin
  • Cards clicáveis - Todo o card leva para a página da peça
  • Responsivo - Adaptado para mobile
  • Parallax effect - Efeito de profundidade no scroll

💾 Arquivos de Backup Criados (21/12/2024):

  • painel/login_backup_20251221.php - Backup do login original
  • painel/action_backup_20251221.php - Backup do action original
  • includes/inc_peca_backup_20251221.php - Backup do header original
  • pecas_20251019.php - Backup da listagem original

🔧 Correções Críticas Implementadas:

  • Google OAuth Redirect: Corrigido redirect_uri de action_2026.php para action.php
  • Logout System: Corrigido redirecionamento de login_2026.php para login.php
  • Session Check: Função checkSession_2026() atualizada para usar arquivos consolidados
  • AJAX Handlers: Todos os handlers AJAX apontando para arquivos corretos

Arquivos protegidos (NÃO modificar sem autorização):

  • css/style.min.css - Usado pelas páginas antigas
  • Todos os demais arquivos do sistema não mencionados

Shortlink de Story + UTM Dinamica por Campanha

Data: 03 de Marco de 2026

Funcionalidades:

  • Novo endpoint de redirecionamento curto: /go/<slug>
  • Redirecionamento com UTM dinamica por campanha ativa
  • Registro de clique no cliente_tracking com evento story_redirect_click
  • Cron de sincronizacao social atualizado para exibir shortlink no overlay do story

Arquivos modificados:

  • go.php (novo)
  • admin/cron/sync_facebook_events.php
  • .htaccess
  • /www/server/panel/vhost/rewrite/rionoteatro.com.br.conf

Documentacao complementar:


Facebook/Instagram Cron: Token Long-Lived + Diagnostico de Saude (Story/ROI)

Data: 03 de Marco de 2026

Problema corrigido:

  • Token de pagina expirado gerava degradacao silenciosa no cron social (IG pulado) e logs pouco objetivos.

Entregas:

  • Novo endpoint de exchange long-lived: admin/modulos/facebook/token_exchange.php.
  • configure.php: botao de token atualizado para fluxo long-lived com escopos de pagina/instagram e fallback.
  • FacebookEventService.php: novo getInstagramIdInfo() para diagnostico real.
  • sync_facebook_events.php: falha explicita com exit(1) quando token invalido/expirado (code 190).
  • monitor_story_roi.php: novos checks facebook_token_health e instagram_binding_live (Graph em tempo real).

Evidencia operacional:

  • Monitor agora aponta claramente token expirado e evita falso "sucesso".
  • Proximo passo operacional: renovar token em admin/modulos/facebook/configure.php e salvar.

Documento tecnico detalhado:


Marketing Playbook Automático (Google Ads + Meta Ads + Social Story)

Data: 03 de Marco de 2026

O que foi entregue:

  • Novo gerador automático de ativos de divulgação: admin/cron/generate_marketing_playbook.php.
  • Exporta automaticamente:
  • JSON consolidado (com contexto e prompts para IA)
  • CSV Google RSA
  • CSV Google PMAX
  • CSV Social captions (Feed/Story)

Gatilhos dinâmicos por sessão:

  • É Hoje, É Amanhã, Essa Semana, Estreia esta semana, Última semana, Últimas apresentações, Única apresentação, além de urgência (Últimos ingressos / Ingressos limitados).

Tracking e campanha por canal:

  • Google Ads com UTM pago (google/cpc).
  • Meta Ads com proteção contra fallback orgânico em mídia paga (facebook/paid_social).
  • Story com shortlink /go/<slug> e UTM story.

Automação (cron):

  • 15 /4 /usr/bin/php -q /www/wwwroot/rionoteatro.com.br/admin/cron/generate_marketing_playbook.php >> /www/server/cron/marketing_playbook.log 2>&1

Documentação técnica:


Alertas WhatsApp para Crons de Marketing/ROI (Debounce + Resiliência)

Data: 03 de Marco de 2026

Objetivo:

  • Notificar automaticamente o número admin do Rionoteatro sobre estado dos crons críticos sem gerar flood.

Entregas técnicas:

  • Novo helper: admin/cron/cron_whatsapp_notify.php
  • Debounce por chave (min_seconds)
  • Persistência de estado em admin/cron/logs/notify_state/
  • Integração segura com includes/whatsapp_helper.php
  • Integrações:
  • admin/cron/generate_marketing_playbook.php
  • alerta de erro de acesso/token
  • alerta de erro SQL
  • resumo de sucesso (throttle)
  • alerta de playbook vazio
  • admin/cron/monitor_story_roi.php
  • alerta de error (prioritário)
  • alerta de warn (throttle maior)
  • heartbeat ok (throttle longo)
  • admin/cron/sync_facebook_events.php
  • alerta de erro de acesso/lock/SQL
  • alerta crítico de token Facebook expirado/inválido
  • resumo consolidado com contadores FB/IG (ok/erro)
  • aviso quando não houver peça pendente

Validação executada:

  • php -l nos 4 arquivos alterados (100% sem erro de sintaxe).
  • Execução real:
  • php admin/cron/generate_marketing_playbook.php (OK)
  • php admin/cron/monitor_story_roi.php (executou e retornou status error por token Facebook expirado; alerta WhatsApp disparado)

Observações operacionais:

  • Houve um evento de fila no WhatsApp helper (Attempted to use detached Frame), com fallback para queue/restart automático já existente.

Documento técnico detalhado:


Copy dinâmico com IA (duas etapas: geração + revisão)

Data: 03 de Marco de 2026

Objetivo:

  • Permitir que conteúdos de feed/story sejam gerados por IA e obrigatoriamente revisados por uma segunda IA antes de uso.

Entregas:

  • Novo helper: admin/cron/ai_copy_helper.php
  • aiCopyRefineCaptions(...) para playbook
  • aiCopyRefinePairMessages(...) para mensagens completas FB/IG no cron social
  • Fallback automático para textos atuais em qualquer falha (timeout, erro, saída vazia, review ausente/reprovada)
  • Integrações aplicadas:
  • admin/cron/generate_marketing_playbook.php
  • passa a registrar captions_source, captions_engine, captions_error
  • resumo com contagem IA/Fallback
  • admin/cron/sync_facebook_events.php
  • tenta refinar copy de FB/IG por IA antes de postar
  • loga Copy source, engine e motivo de fallback

Contrato de revisão por 2 IAs:

  • IA 1 (geradora): RNT_AI_COPY_PRIMARY_ENGINE / RNT_AI_COPY_PRIMARY_MODEL
  • IA 2 (revisora): RNT_AI_COPY_REVIEW_ENGINE / RNT_AI_COPY_REVIEW_MODEL
  • revisão obrigatória por padrão: RNT_AI_COPY_REVIEW_REQUIRED=1

Variáveis de ambiente esperadas:

  • RNT_AI_COPY_ENABLED (default: 1)
  • CEREBRO_COPY_ENDPOINT (endpoint do Cérebro na VPS)
  • CEREBRO_COPY_TOKEN (opcional)
  • CEREBRO_COPY_ENGINE, CEREBRO_COPY_MODEL (roteador externo do Cérebro)
  • CEREBRO_COPY_PRIMARY_ENGINE, CEREBRO_COPY_PRIMARY_MODEL (geração)
  • CEREBRO_COPY_REVIEW_ENGINE, CEREBRO_COPY_REVIEW_MODEL (revisão)
  • CEREBRO_COPY_TIMEOUT
  • Compatibilidade mantida com prefixo antigo RNT_AI_COPY_*

Validação local executada:

  • php -l admin/cron/ai_copy_helper.php
  • php -l admin/cron/generate_marketing_playbook.php
  • php -l admin/cron/sync_facebook_events.php
  • Execução real do playbook: Captions IA: 0 | Fallback: 3 (comportamento esperado sem endpoint configurado).

Documento técnico detalhado:


BK-75 - Telefone de Lista WhatsApp + Ocultação de Peça de Teste nas Listagens Públicas

Data: 03 de Marco de 2026

O que foi entregue:

  • Novo campo no admin de peças para definir quem recebe o link da lista via WhatsApp:
  • admin/modulos/pecas/detalhe.php (campo telefone_lista, aceita múltiplos separados por vírgula).
  • admin/modulos/pecas/action.php (persistência com fallback seguro caso a coluna ainda não exista no banco).
  • Atualização do envio diário em disparaemail.php:
  • parsing robusto de e-mails/telefones (vírgula ou ;),
  • prioridade para pecas.telefone_lista,
  • fallback para clientes.celular do produtor quando telefone_lista estiver vazio,
  • envio para múltiplos destinos WhatsApp com contagem de sucesso/erro.
  • Script SQL de migração criado:
  • sql_updates/add_telefone_lista_pecas.sql
  • coluna adicionada sem AFTER para permanecer no final da tabela (compatibilidade com queries legadas por ordem de coluna).
  • Regra de homologação pública:
  • peças com nome exato Teste (case-sensitive) não aparecem em listagens públicas:
  • index.php, pecas.php, eventos.php, teatros.php, teatro.php.
  • Busca dessas páginas permanece coerente por operar sobre os cards já renderizados.

Validação executada:

  • php -l sem erros em:
  • admin/modulos/pecas/action.php
  • admin/modulos/pecas/detalhe.php
  • disparaemail.php
  • index.php
  • pecas.php
  • eventos.php
  • teatro.php
  • teatros.php

[2026-03-26] BK-171 - Botão manual para regerar pecas.xml

  • a listagem de peças passou a oferecer um botão para atualizar o feed pecas.xml sob demanda;
  • o módulo reaproveita atualizar_feed.php e devolve status/horário ao operador.

[2026-03-26] BK-170 - Squad social automatico com provider/IA configuraveis

  • configuracao oficial do Squad migrada para robot_config.chave = squad_social_runtime_config;
  • aba Squad passou a salvar provider do copy, engine por etapa, fallback chain, modelo do opencode e opinioes extras;
  • runtime do Cérebro passou a respeitar runtimePreferences.steps[stepKey].engine;
  • cron social e approval passaram a consumir a configuracao persistida e a anexar side_opinions.
  • o copy automatico passou a usar execucao sequencial com Gemini como editor final e tolerancia a falha/budget nas opinioes extras.

BK-76 - Ajustes de Rota/Carrossel e Layout de Evento Fora de Cartaz

Data: 03 de Marco de 2026

O que foi entregue:

  • Correção de rota para eventos de produtor com parceria online nas listagens:
  • eventos.php e teatro.php agora apontam para /<slug> (layout de peça) quando parceria_online = 1.
  • Mantém /evento/<slug> apenas para eventos de produtor sem parceria online.

[2026-04-12] BK-267: unificação de duplicatas do BOT + CTA por sessão + classificação padronizada no front

  • bot/handlers/event_matcher.php passou a distinguir melhor same_external_link e same_session_duplicate, priorizando Atualizações quando a origem é a mesma e fortalecendo a identificação de duplicatas reais por sessão/local.
  • follow-up do matcher no mesmo BK:
  • mudança isolada de preço agora deixa rastro em Atualizações em vez de sumir quando não houve adição/remoção de sessão;
  • auto-update de sinopse em evento aprovado só ocorre quando há identidade mínima forte (mesmo link externo ou mesmo nome + mesmo local + mesma sessão).
  • admin/modulos/eventos/conciliacao.php ganhou a ação Unificar dentro de Prováveis Duplicatas.
  • admin/modulos/eventos/action.php passou a persistir sec_link_externo em tab_sessoes e a sustentar o fluxo de unificação sem perder o link externo correto por data/sessão.
  • evento.php passou a abrir modal de escolha de data quando existir mais de um link externo futuro por sessão.
  • evento.php ganhou os minicards Valor Indicativo e Classificação.
  • peca.php, evento.php, pecas.php e eventos.php receberam padronização editorial da classificação etária com selo visual, tooltip operacional e sem uso indevido do símbolo 18+ em faixas menores.
  • docs/documentacao_tecnica/BOT_CAPTADOR_EVENTOS.md foi atualizada com as regras vivas de duplicatas, atualizações, unificação, cautela com preço e escopo fora da rodada.

[2026-04-12] BK-269: aprovados similares + retorno para a mesma aba na conciliação

  • admin/modulos/eventos/conciliacao.php ganhou a aba Aprovados Similares, com corte operacional de 75% e priorização por mesmo_slug/mesmo_teatro.
  • a aba nova expõe pares de eventos já aprovados com links, datas, teatro, score e ações manuais de mesclagem nas duas direções.
  • admin/modulos/eventos/action.php ganhou a action conciliar_unificar_aprovados.
  • a mesclagem manual de aprovados bloqueia o evento de origem quando ele possui pedidos, para evitar desativação indevida.
  • o fluxo de submit/redirect da conciliação passou a preservar a aba ativa via return_tab.
  • Redirecionamento de segurança no evento.php:
  • se acessar /evento/<slug> de um evento com parceria_online = 1, redireciona para /<slug>.
  • Carrossel de ofertas no topo de páginas internas atualizado para refletir a mesma regra da home:
  • inclui peças admin + parcerias online (id_produtor IS NULL OR parceria_online = 1);
  • exclui peça técnica nome = 'Teste'.
  • aplicado em peca.php e evento.php.
  • Layout público de evento fora de cartaz restaurado em modo enxuto (evento.php):
  • não redireciona mais para lista;
  • mostra aviso de fora de cartaz/desatualizado;
  • mantém gênero, sinopse e contato (Mais Informações/WhatsApp);
  • oculta foto, duração, local e ficha técnica;
  • período exibido como Sem informações.

Ajuste complementar (BK-77 - 03/03/2026):

  • regra de layout enxuto fora de cartaz aplicada para qualquer rota pública /evento/... com fim_temporada no passado (não apenas eventos de produtor).

Validação executada:

  • php -l sem erros em:
  • eventos.php
  • teatro.php
  • peca.php
  • evento.php

[2026-04-03] BK-207: Migração PagBank Orders API

  • Migração de Cartão e PIX para API de Orders (JSON).
  • Desativação global de Boleto.
  • Patch de backup: pagbank_migration_BK207.patch

[2026-04-08] BK-228: parceria gratuita, créditos e alertas no dashboard admin

  • painel/modulos/eventos/editar.php recebeu CTA próprio da parceria com visual alinhado ao layout e persistência do fluxo de salvar + WhatsApp.
  • painel/modulos/eventos/ajax_notificar_parceria.php passou a enviar alerta duplo para admin + 5521999915554, com link direto para o cadastro do produtor.
  • painel/modulos/creditos/action.php e painel/modulos/creditos/callback_mp.php passaram a avisar os dois destinos na contratação e na aprovação de pacotes.
  • admin/index.php ganhou bloco de ativações recentes de produtores com ocultação manual por item.
  • changelog detalhado: docs/changelog/2026/CL-2026-04-08-BK-228-parceria-creditos-alertas-admin.md

[2026-04-08] BK-231: upload rápido no dashboard admin

  • admin/index.php ganhou widget de upload rápido com retorno de link público.
  • admin/upload_dashboard_arquivo.php passou a suportar upload, listagem e exclusão em arquivos/download.
  • arquivos/download/.htaccess passou a bloquear execução de script e indexação.
  • changelog detalhado: docs/changelog/2026/CL-2026-04-08-BK-231-upload-dashboard-admin.md

[2026-04-13] BK-267: performance da conciliação por aba ativa

  • admin/modulos/eventos/action.php passou a devolver a aba de retorno como ?tab=<aba>#<aba>, em vez de depender só de hash.
  • admin/modulos/eventos/conciliacao.php passou a renderizar pesado apenas a aba ativa no request inicial.
  • a navegação entre abas da conciliação passou a recarregar a tela com tab= para que o servidor monte só o conteúdo necessário.
  • getAprovadosSimilaresConciliacao() deixou de executar COUNT(pedidos) por evento e passou a pré-carregar os totais em uma query agrupada.
  • admin/modulos/eventos/conciliacao_eventim.php recebeu paridade funcional das melhorias da trilha Sympla:
  • Duplicatas com Unificar, links dos dois lados e preservação de produtor/origem
  • Reativar, Aprovados, Aprovados Similares e Ignorados no mesmo padrão operacional
  • navegação por tab= e render pesado apenas da aba ativa também no Eventim

[2026-04-13] BK-270: roadmap de base única para conciliação multi-fonte

  • aberto roadmap para unificar a estrutura compartilhada entre conciliacao.php e conciliacao_eventim.php
  • objetivo: manter uma base única com configuração por fonte (sympla/eventim) e reduzir drift futuro entre as duas telas

[2026-04-13] BK-271: carrossel flutuante em eventos + scroll direcional

  • eventos.php passou a injetar o carrossel flutuante de ofertas no #carousel-placeholder público
  • evento.php e eventos.php passaram a esconder o carrossel ao rolar para baixo e mostrar ao rolar para cima
  • o selo de classificação dentro do carrossel foi reduzido para a versão mini, sem o texto redundante
  • as duas páginas receberam comentário HTML versionado para validação do admin no código-fonte
  • subfix adicional: o carrossel deixou de depender apenas do frame atual do scroll e passou a manter estado persistente de visibilidade, eliminando o flicker ao parar de rolar
  • follow-up de estabilidade/performance em eventos.php:
  • o shell do carrossel passou a clipar overflow horizontal e a sincronização no scroll foi amortecida por requestAnimationFrame
  • a vitrine passou a revelar cards por lote sem AJAX: 12 iniciais e +6 ao aproximar do fim da tela
  • cards ocultos preservam metadata para busca/agenda, mas só carregam a imagem real quando ficam visíveis
  • a trilha AJAX parcial gerada por worker externo foi descartada antes do fechamento para evitar regressão de layout/rodapé
  • follow-up do header público:
  • includes/inc_peca.php deixou de adicionar header-fixed e header-scrolled ao body das páginas públicas
  • o header continua com efeito visual de scroll no próprio .rnt-header, mas a página pública voltou a sair com <body> limpo
  • simplificação final em eventos.php:
  • o carrossel volta a iniciar oculto e, no primeiro scroll para cima após o trigger, fica travado como visível
  • ele não volta mais a esconder por scroll
  • espelhamento da mesma regra final em evento.php:
  • o carrossel da página individual também passa a iniciar oculto e travar como visível no primeiro scroll para cima após o trigger

2026-05-17 - Feedback automatico pos-evento

  • Criada rotina CLI admin/cron/post_event_feedback_queue.php para montar campanha/fila WhatsApp de feedback para compradores de ingressos 1 dia apos a sessao.
  • Criado helper includes/post_event_feedback_helper.php com texto da mensagem, correlacao de resposta WhatsApp e gravacao de avaliacao pendente em tb_avaliacoes.
  • O feedback do cliente passa a ser recebido por resposta direta no WhatsApp, sem link/formulario publico na mensagem.
  • admin/modulos/comentarios/index.php passou a permitir editar nota, comentario e status antes de aprovar/publicar a avaliacao.
  • A fila nasce em whatsapp_campaigns/whatsapp_campaign_queue com dry_run_only=1, status='imported' e janela neutra 00:00-23:59; envio real continua dependendo da aprovacao/processamento do fluxo WhatsApp existente.
  • Agendado cron diario as 19h para criar automaticamente a fila D+1: post_event_feedback_queue.php --execute --confirm=POST_EVENT_FEEDBACK_QUEUE.
  • Backups criados antes da edicao/configuracao: docs/CHANGELOG.md.bkp_original_20260517_162942, admin/modulos/comentarios/index.php.bkp_original_20260517_164000, /root/AGENTS.md.bkp_original_20260517_162942, /root/crontab.root.bkp_original_20260517_162942.

Ajuste da mesma rodada - resposta direta no WhatsApp

  • A mensagem D+1 passou a usar acentuacao normal em portugues e nao inclui mais link.
  • A assinatura do remetente no corpo foi ajustada para Rio no Teatro, evitando texto com .com linkavel automaticamente pelo WhatsApp.
  • O texto agora orienta o cliente a responder no proprio WhatsApp com nota de 1 a 5 e comentario, sem link e sem opt-out no corpo.
  • bot/whatsapp/webhook.php e bot/whatsapp/cloud_webhook.php passaram a chamar o helper de feedback pos-evento quando uma mensagem recebida chega.
  • includes/post_event_feedback_helper.php agora localiza o ultimo envio pos-evento efetivamente sent daquele telefone e pre-inclui a resposta em tb_avaliacoes como pendente.
  • Apos processar uma resposta, a fila recebe marcador feedback_received, impedindo reprocessamento/reescrita ao reabrir o chat.
  • admin/modulos/bot/whatsapp.php passou a reconciliar mensagens recebidas recentes ao carregar a conversa, cobrindo respostas que entraram no historico sem processamento pelo webhook.
  • Backups adicionais: includes/post_event_feedback_helper.php.bkp_original_20260517_171000, admin/cron/post_event_feedback_queue.php.bkp_original_20260517_171000, bot/whatsapp/webhook.php.bkp_original_20260517_171000, bot/whatsapp/cloud_webhook.php.bkp_original_20260517_171000, admin/modulos/bot/whatsapp.php.bkp_original_20260518_150300, docs/CHANGELOG.md.bkp_original_20260517_171000, /root/AGENTS.md.bkp_original_20260518_150300.

Ajuste da mesma rodada - texto sem opt-out

  • Removida a frase Se não quiser receber novas mensagens, responda SAIR. da mensagem pos-evento.
  • includes/whatsapp_campaign_queue.php passou a pular a injecao automatica de opt-out apenas para campanhas post_event_feedback/variante post_event, preservando o opt-out das campanhas comerciais comuns.
  • Atualizadas as mensagens pendentes da campanha pos-evento existente para refletir o texto sem opt-out.
  • Enviado teste WhatsApp com o message_body real do queue_id=9735 (Hamlet, sessao 2026-05-16 20:00:00) para a allowlist admin 5521999915554; provider local retornou sucesso.
  • Backups adicionais: includes/post_event_feedback_helper.php.bkp_original_20260518_151900, includes/whatsapp_campaign_queue.php.bkp_original_20260518_152500, docs/CHANGELOG.md.bkp_original_20260518_151900.

Ajuste de texto pos-evento - 2026-05-19

  • Substituida a frase de revisao/comunidade na mensagem pos-evento por: Seu comentário poderá aparecer na página da peça para ajudar outros espectadores a conhecerem bons espetáculos e fortalecer uma comunidade em que milhares de clientes se beneficiam.
  • Backups adicionais: includes/post_event_feedback_helper.php.bkp_original_20260519_104500, docs/CHANGELOG.md.bkp_original_20260519_104500, /root/AGENTS.md.bkp_original_20260519_104500.

Entradas detalhadas