# O caminho das pedras — por que uma BM não sobe de tier

**Por que existe.** A BM Diamantes (Multi-BM/Infobip) era verificada, quality
HIGH, entregou 8.832 números **únicos** numa janela de 7 dias — 8,8x o piso
de 1.000 que a Meta exige no próprio painel — e mesmo assim nunca subiu de
2.000 e foi banida. As três explicações óbvias (uso ≥50%, qualidade alta,
verificação da empresa) estavam TODAS satisfeitas. Investigação em
17/08/2026 mediu, confirmou contra a doc oficial e instrumentou os dois
canais (Multi-BM/Infobip e WA Cloud/Tech Provider) pra parar de adivinhar.

---

## 🚨 CORREÇÃO DE 24/08/2026 — o mistério se dissolveu na medição

**O veredito abaixo (o "qualificador oculto de alta qualidade") não se sustenta.**
Três medições ao vivo em 24/08, no painel da própria Meta:

1. **O caminho é público e numérico.** WhatsApp Manager → Limites de mensagens
   do portfólio `179026036276358` mostra a escada **250 → 2.000 → 10.000 →
   100.000 → Ilimitado** e o requisito do próximo degrau em número absoluto:
   *"Inicie conversas iniciadas pela empresa de alta qualidade com **5.000
   clientes únicos** em um período contínuo de 7 dias"*, com o placar ao lado:
   *"Você iniciou **2.011** conversas com clientes únicos nos últimos 7 dias."*
   5.000 é exatamente **metade de 10.000** ⇒ o painel e a doc dizem a MESMA
   coisa: **metade do limite atual, em clientes únicos, em 7 dias corridos.**
   ⇒ Para o degrau 2.000 → 10.000 o piso era **1.000**. Nunca houve gate oculto.

2. **O limite é do PORTFÓLIO, e nós olhávamos a WABA.** No mesmo dia:
   a WABA "Informativo On" tem **4 mensagens entregues em 30 dias**, enquanto o
   contador do portfólio marca **2.011 únicos em 7 dias**. O tráfego é somado
   entre as 9 WABAs do portfólio. Quem mede uma WABA isolada mede a unidade
   errada — e conclui que "não bateu o critério" quando bateu.

3. **A anomalia Diamantes é provavelmente ARTEFATO DE MEDIÇÃO.** O doc dizia
   que ela "nunca subiu de 2.000" mesmo com 8.832 únicos em 7 dias. Mas esse
   "2.000" vinha do **espelho da Infobip** (item 2 do veredito abaixo, que o
   próprio doc admite ter ficado ~25 dias defasado) e do campo por-número
   `messaging_limit_tier`, **que a Meta depreciou**. Medição de 24/08 no banco:
   das 6 BMs, a ÚNICA com `tier_source=meta` mostra **TIER_10K**; as cinco com
   `tier_source=infobip` mostram **LIMIT_2K**. O valor correlaciona com a FONTE
   DA LEITURA, não com o negócio. ⇒ Antes de afirmar que qualquer BM "não
   sobe", ler o tier real na Graph. Provavelmente nunca houve mistério: havia
   um espelho velho de um campo depreciado.

**O que continua VÁLIDO do trabalho de 17-18/08:** os três defeitos nossos eram
reais e foram corrigidos (leitura descartada, tier vindo do espelho, motivo não
capturado). A instrumentação valeu — foi ela que permitiu esta correção.
**O que cai:** a tese do gate invisível.

📌 **Evento medido:** histórico oficial do portfólio registra
*"20 de ago de 2026, 17:15 — Messaging limit updated from 2000 to 10000"*.
Os anúncios do Meta Ads foram aprovados às 15:22 do mesmo dia — 1h53 antes.
A correlação é **temporal, não causal**: ads/CTWA não aparecem em nenhum
critério oficial, e a campanha estava PAUSADA com gasto R$ 0,00. O que explica
a subida é o portfólio ter cruzado 1.000 únicos em 7 dias.
⚠ Divergência de prazo entre fontes: a doc de desenvolvedor diz "dentro de 6
horas"; o painel diz "até 24 horas". O caso real levou < 2h.

