Snapshot: gti-scheduling-mcp (Z-PRO + Klingo adapters) e workflow n8n de agendamento funcionando de ponta a ponta
- gti-scheduling-mcp: MCP server com ZProAdapter e KlingoAdapter (agendar() completo pros dois) - Workflow n8n 'Agendamento - GChat - Z-PRO': webhook -> AI Agent -> MCP -> Z-PRO, testado com sucesso real - Documentacao: specs, plano, runbook de problemas reais e export do workflow Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,237 @@
|
||||
# Mapeamento da API Z-PRO — pasta "Agendamentos e Lembretes"
|
||||
|
||||
> Este documento contém **apenas** informações verificadas diretamente na
|
||||
> coleção pública do Postman em
|
||||
> https://www.postman.com/comunidade-zdg/z-pro/folder/ktt2uon/agendamentos-e-lembretes
|
||||
> (coleção "Z-PRO v4.x.x.x", pasta "📅 Agendamentos e Lembretes").
|
||||
> Nenhum valor de token, header sensível ou segredo real foi copiado.
|
||||
>
|
||||
> **Status: análise incompleta.** A integração real (`ZProAdapter`) **não foi
|
||||
> implementada** nesta etapa porque partes essenciais do contrato não estão
|
||||
> disponíveis na coleção. Veja a seção "O que não foi possível verificar".
|
||||
|
||||
## Itens existentes na pasta
|
||||
|
||||
A pasta contém 10 requests. Apenas os relacionados ao escopo atual
|
||||
(consultar agendamentos / criar agendamento) foram analisados em detalhe;
|
||||
os demais foram apenas identificados para não serem confundidos com o
|
||||
escopo.
|
||||
|
||||
| Nome | Método | Path | No escopo atual? |
|
||||
| ---------------------- | ------ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------ |
|
||||
| AppointmentCreate | POST | `/v2/api/external/{{ApiID}}/appointment/create` | ✅ sim |
|
||||
| AppointmentList | GET | `/v2/api/external/{{ApiID}}/appointment/list` | ✅ sim (única consulta existente — ver ressalva abaixo) |
|
||||
| AppointmentShow | GET | `/v2/api/external/{{ApiID}}/appointment/show/{{id}}` | fora do escopo pedido (não é "consultar horários/agendamentos disponíveis" em lista) |
|
||||
| AppointmentUpdate | POST | `/v2/api/external/{{ApiID}}/appointment/update/{{id}}` | ❌ não (remarcação — explicitamente fora do escopo) |
|
||||
| AppointmentDelete | POST | `/v2/api/external/{{ApiID}}/appointment/delete/{{id}}` | ❌ não (cancelamento — explicitamente fora do escopo) |
|
||||
| ScheduleReminderCreate | POST | `/v2/api/external/{{ApiID}}/scheduleReminder/create` | ❌ não (lembretes — explicitamente fora do escopo) |
|
||||
| ScheduleReminderList | GET | `/v2/api/external/{{ApiID}}/scheduleReminder/list` | ❌ não |
|
||||
| ScheduleReminderToggle | POST | `/v2/api/external/{{ApiID}}/scheduleReminder/toggle/{{id}}` | ❌ não |
|
||||
| ScheduleReminderUpdate | POST | `/v2/api/external/{{ApiID}}/scheduleReminder/update/{{id}}` | ❌ não |
|
||||
| ScheduleReminderDelete | POST | `/v2/api/external/{{ApiID}}/scheduleReminder/delete/{{id}}` | ❌ não |
|
||||
|
||||
**Ressalva importante:** a pasta **não possui nenhum endpoint de "horários
|
||||
disponíveis"** (busca de vagas livres/slots). O único endpoint de consulta
|
||||
relacionado a agendamentos é `AppointmentList`, que **lista agendamentos já
|
||||
existentes** (filtráveis por status `pending | confirmed | cancelled |
|
||||
completed`), não horários livres para agendar. Ver detalhes na seção de
|
||||
ambiguidades.
|
||||
|
||||
## Autenticação
|
||||
|
||||
- Tipo: **Bearer Token** (`auth.type = "bearer"`), definido tanto no nível
|
||||
da coleção quanto em cada request individual.
|
||||
- O token é enviado automaticamente pelo Postman no header
|
||||
`Authorization: Bearer <token>`.
|
||||
- Variável de coleção que armazena o token: `BearerToken` (valor de exemplo
|
||||
na coleção: `your_token_here` — placeholder, não é um token real).
|
||||
- Não há chave de API adicional em header (ex.: `X-API-Key`) documentada
|
||||
nestes requests — apenas o Bearer token.
|
||||
|
||||
## Variáveis de coleção relevantes
|
||||
|
||||
| Variável | Uso verificado |
|
||||
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `BaseUrl` | Host base da API (ex.: `https://{{BaseUrl}}/v2/api/external/...`) |
|
||||
| `ApiID` | Segmento obrigatório da URL em **todos** os endpoints desta pasta: `/v2/api/external/{{ApiID}}/...`. Não há descrição do que ele representa (ex.: id da instância/empresa) além do nome da variável. |
|
||||
| `BearerToken` | Token usado na autenticação Bearer |
|
||||
| `SuperAdminToken` | Não utilizado em nenhum request desta pasta |
|
||||
|
||||
**Achado relevante para configuração:** o `ApiID` é parte obrigatória da
|
||||
URL de todo endpoint. A configuração atual do projeto
|
||||
(`src/config/config.types.ts`, `src/config/env.config.ts`) já prevê
|
||||
`ZPRO_INSTANCE_ID` como **opcional**. Se `ApiID` corresponder a esse campo,
|
||||
ele precisa passar a ser **obrigatório** — mas isso não deve ser alterado
|
||||
sem confirmação, pois não há descrição na coleção que confirme a
|
||||
equivalência semântica entre `ApiID` e "instância".
|
||||
|
||||
## AppointmentCreate (criar agendamento)
|
||||
|
||||
- Método: `POST`
|
||||
- URL: `https://{{BaseUrl}}/v2/api/external/{{ApiID}}/appointment/create`
|
||||
- Auth: Bearer (herdado da coleção)
|
||||
- Headers explícitos no request: nenhum além dos automáticos do Postman
|
||||
(Bearer + `Content-Type: application/json`, implícito pelo modo de body
|
||||
`raw`/`json`)
|
||||
- Query params: nenhum
|
||||
- Corpo da requisição (JSON, `dataMode: raw`, exemplo salvo na coleção):
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "Reunião de Vendas",
|
||||
"description": "Reunião sobre proposta comercial",
|
||||
"contactId": "{{contactId}}",
|
||||
"contactName": "João Silva",
|
||||
"contactPhone": "5511999999999",
|
||||
"whatsappId": "{{whatsappId}}",
|
||||
"startAt": "2026-04-15T14:00:00.000Z",
|
||||
"endAt": "2026-04-15T15:00:00.000Z",
|
||||
"status": "pending",
|
||||
"notes": "Levar proposta impressa"
|
||||
}
|
||||
```
|
||||
|
||||
- Campos identificados no exemplo: `title`, `description`, `contactId`,
|
||||
`contactName`, `contactPhone`, `whatsappId`, `startAt`, `endAt`,
|
||||
`status`, `notes`.
|
||||
- **Não há indicação, na coleção, de quais desses campos são obrigatórios
|
||||
versus opcionais** (o Postman não declara isso fora do exemplo).
|
||||
- Formato de sucesso, formato de erro e o campo que identifica o
|
||||
agendamento criado (`id`, `_id`, `appointment.id`, etc.): **não
|
||||
verificados** — não há nenhuma resposta de exemplo salva neste request na
|
||||
coleção (`responses_order: []`).
|
||||
|
||||
## AppointmentList (única consulta existente no escopo)
|
||||
|
||||
- Método: `GET`
|
||||
- URL: `https://{{BaseUrl}}/v2/api/external/{{ApiID}}/appointment/list`
|
||||
- Auth: Bearer (herdado da coleção)
|
||||
- Query params verificados:
|
||||
|
||||
| Parâmetro | Exemplo | Habilitado por padrão | Descrição na coleção |
|
||||
| ----------- | -------------------------- | --------------------- | --------------------------------------------------- |
|
||||
| `page` | `1` | sim | — |
|
||||
| `limit` | `20` | sim | — |
|
||||
| `status` | `pending` | sim | `pending \| confirmed \| cancelled \| completed` |
|
||||
| `startFrom` | `2026-04-01T00:00:00.000Z` | **não** (disabled) | Filtrar agendamentos a partir desta data (ISO 8601) |
|
||||
| `startTo` | `2026-04-30T23:59:59.000Z` | **não** (disabled) | Filtrar agendamentos até esta data (ISO 8601) |
|
||||
| `search` | `""` | **não** (disabled) | Busca por título ou nome do contato |
|
||||
|
||||
- Corpo da requisição: nenhum (GET).
|
||||
- Formato de sucesso (envelope, paginação, forma de cada item da lista) e
|
||||
formato de erro: **não verificados** — sem resposta de exemplo salva.
|
||||
|
||||
## O que não foi possível verificar
|
||||
|
||||
1. **Formato de sucesso de qualquer endpoint** (envelope da resposta,
|
||||
nomes de campos, se há paginação e como ela é representada).
|
||||
2. **Formato de erro de qualquer endpoint** (estrutura do corpo de erro,
|
||||
nomes de campos como `message`/`error`/`code`).
|
||||
3. **Identificador retornado após a criação** do agendamento (nome do
|
||||
campo, ex.: `id` vs `_id` vs `appointment.id`).
|
||||
4. **Conceito de "horário disponível"**: a pasta não expõe nenhum endpoint
|
||||
de disponibilidade/slots livres. `AppointmentList` lista agendamentos
|
||||
já existentes (ocupados), não horários livres. Não é seguro tratar a
|
||||
ausência de um agendamento como "horário disponível" — isso seria
|
||||
inventar uma regra de negócio não documentada.
|
||||
5. **Relação entre `slotId` (tipo genérico do projeto,
|
||||
`CreateAppointmentInput.slotId`) e a API Z-PRO**: `AppointmentCreate`
|
||||
não recebe um identificador de horário/slot — o agendamento é criado
|
||||
diretamente por `startAt`/`endAt`. Não há, na coleção, nenhum conceito
|
||||
de "slot" com identificador próprio.
|
||||
6. **Campos obrigatórios vs. opcionais** no corpo de `AppointmentCreate`
|
||||
(a coleção só mostra um exemplo, sem schema declarado).
|
||||
7. **Semântica exata de `ApiID`** (é o identificador da instância? da
|
||||
empresa? do WhatsApp conectado?) — não há descrição na coleção.
|
||||
|
||||
## O que é necessário para prosseguir com a integração real
|
||||
|
||||
Qualquer um dos itens abaixo destravaria a implementação:
|
||||
|
||||
- Um **exemplo de resposta salvo** (Postman "Save Response" / "Add
|
||||
Example") para `AppointmentCreate` (sucesso e, se possível, erro) e para
|
||||
`AppointmentList` (sucesso).
|
||||
- Confirmação explícita de que **não existe** endpoint de horários/slots
|
||||
disponíveis na API Z-PRO (para então documentar oficialmente que
|
||||
`consultarHorarios()` não tem correspondência 1:1 e decidir, com o
|
||||
responsável do produto, qual comportamento é aceitável — por exemplo,
|
||||
reinterpretar o escopo, ou aguardar um endpoint futuro).
|
||||
- Documentação (Swagger/OpenAPI, README da API, ou outra coleção) que
|
||||
descreva o schema de resposta dos dois endpoints.
|
||||
- Confirmação do significado e da obrigatoriedade de `ApiID`.
|
||||
|
||||
## Decisão tomada nesta etapa
|
||||
|
||||
Como os contratos de resposta não puderam ser verificados, o
|
||||
`ZProAdapter` **não foi alterado** — permanece lançando `Not implemented`,
|
||||
exatamente como estava antes desta análise, para não implementar por
|
||||
suposição. `npm run build`, `npm run lint` e `npm test` continuam
|
||||
passando sem alterações de código.
|
||||
|
||||
## Atualização — resposta real de sucesso capturada (teste manual controlado)
|
||||
|
||||
Em 09/08/2026, foi executado um teste manual único e controlado de
|
||||
`POST /v2/api/external/{ApiID}/appointment/create`, contra uma instância
|
||||
real do Z-PRO, com dados fictícios (`TESTE-MCP-2026-04-20-001`). O
|
||||
agendamento foi criado de fato (confirmado visualmente na interface do
|
||||
Z-PRO) e a resposta HTTP foi capturada:
|
||||
|
||||
- **Content-Type**: `application/json; charset=utf-8`
|
||||
- **Corpo bruto** (dados fictícios, sem nenhuma credencial):
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"source": "manual",
|
||||
"checkInStatus": "none",
|
||||
"id": 2,
|
||||
"title": "TESTE-MCP-2026-04-20-001",
|
||||
"description": null,
|
||||
"contactId": null,
|
||||
"contactName": "Paciente Teste MCP",
|
||||
"contactPhone": "5511900000000",
|
||||
"whatsappId": null,
|
||||
"tenantId": 4,
|
||||
"startAt": "2026-04-20T14:00:00.000Z",
|
||||
"endAt": "2026-04-20T14:30:00.000Z",
|
||||
"status": "pending",
|
||||
"notes": null,
|
||||
"updatedAt": "2026-08-09T20:32:06.426Z",
|
||||
"createdAt": "2026-08-09T20:32:06.426Z",
|
||||
"bookingPageId": null,
|
||||
"bookingEventTypeId": null,
|
||||
"bookingProfessionalId": null,
|
||||
"userId": null,
|
||||
"location": null,
|
||||
"meetingLink": null,
|
||||
"formAnswers": null,
|
||||
"cancelToken": null,
|
||||
"googleEventId": null,
|
||||
"checkInSentAt": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**O que isso confirma:**
|
||||
|
||||
- Envelope de sucesso: `{ success: boolean, data: {...} }`.
|
||||
- Identificador do agendamento criado: `data.id`, **numérico** (não
|
||||
`_id`, não `appointment.id`).
|
||||
- `data.status` reflete o mesmo valor enviado na requisição (`"pending"`)
|
||||
— **não temos, ainda, um exemplo real de `"confirmed"` ou
|
||||
`"cancelled"` retornados pela API**; o mapeamento desses dois valores
|
||||
no `ZProAdapter` é feito por analogia com o valor documentado no
|
||||
request (`AppointmentList`'s filtro de `status`), não por observação
|
||||
direta.
|
||||
- `data.startAt`/`data.endAt` são ecoados exatamente como enviados.
|
||||
- `data.contactId` continua `null` quando não enviado no request (não
|
||||
inventa um contato).
|
||||
|
||||
**O que continua não verificado:** o formato de erro (4xx/5xx) do
|
||||
endpoint (esse teste só cobriu o caminho de sucesso), e os valores reais
|
||||
de `data.status` para `"confirmed"`/`"cancelled"`. O `ZProAdapter`
|
||||
continua tratando **qualquer HTTP 2xx** como sucesso mesmo que o corpo
|
||||
não bata com este envelope exato (ver `zpro.mapper.ts`), como proteção
|
||||
contra duplicidade caso a API mude de formato em algum caso não coberto
|
||||
por este teste único.
|
||||
Reference in New Issue
Block a user