@wondai/n8n-nodes-nucleo 0.5.4 → 0.5.6

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
@@ -32,6 +32,34 @@ parede. Isso mantém o segredo fora do workflow, a lógica num lugar só e o tok
32
32
  **Resolver Produtos** é a operação inteligente: manda várias consultas numa chamada (máx 10),
33
33
  tolera erro de digitação (`banofe`→Banoffee), falta de acento (`pao frances`→Pão Francês) e
34
34
  apelidos; devolve no máx 3 candidatos por consulta com `status` (`achou`/`ambiguo`/`nao_achou`).
35
+ Produtos configuráveis também devolvem `regras_compra`. O agente deve coletar as opções
36
+ obrigatórias e enviá-las em `itens[].configuracao`; o Núcleo recusa com HTTP 422 qualquer combo
37
+ incompleto, opção inventada, quantidade inválida, prazo impossível ou personalização proibida.
38
+
39
+ Exemplo de item configurável:
40
+
41
+ ```json
42
+ {
43
+ "produto_id": "uuid-do-produto",
44
+ "quantidade": 1,
45
+ "configuracao": {
46
+ "opcoes": [
47
+ {
48
+ "grupo_id": "uuid-do-grupo",
49
+ "opcao_id": "uuid-da-opcao"
50
+ }
51
+ ]
52
+ }
53
+ }
54
+ ```
55
+
56
+ O node não interpreta essas regras: ele encaminha o JSON sem alteração. A validação e o snapshot
57
+ imutável do contrato aplicado pertencem à API do Núcleo (ADR-035).
58
+
59
+ A partir da v0.5.6, `Pedido Criar` falha antes da requisição quando `itens` está vazio ou quando
60
+ alguma linha não traz `produto_id`/`alias` e `quantidade` positiva. É uma proteção de integração:
61
+ o Núcleo continua sendo a autoridade final das regras, mas o workflow não envia pedido vazio por
62
+ erro de tool schema.
35
63
 
36
64
  **Disponibilidade na Rede** ("tem na outra loja?") é exposta como **tool** do AI Agent, mas com regra
37
65
  estrita no prompt: **usar SÓ quando o cliente pedir explicitamente outra unidade**. Passe o `produto_id`
@@ -86,6 +86,29 @@ function asArray(value) {
86
86
  }
87
87
  return [];
88
88
  }
89
+ function hasPedidoItemIdentifier(item) {
90
+ const produtoId = String(item.produto_id ?? "").trim();
91
+ const alias = String(item.alias ?? "").trim();
92
+ return Boolean(produtoId || alias);
93
+ }
94
+ function hasPositiveQuantidade(item) {
95
+ const quantidade = Number(item.quantidade);
96
+ return Number.isFinite(quantidade) && quantidade > 0;
97
+ }
98
+ function assertPedidoCriarItens(itens) {
99
+ if (!Array.isArray(itens) || itens.length === 0) {
100
+ throw new Error("Pedido Criar bloqueado: o campo itens precisa ser um array JSON não vazio com produto_id ou alias e quantidade. Resolva o catálogo e envie os itens estruturados antes de criar o pedido.");
101
+ }
102
+ const invalidIndex = itens.findIndex((item) => {
103
+ if (!item || typeof item !== "object" || Array.isArray(item))
104
+ return true;
105
+ const itemObject = item;
106
+ return !hasPedidoItemIdentifier(itemObject) || !hasPositiveQuantidade(itemObject);
107
+ });
108
+ if (invalidIndex >= 0) {
109
+ throw new Error(`Pedido Criar bloqueado: itens[${invalidIndex}] precisa ter produto_id ou alias e quantidade positiva.`);
110
+ }
111
+ }
89
112
  /** Lê um parâmetro JSON (string ou objeto) como objeto simples. Vazio → null. */
