@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
|
@@ -1,27 +1,31 @@
|
|
|
1
1
|
# Formulários Públicos — Guia Profundo
|
|
2
2
|
|
|
3
3
|
Tudo sobre os Formulários Públicos do LionChat (captação estilo Typeform/Typebot): link público,
|
|
4
|
-
desenho dos blocos, publicação, respostas, funil e
|
|
4
|
+
caixa de WhatsApp vinculada, desenho dos blocos, publicação, respostas, funil, simulador e
|
|
5
|
+
integração com fluxos. Atualizado em 18/08/2026 (v4.10.226).
|
|
5
6
|
|
|
6
7
|
## O que é
|
|
7
8
|
|
|
8
9
|
Um formulário público é uma página de captação hospedada pelo próprio LionChat. O visitante abre um
|
|
9
|
-
link, responde bloco a bloco, e o LionChat cria o contato, guarda as respostas
|
|
10
|
-
dispara um fluxo do FlowBuilder no fim.
|
|
10
|
+
link, responde bloco a bloco, e o LionChat cria o contato, guarda as respostas, abre a conversa na
|
|
11
|
+
caixa de WhatsApp vinculada e (opcionalmente) dispara um fluxo do FlowBuilder no fim.
|
|
11
12
|
|
|
12
13
|
- **Link público**: `{FRONTEND_URL}/forms/<referência da conta>/<slug>` — a referência da conta é o
|
|
13
|
-
`booking_public_slug` (o mesmo
|
|
14
|
-
|
|
14
|
+
`booking_public_slug` (o mesmo da agenda pública). O `slug` do formulário nasce sorteado (8
|
|
15
|
+
caracteres) e **pode ser trocado** (ver Campos). O campo `public_url` já vem pronto na resposta.
|
|
16
|
+
- **Caixa de WhatsApp vinculada é OBRIGATÓRIA para publicar** (`inbox_id`). Só canais
|
|
17
|
+
`Channel::Whatsapp` (oficial) e `Channel::Waha` (QR Code). É por ela que o bloco Enviar mensagem
|
|
18
|
+
dispara e que a conversa do lead nasce.
|
|
15
19
|
- **Dois formatos de exibição** (`display_mode`): `cards` (um bloco por tela, estilo Typeform) ou
|
|
16
20
|
`chat` (bolhas de conversa, estilo Typebot).
|
|
17
21
|
- **Feature flag `lead_forms`, por conta, nasce desligada.** Com a flag desligada a **página pública
|
|
18
22
|
responde 404** — ninguém consegue preencher. As tools de dashboard (listar, criar, editar,
|
|
19
|
-
publicar, ver respostas) **funcionam mesmo assim
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
publicar, ver respostas, simular) **funcionam mesmo assim**. Dá pra montar tudo antes de ligar a
|
|
24
|
+
flag; não adianta divulgar o link antes.
|
|
25
|
+
- **Escrita é só de administrador.** `create`, `update`, `destroy`, `publish`, `duplicate` e
|
|
26
|
+
`check_embed` passam por `check_admin_authorization?`; usuário não-admin recebe **401**. Leitura
|
|
27
|
+
(`list`, `show`, `stats`, `responses`) e o **simulador** (`test_run`) são liberados a qualquer
|
|
28
|
+
agente da conta.
|
|
25
29
|
|
|
26
30
|
### Não confundir
|
|
27
31
|
|
|
@@ -38,13 +42,15 @@ dispara um fluxo do FlowBuilder no fim.
|
|
|
38
42
|
| `lionchat_lead_forms_list` | `GET /lead_forms` | Lista os formulários da conta (mais novos primeiro). Não traz `media_urls` |
|
|
39
43
|
| `lionchat_lead_forms_create` | `POST /lead_forms` | Cria. Admin-only |
|
|
40
44
|
| `lionchat_lead_forms_show` | `GET /lead_forms/:id` | Detalhe completo, único lugar que traz `media_urls` |
|
|
41
|
-
| `lionchat_lead_forms_update` | `PATCH /lead_forms/:id` | Edita. Admin-only |
|
|
45
|
+
| `lionchat_lead_forms_update` | `PATCH /lead_forms/:id` | Edita (inclusive `inbox_id` e `slug`). Admin-only |
|
|
42
46
|
| `lionchat_lead_forms_destroy` | `DELETE /lead_forms/:id` | Exclui (204 sem corpo). Admin-only |
|
|
43
|
-
| `lionchat_lead_forms_publish` | `POST /lead_forms/:id/publish` | Publica: tira a foto do desenho. Admin-only |
|
|
44
|
-
| `lionchat_lead_forms_duplicate` | `POST /lead_forms/:id/duplicate` | Cópia completa com slug novo. Admin-only |
|
|
47
|
+
| `lionchat_lead_forms_publish` | `POST /lead_forms/:id/publish` | Publica: tira a foto do desenho (com a caixa dentro). Admin-only |
|
|
48
|
+
| `lionchat_lead_forms_duplicate` | `POST /lead_forms/:id/duplicate` | Cópia completa com slug novo (copia o `inbox_id`). Admin-only |
|
|
45
49
|
| `lionchat_lead_forms_stats` | `GET /lead_forms/:id/stats` | Números e funil por bloco |
|
|
46
50
|
| `lionchat_lead_forms_responses_list` | `GET /lead_forms/:id/responses` | Lista de respostas (leve, sem as respostas em si) |
|
|
47
51
|
| `lionchat_lead_forms_responses_show` | `GET /lead_forms/:id/responses/:rid` | Detalhe de uma resposta, com o que a pessoa respondeu |
|
|
52
|
+
| `lionchat_lead_forms_test_run` | `POST /lead_forms/:lead_form_id/test_runs` | Simulador: roda o RASCUNHO no motor real, sem deixar rastro. Qualquer agente da conta |
|
|
53
|
+
| `lionchat_lead_forms_check_embed` | `POST /lead_forms/check_embed` | Confere se um site aceita ser exibido em iframe (bloco Página externa). Admin-only |
|
|
48
54
|
|
|
49
55
|
**Imagens não têm ferramenta MCP.** O upload de imagem de bloco (`POST /lead_forms/:id/media`,
|
|
50
56
|
máximo 5 MB, só `image/*`) existe no painel. Pela API dá pra referenciar um `image` que já foi
|
|
@@ -57,45 +63,75 @@ subido, nunca subir um novo.
|
|
|
57
63
|
| `name` | string | **Obrigatório** |
|
|
58
64
|
| `display_mode` | string | `cards` (padrão) ou `chat` |
|
|
59
65
|
| `active` | boolean | Formulário inativo devolve 404 na página pública |
|
|
66
|
+
| `inbox_id` | integer | Caixa de WhatsApp vinculada. Obrigatória para publicar. Ver regras abaixo |
|
|
60
67
|
| `form_data` | jsonb | O desenho (rascunho). Teto de 2 MB serializado |
|
|
61
68
|
| `settings` | jsonb | Ver abaixo |
|
|
62
|
-
| `slug` | string | **
|
|
69
|
+
| `slug` | string | **Editável** em create e update. Normalizado no servidor (acento vira letra, resto vira hífen), 3 a 60 caracteres, minúsculas + números + hífen, único dentro da conta. Vazio/só espaços = mantém o atual (não é erro). Duplicado = 422. Ausente no create = sorteia 8 caracteres |
|
|
63
70
|
| `published_at` | timestamp | Quando a foto foi tirada. Nulo = nunca publicado |
|
|
64
71
|
| `public_url` | string | Montado pelo servidor |
|
|
65
72
|
| `counts` | objeto | `{views, responses, completed}` — contadores do banco |
|
|
66
73
|
|
|
74
|
+
**A resposta de `show`/`list`/`create`/`update` traz também:** `inbox` (`{id, name, channel_type}`
|
|
75
|
+
ou `null` — a caixa resolvida), e `published_inbox_id` (a caixa que está NA FOTO publicada).
|
|
76
|
+
|
|
77
|
+
### Regras do `inbox_id`
|
|
78
|
+
|
|
79
|
+
- Caixa que não é da conta → 422 `{errors: {inbox_id: ["not found in this account"]}}`.
|
|
80
|
+
- Canal fora de `Channel::Whatsapp`/`Channel::Waha` → `["channel not supported"]`.
|
|
81
|
+
- **Trocar de caixa pode; trocar de TIPO de canal não** → `["channel type cannot change"]`.
|
|
82
|
+
Os blocos foram montados pro canal atual (oficial exige modelo aprovado; QR aceita texto livre e
|
|
83
|
+
mídia). Formulário que ainda não tinha caixa escolhe livremente.
|
|
84
|
+
- **A troca só vale depois de republicar**: `publish` grava o `inbox_id` DENTRO da foto
|
|
85
|
+
(`published_data`), e o motor lê sempre a foto.
|
|
86
|
+
|
|
67
87
|
### `settings`
|
|
68
88
|
|
|
69
89
|
| Chave | Valor | Padrão |
|
|
70
90
|
|---|---|---|
|
|
71
91
|
| `primary_color` | hex | `#7C3AED` |
|
|
72
92
|
| `theme` | `light` ou `dark` (valor fora da lista cai em `light`) | `light` |
|
|
73
|
-
| `abandon_minutes` | inteiro, limitado a 1..
|
|
93
|
+
| `abandon_minutes` | inteiro, limitado a 1..15 (minutos) | 15 |
|
|
74
94
|
| `button_label` | string — rótulo do botão da página pública | — |
|
|
95
|
+
| `resume` | booleano — continuar de onde parou entre visitas | `true` |
|
|
96
|
+
| `chat_avatar` | id numérico de blob já subido (`media`) — foto redonda ao lado dos balões, só no formato `chat`. O público recebe `chat_avatar_url` resolvida (nunca o id) | — |
|
|
75
97
|
|
|
76
98
|
## Ciclo de vida
|
|
77
99
|
|
|
78
100
|
1. **Criar** com `lionchat_lead_forms_create` (nome + `form_data`, mesmo que mínimo).
|
|
79
|
-
2. **
|
|
80
|
-
3. **
|
|
81
|
-
`
|
|
82
|
-
4. **
|
|
83
|
-
5. **
|
|
101
|
+
2. **Vincular a caixa de WhatsApp** (`inbox_id`) — sem ela a publicação recusa (`form_without_inbox`).
|
|
102
|
+
3. **Montar o desenho** em `form_data` (contrato na próxima seção). Todo formulário precisa de uma
|
|
103
|
+
pergunta com `map_to: contact.phone` (`form_requires_phone_question`).
|
|
104
|
+
4. **Testar** com `lionchat_lead_forms_test_run` — roda o rascunho de verdade, sem sujar nada.
|
|
105
|
+
5. **Publicar** com `lionchat_lead_forms_publish` — copia `form_data` (+ `inbox_id`) para
|
|
106
|
+
`published_data` e carimba `published_at`.
|
|
107
|
+
6. **Ativar** (`active: true`) e ligar a feature flag `lead_forms` da conta.
|
|
108
|
+
7. **Divulgar** o `public_url`.
|
|
84
109
|
|
|
85
110
|
**Quem preenche usa SEMPRE a foto publicada, nunca o rascunho.** Editar `form_data` não muda nada
|
|
86
|
-
para quem está preenchendo até um novo `publish`.
|
|
87
|
-
com gente respondendo sem quebrar ninguém no meio.
|
|
111
|
+
para quem está preenchendo até um novo `publish`.
|
|
88
112
|
|
|
89
|
-
**O contato nasce
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
113
|
+
**O contato nasce em TRÊS momentos**: quando a pessoa conclui, quando a resposta é marcada como
|
|
114
|
+
abandonada (padrão e teto de 15 minutos sem atividade), ou logo antes de um bloco de Enviar
|
|
115
|
+
WhatsApp. Enquanto isso nada é criado — nem contato, nem conversa, nem card. Efeitos que dependem de
|
|
116
|
+
contato (evento de pixel, gatilho de marco) ficam guardados e disparam no nascimento, com a hora
|
|
117
|
+
original de cada etapa. Quando o contato nasce ali, a conversa começa com uma pílula dizendo qual
|
|
118
|
+
formulário o originou. Contato apagado no painel solta o vínculo — a resposta pode renascer no
|
|
119
|
+
próximo momento. Os textos `{{contact.name}}`/`{{contact.phone}}`/`{{contact.email}}` saem do que a
|
|
120
|
+
pessoa RESPONDEU, não da ficha.
|
|
94
121
|
|
|
95
|
-
|
|
122
|
+
### Link identificado (pula perguntas já conhecidas)
|
|
96
123
|
|
|
97
|
-
|
|
98
|
-
|
|
124
|
+
Toda mensagem de TEXTO enviada numa conversa que contenha um link `/forms/` sai **carimbada
|
|
125
|
+
automaticamente** com `?lt_form=<código>` (sem depender de LionTrack; vale pra mensagem de flow e
|
|
126
|
+
de IA também; nunca em grupo). Quem abre o link carimbado tem a resposta ligada ao contato desde o
|
|
127
|
+
início, e o formulário **PULA as perguntas de identidade que já têm valor** — só as três nativas:
|
|
128
|
+
nome, telefone e e-mail. A resposta pulada é gravada com o valor da ficha e o selo `prefilled`.
|
|
129
|
+
|
|
130
|
+
Seguranças: o valor pulado **nunca aparece na tela nem no payload público**, e o botão Voltar **não
|
|
131
|
+
alcança** a pergunta pulada. Código de outra conta, contato apagado ou qualquer pane = o formulário
|
|
132
|
+
roda anônimo normal. Link copiado do editor e colado fora de uma conversa NÃO tem carimbo.
|
|
133
|
+
|
|
134
|
+
## O contrato do `form_data`
|
|
99
135
|
|
|
100
136
|
```json
|
|
101
137
|
{
|
|
@@ -116,15 +152,18 @@ arestas:
|
|
|
116
152
|
|
|
117
153
|
| `type` | Chaves de `data` permitidas | Saídas (`sourceHandle`) | Aparece pro lead? |
|
|
118
154
|
|---|---|---|---|
|
|
119
|
-
| `start` | `title, description, button_label, image` | `success` | sim |
|
|
120
|
-
| `question` | `label, description, field_type, placeholder, required, map_to, scale, image, accept, invalid_message` | `success` | sim |
|
|
155
|
+
| `start` | `title, description, button_label, image, video_url` | `success` | sim |
|
|
156
|
+
| `question` | `label, description, field_type, placeholder, required, map_to, scale, image, video_url, accept, invalid_message` | `success` | sim |
|
|
121
157
|
| `choice` | `label, description, options, multiple, map_to` | `option_<id da opção>` (escolha única) / `success` (múltipla) | sim |
|
|
122
|
-
| `message` | `title, description, button_label, image` | `success` | sim |
|
|
158
|
+
| `message` | `title, description, button_label, image, video_url` | `success` | sim |
|
|
123
159
|
| `condition` | `branches` | `cond_<id do ramo>` + `else` | não |
|
|
124
|
-
| `action` | `action_type, labels, name, event_name` | `success` | não |
|
|
125
|
-
| `send_message` | `
|
|
126
|
-
| `
|
|
127
|
-
| `
|
|
160
|
+
| `action` | `items, label` (+ formato antigo `action_type, labels, name, event_name`) | `success` | não |
|
|
161
|
+
| `send_message` | `messageItems` (+ formato antigo `content, template_name, template_params`) | `success`, `skipped` | não |
|
|
162
|
+
| `booking` | `label, description, event_type_id, required` | `success` (agendou) + `no_booking` (seguiu sem agendar; some quando `required`) | sim |
|
|
163
|
+
| `set_variable` | `label, assignments` | `success` | não |
|
|
164
|
+
| `embed` | `title, description, url, height, button_label, embed_allowed, show_open_external` | `success` | sim |
|
|
165
|
+
| `end` | `title, description, redirect_url, button_label, image, video_url` | — | sim |
|
|
166
|
+
| `api_request` | `label, method, url, headers, body, timeout` | `success`, `error` | não (mostra bloco de espera) |
|
|
128
167
|
| `ai` | `label, mode, prompt, categories, include_answers, model` | modo `generate`: `success`, `error` / modo `classify`: um `cat_<id>` por categoria + `other` | não (mostra bloco de espera) |
|
|
129
168
|
|
|
130
169
|
**Chave fora da lista não é lida pelo motor** e o editor a **remove no próximo salvamento pela
|
|
@@ -134,94 +173,171 @@ depois some. A lista é fonte única (`LeadForm::NODE_DATA_KEYS` no servidor, es
|
|
|
134
173
|
|
|
135
174
|
### Detalhes por chave
|
|
136
175
|
|
|
137
|
-
**`field_type`** (do `question`): `short_text`, `long_text`, `email`, `phone`, `date`, `
|
|
138
|
-
`rating` (usa `scale`), `url`, `file_upload` (usa `accept`)
|
|
176
|
+
**`field_type`** (do `question`): `short_text`, `long_text`, `email`, `phone`, `date`, `time`,
|
|
177
|
+
`datetime`, `number`, `rating` (usa `scale`), `url`, `file_upload` (usa `accept`), e os documentos
|
|
178
|
+
com validação de verdade: `cpf`, `cnpj`, `cep` (dígito verificador/8 dígitos; gravam só números;
|
|
179
|
+
erros `invalid_cpf`/`invalid_cnpj`/`invalid_cep`).
|
|
180
|
+
- `time` grava `HH:MM` (24h; erro `invalid_time`).
|
|
181
|
+
- `datetime` grava `AAAA-MM-DDTHH:MM` — hora local de quem preencheu, SEM fuso; o fuso de leitura é
|
|
182
|
+
o da definição do atributo de destino (erro `invalid_datetime`).
|
|
139
183
|
|
|
140
|
-
**`
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
**`map_to`**: `''` (não guarda em lugar nenhum), `contact.name`, `contact.email`, `contact.phone`
|
|
145
|
-
ou `custom.<chave de atributo personalizado de contato>`.
|
|
184
|
+
**`video_url`** (em `start`, `question`, `message`, `end`): link de YouTube, Vimeo ou arquivo de
|
|
185
|
+
vídeo direto. Com vídeo válido, ele ocupa o lugar da imagem. Link fora do formato conhecido não
|
|
186
|
+
renderiza e não dá erro.
|
|
146
187
|
|
|
147
|
-
**`
|
|
148
|
-
|
|
188
|
+
**`accept`** (só em `file_upload`): `'any'` ou um array de categorias entre `image`, `video`,
|
|
189
|
+
`audio`, `pdf`, `spreadsheet`, `document`, `xml`. Arquivo executável é recusado sempre.
|
|
190
|
+
|
|
191
|
+
**`map_to`** — para onde vai a resposta:
|
|
192
|
+
- `''` — não guarda em lugar nenhum (só em `answers`).
|
|
193
|
+
- `contact.name`, `contact.email`, `contact.phone` — os três nativos.
|
|
194
|
+
- `contact.cadastral.<campo>` — `cpf`, `rg`, `cnpj`, `passport`, `profession`, `marital_status`,
|
|
195
|
+
`gender`, `date_of_birth` (os dois últimos sem tela; funcionam por API e são normalizados:
|
|
196
|
+
"solteira" vira `solteiro`, "masculino" vira `m`).
|
|
197
|
+
- `contact.address.<campo>` — `cep`, `street`, `number`, `complement`, `neighborhood`, `city`,
|
|
198
|
+
`state`, `country`.
|
|
199
|
+
- `custom.<chave>` — atributo personalizado de CONTATO.
|
|
200
|
+
- `conversation_custom.<chave>` — atributo personalizado da CONVERSA (só chave definida na conta;
|
|
201
|
+
coerção de tipo; teto 1500 caracteres).
|
|
202
|
+
- **Lista de recusa no servidor** (o valor é pulado de forma visível, sem erro): `name`, `email`,
|
|
203
|
+
`phone_number`, `identifier`, `card` e chaves começando com `captain_`, `eclinica_`, `origin_`,
|
|
204
|
+
`lt_`, `booking_`, `waha_`, `whatsapp_`, `ctwa_`, `meta_lead_`.
|
|
205
|
+
- Contato pré-existente recebe valor **só em campo vazio** — exceto o que ESTA resposta gravou e
|
|
206
|
+
ninguém mudou depois (correção via Voltar).
|
|
207
|
+
|
|
208
|
+
**`options`** (do `choice`): `[{ "id": "abc123", "label": "Sim", "image": "" }]`.
|
|
149
209
|
|
|
150
210
|
**`branches`** (do `condition`): lista avaliada em ordem; nenhum ramo casando cai no handle `else`.
|
|
151
|
-
|
|
152
|
-
```json
|
|
153
|
-
[{ "id": "b1", "source_node_id": "q_cidade", "operator": "equals", "value": "São Paulo" }]
|
|
154
|
-
```
|
|
155
|
-
|
|
156
211
|
Operadores: `equals`, `not_equals`, `contains`, `filled`, `not_filled`, `greater_than`,
|
|
157
|
-
`less_than`.
|
|
158
|
-
o caminho dentro da resposta da consulta (ex.: `"path": "localidade"`).
|
|
159
|
-
|
|
160
|
-
**`action_type`**: `add_label` (usa `labels: []`), `milestone` (usa `name` — é o marco que aparece
|
|
161
|
-
no funil e pode disparar fluxo), `meta_pixel_event` e `ga4_event` (usam `event_name`).
|
|
212
|
+
`less_than`. Fonte `api_request` exige `path` (caminho dentro da resposta).
|
|
162
213
|
|
|
163
|
-
**`
|
|
164
|
-
oficial usa `template_name` + `template_params`. Um formulário com `send_message` **obriga** existir
|
|
165
|
-
uma pergunta com `map_to: contact.phone` — sem telefone não há para quem mandar.
|
|
214
|
+
**`items`** (do `action`) — o formato novo, uma LISTA de ações `[{key, config}]`:
|
|
166
215
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
`
|
|
170
|
-
|
|
216
|
+
| `key` | `config` mínimo para publicar | O que faz |
|
|
217
|
+
|---|---|---|
|
|
218
|
+
| `update_contact_attribute` | `attr_key` | Grava atributo personalizado do CONTATO |
|
|
219
|
+
| `update_conversation_attribute` | `attr_key` | Grava atributo personalizado da CONVERSA |
|
|
220
|
+
| `add_label` | `labels` com ≥1 título | Etiqueta o contato |
|
|
221
|
+
| `milestone` | `name` | Marco do funil (pode disparar fluxo) |
|
|
222
|
+
| `meta_pixel_event` | `event_name` | Evento Meta CAPI (`[a-zA-Z0-9_]`, até 40) |
|
|
223
|
+
| `ga4_event` | `event_name` | Evento GA4 |
|
|
224
|
+
|
|
225
|
+
O `config` das ações de atributo aceita também `attr_value` (texto com variáveis) e `attr_op`:
|
|
226
|
+
`set` (sobrescrever, padrão), `plus`, `minus`, `times`, `divided_by`. **A conta é feita no
|
|
227
|
+
servidor** sobre o valor atual, com trava de repetição (roda UMA vez por preenchimento); valor que
|
|
228
|
+
não vira número não grava; divisão por zero não grava. A operação aritmética é a única que passa
|
|
229
|
+
por cima do "só grava em campo vazio". Identidade (nome/telefone/e-mail) NÃO se grava por aqui.
|
|
230
|
+
Formato antigo (`action_type` + campos soltos) continua sendo lido.
|
|
231
|
+
|
|
232
|
+
**`messageItems`** (do `send_message`) — LISTA de mensagens, enviadas na ordem:
|
|
233
|
+
- `{type: "text", content: "..."}` — texto com variáveis (caixa QR).
|
|
234
|
+
- `{type: "whatsapp_template" | "template", template_params: {name, category, language, namespace,
|
|
235
|
+
processed_params}}` — modelo aprovado (caixa oficial; `language` padrão `pt_BR`).
|
|
236
|
+
- `{type: "image" | "video" | "audio" | "file" | "document" | "attachment" | "url_media",
|
|
237
|
+
url | file_url | blob_signed_id, caption}` — mídia.
|
|
238
|
+
Item que falha **não derruba os outros** — vira `skipped_item {index, reason}` em `answers`.
|
|
239
|
+
A caixa é a DO FORMULÁRIO (`inbox_id`) — o bloco não tem caixa própria. Formato antigo
|
|
240
|
+
(`content`/`template_name`/`template_params` soltos) continua sendo lido.
|
|
241
|
+
|
|
242
|
+
**`booking`** — calendário DENTRO do formulário:
|
|
243
|
+
- `event_type_id` = id de um tipo de evento de Agendamento da conta (ativo).
|
|
244
|
+
- `required: true` = só avança depois de agendar (a saída `no_booking` some do desenho).
|
|
245
|
+
- O payload público leva `booking` (slug público, título, duração, `task_type`, `ask_email`,
|
|
246
|
+
`ask_description`), `prefill` (nome/telefone/e-mail já respondidos) e `missing` (o que o tipo
|
|
247
|
+
exige e o formulário não perguntou — e-mail só quando `task_type` é `video_call`). O id interno
|
|
248
|
+
do tipo NUNCA sai. Tipo apagado/desativado depois de publicado: a tela mostra "indisponível" com
|
|
249
|
+
botão de continuar (o `required` é liberado — ninguém fica preso).
|
|
250
|
+
- O valor gravado na resposta é o id do agendamento criado (validado contra a conta).
|
|
251
|
+
|
|
252
|
+
**`set_variable`** — cria variáveis temporárias da resposta (invisível pro lead):
|
|
253
|
+
- `assignments: [{key, value}]` — até 20 linhas, 1000 caracteres por valor; `key` vira
|
|
254
|
+
`{{var.<key>}}` nos blocos seguintes. Linha com `key` vazia é ignorada.
|
|
255
|
+
- SÓ variável. Gravar em contato/conversa é papel do bloco de Ações (um `target` em um item é
|
|
256
|
+
ignorado pelo motor e descartado pela tela).
|
|
257
|
+
|
|
258
|
+
**`embed`** — "Página externa", um site de fora exibido DENTRO do formulário (checkout, simulador,
|
|
259
|
+
agenda externa):
|
|
260
|
+
- `url` precisa começar com `https://` LITERAL (variável só do meio pra frente). Na montagem, as
|
|
261
|
+
variáveis resolvem por posição, com codificação de URL, e o https é reconferido — URL que não
|
|
262
|
+
for https sai VAZIA do payload (proteção contra script via variável).
|
|
263
|
+
- `embed_allowed` NÃO é configuração sua: é o veredito de `lionchat_lead_forms_check_embed`
|
|
264
|
+
gravado no desenho (`true`/`false`/`null`). Com `false`, a página mostra o aviso educado + botão
|
|
265
|
+
"Abrir em nova página" em vez de um quadrado vazio. Escrever `true` na mão mente pro renderizador.
|
|
266
|
+
- `height` em pixels (padrão 480, limitado 240..1200). `show_open_external` (padrão `true`) mostra
|
|
267
|
+
o botão de abrir fora; com o site recusando o iframe, o botão aparece mesmo desligado — é a
|
|
268
|
+
única saída da pessoa.
|
|
269
|
+
- O bloco avança por botão (a página embutida é caixa fechada — não dá pra saber quando um
|
|
270
|
+
pagamento terminou lá dentro). Checkout é justamente o tipo de página que MAIS recusa iframe.
|
|
271
|
+
|
|
272
|
+
**`api_request`**: `method` aceita `GET`, `POST`, `PUT`, `PATCH`, `DELETE` (padrão `GET`). `body`
|
|
273
|
+
(até 64 KB) aceita variáveis — inclusive `{{conta.<variável>}}`; valor do lead entra escapado em
|
|
274
|
+
corpo JSON. A `url` precisa ter **domínio literal** (variáveis só no caminho e nos valores de
|
|
275
|
+
query; `{{conta.*}}` NÃO vale na URL). `headers` aceita literal ou `{{conta.<variável>}}` —
|
|
276
|
+
variável do lead em cabeçalho é recusada na publicação. `timeout` em segundos: padrão 8, teto 15.
|
|
171
277
|
|
|
172
278
|
**`ai`**: `mode` é `generate` (devolve texto em `{{ia.<node_id>}}`) ou `classify` (roteia por
|
|
173
279
|
categoria). `categories` é obrigatório no modo `classify`. `include_answers` (booleano) manda as
|
|
174
|
-
respostas já dadas como contexto. `model` é validado contra a lista suportada
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
`''` = usa o modelo padrão da conta. A IA usa a **chave da própria conta** — conta sem chave faz o
|
|
178
|
-
bloco sair pela saída de erro.
|
|
280
|
+
respostas já dadas como contexto. `model` é validado contra a lista suportada; `''` = modelo padrão
|
|
281
|
+
da conta. A IA usa a **chave da própria conta** — conta sem chave faz o bloco sair pela saída de
|
|
282
|
+
erro.
|
|
179
283
|
|
|
180
284
|
### Variáveis
|
|
181
285
|
|
|
182
|
-
Usáveis no texto de `send_message`, no `prompt` da IA e na `url` do `api_request`:
|
|
183
|
-
|
|
184
286
|
| Variável | O que traz | Onde vale |
|
|
185
287
|
|---|---|---|
|
|
186
|
-
| `{{answer.<node_id>}}` | O que a pessoa respondeu naquele bloco (rótulo da opção ou texto) | mensagem, prompt,
|
|
187
|
-
| `{{contact.name}}` `{{contact.phone}}` `{{contact.email}}` |
|
|
188
|
-
| `{{api.<node_id>.<caminho>}}` | Um pedaço da resposta de uma consulta |
|
|
189
|
-
| `{{ia.<node_id>}}` | O texto que a IA gerou naquele bloco |
|
|
190
|
-
| `{{
|
|
288
|
+
| `{{answer.<node_id>}}` | O que a pessoa respondeu naquele bloco (rótulo da opção ou texto) | mensagem, prompt, valores do bloco, URL/corpo de consulta |
|
|
289
|
+
| `{{contact.name}}` `{{contact.phone}}` `{{contact.email}}` | O que a pessoa respondeu (a ficha é reserva) | idem |
|
|
290
|
+
| `{{api.<node_id>.<caminho>}}` | Um pedaço da resposta de uma consulta | idem |
|
|
291
|
+
| `{{ia.<node_id>}}` | O texto que a IA gerou naquele bloco | idem |
|
|
292
|
+
| `{{var.<nome>}}` | Variável criada pelo bloco Definir variável | idem |
|
|
293
|
+
| `{{conta.<variável>}}` | Variável da conta (cofre) | cabeçalho e CORPO de `api_request` (com segredo); em `set_variable` e no valor das Ações só as NÃO-secretas |
|
|
191
294
|
|
|
192
|
-
Variável desconhecida vira string vazia, em silêncio. `{{conta.*}}`
|
|
193
|
-
mesmo que
|
|
295
|
+
Variável desconhecida vira string vazia, em silêncio. `{{conta.*}}` em texto público sai vazia
|
|
296
|
+
mesmo que exista — é proteção, não defeito.
|
|
194
297
|
|
|
195
298
|
## Regras de publicação
|
|
196
299
|
|
|
197
|
-
`lionchat_lead_forms_publish` devolve **422 com `{errors: [chaves]}
|
|
198
|
-
Cada chave e o que ela quer dizer:
|
|
300
|
+
`lionchat_lead_forms_publish` devolve **422 com `{errors: [chaves]}`**. As 34 chaves atuais:
|
|
199
301
|
|
|
200
302
|
| Chave | Significado |
|
|
201
303
|
|---|---|
|
|
202
|
-
| `no_start` | Não existe bloco `start`
|
|
203
|
-
| `start_without_exit` | O `start` não tem aresta saindo
|
|
204
|
-
| `no_reachable_end` | Nenhum
|
|
205
|
-
| `
|
|
206
|
-
| `
|
|
207
|
-
| `
|
|
208
|
-
| `
|
|
209
|
-
| `
|
|
210
|
-
| `
|
|
211
|
-
| `
|
|
212
|
-
| `
|
|
213
|
-
| `
|
|
214
|
-
| `
|
|
215
|
-
| `
|
|
216
|
-
| `
|
|
217
|
-
| `
|
|
218
|
-
| `
|
|
219
|
-
| `
|
|
220
|
-
| `
|
|
221
|
-
| `
|
|
222
|
-
| `
|
|
223
|
-
| `
|
|
224
|
-
| `
|
|
304
|
+
| `no_start` | Não existe bloco `start` |
|
|
305
|
+
| `start_without_exit` | O `start` não tem aresta saindo |
|
|
306
|
+
| `no_reachable_end` | Nenhum `end` alcançável a partir do `start` |
|
|
307
|
+
| `form_without_inbox` | O formulário não tem caixa de WhatsApp válida vinculada |
|
|
308
|
+
| `form_requires_phone_question` | Nenhuma pergunta guarda o telefone (`map_to: contact.phone`) — obrigatória em TODO formulário |
|
|
309
|
+
| `question_without_label` | Pergunta sem o texto |
|
|
310
|
+
| `question_without_exit` | Pergunta sem saída |
|
|
311
|
+
| `choice_without_options` | Escolha sem opções |
|
|
312
|
+
| `choice_without_exit` | Escolha sem nenhuma saída ligada |
|
|
313
|
+
| `choice_option_without_edge` | Uma opção específica não leva a lugar nenhum |
|
|
314
|
+
| `condition_without_branches` | Condição sem ramos |
|
|
315
|
+
| `condition_without_else` | Condição sem o "senão" ligado |
|
|
316
|
+
| `action_without_config` | Bloco de ação sem nenhum item completo |
|
|
317
|
+
| `send_message_official_without_template` | Caixa OFICIAL vinculada e o bloco de mensagem sem nenhum modelo |
|
|
318
|
+
| `send_message_qr_without_content` | Caixa QR vinculada e o bloco de mensagem sem nenhum item que entregue algo |
|
|
319
|
+
| `api_without_url` | Consulta sem endereço |
|
|
320
|
+
| `api_without_error_exit` | Consulta sem a saída de erro ligada |
|
|
321
|
+
| `api_url_host_not_literal` | O domínio da consulta tem variável |
|
|
322
|
+
| `api_method_invalid` | `method` fora de GET/POST/PUT/PATCH/DELETE |
|
|
323
|
+
| `unknown_variable_in_url` | A URL usa variável de família desconhecida |
|
|
324
|
+
| `unknown_variable_in_body` | O corpo usa variável de família desconhecida (ou `{{` malformado) |
|
|
325
|
+
| `account_variable_in_url` | A URL usa `{{conta.*}}` (só cabeçalho e corpo aceitam) |
|
|
326
|
+
| `lead_variable_in_header` | Cabeçalho usa variável vinda do lead |
|
|
327
|
+
| `ai_without_prompt` | IA sem instrução |
|
|
328
|
+
| `ai_without_categories` | IA em modo classificar sem categorias |
|
|
329
|
+
| `ai_without_error_exit` | IA (gerar) sem a saída de erro |
|
|
330
|
+
| `ai_without_other_exit` | IA (classificar) sem a saída "outros" |
|
|
331
|
+
| `booking_without_event_type` | Agendamento sem tipo de evento escolhido |
|
|
332
|
+
| `booking_event_type_invalid` | O tipo não é da conta ou está desativado |
|
|
333
|
+
| `booking_without_exit` | Agendamento sem a saída "agendou" ligada |
|
|
334
|
+
| `set_variable_without_assignments` | Definir variável sem nenhuma linha com nome |
|
|
335
|
+
| `set_variable_without_exit` | Definir variável sem saída |
|
|
336
|
+
| `embed_without_url` | Página externa sem endereço |
|
|
337
|
+
| `embed_url_not_https` | O endereço da Página externa não começa com `https://` literal |
|
|
338
|
+
|
|
339
|
+
**Não existem mais** (renomeadas em 16/08): `send_message_without_inbox` →
|
|
340
|
+
`form_without_inbox`; `send_message_requires_phone_question` → `form_requires_phone_question`.
|
|
225
341
|
|
|
226
342
|
## Tetos e limites
|
|
227
343
|
|
|
@@ -233,48 +349,76 @@ Cada chave e o que ela quer dizer:
|
|
|
233
349
|
| `days` em `stats` e `responses` | máximo 90 (valor ≤ 0 vira 30) |
|
|
234
350
|
| Página de `responses` | 25 por página, fixo; `page` até 10.000 |
|
|
235
351
|
| Chamadas de IA | 300 por formulário/dia, 1.000 por conta/dia, 10 por resposta |
|
|
236
|
-
| Consultas (`api_request`) | 2.000 por formulário/dia, 10.000 por conta/dia |
|
|
352
|
+
| Consultas (`api_request`) | 2.000 por formulário/dia, 10.000 por conta/dia; corpo até 64 KB |
|
|
237
353
|
| Arquivo enviado pelo lead | 10 MB por arquivo, 10 arquivos por resposta |
|
|
238
354
|
| Imagem de bloco (só pelo painel) | 5 MB, apenas `image/*` |
|
|
239
355
|
| Funil em `stats` | 50.000 respostas (acima disso o funil sai parcial) |
|
|
356
|
+
| Itens do bloco de Ações / linhas do Definir variável | 20 cada; 1000 caracteres por valor |
|
|
357
|
+
| Respostas por chamada do simulador | 60 |
|
|
358
|
+
| Altura da Página externa | 240..1200 px |
|
|
359
|
+
| Conferência de embed | 5 s por salto, até 3 redirecionamentos |
|
|
360
|
+
|
|
361
|
+
## Simulador (`lionchat_lead_forms_test_run`)
|
|
362
|
+
|
|
363
|
+
Roda o **rascunho** (`form_data`, não a foto) no motor de verdade e desfaz tudo no fim — nada fica
|
|
364
|
+
no banco: sem contato, sem conversa, sem WhatsApp, sem pixel, sem resposta nos relatórios. Não
|
|
365
|
+
exige admin. O estado do teste vive em quem chama: cada chamada reproduz o teste inteiro a partir
|
|
366
|
+
da lista de respostas.
|
|
367
|
+
|
|
368
|
+
- Body: `{answers: [{node_id, value}]}` — até 60 itens; vazio = começa do início. Item cujo
|
|
369
|
+
`node_id` não é o bloco atual PARA a reprodução ali (devolve o bloco onde parou).
|
|
370
|
+
- Resposta 200: `{block, log, answers, variables, form}` — `block` é o mesmo envelope da página
|
|
371
|
+
pública; `log` é o caminho percorrido (`[{node_id, type, label, answer, detail}]` — o `detail`
|
|
372
|
+
mostra variável criada, ações executadas, resultado de consulta/IA e o rumo das condições).
|
|
373
|
+
- 422: `{error: "invalid_value", reason}` (uma resposta reprovada na validação) ou
|
|
374
|
+
`{error: "test_run_failed", reason}`.
|
|
240
375
|
|
|
241
376
|
## Respostas e funil
|
|
242
377
|
|
|
243
|
-
**`lionchat_lead_forms_stats`** (`days` opcional) devolve
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
"funnel": [{ "node_id": "q_nome", "type": "question", "label": "Qual é o seu nome?",
|
|
248
|
-
"reached": 175, "percent": 97 }],
|
|
249
|
-
"milestones": [{ "node_id": "n7", "name": "Qualificado", "reached": 62 }] }
|
|
250
|
-
```
|
|
378
|
+
**`lionchat_lead_forms_stats`** (`days` opcional) devolve views/starts/completed/abandoned/
|
|
379
|
+
completion_rate + `funnel` por bloco + `milestones`. O funil lista os blocos que a pessoa vê
|
|
380
|
+
(`start`, `question`, `choice`, `message`, `booking`, `embed`, `end`), na ordem do desenho
|
|
381
|
+
**publicado**.
|
|
251
382
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
`{id, status, contact, current_node_label, answered_count, created_at, completed_at}`.
|
|
383
|
+
**`lionchat_lead_forms_responses_list`** aceita `status` (`in_progress`, `completed`,
|
|
384
|
+
`abandoned`), `days` e `page`. A lista nunca traz as respostas em si — só `{id, status, contact,
|
|
385
|
+
current_node_label, answered_count, created_at, completed_at}`. **`contact` pode vir
|
|
386
|
+
`{id: null, name, phone, pending: true}`** — é quem ainda está preenchendo (o contato só nasce nos
|
|
387
|
+
três momentos); nome e telefone saem do que a pessoa já digitou.
|
|
258
388
|
|
|
259
389
|
**`lionchat_lead_forms_responses_show`** traz o conteúdo:
|
|
260
390
|
|
|
261
|
-
- `answers`: `[{label, value_label, at, download_url?}]
|
|
262
|
-
|
|
263
|
-
- `
|
|
264
|
-
- `
|
|
265
|
-
|
|
266
|
-
sem a query
|
|
391
|
+
- `answers`: `[{label, value_label, at, download_url?}]`. **Atenção:** o bloco de Ações grava
|
|
392
|
+
entradas SINTÉTICAS com chave `"<node_id>::<n>"` — nem toda chave de `answers` é id de bloco.
|
|
393
|
+
- `conversation`: `{id, display_id}` ou `null` — a conversa aberta na caixa vinculada.
|
|
394
|
+
- `milestones`: os marcos atingidos.
|
|
395
|
+
- `effects`: resultado de consulta/IA — `{node_id, label, state, error, status, url_short, at}`.
|
|
396
|
+
`url_short` é esquema + host + caminho, sem a query; corpo, cabeçalhos e prompt nunca saem.
|
|
267
397
|
- `utm_params`: de onde o lead veio.
|
|
268
398
|
|
|
269
|
-
Motivos de erro
|
|
270
|
-
`
|
|
399
|
+
Motivos de erro em `effects`: `http_<código>`, `timeout`, `ssrf_blocked`, `invalid_url`,
|
|
400
|
+
`result_too_large`, `stale`, `busy`, `budget_exceeded`, `no_ai_key`, `ai_error`,
|
|
271
401
|
`unresolved_variable`.
|
|
272
402
|
|
|
403
|
+
## Conferência de iframe (`lionchat_lead_forms_check_embed`)
|
|
404
|
+
|
|
405
|
+
Body `{url}`. Resposta `{status, reason}`:
|
|
406
|
+
|
|
407
|
+
| `status` | `reason` | Significado |
|
|
408
|
+
|---|---|---|
|
|
409
|
+
| `allowed` | — | O site aceita ser exibido dentro do formulário |
|
|
410
|
+
| `blocked` | `not_https` | URL não começa com https:// |
|
|
411
|
+
| `blocked` | `x_frame_options` / `frame_ancestors` | O site declara que recusa iframe |
|
|
412
|
+
| `blocked` | `url_blocked` | Endereço bloqueado por segurança (rede interna etc.) |
|
|
413
|
+
| `unknown` | `unreachable` / `http_<código>` | Não deu pra conferir — a página tenta o iframe e o botão de abrir fora cobre |
|
|
414
|
+
|
|
415
|
+
O veredito deve ser gravado em `embed_allowed` do bloco. Sites de checkout são os que mais recusam.
|
|
416
|
+
|
|
273
417
|
## Receitas
|
|
274
418
|
|
|
275
419
|
### 1. Formulário simples que já publica
|
|
276
420
|
|
|
277
|
-
|
|
421
|
+
Início, nome, telefone, fim — E a caixa vinculada:
|
|
278
422
|
|
|
279
423
|
```json
|
|
280
424
|
{
|
|
@@ -299,112 +443,57 @@ Desenho mínimo que passa na publicação: início, nome, telefone, fim.
|
|
|
299
443
|
}
|
|
300
444
|
```
|
|
301
445
|
|
|
302
|
-
Mande isso em `lionchat_lead_forms_create` junto com `name
|
|
303
|
-
`lionchat_lead_forms_publish` e `lionchat_lead_forms_update` com `active: true`.
|
|
446
|
+
Mande isso em `lionchat_lead_forms_create` junto com `name` e um `inbox_id` de caixa WhatsApp,
|
|
447
|
+
depois `lionchat_lead_forms_publish` e `lionchat_lead_forms_update` com `active: true`.
|
|
304
448
|
|
|
305
449
|
### 2. Consultar o CEP e ramificar pela cidade
|
|
306
450
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
```json
|
|
312
|
-
{
|
|
313
|
-
"nodes": [
|
|
314
|
-
{ "id": "q_cep", "type": "question", "position": { "x": 0, "y": 200 },
|
|
315
|
-
"data": { "label": "Qual é o seu CEP?", "field_type": "short_text", "required": true } },
|
|
316
|
-
{ "id": "api_cep", "type": "api_request", "position": { "x": 0, "y": 400 },
|
|
317
|
-
"data": { "label": "Consulta de CEP",
|
|
318
|
-
"url": "https://viacep.com.br/ws/{{answer.q_cep}}/json/",
|
|
319
|
-
"headers": [], "timeout": 8 } },
|
|
320
|
-
{ "id": "cond_sp", "type": "condition", "position": { "x": 0, "y": 600 },
|
|
321
|
-
"data": { "branches": [ { "id": "b1", "source_node_id": "api_cep",
|
|
322
|
-
"path": "localidade", "operator": "equals",
|
|
323
|
-
"value": "São Paulo" } ] } }
|
|
324
|
-
],
|
|
325
|
-
"edges": [
|
|
326
|
-
{ "id": "e1", "source": "q_cep", "target": "api_cep", "sourceHandle": "success" },
|
|
327
|
-
{ "id": "e2", "source": "api_cep", "target": "cond_sp", "sourceHandle": "success" },
|
|
328
|
-
{ "id": "e3", "source": "api_cep", "target": "fim_erro", "sourceHandle": "error" },
|
|
329
|
-
{ "id": "e4", "source": "cond_sp", "target": "msg_sp", "sourceHandle": "cond_b1" },
|
|
330
|
-
{ "id": "e5", "source": "cond_sp", "target": "msg_outros", "sourceHandle": "else" }
|
|
331
|
-
]
|
|
332
|
-
}
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
Três coisas obrigatórias aqui: a saída `error` ligada, o `else` ligado, e o `path` no ramo (porque a
|
|
336
|
-
fonte é uma consulta). Faltando qualquer uma, a publicação recusa.
|
|
451
|
+
Igual à receita clássica: pergunta o CEP, consulta o ViaCEP (`method` pode ficar de fora — GET é o
|
|
452
|
+
padrão) e ramifica com `path` no ramo cuja fonte é a consulta. Obrigatórios: saída `error` ligada,
|
|
453
|
+
`else` ligado e o `path`.
|
|
337
454
|
|
|
338
455
|
### 3. Triagem por IA
|
|
339
456
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
```json
|
|
343
|
-
{
|
|
344
|
-
"nodes": [
|
|
345
|
-
{ "id": "q_motivo", "type": "question", "position": { "x": 0, "y": 200 },
|
|
346
|
-
"data": { "label": "Conte rapidinho o que você precisa",
|
|
347
|
-
"field_type": "long_text", "required": true } },
|
|
348
|
-
{ "id": "ia_triagem", "type": "ai", "position": { "x": 0, "y": 400 },
|
|
349
|
-
"data": { "label": "Triagem", "mode": "classify",
|
|
350
|
-
"prompt": "Classifique o pedido do cliente em uma das categorias.",
|
|
351
|
-
"categories": [ { "id": "c1", "label": "Orçamento" },
|
|
352
|
-
{ "id": "c2", "label": "Suporte" } ],
|
|
353
|
-
"include_answers": true, "model": "" } }
|
|
354
|
-
],
|
|
355
|
-
"edges": [
|
|
356
|
-
{ "id": "e1", "source": "q_motivo", "target": "ia_triagem", "sourceHandle": "success" },
|
|
357
|
-
{ "id": "e2", "source": "ia_triagem", "target": "fim_orcamento", "sourceHandle": "cat_c1" },
|
|
358
|
-
{ "id": "e3", "source": "ia_triagem", "target": "fim_suporte", "sourceHandle": "cat_c2" },
|
|
359
|
-
{ "id": "e4", "source": "ia_triagem", "target": "fim_geral", "sourceHandle": "other" }
|
|
360
|
-
]
|
|
361
|
-
}
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
Enquanto a consulta ou a IA roda, o lead vê um bloco de espera — o formulário não trava nem perde a
|
|
365
|
-
sessão.
|
|
457
|
+
`mode: "classify"` + `categories` + arestas `cat_<id>` e `other`. Inalterado.
|
|
366
458
|
|
|
367
459
|
### 4. Disparar um fluxo quando o formulário for concluído
|
|
368
460
|
|
|
369
|
-
O gatilho mora no **fluxo
|
|
370
|
-
|
|
461
|
+
O gatilho mora no **fluxo**: `{ "key": "lead_form_completed", "lead_form_id": 12 }` no bloco de
|
|
462
|
+
início do flow. Três gatilhos: `lead_form_completed`, `lead_form_milestone` (aceita
|
|
463
|
+
`milestone_node_id`), `lead_form_abandoned`. O `lead_form_id` pode ir no topo do item ou em
|
|
464
|
+
`config`. **O gatilho só dispara depois que o contato nasce** — antes fica guardado. O fluxo
|
|
465
|
+
recebe `form_name`, `form_kind`, `form_milestone` e uma variável `form_<pergunta>` por resposta.
|
|
466
|
+
|
|
467
|
+
### 5. Agendamento dentro do formulário
|
|
371
468
|
|
|
372
469
|
```json
|
|
373
|
-
{ "
|
|
470
|
+
{ "id": "agenda", "type": "booking", "position": { "x": 0, "y": 500 },
|
|
471
|
+
"data": { "label": "Escolha um horário", "event_type_id": "34", "required": false } }
|
|
374
472
|
```
|
|
375
473
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|---|---|
|
|
380
|
-
| `lead_form_completed` | A pessoa chegou ao bloco de fim |
|
|
381
|
-
| `lead_form_milestone` | A pessoa passou por um bloco de ação do tipo `milestone` |
|
|
382
|
-
| `lead_form_abandoned` | A pessoa parou de responder e passou o tempo de `abandon_minutes` |
|
|
383
|
-
|
|
384
|
-
No `lead_form_milestone` dá pra acrescentar `milestone_node_id` para mirar um marco específico; sem
|
|
385
|
-
ele, qualquer marco daquele formulário dispara. O `lead_form_id` (e o `milestone_node_id`) podem ir
|
|
386
|
-
no topo do item ou dentro de `config` — o motor lê os dois.
|
|
387
|
-
|
|
388
|
-
**O gatilho só dispara se a resposta já tiver contato**, ou seja, se nome e telefone foram
|
|
389
|
-
respondidos. E dispara uma vez só por resposta e por tipo. O fluxo recebe as respostas como
|
|
390
|
-
variáveis: `form_name`, `form_kind`, `form_milestone` e uma variável `form_<pergunta>` por resposta
|
|
391
|
-
dada.
|
|
474
|
+
Ligue `success` (obrigatória) pro caminho de quem agendou e `no_booking` pro de quem seguiu sem
|
|
475
|
+
agendar (com `required: true` essa saída deixa de existir). O tipo de evento vem de
|
|
476
|
+
`lionchat_booking_event_types_list`.
|
|
392
477
|
|
|
393
478
|
## Armadilhas
|
|
394
479
|
|
|
395
|
-
- **O `slug` é
|
|
396
|
-
|
|
480
|
+
- **O `slug` é EDITÁVEL** (mudou em 16/08). Trocar o slug derruba os links já divulgados —
|
|
481
|
+
inclusive os já enviados em mensagens. Vazio mantém o atual; duplicado dá 422.
|
|
482
|
+
- **`inbox_id` só troca por caixa do MESMO tipo de canal** (`channel type cannot change`), e a
|
|
483
|
+
troca só vale ao republicar.
|
|
397
484
|
- **`published_data` nunca sai pela API.** Para saber o que está no ar, olhe `published_at` e
|
|
398
|
-
|
|
399
|
-
- **`form_data` no update SUBSTITUI o desenho inteiro.** Não há merge. Leia com
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
- **
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
-
|
|
406
|
-
-
|
|
407
|
-
|
|
408
|
-
-
|
|
409
|
-
|
|
410
|
-
|
|
485
|
+
`published_inbox_id`.
|
|
486
|
+
- **`form_data` no update SUBSTITUI o desenho inteiro.** Não há merge. Leia com `show`, altere e
|
|
487
|
+
mande o desenho completo de volta.
|
|
488
|
+
- **A cópia nasce desligada e não publicada** — e copia o `inbox_id`. Imagens viram blobs novos.
|
|
489
|
+
- **Publicar não ativa.** `publish` (foto) e `update` com `active: true` são dois passos.
|
|
490
|
+
- **Flag desligada = página 404.** Tools funcionando + link 404 = feature `lead_forms` desligada.
|
|
491
|
+
- **`media_urls` só existe no `show`.**
|
|
492
|
+
- **`answers` tem chaves sintéticas** `"<node_id>::<n>"` (bloco de Ações) — não são ids de bloco.
|
|
493
|
+
- **`embed_allowed` é veredito do servidor** (`check_embed`) — escrever `true` na mão faz a página
|
|
494
|
+
tentar um iframe que pode estar bloqueado.
|
|
495
|
+
- **Nunca escreva segredo em cabeçalho/corpo literal** de `api_request` — use
|
|
496
|
+
`{{conta.<variável>}}`, que resolve só no servidor.
|
|
497
|
+
- **Não há tool de rewind**: o "Voltar e corrigir" é da página pública (o lead corrige a própria
|
|
498
|
+
resposta; telefone corrigido reencaminha a mensagem pro número certo). Pela API de dashboard,
|
|
499
|
+
respostas são somente leitura.
|