@lionchat/mcp-server 1.10.2 → 1.11.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.
@@ -374,6 +374,9 @@ Antes de escolher a ferramenta, cheque se a intenção bate com a coluna da dire
374
374
  | **Definir supervisor** de caixa | `lionchat_inbox_members_create` / `_update` com `supervisor_ids[]` | Supervisor vê TUDO da caixa mas fica fora do rodízio (ver abaixo) |
375
375
  | **Definir supervisor** de time | `lionchat_team_members_create` / `_update` com `supervisor_ids[]` | Contrato diferente do da caixa: `user_ids[]` é a lista COMPLETA e `supervisor_ids[]` um subconjunto dela. Omitir `supervisor_ids` não mexe em papel nenhum (seção 2) |
376
376
  | **Mudar a fila do time** (teto por atendente, janela, online/offline) | `lionchat_teams_update` (`assignment_mode`, `fair_distribution_limit`, `fair_distribution_window`) | Time tem PREFERÊNCIA sobre a caixa na distribuição. Leia a config atual antes de mudar (ver `lionchat://docs/conversation-flows`) |
377
+ | **Criar/atualizar time** | `lionchat_teams_create` / `_update` | **Preencha `description`**: desde 03/08 é ela que o AI Agente lê pra decidir o roteamento por time. Descreva QUANDO mandar pra lá, não o que o time é |
378
+ | **Pausar a automação no meio** (esperar atribuição/card antes da próxima ação) | ação `wait` em `lionchat_automation_rules_create` / `_update` | Segundos, teto 300. Acima disso é caso de FlowBuilder. Ver `lionchat://docs/conversation-flows` |
379
+ | **Buscar contato/conversa/card** | `lionchat_search_*`, `lionchat_contacts_search` | **Termo com letra exige 3+ caracteres** (dígito é isento). Menos que isso volta VAZIO sem erro — não conclua que o registro não existe. Ver `lionchat://docs/api-conventions` |
377
380
  | **Migrar conversas** de uma caixa WhatsApp pra outra (ex.: QR Code → Oficial) | `lionchat_inboxes_inbox_migration_list` (prévia) → `_execute` → acompanhar status | Admin-only e IRREVERSÍVEL: sempre mostre os números da prévia e confirme (seção 7a). Grupos não vão pro canal oficial — ficam na caixa de origem |
378
381
  | Qualquer coisa de Ligação WP (Wavoip) | **NÃO HÁ TOOL** — removidas em 2026-07-16 por decisão do dono | Ligação WP é contratada e configurada **manualmente pelo cliente no painel**. O MCP não conecta, não desconecta, não entrega token de discagem nem muda config. Oriente o usuário a fazer pelo painel (Configurações da caixa → Ligação WP). |
379
382
  | Restringir agendas que a IA oferece | `lionchat_captain_assistants_update` (`config.booking_event_type_ids: [Int]`) | binding de agenda por cenário NÃO é settable via API |
@@ -198,7 +198,7 @@ Ou para listas grandes (cursors):
198
198
  - `includes` / `excludes` (relacionamentos)
199
199
  - `before` / `after` (datas)
200
200
 
201
- ### Atributos personalizados — operadores por TIPO (2026-07-21)
201
+ ### Atributos personalizados — operadores por TIPO (2026-08-07)
202
202
 
203
203
  Vale pra automações, filtro avançado de conversas/contatos e busca "+ Filtro" (motor compartilhado):
204
204
 
@@ -207,10 +207,17 @@ Vale pra automações, filtro avançado de conversas/contatos e busca "+ Filtro"
207
207
  | texto / link | equal_to, not_equal_to, contains, does_not_contain, is_present, is_not_present |
208
208
  | lista | equal_to, not_equal_to, is_present, is_not_present |
209
209
  | número / data | equal_to, not_equal_to, is_present, is_not_present, is_greater_than, is_less_than |
210
+ | moeda / porcentagem / hora / data+hora | equal_to, not_equal_to, is_present, is_not_present, is_greater_than, is_less_than |
210
211
  | sim/não (checkbox) | equal_to, not_equal_to, is_present, is_not_present |
211
212
 
212
213
  - Em atributo de LISTA, `equal_to` com vários `values[]` casa QUALQUER um deles (multi-seleção).
213
214
  - NUNCA use `contains` em número/data/checkbox — o backend rejeita (cast inválido).
215
+ - Moeda e porcentagem são castados como número, hora como hora e data+hora como data —
216
+ por isso desde 30/07 (filtros) e 07/08 (automações) esses quatro tipos também aceitam
217
+ `is_greater_than` / `is_less_than`. Base técnica: `FilterService::ATTRIBUTE_TYPES` casta
218
+ currency/percent como `numeric`, time como `time` e datetime como `date`; `coerce_lt_gt_value`
219
+ aceita numeric/date/time. Tipo NOVO de atributo sem entrada em `ATTRIBUTE_TYPES` quebra o
220
+ filtro com erro de sintaxe SQL (não "não acha nada") — a entrada é obrigatória no mesmo commit.
214
221
 
215
222
  > **Contatos — busca parcial por nome/ID externo:** no `POST /contacts/filter`, os campos `name` e
216
223
  > `identifier` aceitam `contains` / `does_not_contain` (match parcial, case-insensitive). Use isso
