@lionchat/mcp-server 1.30.0 → 1.31.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.
@@ -18,8 +18,9 @@ Comprovado em produção (24/07): a sessão que dizia "não há função de list
18
18
  - **Famílias principais pra pesquisar:** conversations, conversations_messages, contacts,
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
- scheduled_messages, custom_attributes, webhooks, eclinica_integrations, tasks, notifications,
22
- lead_forms (Formulários públicos de captação), signature_documents / signature_envelopes
21
+ scheduled_messages, custom_attributes, custom_attribute_categories (pastas dos atributos), webhooks, eclinica_integrations, tasks, notifications,
22
+ lead_forms (Formulários públicos de captação), whatsapp_forms (Formulário do WhatsApp — o nativo
23
+ da Meta, aberto por botão de modelo), signature_documents / signature_envelopes
23
24
  (assinatura ELETRÔNICA de contratos — modelos e contratos enviados).
24
25
  - **Fora do MCP por decisão do dono (19/08): assinatura DO PLANO (cobrança), plano, fatura, forma de
25
26
  pagamento, cartão, saldo e recarga.** (A assinatura ELETRÔNICA de contratos é outra coisa e TEM
@@ -85,6 +86,12 @@ Account (conta/empresa)
85
86
  ### KanbanItem.item_details.priority (string)
86
87
  - `urgent`, `high`, `medium`, `low`, `none`
87
88
 
89
+ ### Situação do card (desde 11/10/2026)
90
+ - Gravada em `item_details.status`: `open`, `won`, `lost`. Na resposta vem também `effective_status`: `open`,
91
+ **`snoozed`** (adiado — o card sai da coluna até `snoozed_until` e volta sozinho), `won`, `lost`.
92
+ - Adiar: `lionchat_kanban_items_snooze`. Abrir antes da data: `lionchat_kanban_items_unsnooze`. Regras em
93
+ `lionchat://docs/kanban-deep-dive`, seção "Card adiado".
94
+
88
95
  ### AccountUser.role (string)
89
96
  - `agent` (atendente comum)
90
97
  - `administrator` (admin/dono)
@@ -424,6 +431,8 @@ Antes de escolher a ferramenta, cheque se a intenção bate com a coluna da dire
424
431
  |---|---|---|
425
432
  | Listar / ver não lidas | `lionchat_conversations_list` (`conversation_type: 'unread'`) ou `lionchat_conversations_meta` | NÃO use `lionchat_conversations_unread` — é AÇÃO DE ESCRITA (marca como não-lida) |
426
433
  | Ver **uma empresa** (quem trabalha la, anotacoes, conversas) e ligar/tirar contatos | `lionchat_companies_show` + `lionchat_companies_contacts_list` / `_create` / `_destroy`, `lionchat_companies_notes_list`, `lionchat_companies_conversations_list` | Ligar pela ficha: `lionchat_contacts_update` com `company_id` (null tira). Cargo da pessoa = `additional_attributes.job_title`. Escolher a empresa troca o texto `company_name` pelo nome dela |
434
+ | Criar / preencher **campo personalizado da empresa** ("Segmento", "Data de fundacao") | `lionchat_custom_attributes_create` com `attribute_model: company_attribute` + `lionchat_companies_update` com `company.custom_attributes {chave: valor}` | So com o modulo Empresas ligado; tipo segredo recusado. Chave que nao existe como atributo de empresa = 422 e nada do pedido grava: crie a definicao ANTES. MESCLA; `null` limpa. Desde 2026-10-16 vira variavel `{{contact.company.custom_attribute.CHAVE}}`, condicao do fluxo (`attrSource: company`), condicao de automacao e filtro de contatos (`custom_attribute_type: company_attribute`) e a IA le — detalhe no resource data-model |
435
+ | Organizar atributos em **categorias** (pastas que abrem e fecham: "Sobre a casa", "Financeiro") | `lionchat_custom_attribute_categories_create` (`name` + `attribute_model`) e depois `lionchat_custom_attribute_categories_assign` (`attribute_key` + `category_id`; `null` tira) | Categoria e OPCIONAL e de UMA aba so (`conversation_attribute`, `contact_attribute`, `company_attribute` ou `kanban_item_attribute` = Card). Criar categoria NAO aceita atributos junto. Card: `attribute_key` = NOME do campo exatamente como gravado. Excluir categoria nunca apaga atributo (voltam para "Sem categoria"). Ver resource data-model |
427
436
  | Achar a conversa **de uma pessoa/empresa** (nome, telefone, e-mail, CPF/CNPJ, nº da conversa) | `lionchat_search_list` (`/search/conversations`) ou `lionchat_contacts_search` | RÁPIDO. **NÃO use `lionchat_conversations_search`**: apesar do nome, ele é o legado que procura dentro das mensagens e leva **15 a 22 s** (medido em produção 11/08). O sufixo `_list` aqui é histórico — `lionchat_search_list*` **são buscas**, não listagens; a regra "não baixe tudo com `*_list`" vale para `lionchat_<recurso>_list` |
428
437
  | Achar **onde foi dito** algo (palavra dentro da conversa) | `lionchat_search_list_2` (`/search/messages`) | RÁPIDO: índice trigram próprio e teto de tempo em duas fases (2 s / 2 s desde 04/09 — antes a 2ª fase herdava 14 s e o pior caso da seção era 16 s). Estourou as duas, o payload volta com `message_search_interrupted: true` e a lista VAZIA — isso NÃO quer dizer que não existe, quer dizer que não deu tempo: tente de novo. É a substituta certa do `lionchat_conversations_search` |
429
438
  | Não sei se o termo é nome de alguém ou algo dito no papo | `lionchat_search_search` (`/search`) | Cobre os dois numa chamada. Opção mais segura quando a intenção é ambígua |
@@ -64,7 +64,7 @@ que é sua.
64
64
 
65
65
  ---
66
66
 
67
- ## 4. Página pública (cancelar, remarcar, confirmar presença)
67
+ ## 4. Página pública (cancelar, remarcar, confirmar presença, salvar na agenda)
68
68
 
69
69
  O tipo de evento controla o que o cliente pode fazer:
70
70
 
@@ -84,13 +84,53 @@ cliente para fora).
84
84
  ### Variáveis da mensagem de abertura
