Reservatórios e materiais hidráulicos

AdZap
O lojista pede um anúncio no WhatsApp e recebe a peça pronta, com a marca do fabricante.
- Cliente
- FortLev (contratação via CADMO)
- Categoria
- Agente conversacional no WhatsApp para trade marketing
- Período
- 13–23 ago 2026
- Estado
- Entregue: entrou em produção em 15/08/2026
O problema
A FortLev não vende ao consumidor final: vende por uma rede de revendas autorizadas. Cada lojista dessa rede precisa anunciar no status do WhatsApp e no Instagram da loja e não tem designer. Então ou não anuncia, ou improvisa uma peça com o logo errado, ou anuncia o produto do concorrente, que mandou a arte pronta. O que estava em jogo nunca foi gerar imagem: era armar a rede com material on-brand na velocidade de uma conversa e, depois, saber quem anunciou o quê, a que preço e em que região.
O que foi construído
O lojista manda qualquer mensagem, navega categoria → subcategoria → produto por listas nativas do WhatsApp, preenche um Flow de duas telas com o nome da loja, o telefone e o preço dele, e recebe a peça no próprio chat em segundos, com a marca FortLev e a foto real do produto, de um catálogo de 176 SKUs extraídos do site público (161 ativos e ofertáveis). Do mesmo chat ele reaproveita a peça: adapta de Story para Feed, pede a versão em vídeo ou repete com outro preço. Sem app, sem login, sem treinamento: a interface é o WhatsApp que ele já usa o dia inteiro. Por trás, um serviço .NET 9 sem tela própria (a tela é o WhatsApp) cuja decisão de conversa é uma função pura, com queda para a peça de template sempre que a camada generativa falha.
Decisões de engenharia
Máquina de estados pura: o fluxo inteiro roda sem rede
ConversationEngine.Decide() é uma função pura com 9 estados, 10 eventos de entrada e 34 ações de saída, e o projeto Core não tem uma linha de PackageReference nem de ProjectReference. Sem HttpClient, sem relógio, sem Redis: a conversa completa é exercitada offline nos testes, sem conta Meta, sem Imejis e sem catálogo real. O resto do sistema vira encanamento substituível.
src/AgenteFortlev.Core/Services/ConversationEngine.cs (1.005 linhas) · grep -c ': InboundEvent' src/AgenteFortlev.Core/Conversation/InboundEvent.cs → 10 · AgenteFortlev.Core.csproj sem nenhuma referência
Uma raia de fila por conversa, sem corrida de estado
Duas mensagens do mesmo lojista chegando juntas corrompem a máquina de estados. A fila encadeia as tarefas por conversa dentro do lock, e a ordem fica fixada no enfileiramento, não na hora de pegar o trabalho. Assim conversas diferentes correm em paralelo e a mesma conversa nunca disputa. Não é um pool de workers competindo por um canal com semáforo por chave, que é onde a corrida volta.
src/AgenteFortlev.Infrastructure/Queue/WaIdSerializedQueue.cs: lock (_gate) + previous.ContinueWith(...).Unwrap() por Conversation.Value
A chave da conversa é (número, lojista), não só o wa_id
Defeito que só dói quando entra o segundo número: com a sessão em sess:{wa_id}, o mesmo lojista falando com dois representantes dividiria uma conversa só. Escolhia a categoria com um e o produto aparecia na conversa do outro. A chave virou o par (phone_number_id, wa_id), e é ela que serializa a fila, guarda a sessão e agenda a expiração. O perfil da loja continua por wa_id de propósito, porque a loja é a mesma.
src/AgenteFortlev.Core/ValueObjects/ConversationKey.cs · RedisSessionStore grava sess:{numero}:{wa_id} com EXPIRE 7200
O menu que ultrapassava a arte na fila da Meta
O bot mandava a imagem, esperava a resposta da Meta e só então o menu, e mesmo assim o menu chegava primeiro no aparelho. A causa não era a ordem daqui: mensagem de imagem passa pelo processamento de mídia da Meta e a de texto não passa por nada. A cura não foi um sleep de chute, foi esperar o webhook de statuses que o parser sempre descartou, com teto. Se o teto estoura, o menu vai assim mesmo.
src/AgenteFortlev.Infrastructure/WhatsApp/EntregaDeMidia.cs: Confirmar(wamid) e EsperarAsync(wamid, teto, ct) devolvendo bool no estouro, não exceção
Uma peça, três saídas e um portão de cota no vídeo
A mesma arte sai em Story, é adaptada para Feed e vira vídeo, sem o lojista refazer nada. O caro é o vídeo: ele passa por um VideoQuotaGate antes de qualquer chamada externa, e o domínio tem ação própria para cota esgotada e para indisponibilidade, então o bot avisa em vez de travar. O renderizador é uma porta (IVideoRenderer), então trocar de provedor não toca a máquina de estados.
src/AgenteFortlev.Core/Conversation/OutboundAction.cs: RenderAdaptation, SendAdaptedArt, RenderVideo, NotifyVideoQuotaExhausted, NotifyVideoUnavailable entre as 34 ações; src/AgenteFortlev.Core/Ports/IVideoRenderer.cs, IVideoAvailability.cs; src/AgenteFortlev.Infrastructure/Video/VideoQuotaGate.cs, CloudinaryVideoEngine.cs
Stack
Telas





