--- name: Bugs estruturais conhecidos do Whaticket flowbuilder description: Lista dos bugs descobertos e patcheados no flowbuilder do Whaticket em 2026-05-07. Aplicáveis a qualquer cliente, não só Silda. type: project originSessionId: fd5a71bc-b589-4209-bf9b-e8a094889ad9 --- Bugs estruturais reais do flowbuilder do Whaticket que foram patcheados nesta sessão. Todos têm backup com sufixo `.bak.20260507_*`. **Why**: a base de código tem várias inconsistências entre o que o frontend cria e o que o backend processa. Cada novo nó tipo question/menu/condition tende a expor algum problema novo. **How to apply**: ao trabalhar com flowbuilder em qualquer empresa, verifique se esses patches existem antes de criar nós novos. Se backend foi reinstalado/atualizado, podem ter voltado. **Lista** (todos em `/home/deploy/whaticket/backend/{src,dist}`): 1. `ActionsWebhookService` linha 225 e 629: `replaceMessages(webhook, ...)` deve ser `replaceMessages(webhook.variables, ...)`. Sem isso, `{{nome}}` em menus vira string vazia. 2. `wbotMessageListener.ts:4534`: `const lastFlowId = nodes[nodeIndex + 1].id` — pega próximo nó pelo índice do array, não pelas conexões. Crash quando question é o último nó. Patch: usar `connections.find(c => c.source === nodeSelected.id).target`. 3. `ActionsWebhookService` menu handler (~624): `ticket` é `null` quando flow vai direto pro menu sem passar por singleBlock. Patch: `Ticket.findOne({where:{id:idTicket,companyId}})` antes de usar. 4. `ActionsWebhookService` linha 523/700: `dataWebhook: dataWebhook` sobrescreve com null vindo de `flowBuilderQueue`. Patch: `dataWebhook: dataWebhook || ticket.dataWebhook`. 5. `wbotMessageListener` ~3717: query `Webhook.findOne({where:{hash_id:undefined}})` crasha quando ticket está em flow welcome (sem hashFlowId). Patch: condicional `ticket.hashFlowId ? findOne(...) : null`. 6. `SendWhatsAppMediaFlow.ts` `typeSimulation`: usa `${contact.number}@s.whatsapp.net` mas WhatsApp atual usa `@lid` para contatos privados. Resultado: presence (digitando) é silenciosamente ignorado. Patch: usar `contact.remoteJid || ${contact.number}@${...}`. 7. `typeSimulation` precisa chamar `wbot.presenceSubscribe(jid)` antes de `sendPresenceUpdate(presence, jid)` — sem isso o WhatsApp não exibe "digitando". Compare com `typebotListener.ts` que faz certo. 8. `ActionsWebhookService` question handler: `if (!variables || ...)` envolvia o envio da pergunta — pulava qualquer nó question subsequente quando ticket já tinha variables salvas. Patch: remover o wrapper. 9. `wbotMessageListener` salva resposta do question com `dataWebhook:{variables:{[answerKey]:body}}` — sobrescreve TODAS as variáveis anteriores. Patch: spread do oldVars: `{...oldVars, [answerKey]: body}`. 10. `ActionsWebhookService` question handler: aplica só `formatBody(message, ticket.contact)` (Mustache) — destrói `{{nome}}` (PT) que vira vazio. Patch: aplicar `replaceMessages(variables, message)` ANTES do formatBody. 11. `Whatsapps.greetingMessage`/`Queues.greetingMessage` exist no schema mas não em uso. `CompaniesSettings.greetingAcceptedMessage` (mensagem do atendente assumindo) tem coluna no DB mas backend nunca lê — feature parcialmente quebrada (frontend usa, backend não). Implementação patcheada em `UpdateTicketService` no bloco `if status==="open"`. Usa memoization via `global.__greetingSentMap` para prevenir duplicação por chamadas re-entrantes. 12. Tipo `condition` existe no frontend mas backend NÃO tem handler — flow trava se incluir nó Condição. Para ramificação no flow, usar nós Menu (com botões), não Condição. 13. `wbotMessageListener.ts:3923`: menu handler ignora silenciosamente respostas que não são números (`!isNaN(parseInt(lastMessage))`). User fica perdido. Patch: adicionar `else` enviando "Hum, não entendi {{firstName}}. 😅 Por favor, responda apenas com o número da opção..." (com memoization global.__menuInvalidMap pra não spam). 14. **DUPLICAÇÃO frontend vs backend** — `greetingAcceptedMessage` é enviado por DOIS lugares: pelo frontend (`AcceptTicketWithoutQueueModal`, `TicketListItemCustom`, `TicketActionButtonsCustom`) que faz POST `/messages/${id}` com texto literal contendo `{{firstName}}` (Mustache aplica e mapeia pro CONTATO — errado), E pelo meu patch backend que pré-substitui pelo nome do USER atendente. Patch: bloqueio em `MessageController.store` retorna 200 skipped quando body contém `{{firstName}}` ou `{{name}}` literal sem mídia. Assim só meu patch envia, com nome correto. 15. **`outOfHoursMessage` quando `scheduleType="connection"` ou `"company"`** — `wbotMessageListener` no branch de `(settings.scheduleType === "company" || "connection")` (linha ~4429 do .ts) marca `isOutOfHour: true` mas NUNCA enviava o `Whatsapps.outOfHoursMessage`. Só o branch `scheduleType === "queue"` enviava (lendo `queue.outOfHoursMessage`). Patch: antes do `await ticket.update({ isOutOfHour: true })`, adicionar try/catch que envia `whatsapp.outOfHoursMessage` via `wbot.sendMessage` com debounce 1s. Aplicado em .ts e .js (linha 3289 do .js compilado, "PATCH 2026-05-14"). Comportamento: envia a OOH em TODA mensagem fora-do-horário do mesmo ticket (sem guarda `!ticket.isOutOfHour`) — explicitamente pedido pelo Jesiel, evita silêncio pra cliente repetidor. 16. **JID @lid pra contatos com number >= 15 dígitos** — WhatsApp moderno usa `@lid` (não `@s.whatsapp.net`) pra contatos privados sem número público. Crismária (number=171467996164200, 15 dígitos) tem `Contacts.remoteJid='171467996164200@s.whatsapp.net'` no DB (incorreto), mas mensagens manuais usam `@lid` corretamente. Envios automatizados (typeSimulation, OOH) que montam `${number}@s.whatsapp.net` ficam com ack=1 e nunca chegam. Padrão de patch genérico: `const __num = String(contact.number); const __suffix = isGroup ? "g.us" : (__num.length >= 15 ? "lid" : "s.whatsapp.net");` e priorizar `contact.remoteJid` se conter "@lid". Aplicado no patch 15 (OOH). **Padrão de patch**: editar `.ts` e `.js` compilado (PM2 roda dist/). Sempre `cp arquivo arquivo.bak.YYYYMMDD_HHMM` antes. Após editar, `pm2 restart whaticket-backend`.