85
85
 
86
86
  `{{nome}} {{titulo}} {{tipo}} {{agente}} {{data}} {{horario}} {{dia_semana}} {{duracao}}
87
- {{telefone}} {{email}} {{link_cancelar}} {{link_remarcar}} {{link_confirmar}}`
87
+ {{telefone}} {{email}} {{link_cancelar}} {{link_remarcar}} {{link_confirmar}} {{link_salvar_na_agenda}}`
88
88
 
89
89
  As chaves são **em português nos dois idiomas** — elas são o identificador, não a tradução.
90
90
  Variável desconhecida **sai crua** para o cliente ("Olá {{name}}, ...").
91
91
 
92
92
  `{{link_confirmar}}` só é útil com `ask_attendance_confirmation` ligado.
93
93
 
94
+ ### Os QUATRO links da página (16/10)
95
+
96
+ É UMA página só (`/booking/<conta>/manage/<id>?t=<chave>&action=...`). Cada link abre a mesma página numa
97
+ parte diferente. São quatro links, cada um com um nome na mensagem do Booking e outro no fluxo:
98
+
99
+ | O link abre na parte de... | Mensagem do Booking (confirmação, lembretes, texto de abertura) | Fluxo (gatilhos `booking_*`) |
100
+ |---|---|---|
101
+ | Confirmar presença | `{{link_confirmar}}` | `{{booking.confirm_url}}` |
102
+ | Remarcar | `{{link_remarcar}}` | `{{booking.reschedule_url}}` |
103
+ | Cancelar | `{{link_cancelar}}` | `{{booking.cancel_url}}` |
104
+ | **Salvar na agenda** (novo, 16/10) | **`{{link_salvar_na_agenda}}`** | **`{{booking.calendar_url}}`** |
105
+
106
+ **Salvar na agenda** = o cliente põe o compromisso na agenda DELE, sem informar e-mail. Dentro da página há dois
107
+ botões: **Google Agenda** (abre o evento preenchido) e **Agenda do iPhone / Outlook** (arquivo `.ics` com a
108
+ mesma chave do link). Os botões NÃO são variáveis: manda-se só o link e o cliente escolhe lá.
109
+
110
+ Regras para a IA ao montar mensagem ou fluxo:
111
+ - Para pedir "salve na sua agenda", use SEMPRE o quarto link. Mandar `{{link_cancelar}}` com esse texto abre a
112
+ parte de CANCELAR a consulta.
113
+ - As outras três partes também mostram "Salvar na minha agenda" entre as opções (um toque a mais).
114
+ - A parte de salvar aparece mesmo quando o tipo não deixa remarcar nem cancelar. Ela NÃO aparece (a página avisa)
115
+ quando o agendamento ou o compromisso foi cancelado, quando foi concluído e quando a hora já passou.
116
+ - **O compromisso salvo é uma CÓPIA**: nada o atualiza depois. Se a hora mudar, avise o cliente com o gatilho
117
+ "Agendamento remarcado" (`booking_rescheduled`) + `{{booking.calendar_url}}` (já com a hora nova) e peça que
118
+ apague o compromisso antigo.
119
+ - **A hora mostrada ao cliente** (mensagem `{{data}}`/`{{horario}}`/`{{duracao}}`, página e agenda salva) é a do
120
+ COMPROMISSO da Agenda (`account_tasks.scheduled_at`), que é a que o profissional vê.
121
+ - Título salvo: "<tipo> com <profissional> — <empresa>". Sem dado do cliente e sem link com chave dentro.
122
+ - Remarcar ou cancelar pelo link do cliente (ou pela IA) troca a chave: links antigos deixam de abrir. Mudança
123
+ pela tela da Agenda ou pelo Google não troca a chave, e o link antigo passa a salvar a hora nova.
124
+ - Para mandar o link fora das mensagens automáticas: `tasks_list`/`tasks_show` devolvem `booking_calendar_url`
125
+ (e `booking_reschedule_url`) no compromisso que veio do Booking; nulo quando não dá para salvar. O painel tem o
126
+ botão "Link para salvar na agenda" no detalhe do compromisso.
127
+
128
+ ### Remarcar pela IA (16/10)
129
+
130
+ A ferramenta de remarcar da IA grava o agendamento E o compromisso da Agenda juntos (antes de 16/10 a Agenda ficava
131
+ na hora antiga e o gatilho "Agendamento remarcado" não disparava). Ela não remarca consulta concluída nem com
132
+ "Compareceu" ("Faltou" pode). Pelo Copiloto, marcar, remarcar e cancelar ficam no nome do atendente.
133
+
94
134
  ### Presença é CARIMBO, não status
95
135
 
96
136
  `attendance` (`attended` / `no_show`) é um campo separado de `status`. Marcar falta **não devolve o
@@ -251,3 +291,5 @@ e se está atrasada.
251
291
  | Compromisso da filial no Google da matriz | não acontece mais; se aparecer, a conexão daquela agenda está com `agenda_id` errado |
252
292
  | `agendas_create` recusado | recurso `multiple_agendas` desligado ou teto de Extras Manuais atingido |
253
293
  | Histórico sumiu ao filtrar a principal | faltou incluir os compromissos com `agenda_id` **nulo** |
294
+ | Cliente pediu para salvar na agenda e caiu na tela de cancelar | a mensagem usou `{{link_cancelar}}`; o certo é `{{link_salvar_na_agenda}}` (fluxo: `{{booking.calendar_url}}`) |
295
+ | Compromisso salvo pelo cliente ficou na hora antiga | é cópia; avise pelo gatilho "Agendamento remarcado" com `{{booking.calendar_url}}` |
@@ -29,7 +29,22 @@ Participante: `pending` → `viewed` → `verified` (confirmou o código) → `s
29
29
  `created` · `delivered` (link entregue a uma pessoa) · `sent` · `opened` (cada abertura) · `code_requested` ·
