# Guard de categoria de template (Multi-BM)

**Por que existe:** em 09/08/2026 a Meta recategorizou o template
`magicreply2_758730` (BM Diamantes) de UTILITY para MARKETING **sem aviso**.
O sistema gravava a categoria uma única vez na importação e nunca mais
conferia — o disparo seguiu como utility, cobrado como marketing. Prejuízo
real. A Meta faz isso quando o conteúdo entregue (incluindo o RECHEIO das
variáveis) não cumpre os critérios da categoria.

## Como funciona agora

1. **Antes da 1ª tranche e a cada ~60s de envio**, o dispatcher confere a
   categoria REAL do template físico de cada BM do pool (estratégia do dono:
   conferiu → rajada de 1 min → conferiu → rajada).
2. Virou **MARKETING** e o serviço não tem `permitirMarketing`:
   - a BM sai do pool imediatamente (log `campaign_multibm_bm_banned_category`);
   - se o pool inteiro cair, o lote é **CANCELADO automaticamente** com
     estorno dos créditos não usados (`campaign_multibm_cancelled_category`).
     Não é pausa: categoria não desvira — o caminho é subir template novo.
   - ADMINs recebem e-mail (cooldown 60min). O cliente não vê o motivo.
3. **Health sync (30min)** reconcilia a categoria de todas as submissões
   APPROVED — os painéis de cobertura nunca mais mentem.
4. Banco sempre atualizado: `multibm_template_submissions.categoria` passa a
   refletir a Meta no momento da detecção.

## Fontes de verdade (plugável)

- **Com token Meta na BM** (campo "Token Meta p/ categoria de template" no
  cadastro da BM, Usina Multi-BM): leitura DIRETO na Graph API
  (`GET /{waba_id}/message_templates`) — delay zero. O token é cifrado
  (AES-256-GCM) e nunca reexibido; digite `limpar` no campo para remover.
- **Sem token**: endpoint por sender da Infobip
  (`GET /whatsapp/2/senders/{sender}/templates/{id}`) — provado ao vivo em
  10/08 que reflete a recategorização rapidamente.
- Ambos fora do ar ⇒ **fail-open** (não trava disparo legítimo), com log.

## Gerar o token Meta (System User) — passo a passo por BM

O MESMO token faz duas coisas (o campo na Usina se chamava só "categoria de
template", mas hoje também alimenta tier/qualidade/saúde reais — investigação
do tier travado, 18/08/2026):
- **categoria/status de template** ao vivo antes de cada tranche de disparo;
- **tier real, qualidade e `health_status`** (motivo de recusa) no health-sync.

Feito uma vez por BM, dentro do perfil Dolphin dela (~5 min):

1. business.facebook.com → **Configurações do negócio** da BM.
2. **Usuários → Usuários do sistema** → *Adicionar* → nome ex.
   `onsms-leitor`, função **Funcionário** (não precisa ser admin).
3. Selecionar o usuário → **Adicionar ativos** → *Contas do WhatsApp* →
   marcar a WABA.
   🚨 **CORREÇÃO (24/08/2026)** — a versão anterior deste passo dizia que
   "só-leitura basta (PROVADO 18/08)". **Era FALSO.** Inspecionei o usuário
   `onsms-leitor` (61593507778746) no painel e o que está REALMENTE atribuído
   nas 9 WABAs é bem mais amplo: *Modelos de mensagem (apenas visualização)* +
   *Modelos de mensagem (ver e gerenciar)* + *Números de telefone (apenas
   visualização)* + *Números de telefone (ver e gerenciar)* + *Gerenciar
   números de telefone e modelos de mensagens*. Ou seja: funcionou porque a
   permissão era MAIOR do que eu registrei, não porque só-leitura bastasse.
   ⇒ Nunca escrevi "PROVADO" sem ter medido o estado real; aqui deduzi do
   resultado. Se quiser mesmo o mínimo, é preciso TESTAR reduzindo, não supor.
4. **Gerar token** → escolher o app disponível da BM → validade
   **nunca expira** (recomendado; revogável a qualquer momento) → escopos
   **`whatsapp_business_management`** + **`whatsapp_business_messaging`**
   (os dois — a doc da Meta pede ambos; faltar um dá erro de permissão).
5. Copiar o token e colar no campo da BM na Usina Multi-BM
   ("**Token Meta da BM**") → Salvar.
6. **Portfólio com várias WABAs?** Não precisa colar de novo em cada uma. No
   cartão-grupo do portfólio (o bloco "Meta BM {id} · N WABAs") aparece o botão
   **"Aplicar token às N WABAs"** — ele copia o token já cadastrado para as
   irmãs que ainda estão sem. ⚠ **Nunca sobrescreve** quem já tem (o valor é
   cifrado e nunca reexibido; sobrescrever seria perda irreversível) e nunca
   atravessa portfólio nem serviço. O armazenamento continua **por WABA** de
   propósito: o cliente pode não ceder acesso a uma WABA específica dele.

Teste imediato: salvar dispara a limpeza de cache; o próximo health sync (ou
um disparo) já usa a Graph. No log da aplicação a fonte aparece como
`fonte: meta`, e no banco `tier_source=meta` / `quality_source=meta`.
🔑 Formato confirmado na 1ª leitura real: o tier vem como string `"TIER_2K"`
(não número); `quality_rating` vem GREEN/YELLOW/RED e é normalizado para
HIGH/MEDIUM/LOW.

## Limites honestos

