@wondai/n8n-nodes-nucleo 0.5.6 → 0.6.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.
@@ -181,6 +181,11 @@ class Nucleo {
181
181
  { name: "Conversa", value: "conversa" },
182
182
  { name: "Contexto", value: "contexto" },
183
183
  { name: "Telemetria", value: "telemetria" },
184
+ // --- odonto (ADR-036/038): resources próprios do vertical; server roteia pelo token ---
185
+ { name: "Paciente", value: "paciente" },
186
+ { name: "Procedimento", value: "procedimento" },
187
+ { name: "Agenda", value: "agenda" },
188
+ { name: "Agendamento", value: "agendamento" },
184
189
  ],
185
190
  default: "catalogo",
186
191
  },
@@ -322,6 +327,82 @@ class Nucleo {
322
327
  ],
323
328
  default: "enviar",
324
329
  },
330
+ {
331
+ displayName: "Operação",
332
+ name: "operation",
333
+ type: "options",
334
+ noDataExpression: true,
335
+ displayOptions: { show: { resource: ["paciente"] } },
336
+ options: [
337
+ {
338
+ name: "Buscar",
339
+ value: "buscar",
340
+ action: "Buscar paciente por telefone",
341
+ description: "Lookup do paciente (cliente + dados clínicos) + agendamentos recentes",
342
+ },
343
+ ],
344
+ default: "buscar",
345
+ },
346
+ {
347
+ displayName: "Operação",
348
+ name: "operation",
349
+ type: "options",
350
+ noDataExpression: true,
351
+ displayOptions: { show: { resource: ["procedimento"] } },
352
+ options: [
353
+ {
354
+ name: "Resolver",
355
+ value: "resolver",
356
+ action: "Resolver procedimentos por nome",
357
+ description: "Busca vários procedimentos numa chamada, tolera erro de digitação e apelidos e devolve id, duração e preço (status achou/ambiguo/nao_encontrado)",
358
+ },
359
+ ],
360
+ default: "resolver",
361
+ },
362
+ {
363
+ displayName: "Operação",
364
+ name: "operation",
365
+ type: "options",
366
+ noDataExpression: true,
367
+ displayOptions: { show: { resource: ["agenda"] } },
368
+ options: [
369
+ {
370
+ name: "Consultar",
371
+ value: "consultar",
372
+ action: "Consultar horários livres numa data",
373
+ description: "Horários livres por profissional numa data (horário publicado − ocupado). Sem config do dia ⇒ unknown honesto",
374
+ },
375
+ ],
376
+ default: "consultar",
377
+ },
378
+ {
379
+ displayName: "Operação",
380
+ name: "operation",
381
+ type: "options",
382
+ noDataExpression: true,
383
+ displayOptions: { show: { resource: ["agendamento"] } },
384
+ options: [
385
+ {
386
+ name: "Criar",
387
+ value: "criar",
388
+ action: "Criar agendamento",
389
+ description: "Marca um horário (idempotente). Overlap do profissional ⇒ 409",
390
+ },
391
+ {
392
+ name: "Alterar",
393
+ value: "alterar",
394
+ action: "Alterar agendamento",
395
+ description: "Remarca / troca procedimento ou profissional / observação / status (idempotente). Transição inválida ⇒ 409",
396
+ },
397
+ {
398
+ name: "Cancelar",
399
+ value: "cancelar",
400
+ action: "Cancelar agendamento",
401
+ description: "Cancela o agendamento (idempotente). Libera o horário",
402
+ },
403
+ ],
404
+ default: "criar",
405
+ },
325
406
  // ----------------------------------------------------------------- cliente:buscar
