# Arquitetura — OmniMeta

## Objetivo

O **OmniMeta** é um gateway de comunicação com o WhatsApp Business. Atua como broker entre produtos clientes (ex.: OmniPlus) e parceiros de canal (Meta Cloud API, Gupshup, Evolution API).

## Componentes principais

| Componente | Responsabilidade | Evidência |
| :--------- | :--------------- | :-------- |
| API Laravel (`api/`) | HTTP REST, webhooks inbound, OAuth | `api/routes/api.php` |
| Domain / UseCases (`api/src/Core`) | Regras de negócio, Clean Architecture | `api/src/Core/Domain`, `UseCase` |
| Partners (`Gupshup`, `EvolutionAPI`, `Meta`) | Adapters por parceiro | `api/src/Core/Gupshup`, `EvolutionAPI` |
| MongoDB | Persistência operacional (`messages`, `whatsapp_accounts`, …) | `api/app/Models` |
| Filas (RabbitMQ / Pub/Sub) | Processamento async de webhook e notificação | `README.md`, `QueueRoutingEnum` |
| WebhookTrigger | Notificações outbound para o cliente | `WebhookTrigger`, `MessageWebhookTriggerUseCase` |

## Fluxos de alto nível

```mermaid
flowchart LR
  Cliente[Produto cliente] -->|REST /api/v1| OmniMeta
  Parceiro[Meta / Gupshup / Evolution] -->|webhook inbound| OmniMeta
  OmniMeta -->|persist| Mongo[(MongoDB)]
  OmniMeta -->|fila| Broker[RabbitMQ / PubSub]
  Broker -->|notify| OmniMeta
  OmniMeta -->|webhook outbound| Cliente
```

## Padrão: orquestrador multi-parceiro (`Core\OmniMeta\UseCase\*`)

Toda feature que uma conta pode ter em mais de um parceiro (hoje: Meta Cloud API, Gupshup, Evolution API) segue uma separação em duas camadas — não uma única classe com `if partner === X`:

| Camada | Papel | Namespace |
| :----- | :---- | :-------- |
| Orquestrador | Recebe `Partner` já resolvido (por middleware, a partir da `WhatsappAccount`/`GraphApplication`), valida que o partner existe (`PartnersEnum::tryFrom`) e decide **para qual implementação delegar** | `Core\OmniMeta\UseCase\{Feature}\*` |
| Implementação por parceiro | Lógica real de integração com aquele parceiro específico | `Core\UseCase\{Feature}\*` (Meta), `Core\Gupshup\UseCase\*`, `Core\EvolutionAPI\UseCase\*` |

O Controller HTTP injeta e chama **só o orquestrador** — nunca a implementação de um parceiro específico diretamente. Quando um parceiro ainda não tem a feature implementada, o orquestrador lança uma exceção de domínio clara (nunca deixa a implementação Meta ser chamada "por acidente" com credenciais erradas).

```mermaid
flowchart LR
  Ctrl[Controller] --> MW[Middleware resolve WhatsappAccount + Partner]
  MW --> Orq[Core.OmniMeta.UseCase.Feature.XUseCase]
  Orq -->|partner = Meta| Meta[Core.UseCase.Feature.XUseCase]
  Orq -->|partner = Gupshup| Gup[Core.Gupshup.UseCase.XUseCase]
  Orq -->|partner ainda nao suportado| Err[throw FeatureException]
```

**Exemplos já implementados:** envio de mensagem (`Core\OmniMeta\UseCase\Message\SendWhatsappMessageUseCase`), Templates (`Core\OmniMeta\UseCase\Template\*`), WhatsApp Flows (`Core\OmniMeta\UseCase\Flow\*` — ver [WHATSAPP_FLOWS](../modules/WHATSAPP_FLOWS.md)), WhatsApp Groups (`Core\OmniMeta\UseCase\Group\*` — ver [WHATSAPP_GROUPS](../modules/WHATSAPP_GROUPS.md)).

**Regra:** toda feature nova que crie/edite/publique um recurso vinculado a uma `WhatsappAccount` — mesmo que só um parceiro tenha suporte no dia 1 — **deve nascer com essa camada de orquestrador**, e não travar a rota inteira num único parceiro (ex.: bloqueio 422 hard-coded no middleware). Isso evita reescrever controller/rotas/DTOs externos quando outro parceiro ganhar suporte à feature; só se adiciona a implementação do parceiro e um branch no orquestrador.

## Ergonomia de broker: prefira o número de telefone, não o id interno

O OmniMeta existe pra ser consumido por outros produtos (hoje: OmniPlus/OmniPlus-Next) sem eles
precisarem carregar o modelo de dados interno do OmniMeta. Uma `WhatsappAccount` pode ter **vários
números de telefone** — então, sempre que uma feature for naturalmente escopada a um número
específico (não à conta toda), a API deve aceitar/filtrar pelo **`phoneNumber` real** (o número de
WhatsApp, ex.: `5511999999999`), não pelo `phoneId` interno do OmniMeta. O cliente broker
(OmniPlus-Next incluso) já sabe de qual número está mandando mensagem — ele não deveria precisar
de uma chamada extra só pra descobrir o id interno correspondente.