- Contestação de recategorização **não existe via API** (nem Infobip nem
  Graph para template APROVADO — conferido na doc oficial em 10/08). Se
  couber, é manual no WhatsApp Manager da BM, dentro da janela que a Meta dá.
- Exposição residual: 1 tranche de ~1 min após uma conferência aprovada
  (com token Meta; sem token, soma o pequeno lag da Infobip). Antes era
  ilimitada.
- Template novo com o MESMO recheio promocional tende a ser recategorizado
  de novo — o guard protege a cobrança, não muda a regra da Meta.

---

## Paridade Multi-BM × WA Cloud (princípio do dono, 10/08/2026)

> "A alma do WA Cloud deve ser igual à do serviço Multi-BM — guardadas as
> devidas diferenças." Centralizar num código só foi descartado (fontes
> divergem: Meta direto × Infobip). O caminho é **paridade por contrato**.

Como se materializa:
- **Helpers compartilhados** onde a lógica é idêntica: `fetchTemplateStateFromMeta`
  (leitura na Graph) e `decideTemplatePolicy` (tabela categoria×status:
  MARKETING sem permitir ⇒ cancela · PAUSED ⇒ pausa · DISABLED/REJECTED ⇒
  cancela) vivem em `multibmTemplateCategoryGuard.ts` e são consumidos pelos
  DOIS canais.
- **Implementação por canal** onde os contratos diferem: guard cloud
  (`cloudTemplateGuard.ts`) usa o token da conexão do cliente; guard multibm
  usa token da BM ou Infobip.
- **`tests/dispatchParity.test.ts`** é o contrato executável: as cinco
  verdades (conferência pré-run, re-conferência 60s, corte por erro-de-conta,
  contadores semeados, política única) precisam existir NOS DOIS dispatchers
  — proteção nova num canal sem o outro quebra o build.

Erros de CONTA que cortam o run (nunca queimam a lista):
- Multi-BM: créditos Infobip (`errorKind CREDITS` — no erro 4xx E no REJECTED
  dentro da resposta 200, a 2ª cara do incidente de 09/08);
- WA Cloud: `131042` (sem pagamento), `190`/`131026` (token morto), `133010`
  (número desregistrado) — lote pausa, cliente resolve e retoma.

---

## O ciclo completo de defesa (12/08/2026)

O ban do número da BM Diamantes ensinou que detectar a recategorização não
basta. A forense em produção mostrou o **mesmo link encurtado
(`https://revr.be/s/a813b52a`) em NOVE lotes seguidos** com o template
recategorizado, e um segundo (`a5NMvy`) em outros três: a Meta marca o LINK,
não só o texto. Quatro defesas passaram a trabalhar juntas.

### 1. Black-list de links (`server/blockedLinks.ts`, migration 079)
Quando `reconcileSubmissionCategory` vê UTILITY→MARKETING, ele chama
`banirLinksDoTemplate` (best-effort, fora do caminho crítico), que descobre
pelo par `(template_name, bm_id)` em `multibm_messages` **todos** os links que
passaram por aquele template — botão (prefixo + sufixo do lote e de cada
contato), destino do rastreio e CTA da Parte 2 — e os grava em `blocked_links`.
- Enforcement **global**; `owner_user_id` só decide **quem vê** (admin vê tudo,
  o cliente afetado vê os dele, os demais não veem nada).
- `canonicalizeLink` faz `http/https`, `www.`, barra final e caixa colidirem —
  trocar a escrita não libera o link.
- `gateBlockedLinks` roda na criação de campanha antes do débito, **fail-open**
  (banco fora do ar nunca trava disparo legítimo).

### 2. Template recategorizado some da criação
A Meta recategoriza a **submissão da BM**, não o Template Base — e a listagem
só olhava o Base, então o template continuava disponível depois de virar
MARKETING. Agora a categoria do FÍSICO manda, na listagem e no POST.

### 3. Avisos da Meta por BM (`server/bmNotices.ts`, migration 080)
Na Diamantes o aviso de recategorização apareceu dentro da BM e ninguém viu.
Cada sinal (recategorização, pausa, desativação) vira linha indexada por BM;
a aba Alertas da Usina lista, e a lista de BMs ganha chip "sob observação"
enquanto houver aviso não lido em 30 dias.

### 4. Link novo a cada lote + template a pedido do cliente
- **F3**: com Base revr.be, o cliente informa a URL final e o sistema cria um
  link revr **novo e exclusivo por lote** (rótulo = identificador do lote).
  Reuso de link deixou de ser possível por construção.
- **F4**: `POST /api/multibm/my/template-requests` — o cliente pede um template
  com o link dele; o corpo ganha variação de fechamento
  (`server/templateClosingVariation.ts`, porque a Meta rejeita corpo idêntico
  entre WABAs), passa pelas regras da Meta e é submetido em cada BM atribuída,
  com nome derivado por WABA. Limite de 2 pedidos/dia por cliente.
  O template aprovado vira Base com `origem='solicitado_cliente'` +
  `ownerUserId` — mesma casa dos Bases (aparece na criação sozinho), mas
  **visível só ao dono**: o link do negócio de um cliente nunca vaza para outro.

### Limites honestos
- A variante com link **dinâmico** no pedido do cliente está desligada.
- O caminho feliz da F4 até a Infobip **não** foi exercitado ao vivo de
  propósito: sucesso real cria template de verdade nas WABAs. Está coberto por
  teste com `criarTemplate` mockado assertando o payload exato.
- Paridade WA Cloud do auto-feed e dos avisos ainda não existe (o guard cloud
  compartilha os helpers; é a próxima etapa natural).