---

## O veredito [SUPERADO — ver correção acima]

O gate real não é o número de 1.000 — esse é só o piso. É o qualificador
**"de alta qualidade"**, que mede o lado do **destinatário** (bloqueios,
denúncias, engajamento com base opt-in real) — **diferente** do selo
HIGH/MEDIUM/LOW (um bucket agregado e defasado) e **invisível** nos nossos
painéis até esta investigação:

1. **Não registrávamos leitura — e ERA BUG NOSSO** (corrigido em 18/08/2026).
   A primeira análise parou cedo: a assinatura da Infobip TEM "SEEN" e o mapa
   READ→SEEN existe, então concluí "não é nosso". Errado. O relatório de
   leitura chega num payload **sem `status`** — só `messageId/sentAt/seenAt` —
   e o código lia apenas `status.groupName`, então `normalizeWebhookStatus`
   devolvia **"SENT"**; a guarda do UPDATE barrava DELIVERED→SENT e o evento
   sumia em silêncio. **1.614 relatórios de leitura descartados** em produção.
   No WA Oficial era pior: o update era cego e a leitura REGREDIA o status.
   Conserto: `isSeenReport = !evt.status?.groupName && !!evt.seenAt` ⇒ "SEEN"
   + `markContactsRead`, e a guarda de só-avançar passou a existir nos TRÊS
   canais. **Não depende de token.**
   ⚠ Lição de método: "não achei bug do nosso lado" não é "não é nosso bug" —
   eu tinha olhado a assinatura e o mapa, nunca o discriminador do payload.
2. **O tier exibido vinha do espelho da Infobip** (`limit` do sender),
   nunca da Meta direto — e esse espelho chegou a ficar ~25 dias
   desatualizado em duas BMs.
3. **Não capturávamos o motivo.** Os webhooks `business_capability_update`
   e `account_alerts` — onde a Meta manda o valor real de capability e a
   razão de elegibilidade — eram descartados (`field não tratado`).
   ⚠ Correção de 18/08: eu havia escrito que o motivo seria "arquiteturalmente
   impossível" no Multi-BM por depender de webhook. **Falso.** `health_status`
   é campo de LEITURA na Graph (doc "Messaging and Calling Health Status"):
   `can_send_message` = AVAILABLE|LIMITED|BLOCKED e, fora de AVAILABLE,
   `entities[].errors[]` com `error_code`, `error_description` e
   `possible_solution`. No Cloud o motivo é EMPURRADO; no Multi-BM ele se
   **BUSCA** com o token da BM. Já implementado (`metaCapability.ts` +
   `multibmHealthSync`, AVAILABLE limpa o aviso).

## Os campos confirmados (doc oficial, 17/08/2026 — não é achismo)

| Campo | Tipo | O que traz |
|---|---|---|
| `business_capability_update` (webhook) | push | `max_daily_conversations_per_business` (payload real citado da doc: `{"max_daily_conversations_per_business":2000,"max_phone_numbers_per_waba":25}`). Dispara na criação da WABA e em qualquer alta/queda. **Não carrega motivo.** |
| `account_alerts` (webhook) | push | `{alert_type, alert_description}` — é ONDE o motivo de elegibilidade aparece. Enum exato de `alert_type` sem fonte primária pública ainda; gravamos o valor cru. |
| `whatsapp_business_manager_messaging_limit` | poll (GET) | `GET /{phone-number-id}?fields=...` — tier real, substitui `messaging_limit_tier` (que DEPRECA fev/2026; nossa `META_GRAPH_VERSION` está pinada em v23.0, a versão antiga). Formato (string tipo "TIER_2K" vs número cru) **não confirmado por fonte primária** — o código aceita os dois. |

**"Alta qualidade" não é definida numericamente na doc oficial** — é
opacidade proposital da Meta, confirmada por ausência (não por lacuna
nossa). Não existe endpoint pra consultar o motivo de deferral — só o
webhook `account_alerts` ou o WhatsApp Manager visualmente.