326
407
  {
327
408
  displayName: "Telefone",
@@ -854,6 +935,211 @@ class Nucleo {
854
935
  description: "Opcional. UUID do pedido vinculado (validado contra o tenant).",
855
936
  displayOptions: { show: { resource: ["telemetria"], operation: ["enviar"] } },
856
937
  },
938
+ // ===================================================================== ODONTO (ADR-036/038)
939
+ // ----------------------------------------------------------------- paciente:buscar
940
+ {
941
+ displayName: "Telefone",
942
+ name: "pacTelefone",
943
+ type: "string",
944
+ default: "",
945
+ required: true,
946
+ placeholder: "+5511999999999",
947
+ description: "Telefone do paciente em E.164 (só dígitos + DDI). Devolve o paciente (cliente + dados clínicos) e os agendamentos recentes.",
948
+ displayOptions: { show: { resource: ["paciente"], operation: ["buscar"] } },
949
+ },
950
+ // ----------------------------------------------------------------- procedimento:resolver
951
+ {
952
+ displayName: "Consultas",
953
+ name: "procConsultas",
954
+ type: "string",
955
+ typeOptions: { rows: 3 },
956
+ default: "",
957
+ required: true,
958
+ placeholder: "limpeza, clareamento, canal",
959
+ description: "Procedimentos a buscar: um por linha ou separados por vírgula (máx 10). Tolera erro de digitação, falta de acento e apelidos. Devolve status (achou/ambiguo/nao_encontrado) + candidatos com id, duração e preço.",
960
+ displayOptions: { show: { resource: ["procedimento"], operation: ["resolver"] } },
961
+ },
962
+ // ----------------------------------------------------------------- agenda:consultar
963
+ {
964
+ displayName: "Data",
965
+ name: "agdData",
966
+ type: "string",
967
+ default: "",
968
+ required: true,
969
+ placeholder: "2026-07-15",
970
+ description: "UMA data YYYY-MM-DD. Devolve horários livres por profissional (horário publicado − ocupado). Sem config do dia ⇒ status unknown (não invente horário).",
971
+ displayOptions: { show: { resource: ["agenda"], operation: ["consultar"] } },
972
+ },
973
+ {
974
+ displayName: "Procedimento (ID, opcional)",
975
+ name: "agdProcedimentoId",
976
+ type: "string",
977
+ default: "",
978
+ description: "UUID do procedimento (do Procedimento Resolver). Define a duração dos slots; vazio = 30 min.",
979
+ displayOptions: { show: { resource: ["agenda"], operation: ["consultar"] } },
980
+ },
981
+ {
982
+ displayName: "Profissional (ID, opcional)",
983
+ name: "agdProfissionalId",
984
+ type: "string",
985
+ default: "",
986
+ description: "UUID do profissional. Vazio = todos os profissionais ativos.",
987
+ displayOptions: { show: { resource: ["agenda"], operation: ["consultar"] } },
988
+ },
989
+ {
990
+ displayName: "Passo (min, opcional)",
991
+ name: "agdPassoMin",
992
+ type: "number",
993
+ default: 30,
994
+ description: "Granularidade dos horários oferecidos, em minutos (1..240). Default 30.",
995
+ displayOptions: { show: { resource: ["agenda"], operation: ["consultar"] } },
996
+ },
997
+ // ----------------------------------------------------------------- agendamento:criar
998
+ {
999
+ displayName: "Telefone",
1000
+ name: "agcTelefone",
1001
+ type: "string",
1002
+ default: "",
1003
+ required: true,
1004
+ placeholder: "+5511999999999",
1005
+ description: "Telefone do paciente (E.164). Resolve o paciente existente ou cria um novo.",
1006
+ displayOptions: { show: { resource: ["agendamento"], operation: ["criar"] } },
1007
+ },
1008
+ {
1009
+ displayName: "Nome do Paciente",
1010
+ name: "agcNomePaciente",
1011
+ type: "string",
1012
+ default: "",
1013
+ description: "Opcional. Preenche paciente novo ou existente ainda sem nome. Não sobrescreve nome já cadastrado.",
1014
+ displayOptions: { show: { resource: ["agendamento"], operation: ["criar"] } },
1015
+ },
1016
+ {
1017
+ displayName: "Profissional (ID)",
1018
+ name: "agcProfissionalId",
1019
+ type: "string",
1020
+ default: "",
1021
+ required: true,
1022
+ description: "UUID do profissional (obrigatório). Horário ocupado desse profissional ⇒ 409 (indisponível).",
1023
+ displayOptions: { show: { resource: ["agendamento"], operation: ["criar"] } },
1024
+ },
1025
+ {
1026
+ displayName: "Procedimento (ID, opcional)",
1027
+ name: "agcProcedimentoId",
1028
+ type: "string",
1029
+ default: "",
1030
+ description: "UUID do procedimento (do Procedimento Resolver). Define a duração quando 'Fim' não é enviado.",
1031
+ displayOptions: { show: { resource: ["agendamento"], operation: ["criar"] } },
1032
+ },
1033
+ {
1034
+ displayName: "Início",
1035
+ name: "agcInicio",
1036
+ type: "string",
1037
+ default: "",
1038
+ required: true,
1039
+ placeholder: "2026-07-15T14:00:00-03:00",
1040
+ description: "Instante ISO 8601 do início do agendamento.",
1041
+ displayOptions: { show: { resource: ["agendamento"], operation: ["criar"] } },
1042
+ },
1043
+ {
1044
+ displayName: "Fim (opcional)",
1045
+ name: "agcFim",
1046
+ type: "string",
1047
+ default: "",
1048
+ placeholder: "2026-07-15T14:30:00-03:00",
1049
+ description: "Instante ISO 8601 do fim. Vazio = início + duração do procedimento (ou 30 min).",
1050
+ displayOptions: { show: { resource: ["agendamento"], operation: ["criar"] } },
1051
+ },
1052
+ {
1053
+ displayName: "Observações",
1054
+ name: "agcObservacoes",
1055
+ type: "string",
1056
+ default: "",
1057
+ displayOptions: { show: { resource: ["agendamento"], operation: ["criar"] } },
1058
+ },
1059
+ // ----------------------------------------------------------------- agendamento:alterar / cancelar
1060
+ {
1061
+ displayName: "Agendamento ID",
1062
+ name: "agAgendamentoId",
1063
+ type: "string",
1064
+ default: "",
1065
+ required: true,
1066
+ description: "UUID do agendamento (do tenant). Agendamento de outro tenant → 404.",
1067
+ displayOptions: { show: { resource: ["agendamento"], operation: ["alterar", "cancelar"] } },
1068
+ },
1069
+ {
1070
+ displayName: "Início (remarcar)",
1071
+ name: "agaInicio",
1072
+ type: "string",
1073
+ default: "",
1074
+ placeholder: "2026-07-16T09:00:00-03:00",
1075
+ description: "ISO 8601. Vazio = não remarca.",
1076
+ displayOptions: { show: { resource: ["agendamento"], operation: ["alterar"] } },
1077
+ },
1078
+ {
1079
+ displayName: "Fim (remarcar)",
1080
+ name: "agaFim",
1081
+ type: "string",
1082
+ default: "",
1083
+ description: "ISO 8601. Vazio = mantém/recalcula pelo início.",
1084
+ displayOptions: { show: { resource: ["agendamento"], operation: ["alterar"] } },
1085
+ },
1086
+ {
1087
+ displayName: "Procedimento (ID, alterar)",
1088
+ name: "agaProcedimentoId",
1089
+ type: "string",
1090
+ default: "",
1091
+ description: "UUID do novo procedimento. Vazio = não altera. Use 'null' para desvincular.",
1092
+ displayOptions: { show: { resource: ["agendamento"], operation: ["alterar"] } },
1093
+ },
1094
+ {
1095
+ displayName: "Profissional (ID, alterar)",
1096
+ name: "agaProfissionalId",
1097
+ type: "string",
1098
+ default: "",
1099
+ description: "UUID do novo profissional. Vazio = não altera. Overlap ⇒ 409.",
1100
+ displayOptions: { show: { resource: ["agendamento"], operation: ["alterar"] } },
1101
+ },
1102
+ {
1103
+ displayName: "Observações (alterar)",
1104
+ name: "agaObservacoes",
1105
+ type: "string",
1106
+ default: "",
1107
+ description: "Vazio = não altera.",
1108
+ displayOptions: { show: { resource: ["agendamento"], operation: ["alterar"] } },
1109
+ },
1110
+ {
1111
+ displayName: "Status",
1112
+ name: "agaStatus",
1113
+ type: "options",
1114
+ options: [
1115
+ { name: "(não alterar)", value: "" },
1116
+ { name: "Confirmado", value: "confirmado" },
1117
+ { name: "Realizado", value: "realizado" },
1118
+ { name: "Faltou", value: "faltou" },
1119
+ { name: "Cancelado", value: "cancelado" },
1120
+ ],
1121
+ default: "",
1122
+ description: "Transição de status (a API recusa transição inválida com 409). Válidas: agendado→confirmado/cancelado; confirmado→realizado/faltou/cancelado.",
1123
+ displayOptions: { show: { resource: ["agendamento"], operation: ["alterar"] } },
1124
+ },
1125
+ {
1126
+ displayName: "Motivo",
1127
+ name: "agxMotivo",
1128
+ type: "string",
1129
+ default: "",
1130
+ description: "Motivo do cancelamento (opcional).",
1131
+ displayOptions: { show: { resource: ["agendamento"], operation: ["cancelar"] } },
1132
+ },
1133
+ {
1134
+ displayName: "Idempotency Key",
1135
+ name: "agIdempotencyKey",
1136
+ type: "string",
1137
+ default: "",
1138
+ description: "Criar: vazio = gerada automaticamente. Alterar/cancelar: use a MESMA chave (ex.: messageID do WhatsApp) num retry para não reaplicar a operação.",
1139
+ displayOptions: {
1140
+ show: { resource: ["agendamento"], operation: ["criar", "alterar", "cancelar"] },
1141
+ },
1142
+ },
857
1143
  ],
