@wondai/n8n-nodes-nucleo 0.7.0 → 0.8.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.
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Nucleo = void 0;
4
4
  const node_crypto_1 = require("node:crypto");
5
5
  const n8n_workflow_1 = require("n8n-workflow");
6
+ const quote_contract_1 = require("./quote-contract");
6
7
  /**
7
8
  * Node Núcleo Wondai — satélite de IA → Núcleo (ADR-003/013/015/016).
8
9
  *
@@ -336,6 +337,15 @@ class Nucleo {
336
337
  description: "ID da mensagem enviada (opcional), para auditoria da apresentação.",
337
338
  displayOptions: { show: { resource: ["pedido"], operation: ["apresentarCotacao"] } },
338
339
  },
340
+ {
341
+ displayName: "Message ID da Cotação",
342
+ name: "cotacaoRequestMessageId",
343
+ type: "string",
344
+ default: "",
345
+ required: true,
346
+ description: "messageID real do turno. O package combina este valor com o hash do payload para gerar a Idempotency-Key.",
347
+ displayOptions: { show: { resource: ["pedido"], operation: ["cotar"] } },
348
+ },
339
349
  {
340
350
  displayName: "Telefone do Cliente",
341
351
  name: "cotacaoTelefone",
@@ -1529,15 +1539,16 @@ class Nucleo {
1529
1539
  }
1530
1540
  else if (resource === "pedido" && operation === "cotar") {
1531
1541
  const body = asObject(this.getNodeParameter("cotacaoBody", i, "{}")) || {};
1532
- assertPedidoCriarItens(asArray(body.itens));
1533
- idempotencyKey =
1534
- this.getNodeParameter("idempotencyKey", i, "").trim() || undefined;
1542
+ (0, quote_contract_1.assertQuoteContract)(body);
1543
+ const messageId = this.getNodeParameter("cotacaoRequestMessageId", i).trim();
1544
+ idempotencyKey = (0, quote_contract_1.buildQuoteIdempotencyKey)(tokenId, messageId, body);
1535
1545
  method = "POST";
1536
1546
  path = "/api/v1/agent/pedido/cotacao";
1537
1547
  bodyObj = body;
1538
1548
  }
1539
1549
  else if (resource === "pedido" && operation === "validarCotacao") {
1540
1550
  const body = asObject(this.getNodeParameter("cotacaoBody", i, "{}")) || {};
1551
+ (0, quote_contract_1.assertQuoteContract)(body);
1541
1552
  method = "POST";
1542
1553
  path = "/api/v1/agent/pedido/cotacao/validar";
1543
1554
  bodyObj = body;
@@ -0,0 +1,4 @@
1
+ import type { IDataObject } from "n8n-workflow";
2
+ export declare function canonicalQuotePayload(payload: IDataObject): string;
3
+ export declare function buildQuoteIdempotencyKey(tokenId: string, messageId: string, payload: IDataObject): string;
4
+ export declare function assertQuoteContract(payload: IDataObject): void;
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.canonicalQuotePayload = canonicalQuotePayload;
4
+ exports.buildQuoteIdempotencyKey = buildQuoteIdempotencyKey;
5
+ exports.assertQuoteContract = assertQuoteContract;
6
+ const node_crypto_1 = require("node:crypto");
7
+ function canonicalize(value) {
8
+ if (Array.isArray(value))
9
+ return value.map(canonicalize);
10
+ if (!value || typeof value !== "object") {
11
+ return typeof value === "string" ? value.normalize("NFC") : value;
12
+ }
13
+ return Object.fromEntries(Object.entries(value)
14
+ .sort(([left], [right]) => left.localeCompare(right))
15
+ .map(([key, child]) => [key, canonicalize(child)]));
16
+ }
17
+ function canonicalQuotePayload(payload) {
18
+ return JSON.stringify(canonicalize(payload));
19
+ }
20
+ function buildQuoteIdempotencyKey(tokenId, messageId, payload) {
21
+ const message = messageId.trim();
22
+ if (!message)
23
+ throw new Error("Cotar pedido: messageID obrigatorio.");
24
+ const hash = (0, node_crypto_1.createHash)("sha256")
25
+ .update(canonicalQuotePayload(payload), "utf8")
26
+ .digest("hex")
27
+ .slice(0, 16);
28
+ return `quote:${tokenId}:${message}:${hash}`;
29
+ }
30
+ function assertQuoteContract(payload) {
31
+ const items = payload.itens;
32
+ if (!Array.isArray(items) || items.length === 0 || items.length > 10) {
33
+ throw new Error("Cotacao: itens precisa conter entre 1 e 10 linhas.");
34
+ }
35
+ for (const [index, raw] of items.entries()) {
36
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
37
+ throw new Error(`Cotacao: itens[${index}] invalido.`);
38
+ }
39
+ const item = raw;
40
+ const informed = item.quantidade_informada;
41
+ if (!String(item.produto_id ?? "").trim() ||
42
+ !String(item.consulta_original ?? "").trim() ||
43
+ !String(item.quantidade_original ?? "").trim() ||
44
+ !informed ||
45
+ !Number.isFinite(Number(informed.valor)) ||
46
+ Number(informed.valor) <= 0 ||
47
+ !String(informed.unidade ?? "").trim()) {
48
+ throw new Error(`Cotacao: itens[${index}] exige produto_id, consulta_original, quantidade_original e quantidade_informada.`);
49
+ }
50
+ }
51
+ const charges = Array.isArray(payload.cobrancas) ? payload.cobrancas : [];
52
+ for (const [index, raw] of charges.entries()) {
53
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
54
+ continue;
55
+ const charge = raw;
56
+ if (charge.tipo !== "paid_addon")
57
+ continue;
58
+ if (!String(charge.regra_chave ?? "").trim() ||
59
+ !String(charge.operational_config_revision_id ?? "").trim()) {
60
+ throw new Error(`Cotacao: cobrancas[${index}] paid_addon exige regra_chave e operational_config_revision_id.`);
61
+ }
62
+ if (charge.nome != null || charge.valor != null) {
63
+ throw new Error(`Cotacao: cobrancas[${index}] nao pode enviar nome ou valor; o Nucleo resolve pela revisao.`);
64
+ }
65
+ }
66
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wondai/n8n-nodes-nucleo",
3
- "version": "0.7.0",
3
+ "version": "0.8.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",
@@ -25,6 +25,7 @@
25
25
  "scripts": {
26
26
  "build": "node scripts/build.mjs",
27
27
  "dev": "tsc --watch",
28
+ "test": "node scripts/test.mjs",
28
29
  "prepublishOnly": "node scripts/build.mjs"
29
30
  },
30
31
  "files": [