30
30
  `code_verified` · `signed` (com `ip`, `device`, `location`; `panel: true` = assinou no painel) · `all_signed` ·
31
31
  `sealed` (documento final gerado, `sealed_sha256`) · `refused` · `cancelled` · `expired` · `delivery_failed`
32
- (motivo em `delivery.failed[]`) · `reminded` · `resent`. Tudo append-only — é o relatório de evidências.
32
+ (motivo em `delivery.failed[]`) · `reminded` · `resent` · `copy_sent` / `copy_failed` (a via assinada de cada pessoa,
33
+ estado em `delivery.copies[]`). Tudo append-only — é o relatório de evidências. `delivery.failed[]` também mostra a
34
+ recusa que a Meta manda depois (caixa oficial fora da janela de 24 h, motivo `window_expired`) e some sozinha se a
35
+ mensagem acabar entregue.
36
+
37
+ Falhas de e-mail (16/10): o convite e a via por e-mail tentam de novo sozinhos (1, 5 e 15 min). Só se nada der certo
38
+ aparece `envio_falhou` (o servidor de e-mail não respondeu); `email_recusado` (o servidor de e-mail recusou o envio)
39
+ aparece na hora. Falha de convite antigo some se a pessoa abriu o link, pediu o código ou assinou depois. Contrato em
40
+ `signed_pending_seal` = o documento final está sendo gerado, com novas tentativas automáticas por até ~42 min; só um
41
+ `seal_failed` depois disso quer dizer que não deu (oriente falar com o suporte).
42
+
43
+ ## A via assinada (15/10)
44
+ Quando o documento final fica pronto, cada pessoa que assinou pelo link (testemunhas incluídas) recebe a sua via em PDF
45
+ pelo mesmo canal do link: anexada numa mensagem na conversa dela (WhatsApp) ou no e-mail (acima de 10 MB vai sem anexo,
46
+ pedindo que peça a via a quem enviou). Quem assinou no painel não recebe. Não há ferramenta para mandar de novo: oriente
47
+ baixar na tela do documento. O código de 6 dígitos de quem assina por e-mail chega no próprio e-mail.
33
48
 
34
49
  ## Como mandar um contrato (receita)