O papel da CG Mixart
Arquitetura e engenharia inteira: domínio, integrações (Meta Cloud API, WhatsApp Flows, Imejis, OpenAI, Cloudinary), persistência, observabilidade, infra e deploy, de ponta a ponta. Também a leitura de negócio que reposicionou o projeto (pelos campos do próprio formulário, o usuário era o lojista e não a FortLev, e o histórico podia virar relatório de sell-out) e a documentação entregue ao parceiro: 3 PDFs de entrega, custos e escalabilidade. O design das peças é da CADMO, que trouxe também o fluxograma original em FigJam.
Ressalvas e notas técnicas, verificadas no códigoabrir
Entrou em produção em 15/08/2026, em VM Oracle Cloud (PLANO.md e deploy.sh, com backup do src/ na VM, build com o container antigo no ar e portão de /health). O repositório não prova operação hoje: ele para em 23/08/2026. Só voltar ao presente ("atendendo pela FortLev no número oficial") quando FortLev ou CADMO confirmarem por escrito, na data da publicação, que o número segue ativo. Todas as métricas foram conferidas em 28/08/2026 no repositório em /Users/fazplay/Downloads/agente-fortlev, e as fontes ficam publicadas: catálogo em src/AgenteFortlev.Api/catalog.json; os dois formatos da peça em src/AgenteFortlev.Core/Conversation/ArtFormat.cs e na tela FORMATO de flow/anuncio.flow.json; os cinco campos do formulário no mesmo flow contra as constantes de src/AgenteFortlev.Infrastructure/WhatsApp/FlowContract.cs; a data de produção em PLANO.md e deploy.sh. Catálogo: 176 SKUs reais extraídos do site público em 14/08 pela API do WordPress, dos quais 161 estão ativos. Nenhum desses 161 mora em categoria ou subcategoria desligada, então os 161 são todos ofertáveis. Quem perguntar "o lojista escolhe entre quantos produtos?" ouve 161, não 176. Os dois formatos são o que o lojista recebe hoje: a peça já sai no formato escolhido dentro do formulário e o menu seguinte oferece o outro ("Quero em Story/Feed"), reaproveitando a arte base em vez de gerar de novo. Dois é teto de código e de tela: o WhatsApp dá três botões e o terceiro é "Nova arte". A arquitetura já é multi-cliente: o bot responde pelo número por onde a mensagem chegou (`metadata.phone_number_id`) e lê catálogo, textos, template e cotas de um painel por slug, com cache e queda para a última resposta boa. Esse painel vive em repositório próprio (adzap-painel, Next.js) e nada dele foi medido aqui. O módulo de vídeo está construído e está DESLIGADO da conversa por acordo com a FortLev, e é por isso que ele não entra na conta dos formatos. O que foi provado em produção é o caminho do Cloudinary: deploy do bot e do painel em 17/08 e o primeiro vídeo real montado em 33 s (caixa d'água 10.000 L, 160 KB), com a Meta aceitando o MP4 no primeiro upload. O pedido veio do WhatsApp do próprio autor, não do WhatsApp de um lojista. Os outros três motores (Creatomate, Remotion, AtlasCloud) existem como contrato roteado ao painel, não como caminho provado: o PLANO-VIDEO.md registra o Creatomate sem conta e sem o template do designer, e o Remotion ainda por medir na VM. O teto de dez modelos por cliente é código (`VideoTemplateOption.Maximum`, o limite de dez linhas da lista do WhatsApp). Achado de consequência comercial, documentado antes de o cliente perguntar: a Meta passa a cobrar mensagens de serviço em 01/10/2026. O impacto foi medido e escrito, com o aviso explícito de não fechar preço antes de a tarifa oficial sair: a API ainda devolve US$ 0,0000 para serviço. Nada sobre usuários ativos, receita ou uptime aparece nesta ficha porque o repositório não prova nenhum dos três.