## O que foi instrumentado (17/08/2026)

**Multi-BM (Infobip):** `server/metaCapability.ts` — leitor Graph por BM,
usando o token System User já suportado (`business_managers.
meta_template_token_enc`, mesmo recipiente do guard de categoria — receita
completa em `docs/GUARD-CATEGORIA-TEMPLATE.md`). Fetch puro, **sem**
`appsecret_proof` (o token pode pertencer a um app que não é o nosso Tech
Provider). `server/multibmHealthSync.ts` passa a preferir esse valor sobre
o espelho Infobip quando a BM tem token, gravando a origem (`tier_source`/
`quality_source`).

**WA Cloud (Tech Provider):** já lia tier direto da Meta (webhook
`phone_number_quality_update` + poll `fetchPhoneNumbers`) — ganhou os dois
campos que faltavam: `WEBHOOK_SUBSCRIBED_FIELDS` agora inclui
`business_capability_update`/`account_alerts` (`server/metaGraph.ts`), com
handlers em `server/metaWebhookWorker.ts`. Conexões que já estavam vivas
antes do deploy se **autocorrigem** no próximo ciclo do `cloudHealthSync`
(re-`subscribeAppToWaba` idempotente, 30min) — sem script manual em
produção.

**Nos dois canais:** progresso `X/1.000 únicos em 7 dias` (mesmo critério
literal do painel oficial da Meta) e taxa de leitura 30 dias, calculados
direto do banco (`countBmUniquePhonesLast7dByBm`/`readRateLast30dByBm` no
Multi-BM; subqueries equivalentes em `getAllMetaCloudConnectionsEnriched`
no Cloud) — visíveis na Usina de cada canal. Um bug real foi corrigido de
quebra: `updateCloudMessageStatusByWamid` (WA Cloud) não tinha a mesma
proteção anti-clobber que o Multi-BM já tinha — um "delivered" retransmitido
chegando depois do "read" apagava o SEEN de volta pra DELIVERED.

## Receita legítima de maturação

Necessária, mas **não suficiente sozinha** — a Diamantes cumpriu tudo isto
e foi banida:

1. Verificar a empresa **já no onboarding** (não esperar precisar).
2. Base opt-in real que **engaja de volta** — é o eixo que faltava medir.
   Monitorar a taxa de leitura (agora visível); leitura baixa numa base
   "de alta qualidade" nominal é o sinal de alerta mais direto que temos.
3. Cruzar o piso de 1.000 únicos/7 dias **de forma limpa**, sem forçar
   volume artificialmente — o progresso 7d agora aparece na Usina.
4. Ler o `eligibility_alert_type`/`_description` a cada ciclo — é a Meta
   contando o motivo, quando ela manda.
5. Esperar o `business_capability_update` confirmar o nível novo antes de
   aumentar de novo.

## Limites honestos

- O enum exato de `alert_type` em `account_alerts` só fica 100% confirmado
  na primeira leitura real em produção — o código grava o valor cru, não
  normaliza pra um conjunto fechado que poderíamos estar inventando.
- O formato de `whatsapp_business_manager_messaging_limit` (string vs
  número) também só fecha com uma leitura real — o parser aceita os dois.
- Sem token Meta cadastrado, uma BM Multi-BM continua no espelho Infobip
  (comportamento anterior, sem regressão) — gerar o token é ação do dono,
  por BM (~5min, receita em `docs/GUARD-CATEGORIA-TEMPLATE.md`).
- Engajamento (leitura) no Multi-BM depende da Infobip realmente emitir o
  evento SEEN — a assinatura tem o evento ativo e o código está correto,
  mas nenhuma leitura real foi observada ainda; pode ser comportamento de
  plataforma (não confirmado) e não instrumentação nossa.
- Não existe contestação de tier/recategorização via API — só manual no
  WhatsApp Manager, dentro da janela que a Meta dá.