90
113
  function asObject(value) {
91
114
  if (value && typeof value === "object" && !Array.isArray(value))
@@ -189,7 +212,7 @@ class Nucleo {
189
212
  name: "Resolver Produtos",
190
213
  value: "resolver",
191
214
  action: "Resolver produtos do catálogo",
192
- description: "Busca vários produtos numa chamada tolera erro de digitação e apelidos",
215
+ description: "Busca vários produtos numa chamada, tolera erro de digitação e apelidos e devolve regras_compra quando houver escolhas, limites ou restrições obrigatórias",
193
216
  },
194
217
  {
195
218
  name: "Disponibilidade Na Rede",
@@ -319,7 +342,7 @@ class Nucleo {
319
342
  default: "",
320
343
  required: true,
321
344
  placeholder: "banofe, pão francês, coxinha",
322
- description: "Produtos a buscar: um por linha ou separados por vírgula (máx 10). Aceita erro de digitação, falta de acento e apelidos. Ex: 'banofe, pão francês'.",
345
+ description: "Produtos a buscar: um por linha ou separados por vírgula (máx 10). Aceita erro de digitação, falta de acento e apelidos. Para produto configurável, use regras_compra como fonte da verdade e colete todas as opções obrigatórias antes de criar o pedido.",
323
346
  displayOptions: { show: { resource: ["catalogo"], operation: ["resolver"] } },
324
347
  },
325
348
  // ----------------------------------------------------------------- catalogo:disponibilidadeRede
@@ -385,7 +408,7 @@ class Nucleo {
385
408
  type: "json",
386
409
  default: "[]",
387
410
  required: true,
388
- description: 'Array de itens. Cada item: {"produto_id" OU "alias", "variacao_id"?, "quantidade", "personalizacao"?}. Ex: [{"alias":"paozinho","quantidade":6},{"produto_id":"...","quantidade":1}]',
411
+ description: 'Array de itens. Cada item: {"produto_id" OU "alias", "variacao_id"?, "quantidade", "personalizacao"?, "configuracao"?}. Para produto configurável, copie somente escolhas devolvidas em regras_compra: {"configuracao":{"opcoes":[{"grupo_id":"uuid","opcao_id":"uuid"}],"quantidade_referencia":100,"confirmacao_humana":true}}. A API recusa combo incompleto ou opção inventada com 422.',
389
412
  displayOptions: { show: { resource: ["pedido"], operation: ["criar"] } },
390
413
  },
391
414
  {
@@ -531,7 +554,7 @@ class Nucleo {
531
554
  name: "adicionarItens",
532
555
  type: "json",
533
556
  default: "[]",
534
- description: "Mesma forma de Itens. Vazio = não adiciona.",
557
+ description: "Mesma forma de Itens, incluindo configuracao para escolhas obrigatórias retornadas em regras_compra. Vazio = não adiciona; item incompleto é recusado com 422.",
535
558
  displayOptions: { show: { resource: ["pedido"], operation: ["alterar"] } },
536
559
  },
537
560
  {
@@ -884,6 +907,7 @@ class Nucleo {
884
907
  const telefone = this.getNodeParameter("telefone", i).trim();
885
908
  const nomeCliente = this.getNodeParameter("nomeCliente", i, "").trim();
886
909
  const itens = asArray(this.getNodeParameter("itens", i));
910
+ assertPedidoCriarItens(itens);
887
911
  const tipoEntrega = this.getNodeParameter("tipoEntrega", i);
888
912
  const observacoes = this.getNodeParameter("observacoes", i, "").trim();
889
913
  const agendadoPara = this.getNodeParameter("agendadoPara", i, "").trim();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wondai/n8n-nodes-nucleo",
3
- "version": "0.5.4",
3
+ "version": "0.5.6",
4
4
  "description": "Node n8n para o Núcleo Wondai — atendimento de IA (gate liga/desliga, cliente, catálogo fuzzy, disponibilidade em rede, pedidos, contexto operacional, telemetria) com assinatura HMAC v1. Tenant vem do token (a parede, ADR-013).",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",