35
50
  1. `lionchat_signature_documents_list` → escolha o modelo (ou crie um `text` com `signature_documents_create`;
@@ -59,7 +74,9 @@ Participante: `pending` → `viewed` → `verified` (confirmou o código) → `s
59
74
  Passou disso o envio/prévia volta `422 fila_cheia` — é só repetir em instantes (o modelo está bom).
60
75
  - **Limite mensal** (`signature_envelopes_limits`): cada contrato criado consome 1 vaga; cancelar não devolve.
61
76
  - **PDFs** (original, assinado, evidências) e o **QR de conferência** só existem pela tela/download autenticado —
62
- não há ferramenta que devolva os bytes. O `sealed_sha256` é o hash do documento final.
77
+ não há ferramenta que devolva os bytes. O `sealed_sha256` é o hash do documento final. Exceção desde 15/10: a via
78
+ assinada vira anexo da mensagem na conversa de quem assinou por WhatsApp, então aparece nas ferramentas de mensagens
79
+ da conversa (`conversations_messages_*`).
63
80
  - **Só pela tela, de propósito** (existem na API, mas NÃO como ferramenta): importar um Word (`import_docx`),
64
81
  pré-visualizar modelo ou rascunho (`preview`, `preview_draft`) e baixar PDF (`download`). O conector não envia
65
82
  arquivo nem recebe PDF — não prometa "vou gerar a prévia" ou "vou baixar o assinado"; oriente a tela.
@@ -418,6 +418,9 @@ Nas condições de automação, desambigue com `custom_attribute_type`:
418
418
 
419
419
  Sem o tipo, o filtro pode bater no escopo errado. A UI mostra sufixo "(Conversa)/(Contato)".
420
420
 
421
+ Campo personalizado da EMPRESA do contato (desde 2026-10-16): `"custom_attribute_type": "company_attribute"`
422
+ (a chave pode ser igual a de contato; o tipo decide). Na tela aparece no grupo "Atributos da empresa".
423
+
421
424
  ## Meta Lead e CAPI (2026-06)
422
425
 
423
426
  - Conexão Meta Lead agora é **por página selecionada** (a BM virou só rótulo — antes puxava todas
@@ -592,7 +595,8 @@ e o `{{meet_link}}` da mensagem de confirmação do Booking passaram a vir preen
592
595
  vazios) — isso vale inclusive para cliente que não usa IA.
593
596
 
594
597
  **3. A IA não confirma reunião que não existe.** No último instante antes da resposta virar
595
- mensagem: se ela afirma agendamento concluído, a agenda está liberada e não há compromisso real na
598
+ mensagem: se ela afirma agendamento concluído, a agenda está liberada (a conta tem tipo de agendamento E o
599
+ agente tem ao menos uma das 4 ferramentas de agenda ligada — desde 14/10) e não há compromisso real na
596
600
  conversa, o sistema roda UMA tentativa guiada que agenda de verdade com a data/hora prometidas.
597
601
  Agendou, sai a confirmação verdadeira; não agendou, a frase é trocada por uma honesta — nunca
598
602
  silêncio. Diagnóstico: se o cliente disser "a IA confirmou e não tem nada na agenda", esse é o
@@ -729,6 +733,8 @@ Fontes/campos disponíveis (`source` / `field`):
729
733
  | Equipe | conversation | team.name |
730
734
  | Nome da conta | account | name |
731
735
  | Campo personalizado | contact | custom_attributes.CHAVE |
736
+ | Campo personalizado da EMPRESA do contato (2026-10-16) | company | custom_attribute.CHAVE |
737
+ | Razão social / CNPJ / telefone / e-mail / cidade / estado da empresa | variable | contact.company.legal_name (ou .cnpj, .phone, .email, .address.city, .address.state) |
732
738
 
733
739
  Lembrete: todo template recém-criado pelo MCP só mostra o texto na tela depois de **sincronizar**
734
740
  (o MCP cria na Meta, mas o "puxar de volta" o conteúdo é o que a tela faz com o botão Sincronizar).
@@ -278,6 +278,12 @@ responsável, chame `conversations_update` depois — não adianta mandar junto
278
278
  **O que É aproveitado:** `custom_attributes` entram por merge, **sem sobrescrever chave que já tem
279
279
  valor**. A conversa mantém a origem com que nasceu e só ganha o que ainda não tinha.
280
280
 
281
+ **Conversa NOVA nasce com a origem da ficha (16/10/2026).** Quando você (API, automação, fluxo ou
282
+ formulário) cria uma conversa nova e a ficha do contato já traz sinal de anúncio (formulário da Meta, UTM,
283
+ Google, LionTrack), a conversa já nasce com os `origin_*` preenchidos — antes ficava sem origem até o
284
+ cliente escrever. Sem sinal na ficha, nasce sem origem (nunca "Direto"): quem classifica "Direto" é a
285
+ primeira mensagem do cliente. Origem que você mandar na criação não é trocada.
286
+
281
287
  **Reuso não dispara `conversation_created`** — automação, gatilho de flow, webhook de saída,
282
288
  notificação e a Agenda não rodam. Se o seu fluxo depende de um desses, ele não vai acontecer
283
289
  quando houver reuso.
@@ -621,7 +627,7 @@ O conector **liga a condicao com a SEGUINTE**. Logo:
621
627
  | `filter_operator` | SIM | Como comparar |
622
628
  | `values` | SIM (menos em `is_present`/`is_not_present`) | Array, sempre |
623
629
  | `query_operator` | SIM em todas | `"AND"`/`"OR"`; **`null` na ultima** |
624
- | `custom_attribute_type` | so p/ atributo personalizado | `conversation_attribute` ou `contact_attribute` |
630
+ | `custom_attribute_type` | so p/ atributo personalizado | `conversation_attribute`, `contact_attribute` ou `company_attribute` (campo personalizado da EMPRESA do contato, desde 2026-10-16: so atributo personalizado, negativo inclui contato sem empresa, `attribute_changed` recusado, modulo Empresas desligado = nunca casa) |
625
631
 
626
632
  ### Operadores de filtro
627
633
 
@@ -715,16 +721,18 @@ Todos os formatos abaixo foram conferidos linha a linha em `app/services/action_
715
721
  | `snooze_conversation` | `[]` |
716
722
  | `resolve_conversation` / `open_conversation` / `pending_conversation` | `[]` |
717
723
  | `update_contact_attribute` / `update_conversation_attribute` | `[{"attribute_key": "chave", "value": "texto ou {{contact.phone_number}}"}]` — chave PROTEGIDA é recusada na execução e fica só no log (10/09/2026): prefixos `waha_whatsapp_`, `whatsapp_`, `origin_`, `ctwa_`, `meta_lead_`, `lt_` e atributos de sistema como `utm_*`/`gclid`. Vale pra macro e automação |
718
- | `create_kanban_item` | `[{"funnel_id": 31, "funnel_stage": "prospeccao", "allow_duplicates": false}]` |
724
+ | `create_kanban_item` | `[{"funnel_id": 31, "funnel_stage": "prospeccao", "allow_duplicates": false}]` — sem `allow_duplicates`, duas execuções no mesmo instante (formulário + conversa que ele criou) NÃO criam dois cards (09/10/2026), e o texto `"false"` vale como desmarcado. Card ADIADO no funil conta como card que já existe: não cria outro, move se a etapa for diferente e continua adiado (11/10/2026) |
719
725
  | `move_kanban_item_to_stage` | `[{"funnel_id": 31, "funnel_stage": "qualificacao"}]` |
720
726
  | `assign_agent_to_kanban_item` | `[{"funnel_id": 31, "agent_id": 70, "mode": "add"}]` — `mode` aceita `add` (padrao) ou `remove_all` |
721
727
  | `add_note_to_kanban_item` | `[{"funnel_id": 31, "text": "texto da nota"}]` — a chave e **`text`**, nao `note` |
722
- | `set_kanban_item_status` | `[{"funnel_id": 31, "status": "won"}]` — `won`, `lost` ou `open`. Status fora disso e ignorado |
728
+ | `set_kanban_item_status` | `[{"funnel_id": 31, "status": "won"}]` — `won`, `lost` ou `open`. Status fora disso e ignorado. `open` num card ADIADO abre o card na hora e dispara "Card aberto" (11/10/2026) |
729
+ | `snooze_kanban_item` | `[{"funnel_id": 31, "amount": 3, "unit": "days"}]` — ADIA o card por N horas (`hours`) ou dias (`days`); minimo 1 hora, so card aberto (11/10/2026). Em regra de evento de CARD (`kanban_item_*`) o card e o do evento: `[{"amount": 3, "unit": "days"}]`. Diferente das outras acoes de card, sem card no funil, card ganho/perdido ou tempo invalido = FALHA visivel no historico da regra, nunca pula calado. Ver "Card adiado" no kanban-deep-dive |
723
730
  | `start_kanban_item_timer` / `stop_kanban_item_timer` | `[{"funnel_id": 31}]` |
724
731
  | `wait` | `[30]` — segundos. Com conferência na volta: `[{"seconds": 120, "only_if_open": true, "only_if_no_reply": true}]`. Ver "Acao Aguardar" abaixo |
725
732
 
726
733
  **Acoes de Kanban:** todas precisam que a conversa ja tenha um card no funil informado. Sem card,
727
- a acao e pulada (fica so no log do servidor). Use `create_kanban_item` antes, na mesma regra.
734
+ a acao e pulada (fica so no log do servidor) — exceto `snooze_kanban_item`, que FALHA visivel. Use
735
+ `create_kanban_item` antes, na mesma regra.
728
736
 
729
737
  ### Acao `wait` (Aguardar) — novo 2026-08-04
730
738
 
@@ -154,6 +154,9 @@ Company
154
154
  ├── contacts_count
155
155
  ├── avatar (foto)
156
156
  ├── additional_attributes (jsonb: legal_name, cnpj, phone, email, address{...}, social_profiles{...}; 2026-09-25)
157
+ ├── custom_attributes (jsonb: atributos personalizados de EMPRESA, definicoes attribute_model=company_attribute;
158
+ │ 2026-10-15. Gravar: lionchat_companies_update com custom_attributes {chave: valor}; MESCLA; null limpa a chave;
159
+ │ so chave definida como atributo de empresa da conta; criar a definicao exige o modulo Empresas)
157
160
  └── last_activity_at (ultima vez que um contato da empresa teve atividade; 2026-09-25)
158
161
 
159
162
  AttributeChange (histórico de alterações de atributo — 2026-09-11)
@@ -206,6 +209,56 @@ atendente). Cadastro da empresa (25/09): `lionchat_companies_create`/`_update` a
206
209
  valor vazio apaga aquela chave. Excluir empresa: so administrador; os contatos ficam, so deixam de estar na empresa (as anotacoes da
207
210
  empresa vao junto).