Esse já é o padrão do envio de mensagem (`PhoneMiddleware` resolve `phoneNumber` do body via
`PhoneRepositoryInterface::findByPhoneNumberAndClientId`, não um id), de WhatsApp Flow
(`ListFlowsUseCase` aceita `phoneNumber`, resolve pra `whatsappAccountId` internamente) e de
WhatsApp Groups: `Create Group` recebe `phoneNumber`, `List Groups` filtra por `phoneNumber` (ver
[WHATSAPP_GROUPS.md](../modules/WHATSAPP_GROUPS.md)). Billing também segue: `GET /billing/events`
e `GET /billing/summary` aceitam `phone_number` (ver [BILLING_BROKER.md](../modules/BILLING_BROKER.md#api-rest-implementada))
— nesse caso o filtro é direto na collection, já que cada `billing_event` grava o número real no
próprio evento, sem precisar resolver `whatsappAccountId` antes. Ao desenhar uma feature nova
telefone-escopada, siga esse exemplo em vez de introduzir mais um endpoint que só aceita o id
interno — `whatsappAccountId`/`phoneId` continuam existindo e sendo aceitos onde já fazem sentido
(ex.: filtros de conta inteira, relacionamentos internos), isso não é sobre removê-los, é sobre
garantir que o número real seja sempre uma opção quando o recurso é de um número específico.

Isso vale tanto pra entrada (request) quanto pra saída: uma notificação de webhook sobre um
recurso escopado a um número (ex.: evento de grupo) deve incluir o número real no payload —
resolvido no UseCase que já tem ou pode buscar essa informação, nunca inventando um id que o
cliente teria que resolver de volta numa chamada síncrona separada.

## Contrato de webhook uniforme entre parceiros

Pra qualquer `event` de webhook outbound (o que o cliente final assina em `WebhookTrigger.signed_events`),
o payload daquele evento específico deve ser **idêntico não importa o parceiro de origem** — o
cliente nunca deveria precisar de um `if partner === X` pra ler o que chegou. Cada evento tem um
vocabulário fechado pros campos que variam por parceiro (ex.: `type`, `changed`), documentado
centralmente no módulo daquele evento; `raw` fica sempre disponível como escape hatch pra quem
quiser o dado cru do parceiro, mas nunca é parte do contrato estável. Números de telefone sempre
normalizados (`PhoneNumberNormalizerTrait`), ids sempre os locais do OmniMeta, nunca um id/JID
nativo do parceiro. Isso já vale pra `NEW_MESSAGE` (`from`/`to`/`groupId`) e foi replicado pra
`GROUP_LIFECYCLE_UPDATE`/`GROUP_SETTINGS_UPDATE`/`GROUP_PARTICIPANTS_UPDATE` (ver
[WHATSAPP_GROUPS.md](../modules/WHATSAPP_GROUPS.md#contrato-canônico-do-group--precisa-ser-o-mesmo-não-importa-o-parceiro)).

**Ressalva honesta sobre o "envelope" externo** (os campos irmãos do payload nomeado do evento —
`id`, `subscriptionId`, `timestamp`, e onde `clientId`/`whatsappAccountId` aparecem): hoje existem
3 formatos diferentes por razão histórica, não por design:

| Formato | Eventos | Características |
| :------ | :------ | :--------------- |
| Legado, sem `event` | `NEW_MESSAGE`, `UPDATE_MESSAGE_STATUS` | Sem `event`/`clientId`/`whatsappAccountId` no nível raiz — mais antigos que o padrão genérico |
| Genérico, campos no nível raiz | `ACCOUNT_LAST_ERROR`, `GROUP_LIFECYCLE_UPDATE`, `GROUP_SETTINGS_UPDATE`, `GROUP_PARTICIPANTS_UPDATE` | `id`, `event`, `subscriptionId`, `timestamp`, `clientId`, `whatsappAccountId` no nível raiz, payload nomeado à parte |
| Genérico, campos aninhados | `BILLING_UPDATE` | `id`, `event`, `subscriptionId`, `timestamp` no nível raiz; `clientId`/`whatsappAccountId` **dentro** do objeto `billing` |

Não retroagir isso nos eventos existentes — mudar o envelope de `NEW_MESSAGE`/`BILLING_UPDATE` hoje
quebraria clientes já integrados sem aviso. Todo evento **novo** deve seguir o formato "genérico,
campos no nível raiz" (a mesma forma usada por `GROUP_*`) — é o mais completo e o que
`GroupWebhookTriggerUseCase`/`BillingWebhookTriggerUseCase` já usam como referência de código pra
copiar.

## Billing (em desenho / implementação futura)

O módulo **Billing Broker** centraliza cálculo e entrega de custos por mensagem. Detalhe em [BILLING_BROKER](../modules/BILLING_BROKER.md) e [ADR-001](../adr/ADR-001-billing-broker.md).

## Escopo deste documento

Visão de referência. Detalhes de módulo ficam em `docs/modules/*`. Não substitui Swagger (`php artisan l5-swagger:generate`) nem exemplos em `api/WEBHOOK_EXAMPLES.md`.
