@lionchat/mcp-server 1.13.0 → 1.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/docs/instructions.md +10 -3
- package/dist/docs/resources/best-practices.md +18 -0
- package/dist/docs/resources/conversation-flows.md +11 -0
- package/dist/docs/resources/data-model.md +32 -4
- package/dist/docs/resources/filtros-e-relatorios.md +44 -1
- package/dist/docs/resources/flowbuilder-design-guide.md +17 -9
- package/dist/docs/resources/flowbuilder-patterns.md +42 -15
- package/dist/docs/resources/formularios-publicos.md +309 -220
- package/dist/docs/resources/glossary.md +15 -20
- package/dist/docs/resources/reports-guide.md +150 -0
- package/dist/docs/resources/troubleshooting.md +67 -12
- package/dist/sanitize.d.ts +2 -0
- package/dist/sanitize.js +6 -0
- package/dist/sanitize.js.map +1 -1
- package/dist/tools.js +58 -13
- package/dist/tools.js.map +1 -1
- package/endpoints.json +448 -242
- package/package.json +1 -1
|
@@ -19,8 +19,11 @@ Comprovado em produção (24/07): a sessão que dizia "não há função de list
|
|
|
19
19
|
kanban_items, funnels, flows, flow_tools, campaigns, macros, automation_rules, agents, teams,
|
|
20
20
|
inboxes, labels, canned_responses, reports, sla, csat, captain_assistants, captain_documents,
|
|
21
21
|
scheduled_messages, custom_attributes, webhooks, eclinica_integrations, tasks, notifications,
|
|
22
|
-
lead_forms (Formulários públicos de captação)
|
|
23
|
-
|
|
22
|
+
lead_forms (Formulários públicos de captação).
|
|
23
|
+
- **Fora do MCP por decisão do dono (19/08): assinatura, plano, fatura, forma de pagamento, cartão,
|
|
24
|
+
saldo e recarga.** Não existem ferramentas dessas áreas e não devem ser criadas. Se o usuário
|
|
25
|
+
pedir, diga que isso se resolve no painel, na área de assinatura da conta — não tente por
|
|
26
|
+
ferramenta nem por chamada direta.
|
|
24
27
|
- Só depois de pesquisar e não encontrar é que se pode dizer que a função não existe.
|
|
25
28
|
- **Trabalho volumoso** (relatório de muitas conversas/cards): as listas são paginadas (25-100 por
|
|
26
29
|
página) — faça em ETAPAS ao longo de várias respostas, acumulando os resultados; nunca desista
|
|
@@ -344,6 +347,8 @@ Antes de QUALQUER ação que escreve, modifica, apaga ou envia, pare e avalie se
|
|
|
344
347
|
|
|
345
348
|
**Como confirmar:** descreva o efeito em linguagem do usuário e aguarde o "sim". Ex.: *"Vou apagar o contato #312 — isso remove as 4 conversas dele também. Confirma?"*. Se faltar dado, peça só o que falta (1-2 perguntas), nunca um questionário.
|
|
346
349
|
|
|
350
|
+
**Ferramentas com trava dura de confirmação.** Algumas ferramentas **recusam a chamada** sem `confirm: true` — a recusa vem do conector, antes de tocar no sistema. Não é erro: é o pedido de OK. O caminho é sempre o mesmo — descreva ao usuário exatamente o que será criado/alterado/apagado, aguarde o "sim", e só então reenvie a MESMA chamada com `confirm: true`. Nunca mande `confirm: true` na primeira tentativa "pra economizar uma volta": a trava existe justamente porque a descrição sozinha não segurava. Hoje: `lionchat_custom_dashboards_create`, `_update` e `_destroy` (relatório personalizado aparece na tela do cliente; alterar substitui a lista de blocos e apagar não tem desfazer).
|
|
351
|
+
|
|
347
352
|
**Aja sem perguntar apenas em operações idempotentes:** leitura, busca, resumo, classificação.
|
|
348
353
|
|
|
349
354
|
**Excluir agente/usuário (`lionchat_agents_destroy`) — SEMPRE pergunte o destino primeiro.** Se o agente tem recursos vinculados (conversas, tarefas — inclusive as que ele **criou** —, cards do Kanban, campanhas, agendamentos), o sistema EXIGE um destino de reatribuição. Antes de excluir: consulte `lionchat_agents_assigned_resources` (o retorno inclui `tasks_created`, tarefas criadas por ele); se houver QUALQUER recurso, **pergunte pra qual agente transferir tudo** e passe `reassign_to_agent_id` no destroy. Nunca tente excluir sem o destino — o sistema recusa e o trabalho do agente ficaria órfão. Ao excluir, tudo passa pro agente escolhido; o excluído fica só no histórico.
|
|
@@ -364,7 +369,9 @@ Antes de escolher a ferramenta, cheque se a intenção bate com a coluna da dire
|
|
|
364
369
|
| Só a contagem / números | `lionchat_conversations_meta` / `lionchat_reports_summary` | Não liste o conteúdo completo |
|
|
365
370
|
| Criar contato | `lionchat_contacts_create` base (path `/contacts`) | NÃO use as variantes `_1` … `_9` |
|
|
366
371
|
| Pausar a IA agora (botão de pânico) | `lionchat_captain_assistants_update` (`paused: true`, top-level) | — |
|
|
367
|
-
| Criar/editar **formulário público de captação** (página de captura própria) | `lionchat_lead_forms_create` / `_update` | Leia `lionchat://docs/formularios-publicos` ANTES — o desenho vai em `form_data` e só entra no ar depois do `publish` (que valida o desenho e pode voltar 422 com a lista de pendências). Escrita é só de administrador |
|
|
372
|
+
| Criar/editar **formulário público de captação** (página de captura própria) | `lionchat_lead_forms_create` / `_update` | Leia `lionchat://docs/formularios-publicos` ANTES — o desenho vai em `form_data` e só entra no ar depois do `publish` (que valida o desenho e pode voltar 422 com a lista de pendências). Caixa de WhatsApp vinculada (`inbox_id`) é obrigatória pra publicar. Escrita é só de administrador |
|
|
373
|
+
| **Testar um formulário** antes de publicar | `lionchat_lead_forms_test_run` | Roda o RASCUNHO no motor real e desfaz tudo no fim — zero rastro. Mande `answers: [{node_id, value}]` (até 60) pra reproduzir um caminho; a resposta traz o bloco atual + o log do percurso |
|
|
374
|
+
| Conferir se um site **aceita ser exibido dentro do formulário** (bloco Página externa) | `lionchat_lead_forms_check_embed` | Body `{url}`; devolve `{status: allowed\|blocked\|unknown, reason}`. Grave o veredito em `embed_allowed` do bloco. Admin-only |
|
|
368
375
|
| Anexar arquivo a uma mensagem | `lionchat_upload_create` primeiro, depois usar o retorno em `lionchat_conversations_messages_create` | Confira `lionchat_upload_limits` se o arquivo for grande |
|
|
369
376
|
| Marcar conversa como **lida** | `lionchat_conversations_update_last_seen` | NÃO use `unread` (esse marca como NÃO-lida) |
|
|
370
377
|
| Criar/gerenciar **campanha** (disparo WhatsApp/SMS) | `lionchat_campaigns_*` (criar, estatísticas) | SEMPRE rode `campaigns_estimate_audience` antes de criar e mostre a contagem. Disparo em massa = confirmação obrigatória (seção 7b) |
|
|
@@ -833,3 +833,21 @@ Custom attribute em lugar errado vira:
|
|
|
833
833
|
1. Procure no `kanban-deep-dive.md` (Kanban) ou na doc da feature relevante
|
|
834
834
|
2. Liste o config global do recurso (`kanban_config_list`, `voip_settings_list`, etc) — campos `nil`/vazios mas presentes na response = espera input
|
|
835
835
|
3. Olhe `data-model.md` pra colunas jsonb no model — costumam guardar feature nativa
|
|
836
|
+
|
|
837
|
+
|
|
838
|
+
## Regras rápidas de 18/08
|
|
839
|
+
|
|
840
|
+
- **Mensagens: ordene SEMPRE por `created_at`, nunca por `id`.** Conversa com histórico importado
|
|
841
|
+
tem id fora de ordem por desenho (o id cresce na importação, a data é a original). A API já
|
|
842
|
+
devolve em ordem cronológica.
|
|
843
|
+
- **Import de contatos: o DDI é da planilha.** O sistema não completa 55; sem DDI = erro na linha;
|
|
844
|
+
`contacts_import_validate` NÃO valida telefone. No import do Kanban, telefone inválido cria card
|
|
845
|
+
SEM contato em silêncio. Oriente o cliente a subir `5511988887777` ou `+351912345678`.
|
|
846
|
+
- **Caixa oficial com aviso de bloqueio por pagamento** (`whatsapp_send_blocked_billing`):
|
|
847
|
+
`GET /inboxes/{id}/health` consulta a Meta e LIMPA o aviso se o envio voltou — além de sucesso
|
|
848
|
+
de template, é o único jeito de limpar.
|
|
849
|
+
- **Template Cloud que falhou pode virar `sent` sozinho** (socorro automático, até ~1h; o
|
|
850
|
+
`source_id` muda). Não reenvie por conta própria uma mensagem de template recém-falhada de erro
|
|
851
|
+
passageiro — o socorro cuida; reenviar por cima entrega em dobro.
|
|
852
|
+
- **Filtros de data fixa cortam o dia no fuso da CONTA** (padrão America/Sao_Paulo) — e mudar o
|
|
853
|
+
`timezone` da conta move essa fronteira.
|
|
@@ -523,6 +523,17 @@ ficar muda (2026-06-05).
|
|
|
523
523
|
| `message_created` | Nova mensagem (incoming ou outgoing) |
|
|
524
524
|
| `first_reply_created` | Primeira resposta humana |
|
|
525
525
|
|
|
526
|
+
### Gatilhos de Agendamento (Booking nativo) — 2026-08-20
|
|
527
|
+
|
|
528
|
+
O Booking do LionChat (Agenda → aba Booking, link público, IA) também dispara flow pelo node
|
|
529
|
+
`start`: `booking_created`, `booking_cancelled`, `booking_rescheduled`, `booking_completed`.
|
|
530
|
+
Config opcional `{ booking_event_type_ids: ["44"], agent_ids: [], create_conversation: false }`
|
|
531
|
+
(vazio = todos; `create_conversation: true` cria a conversa na 1ª caixa do flow para quem nunca
|
|
532
|
+
conversou). Variáveis `{{booking.*}}` (data, hora, tipo, agente, links de remarcar/cancelar,
|
|
533
|
+
horário anterior no remarcado). Detalhe completo e regras no `lionchat://docs/flowbuilder-design-guide`
|
|
534
|
+
(seção "Gatilhos de AGENDAMENTO"). **Não confundir com os lembretes da e-Clínica** (integração
|
|
535
|
+
separada). Também NÃO são eventos de `automation_rule`.
|
|
536
|
+
|
|
526
537
|
**Os gatilhos de Formulário público (`lead_form_completed`, `lead_form_milestone`,
|
|
527
538
|
`lead_form_abandoned`) NÃO entram nessa tabela** — são gatilhos de **flow**, não eventos de
|
|
528
539
|
automação. Não adianta criar `automation_rule` com esses nomes em `event_name`; o caminho é o node
|
|
@@ -400,13 +400,19 @@ Booking (agendamento confirmado)
|
|
|
400
400
|
|
|
401
401
|
## e-Clínicas (Efficient) — integração de clínicas
|
|
402
402
|
|
|
403
|
-
|
|
403
|
+
15 eventos de webhook, cada um mapeável a automação OU flow na tela Integrações > e-Clínica
|
|
404
|
+
(o mapeamento é SÓ pela tela — não há ferramenta MCP que escreva `event_mapping`):
|
|
404
405
|
`cliente_novo`, `agendamento_novo`, `falta`, `pagamento` (os 4 da doc oficial) +
|
|
405
406
|
`agendamento_atendido`, `agendamento_alterado`, `agendamento_desmarcado`,
|
|
406
407
|
`agendamento_transferido`, `cliente_baixa_pagamento`, `cliente_alteracao`,
|
|
407
|
-
`cliente_inclusao_pagamento`, `controle_laboratorio_novo`, `controle_laboratorio_alterado
|
|
408
|
+
`cliente_inclusao_pagamento`, `controle_laboratorio_novo`, `controle_laboratorio_alterado`,
|
|
409
|
+
`agendamento_aguardando`, `odontograma_aprovado`
|
|
408
410
|
(capturados ao vivo, não documentados pelo e-Clínica).
|
|
409
411
|
|
|
412
|
+
Os 2 últimos são do perfil ODONTOLÓGICO (2026-08-20): `agendamento_aguardando` = a recepção marcou a
|
|
413
|
+
chegada do paciente na clínica (grava a hora da chegada e `eclinica_status_agendamento = aguardando`);
|
|
414
|
+
`odontograma_aprovado` = plano de tratamento aprovado (grava o cabeçalho do odontograma).
|
|
415
|
+
|
|
410
416
|
Atributos de sistema no CONTATO (prefixo `eclinica_`, protegidos, usáveis como variável):
|
|
411
417
|
|
|
412
418
|
| Chave | Tipo | Conteúdo |
|
|
@@ -417,11 +423,12 @@ Atributos de sistema no CONTATO (prefixo `eclinica_`, protegidos, usáveis como
|
|
|
417
423
|
| `eclinica_data_consulta` | date | Data da consulta, ISO |
|
|
418
424
|
| `eclinica_hora_consulta` | **time** | Hora da consulta 24h `"HH:MM"` (novo 2026-07-06) |
|
|
419
425
|
| `eclinica_hora_final` | **time** | Hora final da consulta (novo 2026-07-27) |
|
|
420
|
-
| `eclinica_status_agendamento` | text | agendado / no_show / atendido / desmarcado |
|
|
421
|
-
| `eclinica_situacao` | text | Situação
|
|
426
|
+
| `eclinica_status_agendamento` | text | agendado / aguardando (paciente chegou) / no_show / atendido / desmarcado. **`aguardando` é transitório**: o próximo evento de agenda daquele paciente grava por cima (o último evento recebido é sempre o que vale) |
|
|
427
|
+
| `eclinica_situacao` | text | Situação do agendamento no painel, **POR EXTENSO** (2026-08-21, legenda oficial da e-Clínica): AGUARDANDO, NA CADEIRA, PASSAR FINANCEIRO, AGENDAR RETORNO, ATENDIDO, CONFIRMADO, CONFIRMADO PELO LINK, CONFIRMADO PELA API, FALTA, DESMARCADO, CANCELADO PELO LINK, CANCELADO PELA API. Antes guardava a letra crua (`A`, `C`…). Código nunca visto aparece como veio |
|
|
422
428
|
| `eclinica_compromisso` | text | Tipo da consulta — TEXTO LIVRE da recepção (ex: Consulta, Retorno) |
|
|
423
429
|
| `eclinica_agendatipo` | text | Tipo de Agendamento — NOME da lista fixa do painel, resolvido por unidade (novo 2026-07-28). **Filtrar sempre pelo NOME, nunca pelo número: os números colidem entre filiais** |
|
|
424
430
|
| `eclinica_agendatipo_id` | text | Tipo de Agendamento — número cru (não comparável entre filiais) |
|
|
431
|
+
| `eclinica_cor` | text | Cor do agendamento na agenda (hex, ex `#556B2F`) — novo 2026-08-18. Campo ESPARSO: evento sem cor APAGA a chave (nunca herda a da consulta anterior). No filtro, escrever com o `#`. Nos lembretes, a variável de sessão `{{cor}}` traz a cor da consulta daquele lembrete |
|
|
425
432
|
| `eclinica_profissional` | text | NOME do profissional, resolvido por unidade (novo 2026-07-10) |
|
|
426
433
|
| `eclinica_profissional_id` | text | Número do profissional (não comparável entre filiais) |
|
|
427
434
|
| `eclinica_convenio_id` | text | Convênio (número) |
|
|
@@ -429,6 +436,17 @@ Atributos de sistema no CONTATO (prefixo `eclinica_`, protegidos, usáveis como
|
|
|
429
436
|
| `eclinica_ultimo_pagamento` | text | Valor do último pagamento |
|
|
430
437
|
| `eclinica_ultimo_pagamento_data` | date | Data do último pagamento/baixa (novo 2026-07-06) |
|
|
431
438
|
| `eclinica_ultimo_pagamento_descricao` | text | Descrição da baixa (ex: CONSULTA INICIAL) |
|
|
439
|
+
| `eclinica_ultima_chegada_idagenda` | text | Agendamento em que o paciente chegou pela última vez (2026-08-20) |
|
|
440
|
+
| `eclinica_ultima_chegada_data` | date | Data da última chegada do paciente na clínica |
|
|
441
|
+
| `eclinica_ultima_chegada_hora` | **time** | Hora em que a recepção marcou a chegada (`age_horaguardando`, ex `15:06`) |
|
|
442
|
+
| `eclinica_odontograma_id` | text | ID do último odontograma/plano de tratamento aprovado (2026-08-20) |
|
|
443
|
+
| `eclinica_odontograma_aprovado_em` | date | Dia da aprovação, já no fuso da conta (o webhook manda em UTC) |
|
|
444
|
+
| `eclinica_odontograma_inicio` | date | Data de início do tratamento (`odo_datainicio`) |
|
|
445
|
+
| `eclinica_odontograma_situacao` | text | Código de situação como vem da e-Clínica (ex: `T`) |
|
|
446
|
+
| `eclinica_odontograma_valor` | text | Valor do plano — **pode vir `0.00`** quando a clínica não preenche |
|
|
447
|
+
| `eclinica_odontograma_profissional` | text | NOME do profissional do odontograma |
|
|
448
|
+
| `eclinica_odontograma_convenio_id` | text | Convênio do odontograma (número) |
|
|
449
|
+
| `eclinica_marcador_nome` | text | Marca que a clínica digita no fim do nome do paciente no e-Clínica (ex: `*`, `***`) — retirada do nome e guardada aqui (2026-08-20). Significado é da clínica |
|
|
432
450
|
| `eclinica_laboratorio_data_prevista` | date | Previsão de entrega da medicação (novo 2026-07-23) |
|
|
433
451
|
| `eclinica_laboratorio_data_moldagem` | date | Data do pedido da medicação |
|
|
434
452
|
| `eclinica_laboratorio_data_entrega` | date | Data em que a medicação CHEGOU na unidade — é a data que dispara o aviso de retirada do flow |
|
|
@@ -599,3 +617,13 @@ Account.feature_enabled?('captain_v2') # checa bitfield
|
|
|
599
617
|
- Contact pode ser deletado (raro) — destroi conversations em cascata (cuidado)
|
|
600
618
|
- KanbanItem pode ser deletado livremente
|
|
601
619
|
- Funnel `archived = true` → não aparece na UI mas dados permanecem
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
## Limites da conta (18/08)
|
|
623
|
+
|
|
624
|
+
Os limites que a API devolve (`resource_limits.*` da conta, `email_usage.limit`,
|
|
625
|
+
`ai_agent_usage.limit`) são a SOMA de: limite do plano + ajustes manuais do Super Admin +
|
|
626
|
+
**adicionais contratados na tela de Cobrança** (recurso liberado na hora da contratação). Ou seja:
|
|
627
|
+
duas contas no MESMO plano podem ter limites diferentes — não é defeito. Limite `0` significa
|
|
628
|
+
ILIMITADO em toda a plataforma. A contratação/remoção de adicionais é feita SÓ pela tela de
|
|
629
|
+
Cobrança do painel (não há ferramenta MCP para isso, por decisão do dono).
|
|
@@ -245,7 +245,50 @@ payload=[
|
|
|
245
245
|
**R6 — Cards parados (sem tarefa) com atributo do contato:**
|
|
246
246
|
`kanban_items_filter funnel_id=37 task_filter=no_task contact_custom_attribute_filters="[{\"attribute_key\":\"ja_investe\",\"filter_operator\":\"equal_to\",\"values\":[\"ja_invisto\"]}]"`
|
|
247
247
|
|
|
248
|
+
**R7 — Pergunta de relatório que NENHUMA ferramenta responde pronta** (relatório personalizado
|
|
249
|
+
montado na hora, sem salvar nada). Ex.: *"quantos cards foram ganhos em cada etapa do funil 37 nos
|
|
250
|
+
últimos 30 dias?"*
|
|
251
|
+
|
|
252
|
+
```json
|
|
253
|
+
custom_dashboards_preview_widget
|
|
254
|
+
widget_type=funnel_stages chart_type=bar funnel_id=37
|
|
255
|
+
date_basis=closed status=won time_range=last_30_days timezone_offset=-3
|
|
256
|
+
```
|
|
257
|
+
Resposta: `{shape:"categories", categories:[{label:"Negociação", value:12}, ...]}` — **na ordem das
|
|
258
|
+
etapas do funil**, não por valor. Trocar `date_basis` muda a pergunta: `created` = "entraram no
|
|
259
|
+
funil", `moved` = "entraram na etapa", `closed` = "foram fechados".
|
|
260
|
+
|
|
261
|
+
**R8 — Tabela de produtividade por operador, com etiqueta e taxa de conversão:**
|
|
262
|
+
|
|
263
|
+
```json
|
|
264
|
+
custom_dashboards_preview_widget
|
|
265
|
+
widget_type=agent_report chart_type=table dimension=agent
|
|
266
|
+
time_range=custom from=2026-08-01 to=2026-08-15 timezone_offset=-3
|
|
267
|
+
columns=[
|
|
268
|
+
{"metric":"leads"},
|
|
269
|
+
{"metric":"atendidos"},
|
|
270
|
+
{"metric":"label_count","label_id":5,"as":"Agendados"},
|
|
271
|
+
{"metric":"conversao","numerator":[5],"denominator":"leads","as":"% agendamento"}
|
|
272
|
+
]
|
|
273
|
+
```
|
|
274
|
+
> ⚠️ `numerator` é **lista de IDs de etiqueta** (`[5]`), nunca nome de métrica. Mandar
|
|
275
|
+
> `["label_count"]` faz a coluna de conversão **sumir em silêncio**.
|
|
276
|
+
> ⚠️ `time_range=custom` aceita no máximo **92 dias**, e só existe em `agent_report` e
|
|
277
|
+
> `conversations_timeseries`. Pedir "esse ano" volta 422 com a explicação.
|
|
278
|
+
> ⚠️ O `total` da tabela **não é a soma das linhas** — cada métrica tem contagem própria.
|
|
279
|
+
|
|
280
|
+
**R9 — Salvar isso como relatório na tela do cliente:** teste com o R8, mostre o número, confirme
|
|
281
|
+
com o usuário e só então:
|
|
282
|
+
```json
|
|
283
|
+
custom_dashboards_create confirm=true
|
|
284
|
+
name="Produtividade da equipe"
|
|
285
|
+
widgets=[{"id":"prod-1", ...mesmo desenho do R8...}]
|
|
286
|
+
```
|
|
287
|
+
Para **alterar** depois: leia com `custom_dashboards_list`, mexa no array e devolva-o **completo** —
|
|
288
|
+
`widgets` substitui a lista inteira, e preservar o `id` de cada bloco é o que mantém o vínculo.
|
|
289
|
+
|
|
248
290
|
**Erros clássicos a evitar:** `includes` em labels (não existe — use equal_to); operador de
|
|
249
291
|
número/data em texto; array-de-objetos em query (padrão B exige JSON-string); esquecer
|
|
250
292
|
`custom_attribute_type` quando contato e conversa têm atributo de mesmo nome; esperar paginação
|
|
251
|
-
no kanban_items_filter (não tem — estreite o filtro)
|
|
293
|
+
no kanban_items_filter (não tem — estreite o filtro); montar um `custom_dashboards_update` em cima
|
|
294
|
+
de uma listagem que veio cortada (apaga os blocos que não chegaram).
|
|
@@ -23,7 +23,7 @@ Como criar fluxos do LionChat (FlowBuilder) que abrem certinhos no canvas, sem n
|
|
|
23
23
|
| Classificar intenção da mensagem | `ai` mode `intent` | - |
|
|
24
24
|
| Analisar sentimento | `ai` mode `sentiment` | - |
|
|
25
25
|
| Encerrar fluxo no meio | `action` com `key: 'deactivate_flow'` OU `exit_conditions` no nível do flow | - |
|
|
26
|
-
|
|
|
26
|
+
| Dividir na proporção entre branches (A/B) | `randomizer` | - |
|
|
27
27
|
| Gerir grupo WhatsApp: criar/buscar, nome/foto/descrição, participantes, admins, convite (WAHA) | `update_group` (uma operação por bloco via `groupOperation`) | - |
|
|
28
28
|
| Iniciar outro fluxo | `action` com `key: 'start_flow'` OU node `activate_flow` | - |
|
|
29
29
|
| Encerrar ramo / definir retorno de ai_tool | `end` | - |
|
|
@@ -53,14 +53,18 @@ Todo node tem essa estrutura base:
|
|
|
53
53
|
"position": { "x": 50, "y": 300 },
|
|
54
54
|
"data": {
|
|
55
55
|
"label": "Início",
|
|
56
|
-
"
|
|
57
|
-
{ "
|
|
56
|
+
"items": [
|
|
57
|
+
{ "key": "message_received", "config": { "keywords": ["oi", "ola"], "match_type": "contains" } }
|
|
58
58
|
]
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
-
**
|
|
63
|
+
**ATENÇÃO — chave `items`, não `triggers` (corrigido 19/08/2026).** Os gatilhos vão em **`data.items`**, cada item no formato **`{ "key": "...", "config": { ...filtros... } }`**. O formato antigo (`data.triggers` + `type` + filtros soltos no topo) ainda é aceito pelo motor por compatibilidade, mas **NÃO deve ser usado em fluxo novo**: o editor do FlowBuilder lê apenas `data.items`, então um fluxo gravado no formato antigo **dispara normalmente mas abre com o bloco Início EM BRANCO no painel do cliente** — e um clique em "Concluir" naquele modal apagava o gatilho em silêncio. Foram 53 fluxos de 9 contas nesse estado até o conserto de 19/08. Alguns leitores do backend (`has_webhook_trigger?` do disparo por webhook, o filtro de Campanha de Fluxo, o gatilho LionTrack e o histórico de execução) também só entendem `items`.
|
|
64
|
+
|
|
65
|
+
**Triggers válidos:** `message_received`, `message_sent`, `conversation_created`, `conversation_resolved`, `conversation_reopened`, `team_changed`, `assignee_changed`, `label_added`, `label_removed`, `sla_missed`, `card_created`, `card_moved`, `card_won`, `card_lost`, `conversation_attribute_changed`, `card_attribute_changed`, `contact_attribute_changed`, `date_trigger`, `campaign_trigger`, `manual_trigger`, `webhook_received`, `page_track`, `group_participant_joined`, `group_participant_left`, `lead_form_completed`, `lead_form_milestone`, `lead_form_abandoned`, `booking_created`, `booking_cancelled`, `booking_rescheduled`, `booking_completed`.
|
|
66
|
+
|
|
67
|
+
**NÃO existem** os gatilhos `webhook` (o correto é **`webhook_received`**) nem `cron` (para disparo por data use `date_trigger`; para disparo em lote use `campaign_trigger` + Campanha de Fluxo). Até 19/08/2026 este guia listava os dois por engano — o fluxo 180 da conta 54 ficou ATIVO com `webhook` e **nunca disparou uma vez em 26 dias**, sem erro nenhum.
|
|
64
68
|
|
|
65
69
|
**Trigger `message_sent` (novo 2026-06-11):** par do message_received, mas pra mensagens de SAÍDA — dispara quando atendente, celular (eco do WhatsApp) ou a própria IA/flow envia mensagem (nota privada NÃO dispara). Config: `keywords` (opcional) + `match_type` (`contains`|`exact`). Caso de uso clássico: "quando eu responder do celular, desligar a IA". ATENÇÃO: mensagem da IA também dispara — se a ação for desativar a IA, use keywords que só humanos digitam ou aceite que a primeira resposta da IA aciona o flow. Protegido por anti-loop (profundidade 5) e sessão única por conversa+flow; nunca alimenta `waiting_input`.
|
|
66
70
|
|
|
@@ -74,7 +78,7 @@ Todo node tem essa estrutura base:
|
|
|
74
78
|
|
|
75
79
|
- `date_trigger` — **Gatilho de Data (novo 2026-07-10):** dispara quando uma DATA guardada na ficha do CONTATO chega (aniversário, data de exame, vencimento de plano). Modelo "agendamento de mensagem": a escrita da data já marca o disparo — NÃO existe varredura periódica. Só em flow `conversation` **individual** (a data é de um contato; grupo não tem). Config (item usa `config` ANINHADO, igual `webhook_received`/`attribute_changed`):
|
|
76
80
|
```json
|
|
77
|
-
{ "
|
|
81
|
+
{ "key": "date_trigger", "config": {
|
|
78
82
|
"attr_key": "_date_of_birth", // "_date_of_birth" = Aniversário nativo; OU a chave de um atributo do contato do tipo Data (ex.: "data_exame")
|
|
79
83
|
"offset_direction": "on", // "before" | "on" | "after"
|
|
80
84
|
"offset_days": 0, // 0-365 (relevante só p/ before/after)
|
|
@@ -127,17 +131,19 @@ Receita completa: `flows_create`/`flows_update` com o item no start → `flows_l
|
|
|
127
131
|
**Trigger `webhook` — Webhook Universal EMBUTIDO (novo 2026-06):** o flow pode ser disparado por um webhook próprio, criado automaticamente. Receita via API:
|
|
128
132
|
1. Criar o flow normalmente (`flows_create`).
|
|
129
133
|
2. `POST /custom_webhook_integrations` com `{ "custom_webhook_integration": { "flow_id": <id do flow> } }` — o sistema cria a integração embutida (idempotente: repetir retorna a mesma; nome automático "Flow: <nome>"; auto-mapeia todos os eventos → este flow) e retorna a URL única do webhook.
|
|
130
|
-
3. No node `start`, adicionar item `{ "
|
|
134
|
+
3. No node `start`, adicionar em `data.items` o item `{ "key": "webhook_received", "config": { "integration_id": <id da integração> } }`.
|
|
131
135
|
4. Salvar o flow (`flows_update`) — o save sincroniza a ativação do webhook embutido (remover o item desativa a integração automaticamente).
|
|
132
136
|
Webhooks embutidos NÃO aparecem na listagem de integrações standalone; excluir o flow destrói o webhook; duplicar o flow NÃO copia o gatilho embutido. Rate limit do endpoint público: 60/min por token.
|
|
133
137
|
|
|
138
|
+
**Gatilhos de AGENDAMENTO — Booking NATIVO do LionChat (2026-08-20):** `booking_created` (alguém marcou pelo link público, pela IA ou pelo painel), `booking_cancelled` (paciente cancelou pelo link, IA cancelou ou a equipe cancelou na Agenda), `booking_rescheduled` (a data/hora mudou — link do paciente, IA, editar/adiar na Agenda), `booking_completed` (equipe concluiu na Agenda; exige a Agenda unificada ligada na conta). **Não é e-Clínica.** Config (tudo opcional, vazio = todos): `booking_event_type_ids` (array de ids de tipo de agendamento, **STRING**), `agent_ids` (array de ids de usuário — o RESPONSÁVEL da tarefa), `create_conversation` (boolean, padrão `false`: só dispara para quem já tem conversa numa caixa do flow; `true` = cria a conversa na 1ª caixa do flow). Exemplo: `{ "key": "booking_created", "config": { "booking_event_type_ids": ["44"], "agent_ids": [], "create_conversation": true } }`. São INERTES a evento de conversa (quem dispara é o `Booking::FlowTriggerDispatcher`, por CONTA — caixa do flow só define onde a conversa é criada/reusada). Variáveis no flow: `{{booking.date}}` (DD/MM/AAAA), `{{booking.time}}`, `{{booking.weekday}}`, `{{booking.datetime}}` (ISO), `{{booking.type}}`, `{{booking.type_format}}` (presencial/videochamada), `{{booking.duration}}`, `{{booking.agent}}`, `{{booking.status}}` (confirmado/cancelado/remarcado/concluído), `{{booking.description}}`, `{{booking.cancel_url}}`, `{{booking.reschedule_url}}`, `{{booking.color}}`, `{{booking.id}}`; só no remarcado: `{{booking.previous_date}}`, `{{booking.previous_time}}`, `{{booking.previous_datetime}}`; `{{booking.meeting_url}}` NÃO vem no criado (nasce vazio). Data/hora vêm da TAREFA da Agenda (sempre atuais). Regras: o evento mais novo SUBSTITUI a rodada ativa do mesmo flow na mesma conversa (2ª consulta do mesmo paciente, ou cancelado chegando com o criado ainda esperando resposta); excluir a tarefa, reabrir cancelada ou desfazer conclusão NÃO disparam; adiar (snooze) conta como remarcado; tipo com confirmação/lembrete próprios ligados + `booking_created` = o paciente pode receber duas mensagens (a tela avisa). Colisão entre flows é por CONTA: mesmo kind + tipos se cruzando + agentes se cruzando (vazio = todos).
|
|
139
|
+
|
|
134
140
|
**TRAVA DE GATILHO DUPLICADO (2026-06-16) — leia ANTES de ativar/criar flow ativo:** o sistema BLOQUEIA ter dois flows ATIVOS com o MESMO gatilho na MESMA inbox e mesmo `conversation_mode` (evita o evento disparar dois flows). Colisão = mesmo tipo de gatilho + config cruzando (mesmas keywords/funil/labels/url/etc) + inbox compartilhada + mesmo modo. EXCEÇÕES que podem coexistir: `webhook_received`, `manual_trigger` e `campaign_trigger` (este último porque não dispara por evento — é só uma autorização para a campanha).
|
|
135
141
|
- Ao **ativar** (`flows_toggle` inativo→ativo) ou **criar já ativo**: qualquer conflito é barrado.
|
|
136
142
|
- Ao **editar** um flow já ativo: só conflito NOVO é barrado (duplicados que já existiam são preservados).
|
|
137
143
|
- A API responde **422** com `{ "error_code": "flow_trigger_conflict", "conflicts": [{ flow_id, flow_name, trigger_type, inbox_id, inbox_name }] }`.
|
|
138
144
|
- Existe `POST /flows/check_conflicts` (mesma assinatura, NÃO salva) pra checar antes.
|
|
139
145
|
|
|
140
|
-
**Como a IA deve agir:** antes de ativar um flow, confira via `
|
|
146
|
+
**Como a IA deve agir:** antes de ativar um flow, confira via `flows_check_conflicts` (mesma assinatura do toggle/update, NÃO salva nada e devolve exatamente quem colide) se já não há outro flow ativo no mesmo gatilho+inbox — NÃO tente deduzir isso lendo o `flow_data` de cada item da `flows_list` (a lista é pra escolher/filtrar fluxos, não pra inspecionar blocos). Se receber 422 `flow_trigger_conflict`, NÃO fique reativando — EXPLIQUE o conflito ao usuário (nome do flow conflitante + caixa) e ofereça desativar o outro flow ou ajustar o gatilho/keywords.
|
|
141
147
|
|
|
142
148
|
**Handles que SAEM:** `success`.
|
|
143
149
|
|
|
@@ -700,7 +706,9 @@ Corrigido em 30/07: o motor lê `percent` e aceita `weight` só como dado antigo
|
|
|
700
706
|
`percent`** — randomizador criado com `weight` até roda, mas abre com as porcentagens EM BRANCO na
|
|
701
707
|
tela do cliente (a tela lê só `percent`).
|
|
702
708
|
|
|
703
|
-
**
|
|
709
|
+
**Divisão EXATA, não sorteio (desde 20/08/2026):** nos DOIS modos o bloco divide o que passa por ele na proporção configurada — 50/50 alterna A,B,A,B; 70/30 dá 7 de cada 10 intercalados. Contador por fluxo+bloco; mexer em agente/porcentagem zera a contagem. (Até 20/08 era cara-ou-coroa — 5 leads seguidos pra mesma pessoa num "50/50" era normal.)
|
|
710
|
+
|
|
711
|
+
**Modo `distribute_agents` (divisão PONDERADA):** `data.mode: 'distribute_agents'` + `data.agents: [{ agent_id, percent }]` — escolhe o agente da vez (percentuais somando 100) e atribui automaticamente. Diferença pra AÇÃO `distribute_agents` do node `action` (seção 2.5): a ação é rodízio SIMPLES (1,2,3 sem porcentagem); este modo aceita pesos diferentes (70/30). Com pesos iguais os dois dão o mesmo resultado.
|
|
704
712
|
|
|
705
713
|
**Handles que SAEM:** o `id` de cada branch (`A`, `B`, ...). Em `mode: 'distribute_agents'` é `success`.
|
|
706
714
|
|
|
@@ -881,7 +889,7 @@ Sempre presente:
|
|
|
881
889
|
|
|
882
890
|
| Variável | Conteúdo |
|
|
883
891
|
|---|---|
|
|
884
|
-
| `{{trigger.type}}` | chave do evento (`message_created`, `conversation_created`, `label_added`, `kanban_item_stage_changed`, `webhook_received`, `date_trigger`, `flow_campaign`, `manual`, `pagetrack_visited`…) |
|
|
892
|
+
| `{{trigger.type}}` | chave do evento (`message_created`, `conversation_created`, `label_added`, `kanban_item_stage_changed`, `webhook_received`, `date_trigger`, `flow_campaign`, `manual`, `pagetrack_visited`, `booking_created`, `booking_cancelled`, `booking_rescheduled`, `booking_completed`…) |
|
|
885
893
|
| `{{trigger.name}}` | rótulo legível do MESMO evento ("Mensagem recebida", "Card mudou de etapa") — use este pra ESCREVER pro cliente, e o `type` pra COMPARAR em condição |
|
|
886
894
|
| `{{trigger.activated_at}}` | quando disparou (ISO 8601) |
|
|
887
895
|
| `{{trigger.kanban_item_id}}` / `{{trigger.funnel_id}}` | só quando veio do Kanban |
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
```json
|
|
12
12
|
{
|
|
13
13
|
"nodes": [
|
|
14
|
-
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "
|
|
14
|
+
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "items": [{ "key": "conversation_created", "config": {} }] } },
|
|
15
15
|
{ "id": "n2", "type": "send_message", "position": { "x": 370, "y": 300 }, "data": { "label": "Saudação", "messageItems": [
|
|
16
16
|
{ "id": "m1", "type": "text", "content": "Oi {{contact.name|default:'tudo bem?'}} 👋 Como posso ajudar hoje?\n\n1️⃣ Vendas\n2️⃣ Suporte\n3️⃣ Financeiro" }
|
|
17
17
|
] } },
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
```json
|
|
56
56
|
{
|
|
57
57
|
"nodes": [
|
|
58
|
-
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "
|
|
58
|
+
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "items": [{ "key": "conversation_created", "config": {} }] } },
|
|
59
59
|
{ "id": "n2", "type": "send_message", "position": { "x": 370, "y": 300 }, "data": { "label": "Pede nome", "messageItems": [
|
|
60
60
|
{ "id": "m1", "type": "text", "content": "Olá! Antes de continuar, qual seu nome completo?" }
|
|
61
61
|
] } },
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
```json
|
|
100
100
|
{
|
|
101
101
|
"nodes": [
|
|
102
|
-
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "
|
|
102
|
+
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "items": [{ "key": "message_received", "config": { "keywords": ["proposta","orçamento"], "match_type": "contains" } }] } },
|
|
103
103
|
{ "id": "n2", "type": "send_message", "position": { "x": 370, "y": 300 }, "data": { "label": "B - Budget", "messageItems": [{ "id": "m1", "type": "text", "content": "Pra te ajudar melhor, qual a faixa de investimento que vocês têm em mente?\n\n1️⃣ Até R$ 5k\n2️⃣ R$ 5k a R$ 20k\n3️⃣ Acima de R$ 20k" }] } },
|
|
104
104
|
{ "id": "n3", "type": "wait_response", "position": { "x": 690, "y": 300 }, "data": { "label": "Budget", "waitTime": 60, "waitUnit": "minutes", "validation": "options", "acceptedOptions": ["1","2","3"], "saveTo": "contact_attr", "saveAttrKey": "budget_tier" } },
|
|
105
105
|
{ "id": "n4", "type": "send_message", "position": { "x": 1010, "y": 300 }, "data": { "label": "A - Authority", "messageItems": [{ "id": "m2", "type": "text", "content": "Você é o decisor ou tem outras pessoas envolvidas?" }] } },
|
|
@@ -150,7 +150,7 @@
|
|
|
150
150
|
```json
|
|
151
151
|
{
|
|
152
152
|
"nodes": [
|
|
153
|
-
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "
|
|
153
|
+
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "items": [{ "key": "conversation_resolved", "config": {} }] } },
|
|
154
154
|
{ "id": "n2", "type": "wait", "position": { "x": 370, "y": 300 }, "data": { "label": "Aguarda 5 min", "waitTime": 5, "waitUnit": "minutes" } },
|
|
155
155
|
{ "id": "n3", "type": "send_message", "position": { "x": 690, "y": 300 }, "data": { "label": "Pede nota", "messageItems": [{ "id": "m1", "type": "text", "content": "Antes de fechar: de 1 a 5, como foi seu atendimento?\n\n5 = excelente\n1 = ruim" }] } },
|
|
156
156
|
{ "id": "n4", "type": "wait_response", "position": { "x": 1010, "y": 300 }, "data": { "label": "Nota", "waitTime": 60, "waitUnit": "minutes", "validation": "options", "acceptedOptions": ["1","2","3","4","5"], "saveTo": "conversation_attr", "saveAttrKey": "csat_score" } },
|
|
@@ -182,7 +182,7 @@
|
|
|
182
182
|
```json
|
|
183
183
|
{
|
|
184
184
|
"nodes": [
|
|
185
|
-
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "
|
|
185
|
+
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "items": [{ "key": "conversation_created", "config": {} }] } },
|
|
186
186
|
{ "id": "n2", "type": "condition", "position": { "x": 370, "y": 300 }, "data": { "label": "Horário comercial?", "conditions": [
|
|
187
187
|
{ "id": "c1", "label": "Dentro do horário", "field": "{{now.hour}}", "operator": "greater_than", "value": "8", "valueType": "variable" }
|
|
188
188
|
] } },
|
|
@@ -210,7 +210,7 @@
|
|
|
210
210
|
```json
|
|
211
211
|
{
|
|
212
212
|
"nodes": [
|
|
213
|
-
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "
|
|
213
|
+
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "items": [{ "key": "message_received", "config": {} }] } },
|
|
214
214
|
{ "id": "n2", "type": "ai", "position": { "x": 370, "y": 300 }, "data": {
|
|
215
215
|
"label": "Classifica", "aiMode": "intent",
|
|
216
216
|
"aiPrompt": "Classifique a intenção da última mensagem do cliente",
|
|
@@ -242,7 +242,7 @@
|
|
|
242
242
|
```json
|
|
243
243
|
{
|
|
244
244
|
"nodes": [
|
|
245
|
-
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "
|
|
245
|
+
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "items": [{ "key": "label_added", "config": { "label_names": ["follow-up"] } }] } },
|
|
246
246
|
{ "id": "n2", "type": "wait", "position": { "x": 370, "y": 300 }, "data": { "label": "Espera 7 dias", "waitTime": 7, "waitUnit": "days" } },
|
|
247
247
|
{ "id": "n3", "type": "send_message", "position": { "x": 690, "y": 300 }, "data": { "label": "Template re-engajamento", "messageItems": [
|
|
248
248
|
{ "id": "m1", "type": "whatsapp_template", "templateId": 42, "params": ["{{contact.name}}"] }
|
|
@@ -269,7 +269,7 @@
|
|
|
269
269
|
```json
|
|
270
270
|
{
|
|
271
271
|
"nodes": [
|
|
272
|
-
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "
|
|
272
|
+
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "items": [{ "key": "message_received", "config": {} }] } },
|
|
273
273
|
{ "id": "n2", "type": "condition", "position": { "x": 370, "y": 300 }, "data": { "label": "Pede humano?", "conditions": [
|
|
274
274
|
{ "id": "c1", "label": "Sim", "field": "{{last_response}}", "operator": "contains", "value": "humano", "valueType": "variable" }
|
|
275
275
|
] } },
|
|
@@ -299,7 +299,7 @@
|
|
|
299
299
|
```json
|
|
300
300
|
{
|
|
301
301
|
"nodes": [
|
|
302
|
-
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "
|
|
302
|
+
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "items": [{ "key": "message_received", "config": { "keywords": ["agendar","reunião"], "match_type": "contains" } }] } },
|
|
303
303
|
{ "id": "n2", "type": "send_message", "position": { "x": 370, "y": 300 }, "data": { "label": "Pede data", "messageItems": [{ "id": "m1", "type": "text", "content": "Em que data você prefere? (formato DD/MM)" }] } },
|
|
304
304
|
{ "id": "n3", "type": "wait_response", "position": { "x": 690, "y": 300 }, "data": { "label": "Data", "waitTime": 30, "waitUnit": "minutes", "validation": "regex", "regexPattern": "^\\d{2}/\\d{2}$", "invalidMessage": "Formato inválido. Use DD/MM (ex: 25/05)", "maxRetries": 2, "saveTo": "variable", "saveVariable": "data_pref" } },
|
|
305
305
|
{ "id": "n4", "type": "api", "position": { "x": 1010, "y": 300 }, "data": {
|
|
@@ -333,7 +333,7 @@
|
|
|
333
333
|
```json
|
|
334
334
|
{
|
|
335
335
|
"nodes": [
|
|
336
|
-
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "
|
|
336
|
+
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "items": [{ "key": "label_added", "config": { "label_names": ["urgente"] } }] } },
|
|
337
337
|
{ "id": "n2", "type": "action", "position": { "x": 370, "y": 300 }, "data": { "label": "Dispara webhook", "items": [
|
|
338
338
|
{ "key": "send_webhook", "config": {
|
|
339
339
|
"url": "https://hooks.slack.com/services/XXX/YYY/ZZZ",
|
|
@@ -364,7 +364,7 @@
|
|
|
364
364
|
{
|
|
365
365
|
"nodes": [
|
|
366
366
|
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "items": [
|
|
367
|
-
{ "
|
|
367
|
+
{ "key": "webhook_received", "config": { "integration_id": 123 } }
|
|
368
368
|
] } },
|
|
369
369
|
{ "id": "n2", "type": "action", "position": { "x": 370, "y": 300 }, "data": { "label": "Marca origem", "items": [
|
|
370
370
|
{ "key": "add_conversation_label", "config": { "labels": ["compra-confirmada"] } }
|
|
@@ -417,7 +417,7 @@
|
|
|
417
417
|
```json
|
|
418
418
|
{
|
|
419
419
|
"nodes": [
|
|
420
|
-
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "
|
|
420
|
+
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "items": [{ "key": "message_received", "config": { "keywords": ["orçamento"], "match_type": "contains" } }] } },
|
|
421
421
|
{ "id": "n2", "type": "action", "position": { "x": 370, "y": 300 }, "data": { "label": "Marca", "items": [
|
|
422
422
|
{ "key": "add_conversation_label", "config": { "labels": ["pediu-orcamento"] } },
|
|
423
423
|
{ "key": "add_label", "config": { "labels": ["interessado-em-comprar"] } }
|
|
@@ -440,7 +440,7 @@
|
|
|
440
440
|
```json
|
|
441
441
|
{
|
|
442
442
|
"nodes": [
|
|
443
|
-
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "
|
|
443
|
+
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "items": [{ "key": "conversation_created", "config": {} }] } },
|
|
444
444
|
{ "id": "n2", "type": "send_message", "position": { "x": 370, "y": 300 }, "data": { "label": "Oferece", "messageItems": [
|
|
445
445
|
{ "id": "m1", "type": "text", "content": "Quer receber nossas novidades por e-mail?" }
|
|
446
446
|
] } },
|
|
@@ -520,7 +520,7 @@
|
|
|
520
520
|
{
|
|
521
521
|
"nodes": [
|
|
522
522
|
{ "id": "start", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "items": [
|
|
523
|
-
{ "
|
|
523
|
+
{ "key": "date_trigger", "config": {
|
|
524
524
|
"attr_key": "_date_of_birth",
|
|
525
525
|
"offset_direction": "on",
|
|
526
526
|
"offset_days": 0,
|
|
@@ -561,7 +561,7 @@ funciona em conta com caixa **WhatsApp QR Code (`Channel::Waha`)** — a caixa v
|
|
|
561
561
|
```json
|
|
562
562
|
{
|
|
563
563
|
"nodes": [
|
|
564
|
-
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "
|
|
564
|
+
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "items": [{ "key": "card_won", "config": { "funnel_ids": ["1"] } }] } },
|
|
565
565
|
{ "id": "n2", "type": "update_group", "position": { "x": 370, "y": 300 }, "data": {
|
|
566
566
|
"label": "Cria grupo de onboarding",
|
|
567
567
|
"groupOperation": "create",
|
|
@@ -633,3 +633,30 @@ grupo** (o grupo é a conversa do flow).
|
|
|
633
633
|
6. Após criar, **ative** com `flows_toggle` se o cliente quiser que rode já. ATENÇÃO (2026-06-16): se já existir outro flow ATIVO com o mesmo gatilho na mesma inbox (e mesmo modo), a ativação retorna **422 `flow_trigger_conflict`** — exceto `webhook_received`/`manual_trigger`, que podem coexistir. Não fique reativando: explique o conflito ao usuário (qual flow + qual caixa) e ofereça desativar o outro ou mudar o gatilho. Dá pra checar antes com `POST /flows/check_conflicts` (não salva). Detalhes no `flowbuilder-design-guide` (seção 2.1, "Trava de gatilho duplicado").
|
|
634
634
|
|
|
635
635
|
Se a necessidade não bate com nenhum pattern, monte do zero seguindo o **`flowbuilder-design-guide`** — mas use estes templates como referência de layout/handle/schema válidos.
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
## Regras de autoria por API (18/08)
|
|
639
|
+
|
|
640
|
+
**`start_flow` nunca pode apontar pro PRÓPRIO flow.** O save aceita (sem 422), mas na execução o
|
|
641
|
+
passo é IGNORADO EM SILÊNCIO (só um aviso no log do servidor; nada no histórico de execução) e o
|
|
642
|
+
fluxo para ali. A tela esconde o próprio flow do seletor; por API a responsabilidade é sua:
|
|
643
|
+
`flow_id` do item `start_flow` tem que ser DIFERENTE do flow que está sendo editado.
|
|
644
|
+
|
|
645
|
+
**Condição: preencha `operator` E `field` JUNTOS.** O motor executa pelo `operator`, mas a tela
|
|
646
|
+
reabre a regra casando `operator`+`field` — condição escrita só com um dos dois roda certo e
|
|
647
|
+
APARECE errada pro cliente. Pares corretos:
|
|
648
|
+
|
|
649
|
+
| Condição | `field` | `operator` | `valueType` |
|
|
650
|
+
|---|---|---|---|
|
|
651
|
+
| Conversa TEM etiqueta | `{{conversation.label}}` | `conversation_has_label` | `labels_multi` |
|
|
652
|
+
| Conversa SEM etiqueta | `{{conversation.label}}` | `conversation_no_label` | `labels_multi` |
|
|
653
|
+
| Contato TEM etiqueta | `{{contact.label}}` | `contact_has_label` | `labels_multi` |
|
|
654
|
+
| Contato SEM etiqueta | `{{contact.label}}` | `contact_no_label` | `labels_multi` |
|
|
655
|
+
| Existe card | `_kanban_check` | `kanban_exists` | `funnel_stage` |
|
|
656
|
+
| Card em etapa | `_kanban_stage` | `kanban_in_stage` | `funnel_stage` |
|
|
657
|
+
| Card ganho | `_kanban_status` | `kanban_won` | `funnel_only` |
|
|
658
|
+
| Card perdido | `_kanban_status` | `kanban_lost` | `funnel_only` |
|
|
659
|
+
| Resposta do cliente | `{{last_response}}` | `equal_any` (e variações `_any`) | `response_multi` |
|
|
660
|
+
| Resposta do atendente | `{{last_agent_response}}` | `equal_any` (e variações `_any`) | `response_multi` |
|
|
661
|
+
|
|
662
|
+
Nas condições de etiqueta, `values` aceita array de títulos ou string separada por vírgula.
|