@wondai/n8n-nodes-nucleo 0.6.5 → 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.
@@ -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",
@@ -1446,6 +1527,52 @@ class Nucleo {
1446
1527
  path = `/api/v1/agent/pedido/${encodeURIComponent(pedidoId)}/anexo`;
1447
1528
  bodyObj = b;
1448
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
+ }
1449
1576
  else if (resource === "conversa" && operation === "registrar") {
1450
1577
  const resumo = this.getNodeParameter("resumo", i).trim();
1451
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.5",
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",