Mapa do FlowBuilder (Chatbot Visual)

Rota: /chat-flow | Arquivo principal: src/app/(dashboard)/chat-flow/page.tsx Componentes: src/components/flow-builder/

Estrutura do FlowBuilder

Arquivos principais

src/components/flow-builder/
├── flow-editor.tsx          ← Canvas visual (React Flow / @xyflow/react)
├── node-form.tsx            ← Formulário de configuração de cada nó (~2800 linhas)
├── custom-node.tsx          ← Componente visual de cada nó no canvas
├── time-table-editor.tsx    ← Editor de tabela de horários
└── lib/
    ├── types.ts             ← Interfaces (FlowNodeData, FlowInteraction, etc)
    ├── flow-adapter.ts      ← Conversão React ↔ Vue format
    └── default-flow.ts      ← Fluxo padrão ao criar novo

Tipos de nós

type FlowNodeData.type = "start" | "configurations" | "node" | "timeTable"
TipoFunçãoEditável
startPonto de entrada do fluxoLimitado
configurationsConfigurações globais (welcome msg, keyword, timeout)Sim
nodePasso do fluxo com interações e condiçõesSim
timeTableRamificação por horárioSim

Tipos de interações (actions que o bot executa)

TipoFunção
messageEnviar texto
mediaEnviar imagem/vídeo/documento
delayAguardar X segundos
buttonEnviar botões interativos
listEnviar lista de opções
chatgpt / claude / geminiConsultar IA
typebotRedirecionar para Typebot
n8nChamar webhook n8n
tagAplicar etiqueta ao contato
kanbanMover para lane do kanban
chatflowRedirecionar para outro fluxo
opportunityCriar oportunidade no funil
transferTransferir para fila/agente
chatBotBlockBloquear o chatbot
webhook / webhookAllChamar webhook externo
contactEnviar vCard
locationEnviar localização
stickerEnviar figurinha
videoLinkLink de videoconferência
scheduleAgendar mensagem
reasonsMostrar motivos de fechamento
smsEnviar SMS
vapiChamada VAPI
notesCriar nota interna
appointmentConsulta de agenda

Tipos de condições (routing)

TipoFunção
USQualquer resposta → próximo nó
R + equalsResposta igual a X
R + containsResposta contém X
R + startsWithResposta começa com X
R + endsWithResposta termina com X
R + regexResposta casa com regex
TBaseado em horário (timeTable)

Estrutura do JSON salvo (flow field em ChatFlows)

{
  "name": "Meu Fluxo",
  "nodeList": [
    {
      "id": "start",
      "name": "Início",
      "type": "start",
      "left": "26px", "top": "100px",
      "ico": "mdi-play",
      "viewOnly": true
    },
    {
      "id": "configurations",
      "type": "configurations",
      "configurations": {
        "welcomeMessage": { "message": "Olá! Como posso ajudar?" },
        "keyword": { "message": "*" },
        "notResponseMessage": { "time": 10, "type": 1, "destiny": "" },
        "notOptionsSelectMessage": { "message": "Opção inválida." },
        "maxRetryBotMessage": { "number": 3, "type": 1, "destiny": "" }
      }
    },
    {
      "id": "nodeC",
      "name": "Boas vindas!",
      "type": "node",
      "interactions": [
        {
          "id": "int-001",
          "type": "MessageField",
          "data": { "body": "Olá! Escolha uma opção:" }
        }
      ],
      "conditions": [
        {
          "id": "cond-001",
          "type": "R",
          "comparisonType": "equals",
          "condition": ["1"],
          "nextStepId": "nodeD",
          "action": 0
        }
      ]
    }
  ],
  "lineList": [
    { "from": "start", "to": "nodeC", "paintStyle": { "strokeWidth": 3, "stroke": "#5c67f2" } }
  ]
}

Conversão React ↔ Vue (flow-adapter.ts)

O FlowBuilder usa internamente um formato React normalizado. Ao salvar, converte para o formato Vue (PascalCase) que o backend entende.

// Vue format (salvo no backend)
"type": "MessageField"React: "message"
"type": "ButtonField"React: "button"
"type": "MediaField"React: "media"
// etc.
 
// Regra ao salvar (denormaliseFlowData):
// Nós start e configurations → SEM interactions/conditions/actions no JSON
// Nós node → COM interactions e conditions (convertidas para PascalCase)

Keyword no start block (nossa melhoria)

O keyword está no nó configurations.configurations.keyword.message.

Na UI (node-form.tsx, buscar hasKeyword):

  • Toggle “Qualquer mensagem” → define keyword.message = "*"
  • Toggle “Palavra-chave” → input para texto específico

O backend lê configurations.keyword.message para decidir se ativa o fluxo.

Configurações globais (nó configurations)

CampoFunção
welcomeMessage.messageMensagem inicial ao entrar no bot
keyword.messagePalavra que ativa o bot (* = qualquer)
notOptionsSelectMessage.messageMensagem para opção inválida
notResponseMessage.timeMinutos sem resposta antes de encerrar
maxRetryBotMessage.numberMáximo de tentativas erradas
farewellMessage.messageMensagem de despedida
outOpenHoursComportamento fora do horário
firstInteractionComportamento na primeira interação
autoDistributeTicketsDistribuição automática

Ativar fluxo para um canal

  1. Em Sessões → editar canal → campo “ChatFlow”
  2. Ou em Whatsapps.chatFlowId no banco

Ativar por keyword

O backend verifica configurations.keyword.message contra o body da mensagem:

  • keyword === "*" → ativa para qualquer mensagem (precisa do patch wildcard no backend)
  • keyword === "oi" → ativa apenas quando a msg for “oi” (case insensitive)

⚠️ PATCH PENDENTE: O backend obfuscado ainda pode não suportar keyword === '*'. O arquivo c45_zpro.js foi renomeado na última atualização do fornecedor. Quando encontrado, aplicar: if (keyword === '*' || body.includes(keyword))