208
211
 
212
+ **Atributos personalizados da EMPRESA (2026-10-15):** campos proprios da empresa que o cliente cria ("Segmento",
213
+ "Data de fundacao", "Faturamento anual"...), na aba Empresa de Configuracoes > Atributos personalizados. Sao um
214
+ quarto tipo de definicao, separado de conversa, contato e variavel de conta:
215
+ - **Criar a definicao:** `lionchat_custom_attributes_create` com `attribute_model: "company_attribute"` (SEMPRE com o
216
+ sufixo). So na conta com o modulo Empresas ligado (sem ele: 422 "O modulo Empresas nao esta ligado nesta conta").
217
+ Tipo `secret` e recusado. Chaves reservadas (os campos proprios da empresa) sao recusadas: `name`, `domain`,
218
+ `description`, `legal_name`, `cnpj`, `phone`, `email`, `address`, `social_profiles`, `contacts_count`,
219
+ `created_at`, `last_activity_at`. As mesmas regras valem ao TROCAR o `attribute_model` de uma definicao existente.
220
+ - **Listar:** `lionchat_custom_attributes_list` com `attribute_model=company_attribute`.
221
+ - **Preencher:** `lionchat_companies_update` (ou `_create`) com `custom_attributes: {"segmento": "Varejo"}` dentro do
222
+ wrapper `company`. MESCLA: mande so o que muda; `null` ou texto vazio LIMPA a chave; chave nao mandada fica.
223
+ Valores SIMPLES (texto, numero, verdadeiro/falso ou null) — lista ou objeto dentro = 422. Teto de 1.500 caracteres
224
+ por valor. Data pode ir como `15/10/2020` (grava `2020-10-15`).
225
+ - **So aceita chave que JA existe** como atributo de empresa da conta: chave desconhecida = 422 com a lista das
226
+ validas, e NADA do pedido e gravado (nem o nome nem o cadastro que vieram junto). Crie a definicao antes de
227
+ preencher — a lista e lida na hora, entao criar e preencher logo em seguida funciona.
228
+ - **Ler:** `lionchat_companies_show`/`_list` trazem `custom_attributes` em cada empresa (`{}` quando vazio).
229
+ - **Onde mais ele aparece (2026-10-16), igual aos atributos de contato.** A empresa e a do CONTATO: um contato
230
+ tem no maximo UMA empresa (`contacts.company_id`); a conversa nao tem empresa propria. Com o modulo Empresas ligado:
231
+ - **Variavel** em mensagem, resposta pronta, automacao, fluxo, campanha e mensagem agendada:
232
+ `{{contact.company.custom_attribute.<chave>}}` (data sai DD/MM/AAAA) e o cadastro `{{contact.company.name}}`,
233
+ `{{contact.company.legal_name}}`, `{{contact.company.cnpj}}` (so digitos), `{{contact.company.phone}}`,
234
+ `{{contact.company.email}}`, `{{contact.company.address.city}}`, `{{contact.company.address.state}}`.
235
+ `{{contact.company}}` sozinho imprime o nome. Contato sem empresa = vazio, sem erro.
236
+ - **Modelo oficial do WhatsApp** (`variable_mapping`): campo personalizado com
237
+ `{"source": "company", "field": "custom_attribute.<chave>"}`; cadastro com
238
+ `{"source": "variable", "field": "contact.company.cnpj"}` (tambem `legal_name`, `phone`, `email`,
239
+ `address.city`, `address.state`). O nome da empresa ja e `contact.company_name`.
240
+ - **Condicao do bloco Condicao do fluxo:** regra `{"valueType": "attr_config", "attrSource": "company",
241
+ "attr_key": "<chave>", "operator": "...", "value": "", "values": [...]}`. `attr_key` = chave personalizada OU
242
+ cadastro com prefixo: `company.name`, `company.legal_name`, `company.cnpj` (comparado por digitos),
243
+ `company.phone`, `company.email`, `company.address.city`, `company.address.state`. Contato sem empresa = valor
244
+ vazio (os negativos casam).
245
+ - **Condicao de automacao e filtro da lista de contatos** (`lionchat_contacts_filter`, segmento salvo, acao em
246
+ massa): `{"attribute_key": "<chave>", "filter_operator": "...", "values": [...],
247
+ "custom_attribute_type": "company_attribute"}`. So atributo PERSONALIZADO (o cadastro da empresa fica fora).
248
+ Operadores pelo tipo do atributo: todos aceitam `equal_to`/`not_equal_to`/`is_present`/`is_not_present`;
249
+ texto tambem `contains`/`does_not_contain`; numero, data e hora tambem `is_greater_than`/`is_less_than`.
250
+ Negativo inclui o contato SEM empresa. `attribute_changed` e recusado (a empresa nao dispara evento).
251
+ Chave igual a de um atributo de contato e permitida: quem decide e o `custom_attribute_type`.
252
+ - **Filtro de CONVERSAS** (`lionchat_conversations_filter`) com `company_attribute` = 422 ("Atributo de empresa
253
+ so filtra a lista de contatos").
254
+ - **IA:** o Agente de IA e o Copiloto veem o nome, o cadastro e os campos personalizados preenchidos da empresa do
255
+ contato. Somente leitura: a IA nao grava na empresa.
256
+ - **Modulo Empresas DESLIGADO = para tudo:** variavel sai vazia, condicao nunca casa (nem "diferente de" nem
257
+ "esta vazio"), filtro nao acha ninguem, a IA nao ve a empresa. O que foi preenchido fica guardado.
258
+ - **Ainda NAO:** condicao de SAIDA do fluxo, gatilho do fluxo, parametro de ferramenta de IA, acao "Alterar
259
+ atributo" do fluxo/automacao (nao grava na empresa), exportacao/relatorio, historico de alteracoes e gatilho
260
+ de data.
261
+
209
262
  **Dados cadastrais (`additional_attributes.cadastral`):** CPF, CNPJ, RG, passaporte, nascimento,
210
263
  gênero, estado civil, profissão e endereço completo moram em `additional_attributes->cadastral`.
211
264
  São **IMUTÁVEIS por padrão** (primeira escrita vale) — alterar exige `force_update: true`, que SÓ
@@ -282,6 +335,16 @@ KanbanItem
282
335
  ├── activities (jsonb)
283
336
  ├── timer_started_at / timer_duration
284
337
  └── created_at
338
+ (item_details.status guarda open/won/lost; ADIADO = open + linha em kanban_item_snoozes — a resposta da API
339
+ traz effective_status open|snoozed|won|lost e snoozed_until, calculados; nao sao colunas)
340
+ (13/10/2026: item_details.attributed_to = vendedor do ganho; item_details.co_attributed_to = CO-PARTICIPANTE da
341
+ venda, ex. o SDR — so em card ganho, id de agente; co_attributed_name o card preenche. Ver kanban-deep-dive)
342
+
343
+ KanbanItemSnooze (kanban_item_snoozes — 11/10/2026: ate quando o card esta ADIADO; uma linha por card)
344
+ ├── account_id, kanban_item_id (unico)
345
+ ├── snoozed_until (datetime)
346
+ ├── snoozed_by_id (quem adiou; nulo = automacao, macro ou fluxo)
347
+ └── created_at / updated_at
285
348
 
286
349
  Item do checklist (elemento do array jsonb `checklist`)
287
350
  ├── id (uuid string)
@@ -485,7 +548,9 @@ db/schema.rb, tabela booking_event_types)
485
548
  │ {{nome}} {{email}} {{telefone}} {{data}} {{horario}} {{dia_semana}} (NOVO 24/08 — dia por
486
549
  │ extenso em pt: "terça-feira"; irmã do {{booking.weekday}} do FlowBuilder, mesma grafia)
