diff --git a/docs/superpowers/plans/2026-08-11-confirmacao-agenda-syslife.md b/docs/superpowers/plans/2026-08-11-confirmacao-agenda-syslife.md new file mode 100644 index 0000000..ce380dc --- /dev/null +++ b/docs/superpowers/plans/2026-08-11-confirmacao-agenda-syslife.md @@ -0,0 +1,792 @@ +# Confirmação de Agenda via WhatsApp (SysLife) Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task (não subagent-driven-development — as tasks são chamadas de API do n8n via MCP, não commits de código; não há diff de git pra revisar por task). Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Construir o MCP Server "SysLife - Confirmação de Agenda" (3 tools) + o workflow proativo de lembrete de confirmação (cron diário) + um workflow de exemplo de AI Agent reativo que confirma presença via WhatsApp, todos estruturalmente validados (0 erros), sem clínica SysLife real disponível para teste de ponta a ponta — mesmo padrão já executado para Klingo (`docs/superpowers/plans/2026-08-11-confirmacao-agenda-klingo.md`) e ClinicAll (`docs/superpowers/plans/2026-08-11-confirmacao-agenda-clinicall.md`). + +**Architecture:** Mesmo padrão de MCP Server nativo n8n dos outros providers: `MCP Server Trigger` + N × `Call n8n Sub-Workflow Tool`, cada um apontando pra um sub-workflow `Execute Workflow Trigger` → `Config SysLife` → `Montar query`/`Montar corpo` (Code) → `HTTP Request` → `Classificar resultado` (Code). Reaproveita **exatamente** o estilo de node já usado no MVP de agendamento do SysLife (`MCP Server - SysLife - consultar_horarios`, `GCbnkMM1JTnVXRSr`, lido nesta sessão como referência): `HTTP Request` com `authentication: "genericCredentialType"` + `genericAuthType: "httpBasicAuth"`, credencial `httpBasicAuth` já existente (`u8XXJ6wyrKuGJp4Y`), `sendBody: true` mesmo em métodos GET (documentado assim no manual do SysLife). O recurso `AgendaWSApp` é único e serve três propósitos diferentes conforme o parâmetro enviado — duas tools deste MCP (`listar_agendamentos_do_dia`, `buscar_agendamento_por_telefone`) apontam pra sub-workflows distintos que chamam o mesmo endpoint com parâmetros diferentes. O workflow de lembrete chama a tool via `Execute Workflow` direto (sem IA), reaproveitando o MCP de Mensageria (Z-PRO) já validado. O workflow de exemplo reativo usa `AI Agent` + `Postgres Chat Memory` + tools do MCP SysLife via `MCP Client Tool`. + +**Tech Stack:** n8n (`devn8n.gtigrupo.com.br`), MCP tools do n8n (`n8n_create_workflow`, `n8n_update_partial_workflow`, `n8n_validate_workflow`, `n8n_get_workflow`), SysLife WebService (`http://:9000/AgendaWSApp`, Basic Auth), MCP Server - Mensageria ZPRO (`WsL4RlSD0hZ34gpB` → sub-workflow `enviar_texto` `Y4ndG7GuIUrmVRxV`, já em produção). + +## Global Constraints + +- Nenhum campo de request/response é inventado — usar só os campos confirmados no manual (`docs/Manual WS SysLife V3.4 (1).pdf`). +- **Credencial Basic Auth já existe e é reaproveitada**: `httpBasicAuth`, id `u8XXJ6wyrKuGJp4Y`, nome `SysLife Basic Auth - clínica pendente` — usuário/senha fixos do WebService (`WebSysLife`/`$Sys10Life`), documentados literalmente no manual, não variam por clínica. `baseUrl` fica com o mesmo placeholder já usado nos sub-workflows do MVP de agendamento: `"http://SUBSTITUIR-PELO-IP-REAL-DA-CLINICA:9000"`. +- **Lição da seção 17 do runbook — `$fromAI` só com um único `=` no início**: ao montar `workflowInputs.value` dos nodes `Call n8n Sub-Workflow Tool` do servidor, cada expressão deve ser exatamente `"={{ $fromAI(...) }}"` (um `=`). Como este plano cria os nodes via `n8n_create_workflow` diretamente, o bug do `==` duplicado não deve ocorrer — conferir mesmo assim com `n8n_get_workflow` (mode `filtered`) antes de validar. +- **Campos opcionais precisam de valor-padrão no `$fromAI`**: `$fromAI('nome', 'descrição', 'tipo', valorPadrão)` — senão o schema gerado da tool trata o campo como obrigatório. Neste plano, todos os campos das 3 tools são obrigatórios (o manual não documenta nenhum campo opcional em `AgendaWSApp`), então nenhum `$fromAI` precisa de 4º argumento — mas confira mesmo assim. +- Operações que trocam node + conexões devem ser feitas numa única chamada `n8n_update_partial_workflow`. +- Todo workflow (servidor + sub-workflows) precisa estar `active: true` para responder via protocolo MCP. +- Taxonomia de erro padrão (idêntica à já usada no MVP de agendamento do SysLife): `AUTHENTICATION_ERROR` (401/403), `NOT_FOUND` (404), `VALIDATION_ERROR` (400/422), `SYSLIFE_API_ERROR` (demais). +- `AgendaCentral`, `AgendaRetorno`, `AgendaStatus`, `AgendaConfirmado` ficam **fora de escopo** — não implementar tools para esses endpoints. +- **Regra do manual**: `GET /AgendaWSApp` aceita `QuantidadeDias` **ou** `CodigoAgenda` **ou** `NumeroCelular` — nunca mais de um ao mesmo tempo. Cada sub-workflow de leitura deste plano monta o body só com o parâmetro que lhe cabe, nunca os dois. + +Spec de referência: `docs/superpowers/specs/2026-08-11-confirmacao-agenda-syslife-design.md` + +--- + +## Campos confirmados no manual (`docs/Manual WS SysLife V3.4 (1).pdf`, já lido nesta sessão) + +### `GET /AgendaWSApp` +- Body: **um único** de `QuantidadeDias` (number), `CodigoAgenda` (number) ou `NumeroCelular` (string, DDD + número). +- Resposta: array de objetos com `coAgenda`, `dtAtendimento`, `hrAtendimentoAlterado`, `nmPaciente`, `nmLogonWeb`, `nmSenhaWeb`, `coMedico`, `txTituloMedico`, `nmMedico`, `nmEspecialidade`, `nmOrigemDestino`, `coProcedimento`, `nmProcedimento`, `nuTelefone`, `nuTelefoneDdd`, `nuCelular`, `nuCelularDdd`, `txObservacao`, `txInstrucaoProc`, `txWhatsappProcedimento`, `txWhatsappHorario`, `txWhatsappMedico`, `txEndereco`, `txBairro`, `txComplemento`, `txCidade`, `txEstado`, `nmMsgSms`, `nmMsgSmsExtra`. + +### `POST /AgendaWSApp` +- Body: `CodigoAgenda` (number, obrigatório), `Resposta` (string, obrigatório — `"1"` = SIM, `"2"` = NÃO). +- Resposta: `{ "Status": "?" }` (mensagem de sucesso ou erro). + +--- + +## Pré-requisito + +Nenhum — a credencial Basic Auth já existe no n8n (`u8XXJ6wyrKuGJp4Y`), criada durante o MVP de agendamento do SysLife. Quando houver clínica real: preencher o `baseUrl` real (IP:9000) nos 3 sub-workflows. + +--- + +### Task 1: Sub-workflow `listar_agendamentos_do_dia` + +**Interfaces:** +- Consumes: nenhuma. +- Produces: workflow `MCP Server - SysLife - Confirmação - listar_agendamentos_do_dia`, consumido pela Task 4 (servidor) e pela Task 6 (lembrete proativo). + +- [ ] **Step 1: Criar o sub-workflow** + +`n8n_create_workflow`: + +```json +{ + "name": "MCP Server - SysLife - Confirmação - listar_agendamentos_do_dia", + "nodes": [ + { + "id": "trigger", + "name": "Execute Workflow Trigger", + "type": "n8n-nodes-base.executeWorkflowTrigger", + "typeVersion": 1.1, + "position": [0, 0], + "parameters": { + "workflowInputs": { "values": [{ "name": "quantidadeDias", "type": "number" }] } + } + }, + { + "id": "config", + "name": "Config SysLife", + "type": "n8n-nodes-base.set", + "typeVersion": 3.5, + "position": [260, 0], + "parameters": { + "mode": "manual", + "includeOtherFields": true, + "assignments": { + "assignments": [ + { "id": "cfg-baseurl", "name": "baseUrl", "type": "string", "value": "http://SUBSTITUIR-PELO-IP-REAL-DA-CLINICA:9000" } + ] + } + } + }, + { + "id": "montar-query", + "name": "Montar query", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [520, 0], + "parameters": { + "mode": "runOnceForEachItem", + "jsCode": "const input = $json;\nreturn {\n json: {\n baseUrl: input.baseUrl,\n queryBody: {\n QuantidadeDias: input.quantidadeDias,\n },\n },\n};" + } + }, + { + "id": "get-agendawsapp", + "name": "GET /AgendaWSApp", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [780, 0], + "parameters": { + "method": "GET", + "url": "={{ $json.baseUrl }}/AgendaWSApp", + "authentication": "genericCredentialType", + "genericAuthType": "httpBasicAuth", + "sendBody": true, + "contentType": "json", + "specifyBody": "json", + "jsonBody": "={{ JSON.stringify($json.queryBody) }}", + "options": { + "response": { "response": { "fullResponse": true, "neverError": true, "responseFormat": "autodetect" } }, + "timeout": 15000 + } + }, + "credentials": { + "httpBasicAuth": { "id": "u8XXJ6wyrKuGJp4Y", "name": "SysLife Basic Auth - clínica pendente" } + } + }, + { + "id": "classificar", + "name": "Classificar resultado", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [1040, 0], + "parameters": { + "mode": "runOnceForEachItem", + "jsCode": "const status = $json.statusCode;\nconst rawBody = $json.body;\n\nfunction classifyError(status, context) {\n if (status === 401 || status === 403) {\n return { code: \"AUTHENTICATION_ERROR\", message: `Falha de autenticação na API do SysLife ao ${context} (status ${status}).` };\n }\n if (status === 404) {\n return { code: \"NOT_FOUND\", message: `Recurso não encontrado na API do SysLife ao ${context} (status ${status}).` };\n }\n if (status === 400 || status === 422) {\n return { code: \"VALIDATION_ERROR\", message: `A API do SysLife rejeitou os dados enviados ao ${context} (status ${status}).` };\n }\n return { code: \"SYSLIFE_API_ERROR\", message: `A API do SysLife retornou um erro inesperado ao ${context} (status ${status}).` };\n}\n\nif (status < 200 || status >= 300) {\n return { json: { success: false, error: classifyError(status, \"listar agendamentos do dia\") } };\n}\n\nreturn { json: { success: true, data: rawBody } };" + } + } + ], + "connections": { + "Execute Workflow Trigger": { "main": [[{ "node": "Config SysLife", "type": "main", "index": 0 }]] }, + "Config SysLife": { "main": [[{ "node": "Montar query", "type": "main", "index": 0 }]] }, + "Montar query": { "main": [[{ "node": "GET /AgendaWSApp", "type": "main", "index": 0 }]] }, + "GET /AgendaWSApp": { "main": [[{ "node": "Classificar resultado", "type": "main", "index": 0 }]] } + } +} +``` + +Anotar o `id` retornado — necessário nas Tasks 4 e 6. + +- [ ] **Step 2: Validar** + +`n8n_validate_workflow` com o id retornado. Esperado: `errorCount: 0`. + +--- + +### Task 2: Sub-workflow `buscar_agendamento_por_telefone` + +**Interfaces:** +- Consumes: nenhuma. +- Produces: workflow `MCP Server - SysLife - Confirmação - buscar_agendamento_por_telefone`, consumido pela Task 4 e pelo workflow de exemplo (Task 7). + +- [ ] **Step 1: Criar o sub-workflow** + +`n8n_create_workflow`: + +```json +{ + "name": "MCP Server - SysLife - Confirmação - buscar_agendamento_por_telefone", + "nodes": [ + { + "id": "trigger", + "name": "Execute Workflow Trigger", + "type": "n8n-nodes-base.executeWorkflowTrigger", + "typeVersion": 1.1, + "position": [0, 0], + "parameters": { + "workflowInputs": { "values": [{ "name": "numeroCelular", "type": "string" }] } + } + }, + { + "id": "config", + "name": "Config SysLife", + "type": "n8n-nodes-base.set", + "typeVersion": 3.5, + "position": [260, 0], + "parameters": { + "mode": "manual", + "includeOtherFields": true, + "assignments": { + "assignments": [ + { "id": "cfg-baseurl", "name": "baseUrl", "type": "string", "value": "http://SUBSTITUIR-PELO-IP-REAL-DA-CLINICA:9000" } + ] + } + } + }, + { + "id": "montar-query", + "name": "Montar query", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [520, 0], + "parameters": { + "mode": "runOnceForEachItem", + "jsCode": "const input = $json;\nreturn {\n json: {\n baseUrl: input.baseUrl,\n queryBody: {\n NumeroCelular: input.numeroCelular,\n },\n },\n};" + } + }, + { + "id": "get-agendawsapp", + "name": "GET /AgendaWSApp", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [780, 0], + "parameters": { + "method": "GET", + "url": "={{ $json.baseUrl }}/AgendaWSApp", + "authentication": "genericCredentialType", + "genericAuthType": "httpBasicAuth", + "sendBody": true, + "contentType": "json", + "specifyBody": "json", + "jsonBody": "={{ JSON.stringify($json.queryBody) }}", + "options": { + "response": { "response": { "fullResponse": true, "neverError": true, "responseFormat": "autodetect" } }, + "timeout": 15000 + } + }, + "credentials": { + "httpBasicAuth": { "id": "u8XXJ6wyrKuGJp4Y", "name": "SysLife Basic Auth - clínica pendente" } + } + }, + { + "id": "classificar", + "name": "Classificar resultado", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [1040, 0], + "parameters": { + "mode": "runOnceForEachItem", + "jsCode": "const status = $json.statusCode;\nconst rawBody = $json.body;\n\nfunction classifyError(status, context) {\n if (status === 401 || status === 403) {\n return { code: \"AUTHENTICATION_ERROR\", message: `Falha de autenticação na API do SysLife ao ${context} (status ${status}).` };\n }\n if (status === 404) {\n return { code: \"NOT_FOUND\", message: `Recurso não encontrado na API do SysLife ao ${context} (status ${status}).` };\n }\n if (status === 400 || status === 422) {\n return { code: \"VALIDATION_ERROR\", message: `A API do SysLife rejeitou os dados enviados ao ${context} (status ${status}).` };\n }\n return { code: \"SYSLIFE_API_ERROR\", message: `A API do SysLife retornou um erro inesperado ao ${context} (status ${status}).` };\n}\n\nif (status < 200 || status >= 300) {\n return { json: { success: false, error: classifyError(status, \"buscar agendamento por telefone\") } };\n}\n\nreturn { json: { success: true, data: rawBody } };" + } + } + ], + "connections": { + "Execute Workflow Trigger": { "main": [[{ "node": "Config SysLife", "type": "main", "index": 0 }]] }, + "Config SysLife": { "main": [[{ "node": "Montar query", "type": "main", "index": 0 }]] }, + "Montar query": { "main": [[{ "node": "GET /AgendaWSApp", "type": "main", "index": 0 }]] }, + "GET /AgendaWSApp": { "main": [[{ "node": "Classificar resultado", "type": "main", "index": 0 }]] } + } +} +``` + +Anotar o `id` retornado. + +- [ ] **Step 2: Validar** + +`n8n_validate_workflow` com o id retornado. Esperado: `errorCount: 0`. + +--- + +### Task 3: Sub-workflow `confirmar_presenca` + +**Interfaces:** +- Consumes: nenhuma. +- Produces: workflow `MCP Server - SysLife - Confirmação - confirmar_presenca`, consumido pela Task 4 e pelo workflow de exemplo (Task 7). + +- [ ] **Step 1: Criar o sub-workflow** + +`n8n_create_workflow`: + +```json +{ + "name": "MCP Server - SysLife - Confirmação - confirmar_presenca", + "nodes": [ + { + "id": "trigger", + "name": "Execute Workflow Trigger", + "type": "n8n-nodes-base.executeWorkflowTrigger", + "typeVersion": 1.1, + "position": [0, 0], + "parameters": { + "workflowInputs": { + "values": [ + { "name": "codigoAgenda", "type": "number" }, + { "name": "resposta", "type": "string" } + ] + } + } + }, + { + "id": "config", + "name": "Config SysLife", + "type": "n8n-nodes-base.set", + "typeVersion": 3.5, + "position": [260, 0], + "parameters": { + "mode": "manual", + "includeOtherFields": true, + "assignments": { + "assignments": [ + { "id": "cfg-baseurl", "name": "baseUrl", "type": "string", "value": "http://SUBSTITUIR-PELO-IP-REAL-DA-CLINICA:9000" } + ] + } + } + }, + { + "id": "montar-corpo", + "name": "Montar corpo", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [520, 0], + "parameters": { + "mode": "runOnceForEachItem", + "jsCode": "const input = $json;\nreturn {\n json: {\n baseUrl: input.baseUrl,\n requestBody: {\n CodigoAgenda: input.codigoAgenda,\n Resposta: input.resposta,\n },\n },\n};" + } + }, + { + "id": "post-agendawsapp", + "name": "POST /AgendaWSApp", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [780, 0], + "parameters": { + "method": "POST", + "url": "={{ $json.baseUrl }}/AgendaWSApp", + "authentication": "genericCredentialType", + "genericAuthType": "httpBasicAuth", + "sendBody": true, + "contentType": "json", + "specifyBody": "json", + "jsonBody": "={{ JSON.stringify($json.requestBody) }}", + "options": { + "response": { "response": { "fullResponse": true, "neverError": true, "responseFormat": "autodetect" } }, + "timeout": 15000 + } + }, + "credentials": { + "httpBasicAuth": { "id": "u8XXJ6wyrKuGJp4Y", "name": "SysLife Basic Auth - clínica pendente" } + } + }, + { + "id": "classificar", + "name": "Classificar resultado", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [1040, 0], + "parameters": { + "mode": "runOnceForEachItem", + "jsCode": "const status = $json.statusCode;\nconst rawBody = $json.body;\n\nfunction classifyError(status, context) {\n if (status === 401 || status === 403) {\n return { code: \"AUTHENTICATION_ERROR\", message: `Falha de autenticação na API do SysLife ao ${context} (status ${status}).` };\n }\n if (status === 404) {\n return { code: \"NOT_FOUND\", message: `Recurso não encontrado na API do SysLife ao ${context} (status ${status}).` };\n }\n if (status === 400 || status === 422) {\n return { code: \"VALIDATION_ERROR\", message: `A API do SysLife rejeitou os dados enviados ao ${context} (status ${status}).` };\n }\n return { code: \"SYSLIFE_API_ERROR\", message: `A API do SysLife retornou um erro inesperado ao ${context} (status ${status}).` };\n}\n\nif (status < 200 || status >= 300) {\n return { json: { success: false, error: classifyError(status, \"confirmar presença\") } };\n}\n\nreturn { json: { success: true, data: rawBody } };" + } + } + ], + "connections": { + "Execute Workflow Trigger": { "main": [[{ "node": "Config SysLife", "type": "main", "index": 0 }]] }, + "Config SysLife": { "main": [[{ "node": "Montar corpo", "type": "main", "index": 0 }]] }, + "Montar corpo": { "main": [[{ "node": "POST /AgendaWSApp", "type": "main", "index": 0 }]] }, + "POST /AgendaWSApp": { "main": [[{ "node": "Classificar resultado", "type": "main", "index": 0 }]] } + } +} +``` + +`resposta` deve ser documentado na `description` da tool (Task 4) como +aceitando só `"1"` (SIM) ou `"2"` (NÃO), conforme o manual. + +Anotar o `id` retornado. + +- [ ] **Step 2: Validar** + +`n8n_validate_workflow` com o id retornado. Esperado: `errorCount: 0`. + +--- + +### Task 4: Servidor MCP "SysLife - Confirmação de Agenda" com as 3 tools + +**Interfaces:** +- Consumes: os 3 workflow ids das Tasks 1–3. +- Produces: workflow `MCP Server - SysLife - Confirmação de Agenda` (servidor), path `mcp-server-syslife-confirmacao`, consumido pela Task 7 (workflow de exemplo). + +- [ ] **Step 1: Criar o servidor com o `MCP Server Trigger` e os 3 nodes de tool** + +`n8n_create_workflow` — substituir `ID_LISTAR`, `ID_BUSCAR_TELEFONE`, +`ID_CONFIRMAR` pelos ids reais anotados nas Tasks 1–3: + +```json +{ + "name": "MCP Server - SysLife - Confirmação de Agenda", + "nodes": [ + { + "id": "mcp-trigger", + "name": "MCP Server Trigger", + "type": "@n8n/n8n-nodes-langchain.mcpTrigger", + "typeVersion": 1, + "position": [0, 256], + "parameters": { "path": "mcp-server-syslife-confirmacao", "authentication": "none" } + }, + { + "id": "tool-listar", + "name": "listar_agendamentos_do_dia", + "type": "@n8n/n8n-nodes-langchain.toolWorkflow", + "typeVersion": 2.2, + "position": [-320, 448], + "parameters": { + "description": "Lista os agendamentos de uma data futura no SysLife, contando a partir de hoje. Usado como base do lembrete proativo de confirmação — use quantidadeDias=1 para os agendamentos de amanhã.", + "workflowId": { "value": "ID_LISTAR", "mode": "id" }, + "workflowInputs": { + "mappingMode": "defineBelow", + "value": { "quantidadeDias": "={{ $fromAI('quantidadeDias', 'Quantidade de dias a partir de hoje (1 = amanhã, 2 = depois de amanhã, etc.)', 'number') }}" }, + "matchingColumns": [], + "schema": [{ "id": "quantidadeDias", "displayName": "quantidadeDias", "required": false, "defaultMatch": false, "display": true, "canBeUsedToMatch": true, "type": "number" }], + "attemptToConvertTypes": false, + "convertFieldsToString": false + } + } + }, + { + "id": "tool-buscar-telefone", + "name": "buscar_agendamento_por_telefone", + "type": "@n8n/n8n-nodes-langchain.toolWorkflow", + "typeVersion": 2.2, + "position": [-160, 448], + "parameters": { + "description": "Busca o(s) agendamento(s) pendente(s) de um paciente pelo número de celular (DDD + número, só dígitos). Use isso para descobrir qual agendamento o paciente está confirmando quando ele responde 'sim'/'não'.", + "workflowId": { "value": "ID_BUSCAR_TELEFONE", "mode": "id" }, + "workflowInputs": { + "mappingMode": "defineBelow", + "value": { "numeroCelular": "={{ $fromAI('numeroCelular', 'Número de celular do paciente, com DDD, somente dígitos (ex.: 71999999999)', 'string') }}" }, + "matchingColumns": [], + "schema": [{ "id": "numeroCelular", "displayName": "numeroCelular", "required": false, "defaultMatch": false, "display": true, "canBeUsedToMatch": true, "type": "string" }], + "attemptToConvertTypes": false, + "convertFieldsToString": false + } + } + }, + { + "id": "tool-confirmar", + "name": "confirmar_presenca", + "type": "@n8n/n8n-nodes-langchain.toolWorkflow", + "typeVersion": 2.2, + "position": [0, 448], + "parameters": { + "description": "Registra a confirmação de presença de um agendamento no SysLife. resposta deve ser exatamente '1' (paciente confirmou) ou '2' (paciente não confirmou/recusou) — nenhum outro valor é aceito. Use depois de identificar o codigoAgenda correto (ex.: via buscar_agendamento_por_telefone).", + "workflowId": { "value": "ID_CONFIRMAR", "mode": "id" }, + "workflowInputs": { + "mappingMode": "defineBelow", + "value": { + "codigoAgenda": "={{ $fromAI('codigoAgenda', 'Código do agendamento (coAgenda) a confirmar', 'number') }}", + "resposta": "={{ $fromAI('resposta', \"'1' se o paciente confirmou presença, '2' se recusou/não vai comparecer\", 'string') }}" + }, + "matchingColumns": [], + "schema": [ + { "id": "codigoAgenda", "displayName": "codigoAgenda", "required": false, "defaultMatch": false, "display": true, "canBeUsedToMatch": true, "type": "number" }, + { "id": "resposta", "displayName": "resposta", "required": false, "defaultMatch": false, "display": true, "canBeUsedToMatch": true, "type": "string" } + ], + "attemptToConvertTypes": false, + "convertFieldsToString": false + } + } + } + ], + "connections": { + "listar_agendamentos_do_dia": { "ai_tool": [[{ "node": "MCP Server Trigger", "type": "ai_tool", "index": 0 }]] }, + "buscar_agendamento_por_telefone": { "ai_tool": [[{ "node": "MCP Server Trigger", "type": "ai_tool", "index": 0 }]] }, + "confirmar_presenca": { "ai_tool": [[{ "node": "MCP Server Trigger", "type": "ai_tool", "index": 0 }]] } + } +} +``` + +Anotar o `id` do servidor retornado — necessário na Task 7. + +- [ ] **Step 2: Conferir visualmente os textos salvos dos `$fromAI`** + +`n8n_get_workflow` com `{"id": "", "mode": "filtered", "nodeNames": ["listar_agendamentos_do_dia", "buscar_agendamento_por_telefone", "confirmar_presenca"]}`. Conferir que cada valor de `workflowInputs.value` começa com um único `=` (não `==`). + +- [ ] **Step 3: Validar** + +`n8n_validate_workflow` com o id do servidor. Esperado: `errorCount: 0`. + +--- + +### Task 5: Ativar os 4 workflows do MCP Server + +**Interfaces:** +- Consumes: os 4 workflow ids (3 sub-workflows das Tasks 1–3 + servidor da Task 4). +- Produces: MCP Server "SysLife - Confirmação de Agenda" respondendo via protocolo MCP. + +- [ ] **Step 1: Ativar cada um dos 4 workflows** + +Para cada um dos 4 ids (3 sub-workflows + servidor), rodar +`n8n_update_partial_workflow` com `{"id": "", "operations": [{"type": "activateWorkflow"}]}`. + +- [ ] **Step 2: Confirmar ativação** + +`n8n_get_workflow` com `mode: "minimal"` em cada um dos 4 ids — +confirmar `"active": true` em todos. + +--- + +### Task 6: Workflow proativo "Lembrete de Confirmação - SysLife (exemplo)" + +**Interfaces:** +- Consumes: workflow `listar_agendamentos_do_dia` (Task 1) via `Execute Workflow`; tool `enviar_texto` do MCP de Mensageria ZPRO (`Y4ndG7GuIUrmVRxV`, já em produção) via `Execute Workflow`. +- Produces: workflow `Lembrete de Confirmação - SysLife (exemplo)`. + +- [ ] **Step 1: Criar o workflow** + +`n8n_create_workflow` — substituir `ID_LISTAR_AGENDAMENTOS` pelo id +anotado na Task 1: + +```json +{ + "name": "Lembrete de Confirmação - SysLife (exemplo)", + "nodes": [ + { + "id": "cron", + "name": "Cron 8h diário", + "type": "n8n-nodes-base.scheduleTrigger", + "typeVersion": 1.2, + "position": [0, 0], + "parameters": { + "rule": { "interval": [{ "field": "cronExpression", "expression": "0 8 * * *" }] } + } + }, + { + "id": "listar", + "name": "Listar agendamentos de amanhã", + "type": "n8n-nodes-base.executeWorkflow", + "typeVersion": 1.2, + "position": [224, 0], + "parameters": { + "workflowId": { "value": "ID_LISTAR_AGENDAMENTOS", "mode": "id" }, + "workflowInputs": { + "mappingMode": "defineBelow", + "value": { "quantidadeDias": 1 } + } + } + }, + { + "id": "extrair-lista", + "name": "Extrair lista de agendamentos", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [448, 0], + "parameters": { + "jsCode": "const result = $input.item.json;\nif (!result.success) {\n throw new Error('Falha ao listar agendamentos do dia: ' + JSON.stringify(result.error));\n}\nconst agendamentos = Array.isArray(result.data) ? result.data : [];\nreturn agendamentos.map(a => ({ json: a }));" + } + }, + { + "id": "montar-mensagem", + "name": "Montar mensagem de lembrete", + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [672, 0], + "parameters": { + "assignments": { + "assignments": [ + { "name": "numeroWhatsapp", "type": "string", "value": "={{ $json.nuCelularDdd }}{{ $json.nuCelular }}" }, + { "name": "textoLembrete", "type": "string", "value": "=Olá, {{ $json.nmPaciente }}! Passando para confirmar sua consulta{{ $json.txWhatsappProcedimento ? ' de ' + $json.txWhatsappProcedimento : ($json.nmProcedimento ? ' de ' + $json.nmProcedimento : '') }}{{ $json.txWhatsappMedico ? ' com ' + $json.txWhatsappMedico : ($json.nmMedico ? ' com ' + $json.nmMedico : '') }}{{ $json.txWhatsappHorario ? ' em ' + $json.txWhatsappHorario : ' em ' + $json.dtAtendimento + ' às ' + $json.hrAtendimentoAlterado }}. Você confirma presença? Responda SIM ou NÃO." }, + { "name": "externalKey", "type": "string", "value": "=lembrete-syslife-{{ $json.coAgenda }}" } + ] + }, + "includeOtherFields": true + } + }, + { + "id": "enviar", + "name": "Enviar lembrete via Mensageria ZPRO", + "type": "n8n-nodes-base.executeWorkflow", + "typeVersion": 1.2, + "position": [896, 0], + "parameters": { + "workflowId": { "value": "Y4ndG7GuIUrmVRxV", "mode": "id" }, + "workflowInputs": { + "mappingMode": "defineBelow", + "value": { + "number": "={{ $json.numeroWhatsapp }}", + "body": "={{ $json.textoLembrete }}", + "externalKey": "={{ $json.externalKey }}" + } + } + } + } + ], + "connections": { + "Cron 8h diário": { "main": [[{ "node": "Listar agendamentos de amanhã", "type": "main", "index": 0 }]] }, + "Listar agendamentos de amanhã": { "main": [[{ "node": "Extrair lista de agendamentos", "type": "main", "index": 0 }]] }, + "Extrair lista de agendamentos": { "main": [[{ "node": "Montar mensagem de lembrete", "type": "main", "index": 0 }]] }, + "Montar mensagem de lembrete": { "main": [[{ "node": "Enviar lembrete via Mensageria ZPRO", "type": "main", "index": 0 }]] } + } +} +``` + +Nota: `quantidadeDias: 1` é passado como literal fixo (não expressão) — +diferente do Klingo/ClinicAll, o SysLife já calcula "amanhã" no próprio +servidor a partir desse número, então não precisa de um node +`Calcular data` separado. Telefone montado direto de +`nuCelularDdd`+`nuCelular` do próprio item (sem objeto aninhado, +diferente do ClinicAll). O texto usa os campos já formatados pro +WhatsApp (`txWhatsappProcedimento`/`txWhatsappMedico`/ +`txWhatsappHorario`) quando presentes, com fallback pros campos brutos +(`nmProcedimento`/`nmMedico`/`dtAtendimento`+`hrAtendimentoAlterado`). + +- [ ] **Step 2: Validar** + +`n8n_validate_workflow` com o id retornado. Esperado: `errorCount: 0`. + +- [ ] **Step 3: Deixar inativo** + +Mesma razão do Klingo e do ClinicAll — sem clínica SysLife real, um +cron ativo geraria chamadas reais com erro contra a API e tentativas +de envio com dados vazios. Ativação fica para quando houver clínica +real (a credencial Basic Auth já existe; só falta o `baseUrl`). + +--- + +### Task 7: Workflow de exemplo "AI Agent — Confirmação SysLife (exemplo)" + +**Interfaces:** +- Consumes: workflow servidor `MCP Server - SysLife - Confirmação de Agenda` (Task 4, id do servidor). +- Produces: workflow `Confirmação de Agenda - GChat - SysLife+Z-PRO (exemplo)`. + +- [ ] **Step 1: Criar o workflow de exemplo** + +`n8n_create_workflow` — mesmo contrato de webhook Z-PRO/GChat já usado +nos exemplos do Klingo e do ClinicAll (`body.protocol`→`sessionId`, +`body.entrada`→`mensagem`, `body.contactName`→`nomeContato`, +`body.number`→`telefone`), mesma credencial OpenAI real e +compartilhada (`29vMIDah5DTIhUYJ`, "GPT"): + +```json +{ + "name": "Confirmação de Agenda - GChat - SysLife+Z-PRO (exemplo)", + "nodes": [ + { + "id": "webhook", + "name": "Webhook GChat", + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [0, 0], + "parameters": { "httpMethod": "POST", "path": "confirmacao-agenda-syslife-exemplo", "responseMode": "lastNode", "options": {} } + }, + { + "id": "extrair-campos", + "name": "Extrair campos do webhook", + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [224, 0], + "parameters": { + "assignments": { + "assignments": [ + { "name": "sessionId", "type": "string", "value": "={{ $json.body.protocol }}" }, + { "name": "mensagem", "type": "string", "value": "={{ String($json.body.entrada || '').trim() }}" }, + { "name": "nomeContato", "type": "string", "value": "={{ $json.body.contactName }}" }, + { "name": "telefone", "type": "string", "value": "={{ $json.body.number }}" } + ] + } + } + }, + { + "id": "ai-agent", + "name": "AI Agent", + "type": "@n8n/n8n-nodes-langchain.agent", + "typeVersion": 2.1, + "position": [448, 0], + "parameters": { + "text": "={{ $json.mensagem }}", + "systemMessage": "Você é um assistente de confirmação de presença em consultas médicas, via WhatsApp, para uma clínica que usa o sistema SysLife. Seu trabalho: 1) entender se a mensagem do paciente é uma resposta de confirmação (ex.: 'sim', 'confirmo') ou recusa (ex.: 'não', 'não posso ir') a uma consulta agendada; 2) se for, use o telefone do paciente (o telefone do contato do WhatsApp já está disponível — {{ $json.telefone }} — mas confirme com o paciente se não tiver certeza) e chame a tool buscar_agendamento_por_telefone para achar o codigoAgenda pendente; 3) chame confirmar_presenca com esse codigoAgenda e resposta='1' (se o paciente confirmou) ou resposta='2' (se recusou) — nenhum outro valor de resposta é aceito; 4) responda ao paciente confirmando que o registro foi feito. Se a mensagem não for uma resposta de confirmação/recusa clara, peça esclarecimento. Nunca invente um codigoAgenda — sempre busque antes de confirmar." + } + }, + { + "id": "openai-chat-model", + "name": "OpenAI Chat Model", + "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi", + "typeVersion": 1.3, + "position": [448, -160], + "parameters": { "model": { "mode": "id", "value": "gpt-4o-mini" }, "builtInTools": {}, "options": {} }, + "credentials": { "openAiApi": { "id": "29vMIDah5DTIhUYJ", "name": "GPT" } } + }, + { + "id": "memoria", + "name": "Memória da Sessão (Postgres)", + "type": "@n8n/n8n-nodes-langchain.memoryPostgresChat", + "typeVersion": 1.4, + "position": [448, 160], + "parameters": { "sessionIdType": "customKey", "sessionKey": "={{ $json.sessionId }}", "tableName": "n8n_chat_histories", "contextWindowLength": 20 }, + "credentials": { "postgres": { "id": "PLACEHOLDER_SEM_CREDENCIAL", "name": "Postgres - (sem cliente real)" } } + } + ], + "connections": { + "Webhook GChat": { "main": [[{ "node": "Extrair campos do webhook", "type": "main", "index": 0 }]] }, + "Extrair campos do webhook": { "main": [[{ "node": "AI Agent", "type": "main", "index": 0 }]] }, + "OpenAI Chat Model": { "ai_languageModel": [[{ "node": "AI Agent", "type": "ai_languageModel", "index": 0 }]] }, + "Memória da Sessão (Postgres)": { "ai_memory": [[{ "node": "AI Agent", "type": "ai_memory", "index": 0 }]] } + } +} +``` + +Anotar o `id` retornado. + +- [ ] **Step 2: Validar** + +`n8n_validate_workflow` com o id retornado. Esperado: `errorCount: 0` +(a credencial `postgres` é placeholder — aceitável, sem cliente real). + +--- + +### Task 8: Conectar as tools do MCP SysLife ao AI Agent de exemplo + +**Interfaces:** +- Consumes: workflow servidor `MCP Server - SysLife - Confirmação de Agenda` (Task 4, id do servidor) e workflow `Confirmação de Agenda - GChat - SysLife+Z-PRO (exemplo)` (Task 7). +- Produces: `AI Agent` do workflow de exemplo com acesso às tools `buscar_agendamento_por_telefone` e `confirmar_presenca` via `MCP Client Tool`. + +- [ ] **Step 1: Adicionar o node `MCP Client Tool` conectado ao AI Agent** + +`n8n_update_partial_workflow` no workflow da Task 7, numa única chamada +atômica: + +```json +{ + "id": "", + "operations": [ + { + "type": "addNode", + "node": { + "id": "mcp-client-syslife-confirmacao", + "name": "MCP SysLife Confirmação", + "type": "@n8n/n8n-nodes-langchain.mcpClientTool", + "typeVersion": 1, + "position": [448, 320], + "parameters": { + "endpointUrl": "https://devn8n.gtigrupo.com.br/mcp/mcp-server-syslife-confirmacao", + "authentication": "none", + "include": "selected", + "includeTools": ["buscar_agendamento_por_telefone", "confirmar_presenca"] + } + } + }, + { "type": "addConnection", "source": "MCP SysLife Confirmação", "target": "AI Agent", "sourceOutput": "ai_tool", "targetIndex": 0 } + ] +} +``` + +- [ ] **Step 2: Validar** + +`n8n_validate_workflow` no workflow da Task 7. Esperado: `errorCount: 0`. + +- [ ] **Step 3: Deixar inativo** + +Mesma razão das outras tasks — sem credencial Postgres real nem +clínica SysLife real, ativar não teria efeito útil. Fica pronto pra +ativar quando houver cliente real. + +--- + +### Task 9: Atualizar o runbook e commit + +**Interfaces:** +- Consumes: todos os ids de workflow criados nas Tasks 1–8. + +- [ ] **Step 1: Adicionar seção 20 ao runbook** + +Adicionar em `docs/n8n-agendamento-runbook.md`, após a seção 19, uma +seção "20. Confirmação de Agenda via WhatsApp (SysLife)" cobrindo: as +3 tools do MCP Server e seus endpoints (com destaque pro fato de +`GET /AgendaWSApp` servir três propósitos via parâmetro exclusivo), os +ids de todos os 7 workflows criados (3 sub-workflows + servidor + +lembrete proativo + AI Agent de exemplo), a nota de que a credencial +Basic Auth já existe e foi reaproveitada do MVP de agendamento (não +precisou criar nada novo), a simplicidade do enum `Resposta` (`"1"`/`"2"`, +sem terceiro estado como no Klingo e sem strings longas como no +ClinicAll), e o status "estrutura pronta, sem teste de ponta a ponta — +falta só o `baseUrl` real de uma clínica". Seguir o mesmo estilo de +prosa das seções 14, 17, 18 e 19. + +- [ ] **Step 2: Commit** + +```bash +cd "D:\PROJETOS\KLINGO-CLINICAL-SYSLIFE-GCHAT-AGENDAMENTO" +git add docs/n8n-agendamento-runbook.md +git commit -m "docs: registra MCP SysLife Confirmação e confirmação de agenda via WhatsApp + +Co-Authored-By: Claude Sonnet 5 " +``` + +--- + +## Observação sobre execução + +Mesma disciplina dos planos anteriores: validar +(`n8n_validate_workflow`) depois de cada workflow criado/modificado, e +conferir visualmente o texto salvo de qualquer expressão `$fromAI` +antes de considerar uma tool pronta (Task 4, Step 2). Os workflows de +lembrete proativo (Task 6) e de AI Agent de exemplo (Tasks 7–8) ficam +**intencionalmente inativos** ao final deste plano — sem clínica +SysLife real (mesmo com a credencial Basic Auth já pronta, falta o +`baseUrl` real) e sem credencial Postgres válida, ativá-los só geraria +execuções com erro. Diferente do Klingo e do ClinicAll, este é o único +dos três providers de confirmação onde a autenticação **já está +resolvida** (credencial `httpBasicAuth` `u8XXJ6wyrKuGJp4Y` reaproveitada +do MVP de agendamento) — só falta o endereço da clínica real pra testar +de ponta a ponta.