@wondai/n8n-nodes-nucleo 0.6.4 → 0.7.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/README.md CHANGED
@@ -80,6 +80,11 @@ telefone/endereço (allowlist) e distância opcional. **Disponibilidade NÃO é
80
80
  declarou e oferece o contato para o cliente confirmar. Este endpoint **não** entra no contexto fixo do
81
81
  gate; é chamado sob demanda. Tenant/unidade vêm do token — nunca do prompt.
82
82
 
83
+ A partir da v0.6.5, `Pedido Detalhar` aceita `Telefone do Cliente` opcional. Quando preenchido, o
84
+ Nucleo so devolve o pedido se o telefone do cliente do pedido bater com esse valor. Use este campo no
85
+ WhatsApp para perguntas como "meu pedido 61", evitando que o agente consulte pedido de outro cliente
86
+ por numero curto.
87
+
83
88
  **Contexto -> Consultar por Escopo** deve ser usado como **AI Tool** dentro do agente, sob demanda.
84
89
  Passe `escopos` para buscar apenas o necessario:
85
90
 
@@ -63,8 +63,11 @@ function parseConsultas(raw, max = 10) {
63
63
  continue;
64
64
  seen.add(k);
65
65
  out.push(s);
66
- if (out.length >= max)
67
- break;
66
+ }
67
+ // Plano 030: rejeicao EXPLICITA acima do limite. O break silencioso descartava consultas
68
+ // (incidente 65144: 4 consultas perdidas), produzindo resultado parcial com aparencia de sucesso.
69
+ if (out.length > max) {
70
+ throw new Error(`Catalogo Resolver: ${out.length} consultas acima do limite de ${max}. Envie no maximo ${max} consultas de PRODUTO por chamada; adicionais e opcoes ja resolvidos nao sao novas consultas.`);
68
71
  }
69
72
  return out;
70
73
  }
@@ -268,9 +271,87 @@ class Nucleo {
268
271
  action: "Anexar foto-referência ao pedido",
269
272
  description: "Anexa a FOTO que o cliente mandou no WhatsApp (a decoração que ele quer) ao pedido — a imagem literal, não uma descrição. Recebe base64 (data-URL ou puro + mime). Idempotente pelo messageID.",
270
273
  },
274
+ {
275
+ name: "Cotar",
276
+ value: "cotar",
277
+ action: "Cotar pedido (cotação canônica)",
278
+ description: "Gera a COTAÇÃO CANÔNICA do Núcleo (preço/quantidade/config oficiais; nada de texto) e a persiste como pending. Use os mesmos itens estruturados de Criar.",
279
+ },
280
+ {
281
+ name: "Validar Cotação (Dry-Run)",
282
+ value: "validarCotacao",
283
+ action: "Validar cotação sem escrever (shadow)",
284
+ description: "Roda o preparo/precificação e devolve o mesmo shape da cotação com dry_run:true, SEM escrever nada.",
285
+ },
286
+ {
287
+ name: "Marcar Cotação Apresentada",
288
+ value: "apresentarCotacao",
289
+ action: "Marcar cotação apresentada",
290
+ description: "pending -> presented. Chamar SÓ no ramo de sucesso do envio público. Requer cotacao_id e cotacao_version.",
291
+ },
292
+ {
293
+ name: "Consultar Cotação Ativa",
294
+ value: "consultarCotacaoAtiva",
295
+ action: "Consultar cotação apresentada ativa",
296
+ description: "Devolve a cotação presented ativa do cliente (por telefone). Sem cotação apresentada => 404.",
297
+ },
298
+ {
299
+ name: "Confirmar Cotação",
300
+ value: "confirmarCotacao",
301
+ action: "Confirmar cotação (cria 1 pedido)",
302
+ description: "Revalida a cotação por fingerprint e cria exatamente UM pedido (transacional). Divergência => 409 e novo resumo. Requer cotacao_id, cotacao_version e cliente_telefone.",
303
+ },
271
304
  ],
272
305
  default: "criar",
273
306
  },