@@ -379,6 +386,29 @@ falha silenciosa, sem erro na tela. Conversa se altera por `fields` (+ `labels:
379
386
  **Multi-termo com E:** `q` aceita String (espaços = AND implícito) ou Array (`q[]=joao&q[]=silva`),
380
387
  cap de 5 termos — cada termo precisa bater (cada um mantém seu OR entre campos).
381
388
 
389
+ **PISO DE 3 LETRAS (novo 2026-08-04) — leia antes de concluir "não existe".**
390
+
391
+ Termo com LETRA precisa de **3 caracteres ou mais**. Termo **só de dígitos** é isento (é assim
392
+ que se busca pelo número da conversa ou do card, e conta nova tem todos com 1-2 dígitos).
393
+
394
+ | Busca | O que acontece |
395
+ |---|---|
396
+ | `q=Bo` | Devolve **vazio**. Não é erro, não é 422 — vem `payload: []` |
397
+ | `q=Bor` | Busca normal |
398
+ | `q=42` | Busca normal (só dígitos, isento) |
399
+ | `q=jo silva` | Vale: basta **um** termo utilizável na frase |
400
+ | `q=zq` + `contact_filters=[...]` | Vazio. O termo curto **invalida a busca inteira**, mesmo com filtro junto |
401
+
402
+ Vale nas CINCO buscas (contato, conversa, mensagem, card e artigo).
403
+
404
+ **Consequência prática:** resultado vazio com termo de 1-2 letras **não significa que o registro
405
+ não existe** — significa que a busca não rodou. Nunca responda "não encontrei esse contato" nessa
406
+ situação. Ou complete o termo, ou pergunte o nome inteiro, ou use `/contacts/filter` com
407
+ `filter_operator: "contains"`, que não tem esse piso.
408
+
409
+ Sem `q` e só com filtros estruturados continua funcionando normalmente (contrato antigo, busca só
410
+ por condições) — o piso só descarta termo de texto curto, não o pedido inteiro.
411
+
382
412
  **Busca cadastral:** CPF/CNPJ/RG/endereço/profissão entram na busca de contatos e conversas, com
383
413
  match por dígitos que ignora pontuação (`31.104.475/0001-51` == `31104475000151`; mín 3 dígitos).
384
414
  Telefone idem. Cards do Kanban são achados pelo contato/conversa vinculados.
@@ -131,6 +131,32 @@ Pra reativar: `paused=false`. Toggle é auditado (`audited only: [:paused]`).
131
131
 
132
132
  O `config` DEVE ser enviado como **objeto** (ex.: `config: { disabled_tools: [...] }`), NUNCA como string/texto. Se vier em formato errado, o backend retorna **422** (não salva ignorando em silêncio). O update do `config` é **merge parcial** — mandar só o subset que muda (ex.: só `disabled_tools`) preserva os demais campos.
133
133
 
134
+ ## AI Agente roteando por TIME (novo 2026-08-03)
135
+
136
+ O AI Agente ganhou 3 ferramentas de time: **Listar Times**, **Mover para Time** e **Tirar do Time**.
137
+ Antes ele só conseguia atribuir uma PESSOA; trocar de time exigia automação ou bloco de fluxo, com
138
+ o time escolhido de antemão por quem montou a regra.
139
+
140
+ **A descrição do time virou peça funcional.** É lendo `teams.description` que a IA decide para onde
141
+ mandar a conversa. Time sem descrição obriga a chumbar o nome no cenário — que é justamente o que
142
+ essas ferramentas vieram resolver.
143
+
144
+ > **Ao criar ou atualizar time (`lionchat_teams_create` / `_update`), preencha `description`
145
+ > dizendo QUANDO mandar para lá.** Ex.: `"Financeiro — cobrança, boleto, nota fiscal, reembolso,
146
+ > segunda via"`. Não é enfeite de tela: é o que a IA lê.
147
+
148
+ Travas que valem conhecer (não dá pra desligar):
149
+
150
+ | Trava | Efeito |
151
+ |---|---|
152
+ | Conta sem nenhum time | As 3 ferramentas nem aparecem para o agente |
153
+ | Alguém está atendendo (`assignee_id` preenchido) | Mover e Tirar **não agem**. Evita a IA arrancar a conversa da mão do atendente |
154
+ | Nome do time no cenário | Resolve por nome exato; não achando, tenta CONTÉM. Se der mais de um candidato, devolve a lista e **não adivinha** |
155
+ | Mover de time | **Não** faz handoff sozinho. Se a intenção é o humano assumir, o cenário precisa mandar chamar o handoff no mesmo turno |
156
+
157
+ `list_teams` é ferramenta de descoberta: fica escondida da tela de ferramentas e só carrega se
158
+ "Mover para Time" estiver ativa. Não tente ligá-la ou desligá-la sozinha em `config.disabled_tools`.
159
+
134
160
  ## Limite do prompt e cache OpenAI (2026-05-22)
135
161
 
136
162
  - `config.instructions` (system prompt do agente) aceita até **20.000 caracteres** (antes 10k). Acima de 15k, o frontend mostra aviso "lost in the middle" — prefira colocar instruções críticas no início ou final.
@@ -306,6 +332,7 @@ Como a coleta de dados/cenários funciona hoje (importante pra diagnosticar "IA
306
332
  - Quando UM cenário fixa a tool, a execução vira determinística (a IA não escolhe): mídia e kanban são aplicados pós-turno pelo BindingResolver (ordem texto→ação, sem duplicar, idempotente); o agendamento força/restringe a agenda mas a IA ainda define data/hora.
307
333
  - Via MCP: scenarios_create/update persiste binding de send_media_asset e create_kanban_item/move_kanban_item (re-escopados por conta). O binding de create_booking NÃO é aceito pela API — pra limitar agendas via MCP, use config.booking_event_type_ids no assistente (captain_assistants_update).
308
334
  - Receita pra criar/editar cenário com binding via MCP (scenarios_create / scenarios_update): (1) o corpo vai SEMPRE embrulhado em `scenario`; (2) a instrução PRECISA mencionar a ferramenta como link markdown `[Rótulo](tool://slug)` — menção crua `(tool://slug)` NÃO conta e o binding é descartado no salvamento; (3) envie `tool_bindings` com o shape: `send_media_asset` → `{ "asset_ids": [Int] }`, `create_kanban_item`/`move_kanban_item` → `{ "funnel_id": Int, "stage": "<chave da etapa>" }`. Não precisa mandar `tools` (é auto-extraído da instrução e sobrescrito). `create_booking` NÃO entra em tool_bindings pela API.
335
+ - Pra APONTAR uma página da base de conhecimento num cenário via MCP, escreva `[@Nome](document://ID)` (o ID numérico do documento) DENTRO do texto da `instruction` — igual ao link de tool, o motor deriva daí o campo `document_ids`. NÃO existe param `document_ids` na API/MCP (mandar seria descartado em silêncio). Teto de 3 documentos; a página apontada ganha PRIORIDADE na busca RAG, NÃO exclusividade (o resto da base continua pesquisável).
309
336
 
310
337
  ## Variáveis {{ }} nas instruções do AI Agente (2026-06)
311
338
 
@@ -502,6 +529,20 @@ opcional `variable_mapping` junto.
502
529
  - Só entram no mapping as que devem auto-preencher; as demais `{{N}}` continuam manuais.
503
530
  - O `variable_mapping` é salvo localmente (NÃO vai pra Meta) e usado no momento do envio.
504
531
 
532
+ **Variável sem mapping tem preço (2026-08-04).** Placeholder que ninguém disse como preencher:
533
+
534
+ 1. **Tira o modelo do alcance do AI Agente.** Basta UMA variável sem mapping para o modelo inteiro
535
+ deixar de ser oferecido no follow-up automático fora da janela de 24h.
536
+ 2. **Faz o envio manual falhar** com o erro `#132000` da Meta (número de parâmetros não bate).
537
+
538
+ Vale para os dois formatos de marcador: posicional (`{{1}}`) e nomeado (`{{nome_cliente}}`).
539
+
540
+ > Ao registrar modelo por API, **mapeie todas as variáveis do corpo** — ou preencha na hora do envio
541
+ > via `processed_params`. Modelo com variável órfã é modelo que a IA não usa e que o disparo derruba.
542
+
543
+ Confira o que ficou de fora lendo o modelo com `lionchat_inboxes_whatsapp_templates_list` e
544
+ comparando os `{{...}}` do texto do BODY com as chaves do `variable_mapping`.
545
+
505
546
  Exemplo — `{{1}}` vira o primeiro nome do contato:
506
547
 
507
548
  ```json
@@ -533,6 +574,29 @@ Fontes/campos disponíveis (`source` / `field`):
533
574
  Lembrete: todo template recém-criado pelo MCP só mostra o texto na tela depois de **sincronizar**
534
575
  (o MCP cria na Meta, mas o "puxar de volta" o conteúdo é o que a tela faz com o botão Sincronizar).
535
576
 
577
+ ### Apontar variáveis SEM reenviar pra Meta (2026-08-07)
578
+
579
+ Antes, mudar o `variable_mapping` de um template JÁ APROVADO exigia o endpoint de update
580
+ (`POST .../whatsapp_templates/{id}`) — que reenvia o modelo pra Meta e o status volta pra
581
+ **Pendente** à toa. Agora existe um endpoint SÓ pro apontamento local:
582
+
583
+ ```
584
+ PATCH /api/v1/accounts/{account_id}/inboxes/{inbox_id}/whatsapp_templates/{template_id}/variable_mapping
585
+ Body: { "variable_mapping": { "1": { "source": "contact", "field": "name", "label": "Nome do contato" } } }
586
+ ```
587
+
588
+ - **ZERO chamada à Meta**: o template continua Aprovado, sem nova revisão. Use SEMPRE este quando a
589
+ mudança for só o apontamento; o update de verdade fica pra quando o TEXTO/estrutura mudar.
590
+ - `{template_id}` é o **id numérico da Meta** (o campo `id` do template na listagem), não o nome.
591
+ - O conjunto enviado **substitui** todos os apontamentos de corpo: mande TODAS as variáveis que
592
+ devem ficar apontadas, não só a que mudou (a ausente é removida). Vazio `{}` limpa tudo.
593
+ - Chaves `_` (`_button_url`, `_header_media`, `_header_location`) são preservadas sozinhas e
594
+ **recusadas** como entrada — pra mexer nelas, use o update normal.
595
+ - Recusas (422): categoria AUTHENTICATION, chave que não existe no corpo aprovado, valor sem
596
+ `{source, field}`, `source` fora de contact/conversation/account/custom, caixa que não seja
597
+ WhatsApp Cloud oficial.
598
+ - Papel: administrador.
599
+
536
600
  ### Botão de link do template com variável — link rastreável por cliente (2026-07-25)
537
601
 
538
602
  O botão de URL do template pode terminar com uma variável, pra montar um link diferente por contato
@@ -255,6 +255,33 @@ Conversa resolvida que recebe nova mensagem do cliente:
255
255
  - Default: cria NOVA conversa (mantém histórico)
256
256
  - Se locked: reabre a mesma conversa (`status` volta a 0)
257
257
 
258
+ ### 10. Criar conversa por API — REGRA NOVA (2026-08-04)
259
+
260
+ **Nunca nasce conversa nova se o contato já tem uma ABERTA naquela caixa.** Vale para todo caminho
261
+ iniciado por nós: `lionchat_conversations_create`, campanha, flow, booking, integração.
262
+
263
+ | Situação do contato naquela caixa | O que `conversations_create` devolve |
264
+ |---|---|
265
+ | Tem conversa **aberta / pendente / adiada / silenciada** | A **conversa existente**, com o mesmo `id`. Não cria |
266
+ | Só tem conversa **resolvida** | Cria conversa NOVA (invariante do produto: "fechou = abre nova") |
267
+ | Caixa com `lock_to_single_conversation` LIGADO | **Sempre** a existente, mesmo resolvida — reabre |
268
+ | Caixa de **e-mail** | Sempre cria nova (o assunto da thread vem da conversa; reusar mandaria o e-mail com o assunto velho) |
269
+
270
+ **Como saber se reusou:** compare o `id` devolvido com o que você esperava. Vem HTTP 200 nos dois
271
+ casos, sem aviso. Se precisa garantir conversa nova, feche a anterior antes
272
+ (`conversations_toggle_status` para `resolved`).
273
+
274
+ **O que é DESCARTADO no reuso** (a conversa já existe; o disparo não reescreve o estado dela):
275
+ `status`, `snoozed_until`, `additional_attributes` e `assignee_id`. Se você precisa trocar o
276
+ responsável, chame `conversations_update` depois — não adianta mandar junto na criação.
277
+
278
+ **O que É aproveitado:** `custom_attributes` entram por merge, **sem sobrescrever chave que já tem
279
+ valor**. A conversa mantém a origem com que nasceu e só ganha o que ainda não tinha.
280
+
281
+ **Reuso não dispara `conversation_created`** — automação, gatilho de flow, webhook de saída,
282
+ notificação e a Agenda não rodam. Se o seu fluxo depende de um desses, ele não vai acontecer
283
+ quando houver reuso.
284
+
258
285
  ## Estado relacional
259
286
 
260
287
  ```
@@ -634,10 +661,41 @@ Todos os formatos abaixo foram conferidos linha a linha em `app/services/action_
634
661
  | `add_note_to_kanban_item` | `[{"funnel_id": 31, "text": "texto da nota"}]` — a chave e **`text`**, nao `note` |
635
662
  | `set_kanban_item_status` | `[{"funnel_id": 31, "status": "won"}]` — `won`, `lost` ou `open`. Status fora disso e ignorado |
636
663
  | `start_kanban_item_timer` / `stop_kanban_item_timer` | `[{"funnel_id": 31}]` |
664
+ | `wait` | `[30]` — segundos. Ver "Acao Aguardar" abaixo |
637
665
 
638
666
  **Acoes de Kanban:** todas precisam que a conversa ja tenha um card no funil informado. Sem card,
639
667
  a acao e pulada (fica so no log do servidor). Use `create_kanban_item` antes, na mesma regra.
640
668
 
669
+ ### Acao `wait` (Aguardar) — novo 2026-08-04
670
+
671
+ Pausa a regra: as acoes **seguintes** so rodam depois do tempo passar. As anteriores ja rodaram.
672
+
673
+ ```json
674
+ { "action_name": "wait", "action_params": [30] }
675
+ ```
676
+
677
+ | Regra | Detalhe |
678
+ |---|---|
679
+ | Unidade | **segundos** |
680
+ | Teto | **300** (5 minutos). Valor maior e limitado a 300 em silencio, sem erro |
681
+ | Formatos aceitos | `[30]`, `30`, `"30"` e `{"seconds": 30}` — todos funcionam |
682
+ | Tempo invalido | `[]`, `[0]`, `[""]` ou ausente = **nao pausa**, a regra segue direto. Nao da erro |
683
+ | Ultima acao da regra | Nao agenda nada (nao ha o que retomar depois) |
684
+ | Pilula "Automacao X disparou" | So aparece na 1a rodada, nao se repete na volta |
685
+
686
+ **Para que serve.** O caso que originou: a regra mandava a mensagem **antes** da politica de
687
+ atribuicao carimbar o responsavel, entao a variavel com o nome do atendente saia vazia. Meio
688
+ segundo de espera resolve. Use quando a acao seguinte depende de algo que outro processo ainda
689
+ esta gravando (atribuicao, card recem-criado, atributo que outra regra vai escrever).
690
+
691
+ **Precisa de mais de 5 minutos?** O lugar certo e o FlowBuilder, que tem espera em horas e sessao
692
+ propria pra sustentar o estado. A automacao nao foi feita pra isso.
693
+
694
+ **A regra nao "dorme":** ao encontrar a espera ela encerra a rodada e agenda a retomada. Varias
695
+ conversas esperam ao mesmo tempo sem travar o atendimento. Na volta, o sistema confere se a regra
696
+ ainda existe e esta ligada, se a conversa ainda existe e se a caixa nao foi excluida — se algo
697
+ disso mudou, ele simplesmente nao retoma.
698
+
641
699
  Lista autoritativa de nomes validos: `AutomationRule#actions_attributes` no backend. Nome de acao
642
700
  fora dessa lista e recusado com 422 ao salvar — isso a API valida. O que ela **nao** valida e o
643
701
  conteudo do `action_params`. `apply_kanban_checklist_template` existe no motor mas **nao** e uma
@@ -286,12 +286,16 @@ Captain::Scenario (cenários do assistente — instruções inline, sem handoff)
286
286
  │ ⚠️ trigger_type NAO é editável via API hoje — todo cenário criado por API fica em
287
287
  │ llm_interpreted (a IA decide aplicar lendo a conversa). Os modos programáticos só
288
288
  │ via banco/console por enquanto.
289
- └── tool_bindings (jsonb — parâmetro FIXADO pelo admin por tool; "IA decide" = ausente/vazio)
290
- send_media_asset → { asset_ids: [Int] } | create_kanban_item / move_kanban_item →
291
- { funnel_id: Int, stage: "<key>" } | create_booking → { event_type_ids: [Int] }
292
- Obs: via API/MCP, scenarios_update SÓ persiste send_media_asset/create_kanban_item/
293
- move_kanban_item. O binding create_booking é só de UI/runtime — NÃO round-trippa pela API.
294
- Use config.booking_event_type_ids no assistente pra restringir agendas via MCP.
289
+ ├── tool_bindings (jsonb — parâmetro FIXADO pelo admin por tool; "IA decide" = ausente/vazio)
290
+ │ send_media_asset → { asset_ids: [Int] } | create_kanban_item / move_kanban_item →
291
+ │ { funnel_id: Int, stage: "<key>" } | create_booking → { event_type_ids: [Int] }
292
+ │ Obs: via API/MCP, scenarios_update SÓ persiste send_media_asset/create_kanban_item/
293
+ │ move_kanban_item. O binding create_booking é só de UI/runtime — NÃO round-trippa pela API.
294
+ │ Use config.booking_event_type_ids no assistente pra restringir agendas via MCP.
295
+ └── document_ids (jsonb, default [] — DERIVADO das menções [@Nome](document://ID) escritas na
296
+ instruction; NUNCA aceito como param na API/MCP (é ignorado se enviado); re-escopado por
297
+ assistente+conta; teto de 3 documentos. Serve pra IA PRIORIZAR a busca RAG naquelas páginas —
298
+ NÃO é exclusividade: o resto da base continua pesquisável)
295
299
 
296
300
  Captain::AssistantResponse (FAQ)
297
301
  ├── assistant_id (FK)
@@ -412,14 +416,25 @@ Atributos de sistema no CONTATO (prefixo `eclinica_`, protegidos, usáveis como
412
416
  | `eclinica_idagenda` | text | ID do último agendamento |
413
417
  | `eclinica_data_consulta` | date | Data da consulta, ISO |
414
418
  | `eclinica_hora_consulta` | **time** | Hora da consulta 24h `"HH:MM"` (novo 2026-07-06) |
419
+ | `eclinica_hora_final` | **time** | Hora final da consulta (novo 2026-07-27) |
415
420
  | `eclinica_status_agendamento` | text | agendado / no_show / atendido / desmarcado |
416
- | `eclinica_compromisso` | text | Tipo da consulta (ex: Consulta, Retorno, Exame) |
421
+ | `eclinica_situacao` | text | Situação crua da agenda (age_situacao do webhook) |
422
+ | `eclinica_compromisso` | text | Tipo da consulta — TEXTO LIVRE da recepção (ex: Consulta, Retorno) |
423
+ | `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
+ | `eclinica_agendatipo_id` | text | Tipo de Agendamento — número cru (não comparável entre filiais) |
425
+ | `eclinica_profissional` | text | NOME do profissional, resolvido por unidade (novo 2026-07-10) |
426
+ | `eclinica_profissional_id` | text | Número do profissional (não comparável entre filiais) |
427
+ | `eclinica_convenio_id` | text | Convênio (número) |
428
+ | `eclinica_ultimo_evento` | text | Tipo do último webhook recebido (ex: falta, agendamento_novo) |
417
429
  | `eclinica_ultimo_pagamento` | text | Valor do último pagamento |
418
430
  | `eclinica_ultimo_pagamento_data` | date | Data do último pagamento/baixa (novo 2026-07-06) |
419
431
  | `eclinica_ultimo_pagamento_descricao` | text | Descrição da baixa (ex: CONSULTA INICIAL) |
420
432
  | `eclinica_laboratorio_data_prevista` | date | Previsão de entrega da medicação (novo 2026-07-23) |
421
433
  | `eclinica_laboratorio_data_moldagem` | date | Data do pedido da medicação |
422
- | `eclinica_laboratorio_data_entrega` | date | Data de entrega da medicação |
434
+ | `eclinica_laboratorio_data_entrega` | date | Data em que a medicação CHEGOU na unidade — é a data que dispara o aviso de retirada do flow |
435
+ | `eclinica_laboratorio_data_retirada` | date | Data em que o paciente RETIROU a medicação (coa_datainstalacao; novo 2026-08-03). NÃO confundir com a entrega. Vazia = peça atual não retirada |
436
+ | `eclinica_laboratorio_trabalho_id` | text | Trabalho/tipo da medicação — número cru (aparelho_id; novo 2026-08-03). Lista própria de cada unidade (COMPRIMIDO/POMADA/SPRAY...); número não comparável entre filiais |
437
+ | `eclinica_laboratorio_trabalho` | text | Trabalho/tipo da medicação — NOME resolvido na lista da própria unidade ("SPRAY", "POMADA"...; novo 2026-08-03). Best-effort: pode ficar vazio se a API da unidade estiver fora do ar. Para filtrar/personalizar flows, use SEMPRE este NOME, nunca o número |
423
438
  | `eclinica_laboratorio_situacao` | text | Situação do pedido no laboratório |
424
439
  | `eclinica_ultima_cobranca_valor` | text | Valor da última cobrança/carnê gerado |
425
440
  | `eclinica_ultima_cobranca_vencimento` | date | Vencimento da última cobrança |
@@ -108,6 +108,12 @@ stage_id/inbox_id/sort_by/sort_direction/page foram REMOVIDOS, eram ignorados pe
108
108
  - `funnel_id` (obrigatório), `stages[]` (chaves internas das etapas), `priorities[]`,
109
109
  `statuses[]` (open/won/lost), `agent_id`, `value_min`/`value_max`
110
110
  - `date_start`/`date_end` (criação do card, YYYY-MM-DD)
111
+ - `stage_date_start`/`stage_date_end` (2026-08-07) — **quando o card ENTROU na etapa em que ele está
112
+ agora** (`stage_entered_at`, caindo em `created_at` para card que nunca se moveu). NÃO confundir com
113
+ `date_start`/`date_end`: um card criado em janeiro e arrastado ontem casa AQUI e não casa lá. É o que
114
+ responde "o que se mexeu essa semana". Pontas independentes, e os dois períodos podem ser combinados.
115
+ Nuance ao explicar pro usuário: card criado ontem que nunca se moveu TAMBÉM entra (ele entrou na
116
+ etapa ao nascer) — o que o filtro separa de verdade é o card VELHO que se moveu.
111
117
  - `scheduled_date_start`/`scheduled_date_end` (agendamento: scheduled_at/deadline_at do card E
112
118
  tarefas da agenda vinculadas)
113
119
  - `task_filter`: has_task | no_task | today | tomorrow | this_week | overdue
@@ -125,9 +131,47 @@ NÃO há filtro por time aqui (por time: use o relatório agregado abaixo, ou fi
125
131
 
126
132
  `GET /kanban_items/reports` — a resposta é um OBJETO de métricas (não lista): valores GANHOS,
127
133
  contagem/valor por etapa, forecast ponderado, top negócios, cards parados, metas do funil.
128
- Params: `funnel_id` (obrigatório), `from`, `to`, `user_ids[]` (agentes), `team_id`.
134
+ Params: `funnel_id` (obrigatório), `from`, `to`, `user_ids[]` (agentes), `team_id`, `date_basis`.
129
135
  É a ferramenta certa pra "relatório de vendas/ganhos do mês por agente ou time".
130
136
 
137
+ **`date_basis` (2026-08-07) — qual data o período considera.** A resposta devolve a régua aplicada em
138
+ `dateBasis`; valor inválido cai no padrão sem erro.
139
+
140
+ | Valor | O que o período recorta | Quando usar |
141
+ |---|---|---|
142
+ | `any` (PADRÃO) | criado no período **OU** fechado no período | É a régua histórica e a mais larga. **Omitir mantém exatamente os números de sempre.** É a certa pra RECEITA: com qualquer outra, venda fechada no período de um lead antigo some (medido 07/08: 17% dos ganhos da frota) |
143
+ | `created` | só quando o card nasceu | "quantos leads entraram" |
144
+ | `moved` | só quando entrou na etapa atual (mesma expressão do `stage_date_*` da seção 3) | "o que se mexeu no período" |
145
+ | `closed` | só ganhos/perdidos no período | **a pergunta de receita** — é a régua certa pra "quanto vendemos em julho" |
146
+
147
+ O relatório de UMA etapa — tool `lionchat_funnels_stage_report` (`GET /funnels/:id/stage_report`, o
148
+ painel que abre dentro do quadro) — tem as MESMAS 4 opções de régua, mesmo parâmetro `date_basis` e as
149
+ MESMAS expressões da seção 3, mas o **padrão dele é `moved`**, não `any`. Não é inconsistência: ele
150
+ responde uma pergunta só ("quantos cards entraram nesta etapa no período"), enquanto o
151
+ `/kanban_items/reports` é uma rede larga que alimenta várias métricas de uma vez. Se o usuário disser
152
+ que as duas telas não batem, a resposta é: escolha a MESMA régua nas duas.
153
+
154
+ Ela devolve números calculados no SERVIDOR (total de cards da etapa, faixa de valores, ganhos/perdidos,
155
+ distribuição por prioridade, ranking de vendedores da etapa) — antes a tela só somava os cards já
156
+ carregados na coluna.
157
+
158
+ **Parâmetros de `lionchat_funnels_stage_report`:**
159
+
160
+ | Param | Obrigatório | O que faz |
161
+ |---|---|---|
162
+ | `id` (path) | sim | id do funil |
163
+ | `stage` | sim | chave interna da etapa |
164
+ | `date_basis` | não | `any` / `created` / `moved` / `closed` — **PADRÃO `moved`** (entrada na etapa). ATENÇÃO: no relatório agregado do funil o padrão é `any`; aqui é `moved` de propósito |
165
+ | `report_from` / `report_to` | não | janela (YYYY-MM-DD) da régua de data do relatório |
166
+ | `priorities[]` | não | array de prioridades |
167
+ | `agent_id` | não | responsável; `-1` = sem responsável |
168
+ | `value_min` / `value_max` | não | faixa de valor do card (número) |
169
+ | `date_start` / `date_end` | não | filtro por data de CRIAÇÃO do card |
170
+ | `scheduled_date_start` / `scheduled_date_end` | não | filtro por agendamento |
171
+
172
+ Ao apresentar número pro usuário, DIGA qual régua usou. Duas réguas diferentes respondem a mesma
173
+ pergunta com números diferentes, e o usuário conclui que o relatório está errado.
174
+
131
175
  ## 5. Busca com filtro estruturado
132
176
 
133
177
  `search_search` (tudo), `conversations_search`, `contacts_search`, `kanban_items_search` aceitam
@@ -24,7 +24,7 @@ Como criar fluxos do LionChat (FlowBuilder) que abrem certinhos no canvas, sem n
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
  | Distribuir aleatório entre branches | `randomizer` | - |
27
- | Atualizar info de grupo WhatsApp (WAHA) | `update_group` | - |
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` | - |
30
30
  | Anotação visual no canvas (não executa) | `note` | - |
@@ -337,6 +337,7 @@ só pra destravar, ela pode sair.
337
337
  | `is_number` / `is_letter` / `is_email` / `is_phone` | validação de formato |
338
338
  | `regex` | padrão regex em `value` |
339
339
  | `equal_any` / `not_equal_any` / `contains_any` / `not_contains_any` | multi-valor (usa `values` array). `contains_any` = contém ALGUMA; `not_contains_any` = não contém NENHUMA (útil pra "seguir só quando a msg não tem nenhuma das palavras-chave de outros flows") |
340
+ | `starts_with_any` / `ends_with_any` (novo 2026-08-07) | multi-valor (usa `values` array), caixa-insensível. `starts_with_any` = o texto COMEÇA com ALGUMA das palavras da lista; `ends_with_any` = o texto TERMINA com alguma |
340
341
  | `business_hours` / `outside_business_hours` | horário comercial (par: dentro/fora). `business_hours` aceita `start_hour`/`end_hour` (0-23), `days` (array 0=Dom..6=Sab, ausente=todos) e **`timezone`** (IANA, ex.: `America/Sao_Paulo` — default se ausente). **REGRA:** a saída `outside_business_hours` HERDA `start_hour`/`end_hour`/`days`/`timezone` da `business_hours` ANTERIOR no array — pode deixá-los ausentes na "fora" (o backend preenche). O horário é avaliado no `timezone` (não em UTC) |
341
342
  | `can_reply` / `can_reply_closed` | janela 24h aberta/fechada |
342
343
  | `conversation_has_agent` / `conversation_no_agent` / `conversation_not_agent` | agente atribuído (HUMANO) |
@@ -433,6 +434,7 @@ o botão "Usar variável" ao lado do campo alterna lista fixa ↔ variável.
433
434
  | `add_conversation_label` | `{ labels: ['slug'] }` | Adiciona labels à CONVERSA (não ao contato) |
434
435
  | `remove_conversation_label` | `{ labels: ['slug'] }` | Remove labels da CONVERSA |
435
436
  | `mute_conversation` | `{}` | Silencia notificações |
437
+ | `mark_unread` | `{}` | Marca a conversa como NÃO LIDA pro time (espelho do `mute_conversation`; típico logo após `assign_agent`/`assign_team` numa transferência) |
436
438
  | `add_private_note` | `{ content: 'texto' }` | Adiciona nota interna |
437
439
  | `create_kanban_item` | `{ funnel_id, funnel_stage, title?, description? }` | Cria card no Kanban — `funnel_id` e `funnel_stage` OBRIGATÓRIOS; `title`/`description` aceitam variáveis `{{ }}` |
438
440
  | `move_kanban_item_to_stage` | `{ funnel_stage }` | Move card (precisa ter card vinculado) |
@@ -696,9 +698,29 @@ tela do cliente (a tela lê só `percent`).
696
698
 
697
699
  **Handles que SAEM:** o `id` de cada branch (`A`, `B`, ...). Em `mode: 'distribute_agents'` é `success`.
698
700
 
699
- ### 2.11 `update_group` (WAHA apenas)
701
+ ### 2.11 `update_group` — Gestão de Grupos (WAHA apenas)
700
702
 
701
- Atualiza nome/descrição/foto de grupo WhatsApp. Use só quando flow roda em inbox WAHA e a conversa for de grupo.
703
+ Bloco "Gestão de Grupos". Faz **UMA operação por bloco** (não mais um formulário-só com nome/foto/descrição
704
+ juntos) — a operação vai em `data.groupOperation`. São **17 operações**, agrupadas por tema:
705
+
706
+ - **Grupo:** `create` (criar), `find_by_id` (buscar por id), `find_by_name` (buscar por nome), `update_subject`
707
+ (mudar nome), `update_description` (mudar descrição), `update_picture` (mudar foto), `settings` (3
708
+ permissões: quem edita infos, quem manda mensagem, quem adiciona membro), `leave` (sair do grupo).
709
+ - **Participantes:** `list_participants` (listar), `add_participants` (adicionar), `remove_participants` (remover).
710
+ - **Admins:** `promote_admin` (promover a admin), `demote_admin` (rebaixar).
711
+ - **Convite:** `get_invite` (pegar link/código de convite), `revoke_invite` (revogar), `send_invite` (mandar
712
+ o convite por mensagem a um telefone).
713
+
714
+ **Disponível em fluxo de QUALQUER canal**, desde que a conta tenha uma caixa **WhatsApp QR Code
715
+ (`Channel::Waha`)** — informe a caixa em `data.groupInboxId`. Esse campo é **OBRIGATÓRIO em fluxo não-grupo**
716
+ (individual/qualquer canal) e **opcional em fluxo de grupo** (ali o padrão é o grupo da própria conversa).
717
+
718
+ Cada operação guarda o resultado na variável `data.groupResponseVar` (padrão `"grupo"`) — leia depois com
719
+ `{{grupo.CAMPO}}` (ex.: `{{grupo.id}}`, `{{grupo.participants_count}}`, `{{grupo.invite_link}}`).
720
+
721
+ Teto de **20 participantes por execução** (trava anti-banimento — adicionar em lote é o que mais rápido
722
+ derruba número no WhatsApp não-oficial). Detalhe das chaves de `data` por operação: ver o pattern
723
+ "Gestão de Grupos WhatsApp por fluxo" no `flowbuilder-patterns`.
702
724
 
703
725
  **Handles que SAEM:** `success`, `error`.
704
726
 
@@ -803,14 +825,15 @@ Na tela o usuário vê isso como TRÊS opções ao criar: "Mensagem" (= `convers
803
825
 
804
826
  | | individual (Mensagem) | group (Grupo) |
805
827
  |---|---|---|
806
- | Node `update_group` (Configurar Grupo: nome, foto, permissões — WAHA) | NÃO existe | **SÓ aqui** |
828
+ | Node `update_group` (Gestão de Grupos — WAHA) | disponível se a conta tem caixa QR Code — exige `groupInboxId` | disponível; `groupInboxId` opcional (padrão = grupo da conversa) |
807
829
  | Gatilhos e condições de **LionTrack** (visita de página / evento do site) | disponíveis | **NÃO** (grupo não tem um contato único navegando) |
808
830
  | Gatilho `date_trigger` (data do contato) | disponível | **NÃO** (grupo não tem um contato único) |
809
831
  | Todos os outros nodes (`send_message`, `wait_response`, `condition`, `action`, `api`, `ai`, `set_variable`, `wait`, `randomizer`, `note`, `end`) | iguais | iguais |
810
832
 
811
833
  Regras práticas ao montar via API:
812
- - Só use o node `update_group` se o flow foi criado com `conversation_mode: "group"`. Colocá-lo num flow
813
- individual é rejeitado pela validação.
834
+ - O node `update_group` (Gestão de Grupos) funciona em fluxo de QUALQUER canal, desde que a conta tenha
835
+ caixa WhatsApp QR Code (`Channel::Waha`): informe a caixa em `data.groupInboxId` (OBRIGATÓRIO em fluxo
836
+ não-grupo; opcional em fluxo de grupo, onde o padrão é o grupo da própria conversa).
814
837
  - Não use gatilho/condição de LionTrack (pagetrack) em flow de grupo.
815
838
  - Na dúvida entre os dois, é `individual` (o default).
816
839
  Não tente contornar criando flows intermediários.
@@ -866,6 +889,14 @@ Presentes conforme o evento (ausente = resolve vazio, sem erro):
866
889
  | `{{trigger.contact.*}}` | `id`, `name`, `phone` |
867
890
  | `{{trigger.ad.*}}` (anúncio/CTWA) | `id`, `name`, `creative_id`, `creative_name`, `adset_id`, `adset_name`, `campaign_id`, `campaign_name`, `source`, `headline`, `description`, `cta`, `url` |
868
891
  | `{{trigger.attribute.*}}` | `name`, `previous_value`, `current_value`, `changed_at` — o valor ANTES e DEPOIS do atributo que disparou (só nos gatilhos de mudança de atributo) |
892
+ | `{{trigger.user_name}}` | nome do usuário/atendente que disparou (gatilho manual ou ativação manual) |
893
+ | `{{trigger.campaign_title}}` | título da campanha (quando disparado por Campanha de Fluxo) |
894
+ | `{{trigger.event_name}}` | nome do evento do site que disparou (LionTrack / visita de página) |
895
+ | `{{trigger.page_url}}` | URL da página (LionTrack / visita de página) |
896
+ | `{{trigger.source_flow_name}}` | nome do flow de origem (quando outro flow iniciou este, ex.: action `start_flow`) |
897
+
898
+ Os cinco últimos são **novos 2026-08-07** e passam a aparecer no autocomplete de TODO bloco (ausente para
899
+ o gatilho que não os fornece = resolve vazio, sem erro).
869
900
 
870
901
  Dois atalhos: `{{trigger}}` devolve o contexto INTEIRO em JSON e `{{trigger.data}}` devolve o mesmo
871
902
  sem os metadados — úteis pra jogar tudo dentro de um node `ai` ou `api`. Bloco (Hash/Array) inteiro
@@ -551,6 +551,78 @@
551
551
 
552
552
  ---
553
553
 
554
+ ## 17. Gestão de Grupos WhatsApp por fluxo (`update_group`)
555
+
556
+ **Caso:** ao marcar um lead como GANHO no Kanban, criar automaticamente um grupo de onboarding com ele e
557
+ confirmar pela conversa. Usa o node `update_group` (bloco "Gestão de Grupos") na operação `create`. Só
558
+ funciona em conta com caixa **WhatsApp QR Code (`Channel::Waha`)** — a caixa vai em `groupInboxId`
559
+ (OBRIGATÓRIO neste fluxo, que não é de grupo).
560
+
561
+ ```json
562
+ {
563
+ "nodes": [
564
+ { "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "triggers": [{ "type": "card_won", "funnel_ids": ["1"] }] } },
565
+ { "id": "n2", "type": "update_group", "position": { "x": 370, "y": 300 }, "data": {
566
+ "label": "Cria grupo de onboarding",
567
+ "groupOperation": "create",
568
+ "groupInboxId": 5,
569
+ "groupName": "Onboarding — {{contact.name}}",
570
+ "groupParticipants": ["5511999999999@c.us"],
571
+ "groupResponseVar": "grupo"
572
+ } },
573
+ { "id": "n3", "type": "send_message", "position": { "x": 690, "y": 300 }, "data": { "label": "Confirma", "messageItems": [
574
+ { "id": "m1", "type": "text", "content": "Prontinho, {{contact.name}}! Criei seu grupo de onboarding (id {{grupo.id}}) com {{grupo.participants_count}} participante(s). Já te chamo por lá." }
575
+ ] } }
576
+ ],
577
+ "edges": [
578
+ { "id": "e1", "source": "n1", "target": "n2", "sourceHandle": "success", "type": "deletable", "animated": true },
579
+ { "id": "e2", "source": "n2", "target": "n3", "sourceHandle": "success", "type": "deletable", "animated": true }
580
+ ]
581
+ }
582
+ ```
583
+
584
+ **Chaves de `data` por operação (`groupOperation`):**
585
+
586
+ Comuns a (quase) todas:
587
+ - `groupOperation` — a operação (uma por bloco).
588
+ - `groupTargetId` — o grupo alvo. Vazio = grupo da conversa (só faz sentido em fluxo de grupo); aceita
589
+ `"1203...@g.us"`, só dígitos ou uma variável `{{var}}`. **Ignorado** em `create` e `find_by_name` (não têm alvo).
590
+ - `groupInboxId` — id da caixa QR Code que fala com o WhatsApp. **OBRIGATÓRIO em fluxo não-grupo**; opcional em
591
+ fluxo de grupo (padrão = grupo da conversa).
592
+ - `groupResponseVar` — nome da variável de saída (padrão `"grupo"`); leia com `{{grupo.CAMPO}}`.
593
+
594
+ | Operação | Chaves específicas | Retorno (`{{grupo.*}}`) |
595
+ |---|---|---|
596
+ | `create` | `groupName` (obrig), `groupDescription`, `groupPicture` (url), `groupInitialAdmins`, `groupParticipants` | `id`, `name`, `participants_count` |
597
+ | `add_participants` | `groupParticipants` (obrig — ex.: `["5511999999999@c.us"]` ou lista) | `added`, `not_added`, `added_count` |
598
+ | `send_invite` | `groupInviteTo` (obrig — telefone de destino), `groupInviteMessage` (opc) | `invite_link` |
599
+ | `settings` | pelo menos uma de `infoAdminOnly` / `messagesAdminOnly` / `membersCanAddNewMember` (booleans) | — |
600
+
601
+ **ATENÇÃO na `settings` — semântica INVERTIDA de `membersCanAddNewMember`:** `true` = **TODOS** podem adicionar
602
+ membros. Já `infoAdminOnly: true` e `messagesAdminOnly: true` = **SÓ admin** edita infos / manda mensagem.
603
+ Confundir os dois LIBERA o grupo achando que está trancando.
604
+
605
+ **Gatilho novo `group_participant_joined` (entrou no grupo → manda boas-vindas):** em um flow de grupo
606
+ (`conversation_mode: "group"`), o start pode ter o item `{ "key": "group_participant_joined", "config": {
607
+ "group_match": "any", "group_name": "", "group_ids": [] } }`. Filtro OPCIONAL de quais grupos disparam:
608
+ `group_match` (`any` padrão, ou os mesmos operadores do "Buscar por nome": `contains`, `equal_to`, `starts_with`,
609
+ `ends_with`, `does_not_contain`, `not_equal_to`) + `group_name`, e/ou `group_ids` (mira exata por id — casa por
610
+ dígitos, aceita `1203...@g.us` ou só o número). Quando alguém entra, o `send_message` seguinte cai **no próprio
611
+ grupo** (o grupo é a conversa do flow).
612
+
613
+ **Lembretes:**
614
+ - **Teto de 20 participantes por execução** (`add_participants`/`create`) — trava ANTI-BANIMENTO, não performance.
615
+ Adicionar em lote é o que mais rápido derruba número no WhatsApp não-oficial.
616
+ - O fluxo só dispara/roda em caixa `Channel::Waha` (QR Code). WhatsApp oficial/Instagram/Facebook/e-mail não
617
+ têm grupo.
618
+ - No fluxo de card ganho acima, o `send_message` vai para a conversa do CONTATO (o lead), não para o grupo
619
+ criado. Para postar DENTRO do grupo, use um flow de grupo (`conversation_mode: "group"`) — normalmente com o
620
+ gatilho `group_participant_joined`.
621
+ - Em `add_participants`, quem tem privacidade de grupo ligada pode não entrar mesmo com resposta de sucesso do
622
+ WhatsApp: confira `{{grupo.not_added}}` / `{{grupo.not_added_count}}`.
623
+
624
+ ---
625
+
554
626
  ## Como usar este catálogo
555
627
 
556
628
  1. **Identifique o pattern mais próximo** do que o cliente pediu
@@ -254,7 +254,10 @@ comportamento intencional.
254
254
  - `config.instructions`: prompt sistema (Liquid template, até 20.000 chars)
255
255
  - `config.activation_label`: etiqueta que ativa o agente na conversa (única por conta)
256
256
  - **Cenário (Captain::Scenario)**: instrução situacional inline do assistente; o raciocínio de
257
- aplicação fica no "caderninho do cenário" da conversa (scenario_checklist, card Raciocínio)
257
+ aplicação fica no "caderninho do cenário" da conversa (scenario_checklist, card Raciocínio).
258
+ Pode apontar páginas da base de conhecimento escrevendo `[@Nome](document://ID)` na instrução
259
+ (campo derivado `document_ids`, teto de 3) para a IA PRIORIZAR a busca RAG naquelas páginas —
260
+ prioridade, não exclusividade
258
261
  - **Regra DITO ≠ SALVO**: a IA só considera coletado o dado que foi PERSISTIDO — salva cada dado
259
262
  na hora e não repergunta o que já salvou
260
263
  - **Cenário com parâmetro fixo (tool_bindings)**: o admin pode fixar o parâmetro de uma ferramenta do cenário — quais mídias enviar, qual funil+etapa criar/mover o card, quais agendas usar no agendamento. Com 1 cenário fixando a tool, a execução é DETERMINÍSTICA (a IA não escolhe): mídia/kanban rodam pós-turno (BindingResolver); o agendamento força/restringe a agenda mas a IA ainda escolhe a data/hora.
@@ -115,7 +115,8 @@ Um card individual dentro de uma etapa.
115
115
  | `funnel_stage` | Chave da etapa (ex: `"novo_lead"`) |
116
116
  | `position` | Ordem dentro da etapa |
117
117
  | `stage_entered_at` | Quando entrou na etapa atual (pra métrica de tempo) |
118
- | `conversation_display_id` | Conversa principal vinculada |
118
+ | `conversation_display_id` | Conversa principal vinculada. **O sistema move este ponteiro sozinho (desde 04/08/2026)**: quando nasce uma conversa nova do mesmo contato naquela caixa, o card passa a apontar pra ela. O card é da PESSOA, não do atendimento. **NÃO trate este valor como estável** — releia antes de usar. Enviar `conversation_display_id` num UPDATE de card é ignorado de propósito (senão o reenvio do formulário regravaria o número antigo por cima). |
119
+ | `origin_conversation_display_id` | Conversa em que o card NASCEU (só preenchida se o vínculo já foi movido). É o que o relatório de origem de lead usa, pra uma venda não mudar de mês nem de anúncio quando o card anda. |
119
120
  | `linked_conversations` | jsonb array `[{display_id: 123}, {display_id: 456}]` — múltiplas conversas |
120
121
  | `item_details` | jsonb (ver abaixo) |
121
122
  | `custom_attributes` | jsonb — campos custom (igual contatos) |
@@ -90,10 +90,12 @@ um número errado com confiança.
90
90
  | Cumprimento de prazo (SLA) | "100%" para conta com ZERO prazo aplicado — escondia "o SLA não está rodando" | Vazio = sem dados |
91
91
  | Por CANAL — conversa de caixa EXCLUÍDA (29/07) | Sumia do relatório: excluir a caixa deixa a conversa sem caixa, e o join descartava essas linhas em silêncio. 512 conversas órfãs de 10 contas em 30 dias; a **conta 60 perdia 76%** do total, a 40 perdia 38%, a 38 perdia 24% | Entram sob a chave **`Channel::None`** ("Caixa excluída"). Some essa chave também — se ignorar, você subconta de novo |
92
92
  | Horário de PICO do mapa de calor (29/07) | Dias e horas saíam em **UTC** mesmo mandando `timezone_offset`: no Brasil, o pico aparecia **3 horas adiantado** (o movimento das 11:00 de Brasília era rotulado como 14:00). O cabeçalho do CSV escrevia "(GMT-03:00) Brasília", o que dava aparência de certo | Balde de dia/hora sai do fuso pedido |
93
+ | "Nº de Conversas" por ETIQUETA (07/08) | Contava conversas **CRIADAS** no período que têm a etiqueta — etiquetar hoje uma conversa antiga não mexia no relatório de hoje (caso real: dono etiquetou 2 e o número ficou em 1) | Conta conversas que **RECEBERAM a etiqueta** no período (`taggings.created_at`), no resumo E no detalhe (cartão + gráfico). Renomear etiqueta preserva as datas. Consequência: mês fechado passa a MUDAR se etiquetas forem adicionadas/removidas retroativamente |
93
94
 
94
95
  **Como reconhecer instalação antiga:** soma dos canais menor que o total do resumo; tempo médio por
95
96
  etiqueta absurdamente baixo perto do tempo por atendente; "pendentes" zerado no ao vivo tendo
96
- conversa pendente na lista; "100%" de prazo com nenhum prazo aplicado.
97
+ conversa pendente na lista; "100%" de prazo com nenhum prazo aplicado; etiqueta aplicada hoje em
98
+ conversa antiga que NÃO aparece no relatório de hoje (régua antiga por criação da conversa).
97
99
 
98
100
  ## Mapeamento dos endpoints `lionchat_reports_*`
99
101