- 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>
238 lines
14 KiB
Markdown
238 lines
14 KiB
Markdown
# 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.
|