307
+ {
308
+ displayName: "Cotação (Corpo JSON)",
309
+ name: "cotacaoBody",
310
+ type: "json",
311
+ default: "{}",
312
+ description: "Corpo da cotação (mesmo formato de Criar): telefone, itens[], tipo_entrega, endereco_entrega, agendado_para, forma_pagamento, taxa_entrega, desconto, cobrancas[], observacoes.",
313
+ displayOptions: { show: { resource: ["pedido"], operation: ["cotar", "validarCotacao"] } },
314
+ },
315
+ {
316
+ displayName: "Cotação ID",
317
+ name: "cotacaoId",
318
+ type: "string",
319
+ default: "",
320
+ description: "cotacao_id retornado por Cotar (vindo por expressão do workflow, nunca da LLM).",
321
+ displayOptions: { show: { resource: ["pedido"], operation: ["apresentarCotacao", "confirmarCotacao"] } },
322
+ },
323
+ {
324
+ displayName: "Cotação Versão",
325
+ name: "cotacaoVersion",
326
+ type: "number",
327
+ default: 1,
328
+ description: "cotacao_version retornado por Cotar.",
329
+ displayOptions: { show: { resource: ["pedido"], operation: ["apresentarCotacao", "confirmarCotacao"] } },
330
+ },
331
+ {
332
+ displayName: "Message ID Apresentado",
333
+ name: "cotacaoMessageId",
334
+ type: "string",
335
+ default: "",
336
+ description: "ID da mensagem enviada (opcional), para auditoria da apresentação.",
337
+ displayOptions: { show: { resource: ["pedido"], operation: ["apresentarCotacao"] } },
338
+ },
339
+ {
340
+ displayName: "Telefone do Cliente",
341
+ name: "cotacaoTelefone",
342
+ type: "string",
343
+ default: "",
344
+ description: "Telefone normalizado, injetado por expressão do workflow.",
345
+ displayOptions: { show: { resource: ["pedido"], operation: ["consultarCotacaoAtiva", "confirmarCotacao"] } },
346
+ },
347
+ {
348
+ displayName: "Nome do Cliente",
349
+ name: "cotacaoNome",
350
+ type: "string",
351
+ default: "",
352
+ description: "Nome do cliente (opcional).",
353
+ displayOptions: { show: { resource: ["pedido"], operation: ["confirmarCotacao"] } },
354
+ },
274
355
  {
275
356
  displayName: "Operação",
276
357
  name: "operation",
@@ -502,6 +583,15 @@ class Nucleo {
502
583
  description: "Número curto (ex: 64) ou UUID do pedido. Devolve o pedido com itens e seus IDs. Pedido de outra padaria → 404.",
503
584
  displayOptions: { show: { resource: ["pedido"], operation: ["detalhar"] } },
504
585
  },
586
+ {
587
+ displayName: "Telefone do Cliente",
588
+ name: "pedidoTelefoneCliente",
589
+ type: "string",
590
+ default: "",
591
+ placeholder: "555199999999",
592
+ description: "Opcional. Quando informado, o Nucleo so devolve o pedido se ele pertencer ao cliente desse telefone. Use no WhatsApp para perguntas como 'meu pedido 61'.",
593
+ displayOptions: { show: { resource: ["pedido"], operation: ["detalhar"] } },
594
+ },
505
595
  // ----------------------------------------------------------------- pedido:criar
506
596
  {
507
597
  displayName: "Telefone",
@@ -1294,8 +1384,12 @@ class Nucleo {
1294
1384
  }
1295
1385
  else if (resource === "pedido" && operation === "detalhar") {
1296
1386
  const ref = this.getNodeParameter("ref", i).trim();
1387
+ const telefoneCliente = this.getNodeParameter("pedidoTelefoneCliente", i, "").trim();
1297
1388
  method = "GET";
1298
1389
  path = `/api/v1/agent/pedido/${encodeURIComponent(ref)}`;
1390
+ if (telefoneCliente) {
1391
+ path += `?telefone_cliente=${encodeURIComponent(telefoneCliente)}`;
1392
+ }
1299
1393
  }
1300
1394
  else if (resource === "pedido" && operation === "criar") {
1301
1395
  const telefone = this.getNodeParameter("telefone", i).trim();
@@ -1433,6 +1527,52 @@ class Nucleo {
1433
1527
  path = `/api/v1/agent/pedido/${encodeURIComponent(pedidoId)}/anexo`;
1434
1528
  bodyObj = b;
1435
1529
  }
1530
+ else if (resource === "pedido" && operation === "cotar") {
1531
+ const body = asObject(this.getNodeParameter("cotacaoBody", i, "{}")) || {};
1532
+ assertPedidoCriarItens(asArray(body.itens));
1533
+ idempotencyKey =
1534
+ this.getNodeParameter("idempotencyKey", i, "").trim() || undefined;
1535
+ method = "POST";
1536
+ path = "/api/v1/agent/pedido/cotacao";
1537
+ bodyObj = body;
1538
+ }
1539
+ else if (resource === "pedido" && operation === "validarCotacao") {
1540
+ const body = asObject(this.getNodeParameter("cotacaoBody", i, "{}")) || {};
1541
+ method = "POST";
1542
+ path = "/api/v1/agent/pedido/cotacao/validar";
1543
+ bodyObj = body;
1544
+ }
1545
+ else if (resource === "pedido" && operation === "apresentarCotacao") {
1546
+ const cotacaoId = this.getNodeParameter("cotacaoId", i).trim();
1547
+ const version = Number(this.getNodeParameter("cotacaoVersion", i));
1548
+ const messageId = this.getNodeParameter("cotacaoMessageId", i, "").trim();
1549
+ const b = { cotacao_version: version };
1550
+ if (messageId)
1551
+ b.presented_message_id = messageId;
1552
+ method = "POST";
1553
+ path = `/api/v1/agent/pedido/cotacao/${encodeURIComponent(cotacaoId)}/apresentar`;
1554
+ bodyObj = b;
1555
+ }
1556
+ else if (resource === "pedido" && operation === "consultarCotacaoAtiva") {
1557
+ const telefone = this.getNodeParameter("cotacaoTelefone", i).trim();
1558
+ method = "POST";
1559
+ path = "/api/v1/agent/pedido/cotacao/ativa";
1560
+ bodyObj = { telefone };
1561
+ }
1562
+ else if (resource === "pedido" && operation === "confirmarCotacao") {
1563
+ const cotacaoId = this.getNodeParameter("cotacaoId", i).trim();
1564
+ const version = Number(this.getNodeParameter("cotacaoVersion", i));
1565
+ const telefone = this.getNodeParameter("cotacaoTelefone", i).trim();
1566
+ const nome = this.getNodeParameter("cotacaoNome", i, "").trim();
1567
+ const b = { cotacao_version: version, cliente_telefone: telefone };
1568
+ if (nome)
1569
+ b.cliente_nome = nome;
1570
+ // Idempotência DETERMINÍSTICA montada pelo package (nunca pela LLM), Plano 030 §9.5.
1571
+ idempotencyKey = `quote-confirm:${cotacaoId}:${version}`;
1572
+ method = "POST";
1573
+ path = `/api/v1/agent/pedido/cotacao/${encodeURIComponent(cotacaoId)}/confirmar`;
1574
+ bodyObj = b;
1575
+ }
1436
1576
  else if (resource === "conversa" && operation === "registrar") {
1437
1577
  const resumo = this.getNodeParameter("resumo", i).trim();
1438
1578
  const resultado = this.getNodeParameter("resultado", i);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wondai/n8n-nodes-nucleo",
3
- "version": "0.6.4",
3
+ "version": "0.7.0",
4
4
  "description": "Node n8n para o Núcleo Wondai — atendimento de IA multi-vertical (gate liga/desliga, cliente/paciente, catálogo fuzzy, pedidos [padaria], procedimento/agenda/agendamento [odonto], contexto operacional, telemetria) com assinatura HMAC v1. Tenant e vertical vêm do token (a parede, ADR-013/038).",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",