37 KiB
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://<IP>: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, idu8XXJ6wyrKuGJp4Y, nomeSysLife Basic Auth - clínica pendente— usuário/senha fixos do WebService (WebSysLife/$Sys10Life), documentados literalmente no manual, não variam por clínica.baseUrlfica 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 —
$fromAIsó com um único=no início: ao montarworkflowInputs.valuedos nodesCall n8n Sub-Workflow Tooldo servidor, cada expressão deve ser exatamente"={{ $fromAI(...) }}"(um=). Como este plano cria os nodes vian8n_create_workflowdiretamente, o bug do==duplicado não deve ocorrer — conferir mesmo assim comn8n_get_workflow(modefiltered) 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 emAgendaWSApp), então nenhum$fromAIprecisa 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: truepara 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,AgendaConfirmadoficam fora de escopo — não implementar tools para esses endpoints.- Regra do manual:
GET /AgendaWSAppaceitaQuantidadeDiasouCodigoAgendaouNumeroCelular— 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) ouNumeroCelular(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:
{
"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:
{
"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:
{
"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), pathmcp-server-syslife-confirmacao, consumido pela Task 7 (workflow de exemplo). -
Step 1: Criar o servidor com o
MCP Server Triggere os 3 nodes de tool
n8n_create_workflow — substituir ID_LISTAR, ID_BUSCAR_TELEFONE,
ID_CONFIRMAR pelos ids reais anotados nas Tasks 1–3:
{
"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": "<id do servidor>", "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": "<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) viaExecute Workflow; toolenviar_textodo MCP de Mensageria ZPRO (Y4ndG7GuIUrmVRxV, já em produção) viaExecute 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:
{
"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"):
{
"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 workflowConfirmação de Agenda - GChat - SysLife+Z-PRO (exemplo)(Task 7). -
Produces:
AI Agentdo workflow de exemplo com acesso às toolsbuscar_agendamento_por_telefoneeconfirmar_presencaviaMCP Client Tool. -
Step 1: Adicionar o node
MCP Client Toolconectado ao AI Agent
n8n_update_partial_workflow no workflow da Task 7, numa única chamada
atômica:
{
"id": "<id do workflow da Task 7>",
"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
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 <noreply@anthropic.com>"
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.