487
550
  │ {{tipo_evento}} {{titulo}} {{duracao}} {{agente}} {{descricao}} {{meet_link}}
488
- │ {{link_cancelar}} {{link_remarcar}} {{horas_ate_evento}} {{horas_desde_evento}}
551
+ │ {{link_cancelar}} {{link_remarcar}} {{link_confirmar}} {{link_salvar_na_agenda}} (16/10: abre a
552
+ │ página onde o cliente salva no Google Agenda ou na agenda do iPhone/Outlook)
553
+ │ {{horas_ate_evento}} {{horas_desde_evento}}. Data, hora e duração saem do COMPROMISSO da Agenda (16/10)
489
554
  └── booking_availabilities (has_many — dias/horários; `booking_availabilities_attributes` no create)
490
555
 
491
556
  Booking (agendamento confirmado — tabela bookings)
@@ -770,6 +835,16 @@ LeadFormResponse (um preenchimento do formulário)
770
835
  └── answers (respostas) / milestones (marcos atingidos) / utm_params / started_at / completed_at /
771
836
  abandoned_at
772
837
 
838
+ WhatsappForm (Formulário do WhatsApp — formulário nativo da Meta, 17/10/2026; mesma liberação dos Formulários)
839
+ ├── account_id (FK) / inbox_id (caixa oficial usada para a credencial) / waba_id (conta do WhatsApp)
840
+ ├── name (único na conta do WhatsApp) / status (draft/publishing/published/deprecated) / meta_flow_id (número na
841
+ │ Meta — vai no botão FLOW do modelo) / meta_status / last_error / published_at
842
+ ├── editor_data (jsonb: telas e campos, códigos f_<n> e o_<n>) / save_targets (jsonb: "Salvar em" por código)
843
+ ├── images (arquivos anexados; o campo de imagem guarda o blob_id)
844
+ └── publications (has_many: uma por publicação — number, token, editor_data e flow_json da época)
845
+ → a resposta do cliente vira MENSAGEM entrante content_type form (items + submitted_values +
846
+ whatsapp_form), com foto/arquivo como anexos
847
+
773
848
  CustomWebhookIntegration (Webhook Universal — entrada)