858
1144
  };
859
1145
  }
@@ -1105,6 +1391,96 @@ class Nucleo {
1105
1391
  path = "/api/v1/agent/telemetria";
1106
1392
  bodyObj = b;
1107
1393
  }
1394
+ else if (resource === "paciente" && operation === "buscar") {
1395
+ const telefone = this.getNodeParameter("pacTelefone", i).trim();
1396
+ method = "POST";
1397
+ path = "/api/v1/agent/paciente";
1398
+ bodyObj = { telefone };
1399
+ }
1400
+ else if (resource === "procedimento" && operation === "resolver") {
1401
+ const consultas = parseConsultas(this.getNodeParameter("procConsultas", i));
1402
+ method = "POST";
1403
+ path = "/api/v1/agent/procedimento/resolver";
1404
+ bodyObj = { consultas };
1405
+ }
1406
+ else if (resource === "agenda" && operation === "consultar") {
1407
+ const data = this.getNodeParameter("agdData", i).trim();
1408
+ const procedimentoId = this.getNodeParameter("agdProcedimentoId", i, "").trim();
1409
+ const profissionalId = this.getNodeParameter("agdProfissionalId", i, "").trim();
1410
+ const passoMin = Number(this.getNodeParameter("agdPassoMin", i, 30));
1411
+ const b = { data };
1412
+ if (procedimentoId)
1413
+ b.procedimento_id = procedimentoId;
1414
+ if (profissionalId)
1415
+ b.profissional_id = profissionalId;
1416
+ if (Number.isFinite(passoMin) && passoMin > 0)
1417
+ b.passo_min = passoMin;
1418
+ method = "POST";
1419
+ path = "/api/v1/agent/agenda/consultar";
1420
+ bodyObj = b;
1421
+ }
1422
+ else if (resource === "agendamento" && operation === "criar") {
1423
+ const telefone = this.getNodeParameter("agcTelefone", i).trim();
1424
+ const nomePaciente = this.getNodeParameter("agcNomePaciente", i, "").trim();
1425
+ const profissionalId = this.getNodeParameter("agcProfissionalId", i).trim();
1426
+ const procedimentoId = this.getNodeParameter("agcProcedimentoId", i, "").trim();
1427
+ const inicio = this.getNodeParameter("agcInicio", i).trim();
1428
+ const fim = this.getNodeParameter("agcFim", i, "").trim();
1429
+ const observacoes = this.getNodeParameter("agcObservacoes", i, "").trim();
1430
+ // Escrita de agenda EXIGE Idempotency-Key no servidor (400 se vazia) — gera se não vier.
1431
+ idempotencyKey =
1432
+ this.getNodeParameter("agIdempotencyKey", i, "").trim() || (0, node_crypto_1.randomUUID)();
1433
+ const b = { telefone, profissional_id: profissionalId, inicio };
1434
+ if (nomePaciente)
1435
+ b.nome_paciente = nomePaciente;
1436
+ if (procedimentoId)
1437
+ b.procedimento_id = procedimentoId;
1438
+ if (fim)
1439
+ b.fim = fim;
1440
+ if (observacoes)
1441
+ b.observacoes = observacoes;
1442
+ method = "POST";
1443
+ path = "/api/v1/agent/agendamento";
1444
+ bodyObj = b;
1445
+ }
1446
+ else if (resource === "agendamento" && operation === "alterar") {
1447
+ const agendamentoId = this.getNodeParameter("agAgendamentoId", i).trim();
1448
+ const inicio = this.getNodeParameter("agaInicio", i, "").trim();
1449
+ const fim = this.getNodeParameter("agaFim", i, "").trim();
1450
+ const procedimentoId = this.getNodeParameter("agaProcedimentoId", i, "").trim();
1451
+ const profissionalId = this.getNodeParameter("agaProfissionalId", i, "").trim();
1452
+ const observacoes = this.getNodeParameter("agaObservacoes", i, "").trim();
1453
+ const status = this.getNodeParameter("agaStatus", i, "").trim();
1454
+ const b = {};
1455
+ if (inicio)
1456
+ b.inicio = inicio;
1457
+ if (fim)
1458
+ b.fim = fim;
1459
+ // "null" (string) desvincula o procedimento; vazio = não mexe no campo.
1460
+ if (procedimentoId) {
1461
+ b.procedimento_id = procedimentoId.toLowerCase() === "null" ? null : procedimentoId;
1462
+ }
1463
+ if (profissionalId)
1464
+ b.profissional_id = profissionalId;
1465
+ if (observacoes)
1466
+ b.observacoes = observacoes;
1467
+ if (status)
1468
+ b.status = status;
1469
+ idempotencyKey =
1470
+ this.getNodeParameter("agIdempotencyKey", i, "").trim() || (0, node_crypto_1.randomUUID)();
1471
+ method = "PATCH";
1472
+ path = `/api/v1/agent/agendamento/${encodeURIComponent(agendamentoId)}`;
1473
+ bodyObj = b;
1474
+ }
1475
+ else if (resource === "agendamento" && operation === "cancelar") {
1476
+ const agendamentoId = this.getNodeParameter("agAgendamentoId", i).trim();
1477
+ const motivo = this.getNodeParameter("agxMotivo", i, "").trim();
1478
+ idempotencyKey =
1479
+ this.getNodeParameter("agIdempotencyKey", i, "").trim() || (0, node_crypto_1.randomUUID)();
1480
+ method = "POST";
1481
+ path = `/api/v1/agent/agendamento/${encodeURIComponent(agendamentoId)}/cancelar`;
1482
+ bodyObj = motivo ? { motivo } : {};
1483
+ }
1108
1484
  else {
1109
1485
  throw new n8n_workflow_1.NodeApiError(this.getNode(), {
1110
1486
  message: `Operação não suportada: ${resource}.${operation}`,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@wondai/n8n-nodes-nucleo",
3
- "version": "0.5.6",
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).",
3
+ "version": "0.6.0",
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",
7
7
  "n8n",
@@ -9,6 +9,7 @@
9
9
  "nucleo",
10
10
  "crm",
11
11
  "padaria",
12
+ "odonto",
12
13
  "ai-tool"
13
14
  ],
14
15
  "license": "MIT",