Files
KLINGO-CLINICAL-SYSLIFE-GCHAT/docs/superpowers/plans/2026-08-11-confirmacao-agenda-syslife.md
2026-08-11 16:21:10 -03:00

37 KiB
Raw Permalink Blame History

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, 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:

{
  "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), 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:

{
  "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) 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:

{
  "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 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:

{
  "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.