774
849
  ├── account_id (FK)
775
850
  ├── name / token (URL única) / active
@@ -914,3 +989,46 @@ SignatureDocument (modelo) 1 ──── N SignatureEnvelope (contrato de UMA
914
989
  - `roles_layout` é jsonb aberto e MESCLADO no update; `progress` é calculado (aguardando/visualizado/assinado/encerrado).
915
990
  - Feature por conta: `document_signing`. Limite mensal em `usage_limits.signature_requests_per_month` (0 = ilimitado).
916
991
  - Detalhe completo: resource `lionchat://docs/assinatura-eletronica`.
992
+
993
+ ## Categorias dos atributos personalizados (17/10/2026)
994
+
995
+ Pastas que abrem e fecham para organizar os atributos de cada aba de Configuracoes > Atributos personalizados (estilo
996
+ "Data fields" do Pipedrive). Aparecem tambem onde a equipe preenche: painel lateral da conversa (atributos da conversa
997
+ e do contato, grupos que abrem e fecham por pessoa), ficha do contato, pagina da empresa e bloco "Dados Adicionais"
998
+ do card.
999
+
1000
+ - **Opcional:** conta sem categoria ve tudo como antes. Atributo que nao esta em nenhuma categoria aparece em "Sem
1001
+ categoria" (primeiro grupo da tela).
1002
+ - **Uma aba por categoria** (`attribute_model`): `conversation_attribute`, `contact_attribute`, `company_attribute`
1003
+ (so com o modulo Empresas) ou `kanban_item_attribute` (aba Card). Variavel de conta nao tem categoria. O mesmo nome
1004
+ pode existir em abas diferentes.
1005
+ - **Um atributo em UMA categoria so.** Colocar numa nova tira da anterior; ele entra no fim da categoria.
1006
+ - **Chave do Card:** os campos do Card vivem em `global_custom_attributes` da configuracao do Kanban
1007
+ (`lionchat_kanban_config_list`) e sao identificados pelo `name` EXATO (maiusculas e espacos). Nunca use uma chave
1008
+ "em minuscula com sublinhado" derivada do nome: o servidor recusa (`unknown_attribute`).
1009
+ - **Regras do nome:** 1 a 60 caracteres, sem repetir na aba (sem diferenciar maiuscula), "Sem categoria" reservado,
1010
+ ate 50 categorias por aba.
1011
+ - **Excluir categoria nunca apaga atributo nem valor:** os atributos voltam para "Sem categoria".
1012
+ - **Atributo apagado:** o vinculo fica guardado sem efeito. Se o atributo for recriado PELA API com a mesma chave, ele
1013
+ volta para a categoria; pela tela, vale a categoria escolhida no formulario.
1014
+ - Escrita so de administrador (401 para os demais); leitura para todos.
1015
+
1016
+ Ferramentas:
1017
+
1018
+ | Tool | Para que |
1019
+ |---|---|
1020
+ | `lionchat_custom_attribute_categories_list` | Todas as categorias da conta (`id, attribute_model, name, position, attribute_keys`) |
1021
+ | `lionchat_custom_attribute_categories_create` | Cria na aba (`name`, `attribute_model`); entra no fim |
1022
+ | `lionchat_custom_attribute_categories_update` | Renomeia (`name`); a aba nao muda |
1023
+ | `lionchat_custom_attribute_categories_destroy` | Exclui (atributos voltam para "Sem categoria") |
1024
+ | `lionchat_custom_attribute_categories_reorder` | Nova ordem da aba: `ids` com TODOS os ids da aba (faltou/sobrou = 422; releia e mande de novo) |
1025
+ | `lionchat_custom_attribute_categories_assign` | Coloca (`category_id`) ou tira (`category_id` nulo) um atributo |
1026
+
1027
+ Receita "organizar os atributos de contato do cliente": `lionchat_custom_attributes_list` (`attribute_model:
1028
+ contact_attribute`) para ver as chaves -> `lionchat_custom_attribute_categories_create` para cada grupo -> um
1029
+ `lionchat_custom_attribute_categories_assign` por atributo. Para criar um atributo ja dentro de uma categoria:
1030
+ `lionchat_custom_attributes_create` e depois `assign` com a `attribute_key` DEVOLVIDA pelo create (o servidor
1031
+ normaliza a chave: sem acento, minuscula).
1032
+
1033
+ FUTURO (ainda nao existe): usar a categoria como filtro (ex.: um funil mostrar so certas categorias). Nao prometa isso
1034
+ ao cliente.
@@ -35,6 +35,8 @@ E/OU é encadeado condição a condição (sem parênteses).
35
35
 
36
36
  Atributo personalizado: informe `custom_attribute_type` na condição — `'conversation_attribute'`
37
37
  (atributo da conversa) ou `'contact_attribute'`. Sem ele o escopo pode sair errado.
38
+ `'company_attribute'` (atributo da empresa) NÃO vale em conversas: 422 (desde 16/10/2026; use o filtro de
39
+ CONTATOS).
38
40
 
39
41
  **`contact_attribute` lê a FICHA DO CONTATO da conversa (funciona de verdade desde 24/09/2026).** Antes o
40
42
  servidor ignorava o valor e lia o atributo da própria conversa — por isso lead de formulário da Meta
@@ -102,6 +104,11 @@ Mesmo shape de condição. 15/página.
102
104
  - `card_funnel_id`, `card_stage`, `card_priority`, `card_status` (equal_to/not_equal_to) — card
103
105
  do Kanban vinculado.
104
106
  - Condição com `custom_attribute_type:'conversation_attribute'` — atributo da conversa vinculada.
107
+ - Condição com `custom_attribute_type:'company_attribute'` (16/10/2026) — campo PERSONALIZADO da empresa do
108
+ contato, ex. `{attribute_key:'segmento', filter_operator:'equal_to', values:['Varejo'],
109
+ custom_attribute_type:'company_attribute'}`. Operadores pelo tipo (texto: igual/diferente/contém/não contém/
110
+ preenchido; número/data/hora: + maior/menor). Negativo inclui contato SEM empresa. Módulo Empresas desligado =
111
+ ninguém. Chave que não existe como atributo de empresa = 422.
105
112
 
106
113
  ### Duas regras que enganam nos filtros
107
114
 
@@ -119,7 +126,8 @@ GET com parâmetros FLAT (não usa payload). Filtros REAIS (2026-07-24 — parâ
119
126
  stage_id/inbox_id/sort_by/sort_direction/page foram REMOVIDOS, eram ignorados pelo backend):
120
127
 
121
128
  - `funnel_id` (obrigatório), `stages[]` (chaves internas das etapas), `priorities[]`,
122
- `statuses[]` (open/won/lost), `agent_id`, `value_min`/`value_max`
129
+ `statuses[]` (open/won/lost/snoozed — desde 11/10/2026 `open` = aberto e NÃO adiado e `snoozed` = adiado; ver
130
+ "Card adiado" no kanban-deep-dive), `agent_id`, `value_min`/`value_max`
123
131
  - `date_start`/`date_end` (criação do card, YYYY-MM-DD)
124
132
  - `stage_date_start`/`stage_date_end` (2026-08-07) — **quando o card ENTROU na etapa em que ele está
125
133
  agora** (`stage_entered_at`, caindo em `created_at` para card que nunca se moveu). NÃO confundir com
@@ -158,9 +166,30 @@ NÃO há filtro por time aqui (por time: use o relatório agregado abaixo, ou fi
158
166
 
159
167
  `GET /kanban_items/reports` — a resposta é um OBJETO de métricas (não lista): valores GANHOS,
160
168
  contagem/valor por etapa, forecast ponderado, top negócios, cards parados, metas do funil.
161
- Params: `funnel_id` (obrigatório), `from`, `to`, `user_ids[]` (agentes), `team_id`, `date_basis`.
169
+ Params: `funnel_id` (obrigatório), `from`, `to`, `user_ids[]` (agentes), `team_id`, `date_basis`,
170
+ `label_names[]` (só cards com essas etiquetas — nome da etiqueta, como aparece na conversa; desde 02/10/2026),
171
+ `win_reason_ids[]` (só ganhos com esses motivos — o código do motivo em `lionchat_kanban_config_list`; desde 02/10/2026)
172
+ e `include_shared_sales` (true = traz o quadro "sozinho x compartilhadas", ver abaixo; 13/10/2026).
162
173
  É a ferramenta certa pra "relatório de vendas/ganhos do mês por agente ou time".
163
174
 
175
+ **Co-participante da venda (13/10/2026).** No ganho dá para marcar, além do vendedor, UM co-participante (ex.: o SDR
176
+ que trouxe a venda — ver `lionchat://docs/kanban-deep-dive`, seção "Co-participante da venda"). O relatório mostra isso
177
+ SEM mudar nenhum número antigo (Ganhos e Receita continuam do vendedor):
178
+ - `coParticipantRanking` (sempre vem): `[{id, name, sales, value}]`, uma linha por co-participante, contando cards
179
+ GANHOS no período com os mesmos recortes de funil, régua, etiqueta e motivo. O filtro de pessoa/time vale sobre o
180
+ co-participante. Co-participante igual ao vendedor não conta. Lista vazia = a conta não usa o recurso.
181
+ - Com `include_shared_sales=true`: `sharedSalesSummary` `{solo, shared, sharedSellerLeft}` (cada um `{count, value}`; sozinho + compartilhadas = ganhos do período; `sharedSellerLeft` = compartilhadas cujo vendedor saiu da conta)
182
+ e `sharedSalesBySeller` `[{id, sales, value}]` (parte compartilhada de cada vendedor). Sem o parâmetro a resposta é
183
+ a de sempre. Venda compartilhada = ganha no período com co-participante DIFERENTE do vendedor gravado no card; sair da
184
+ conta NÃO transforma venda compartilhada em individual.
185
+ - **A lista das vendas compartilhadas** (data, negócio, valor, vendedor, co-participante) é a tool
186
+ `lionchat_kanban_items_shared_sales` (`GET /kanban_items/shared_sales`): mesmos filtros (`funnel_id` opcional, `from`,
187
+ `to`, `date_basis`, `user_ids[]`, `team_id`, `label_names[]`, `win_reason_ids[]`), a mais recente no topo, 50 por vez
188
+ (`limit` até 500). Para a página seguinte, devolva o `nextCursor` recebido em `cursor_at` e `cursor_id`; `nextCursor`
189
+ nulo = acabou. Com filtro de pessoa/time só entra venda em que alguém do filtro foi VENDEDOR ou CO-PARTICIPANTE, e vêm
190
+ `asSellerCount`/`asCoCount` (sem filtro, nulos). `canOpen` por linha diz se quem pediu pode abrir o card; quem vê só
191
+ parte do Kanban recebe as vendas que não vê como `hiddenCount` (nunca o título).
192
+
164
193
  **`date_basis` (2026-08-07) — qual data o período considera.** A resposta devolve a régua aplicada em
165
194
  `dateBasis`; valor inválido cai no padrão sem erro.
166
195