sgp-sdk 1.2.0 → 2.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/CHANGELOG.md +234 -0
  2. package/COMPATIBILITY.md +231 -0
  3. package/MIGRATION.md +344 -0
  4. package/README.md +464 -265
  5. package/dist/address.d.ts +46 -0
  6. package/dist/annotation-selection.d.ts +32 -0
  7. package/dist/budget.d.ts +29 -0
  8. package/dist/classify.d.ts +15 -0
  9. package/dist/client.d.ts +938 -165
  10. package/dist/config.d.ts +115 -0
  11. package/dist/constants.d.ts +85 -0
  12. package/dist/document.d.ts +11 -0
  13. package/dist/effect.d.ts +22 -0
  14. package/dist/executor.d.ts +9 -0
  15. package/dist/exposure.d.ts +21 -0
  16. package/dist/guards.d.ts +2 -0
  17. package/dist/hooks.d.ts +16 -0
  18. package/dist/ids.d.ts +19 -0
  19. package/dist/index.d.ts +55 -49
  20. package/dist/index.js +3750 -1293
  21. package/dist/normalize.d.ts +9 -0
  22. package/dist/operation-sensitivity.d.ts +729 -0
  23. package/dist/operation.d.ts +145 -0
  24. package/dist/operations/catalogs-list-billing-profiles.d.ts +16 -0
  25. package/dist/operations/catalogs-list-nas.d.ts +25 -0
  26. package/dist/operations/catalogs-list-pops.d.ts +14 -0
  27. package/dist/operations/contracts-check-connection.d.ts +56 -0
  28. package/dist/operations/contracts-list-services.d.ts +103 -0
  29. package/dist/operations/contracts-list-status-reasons.d.ts +15 -0
  30. package/dist/operations/contracts-list.d.ts +52 -0
  31. package/dist/operations/contracts-update-status.d.ts +41 -0
  32. package/dist/operations/contracts-update.d.ts +39 -0
  33. package/dist/operations/coverage-check-address.d.ts +46 -0
  34. package/dist/operations/coverage-check.d.ts +43 -0
  35. package/dist/operations/coverage-list-gateways.d.ts +16 -0
  36. package/dist/operations/crm-contracts-create-by-cpf-cnpj.d.ts +97 -0
  37. package/dist/operations/crm-contracts-create.d.ts +221 -0
  38. package/dist/operations/crm-contracts-list.d.ts +50 -0
  39. package/dist/operations/crm-customers-create.d.ts +165 -0
  40. package/dist/operations/crm-customers-update-status.d.ts +41 -0
  41. package/dist/operations/customers-list.d.ts +104 -0
  42. package/dist/{endpoints/ura/lookup-customer.d.ts → operations/customers-search.d.ts} +56 -46
  43. package/dist/operations/documents-add.d.ts +29 -0
  44. package/dist/operations/documents-list.d.ts +31 -0
  45. package/dist/operations/experimental-annotations-list.d.ts +67 -0
  46. package/dist/operations/invoices-list.d.ts +148 -0
  47. package/dist/operations/maintenance-list.d.ts +35 -0
  48. package/dist/operations/occurrences-create.d.ts +125 -0
  49. package/dist/operations/occurrences-list-methods.d.ts +14 -0
  50. package/dist/operations/occurrences-list-types.d.ts +20 -0
  51. package/dist/operations/occurrences-list.d.ts +127 -0
  52. package/dist/{endpoints/ura/list-plans.d.ts → operations/plans-list.d.ts} +33 -23
  53. package/dist/operations/pre-registrations-create.d.ts +276 -0
  54. package/dist/operations/pre-registrations-list-due-days.d.ts +15 -0
  55. package/dist/operations/pre-registrations-list-plans.d.ts +24 -0
  56. package/dist/operations/pre-registrations-list-sellers.d.ts +16 -0
  57. package/dist/operations/services-add-generic.d.ts +52 -0
  58. package/dist/operations/services-add-tv.d.ts +47 -0
  59. package/dist/operations/services-change-plan.d.ts +40 -0
  60. package/dist/operations/services-remove-generic.d.ts +28 -0
  61. package/dist/operations/services-remove-tv.d.ts +29 -0
  62. package/dist/operations/work-orders-get.d.ts +99 -0
  63. package/dist/operations/work-orders-list-comments.d.ts +34 -0
  64. package/dist/operations/work-orders-list-reasons.d.ts +20 -0
  65. package/dist/operations/work-orders-list.d.ts +81 -0
  66. package/dist/path.d.ts +13 -0
  67. package/dist/raw-request.d.ts +26 -0
  68. package/dist/request.d.ts +28 -0
  69. package/dist/result.d.ts +113 -41
  70. package/dist/retry.d.ts +34 -0
  71. package/dist/sensitivity.d.ts +89 -4
  72. package/dist/tolerant.d.ts +21 -0
  73. package/dist/validation.d.ts +3 -0
  74. package/package.json +6 -3
  75. package/dist/endpoint.d.ts +0 -66
  76. package/dist/endpoints/crm/address.d.ts +0 -20
  77. package/dist/endpoints/crm/create-contract.d.ts +0 -109
  78. package/dist/endpoints/crm/create-customer.d.ts +0 -65
  79. package/dist/endpoints/crm/update-customer-status.d.ts +0 -43
  80. package/dist/endpoints/os/list-occurrence-reasons.d.ts +0 -24
  81. package/dist/endpoints/os/list-occurrence-types.d.ts +0 -25
  82. package/dist/endpoints/precadastro/list-due-days.d.ts +0 -19
  83. package/dist/endpoints/precadastro/list-sellers.d.ts +0 -21
  84. package/dist/endpoints/suporte/add-customer-document.d.ts +0 -24
  85. package/dist/endpoints/suporte/list-customer-documents.d.ts +0 -25
  86. package/dist/endpoints/ura/check-coverage.d.ts +0 -46
  87. package/dist/endpoints/ura/list-annotations.d.ts +0 -32
  88. package/dist/endpoints/ura/list-clients.d.ts +0 -58
  89. package/dist/endpoints/ura/list-contracts.d.ts +0 -45
  90. package/dist/endpoints/ura/list-invoices.d.ts +0 -107
  91. package/dist/endpoints/ura/list-nas.d.ts +0 -29
  92. package/dist/endpoints/ura/list-occurrence-methods.d.ts +0 -19
  93. package/dist/endpoints/ura/list-payment-carriers.d.ts +0 -20
  94. package/dist/endpoints/ura/list-pops.d.ts +0 -19
  95. package/dist/endpoints/ura/list-service-orders.d.ts +0 -64
  96. package/dist/endpoints/ura/open-ticket.d.ts +0 -81
  97. package/dist/errors.d.ts +0 -59
  98. package/dist/schema/parse.d.ts +0 -19
  99. package/dist/schema/paths.d.ts +0 -38
  100. package/dist/transport.d.ts +0 -65
package/CHANGELOG.md ADDED
@@ -0,0 +1,234 @@
1
+ # Changelog
2
+
3
+ Formato baseado em [Keep a Changelog](https://keepachangelog.com/pt-BR/1.1.0/); versionamento
4
+ [SemVer](https://semver.org/lang/pt-BR/). `experimental.*` é a exceção declarada: pode mudar de forma
5
+ incompatível em uma versão menor.
6
+
7
+ ## 2.0.0-beta.2 (2026-11-06)
8
+
9
+ ### Adicionado
10
+
11
+ - `contracts.checkConnection(input)`: o veredito do SGP sobre a conexão (`POST /api/ura/verificaacesso/`,
12
+ form, com barra final). `contrato` ou `telefone`; `status_all: 1` e, pela documentação,
13
+ `status_filter` e `uracontato`; `protocolo_ura` **não** é oferecido (abre ocorrência: use
14
+ `occurrences.create`). **Declarada ESCRITA**: o fornecedor documenta que, em manutenção ativa, o SGP
15
+ abre uma ocorrência por padrão mesmo sem `protocolo_ura` (não testado). Nunca é repetida, uma falha
16
+ traz `effect` (em geral `"unknown"`), e o JSDoc recomenda `maintenance.list()` antes. `data` é
17
+ SEMPRE um array: o SGP devolve um array (com `status_all`), `[]` (contrato fora do filtro padrão) ou
18
+ um objeto único (contrato ativo sem `status_all`), que o SDK embrulha. Novo `CONNECTION_STATUS`
19
+ (0 nada checado, 1 online, 2 offline, 9 manutenção; só 1 e 2 observados). `login` é segredo;
20
+ `razaoSocial` e `cpfCnpj` são pessoais. Fora da `scripts/probe.ts`, por ser escrita.
21
+ - `contracts.listServices(input?)`: serviços de um contrato com estado online e sessão
22
+ (`POST /api/suporte/contrato/list/`, form, com barra final). Um array com uma linha por serviço
23
+ (internet e TV); a linha online traz a sessão RADIUS (`rad_*`). Filtros do SGP: `contrato_id` e
24
+ `cliente_id` (exercitados), `cliente_nome`, `cliente_cpfcnpj`, `servico_login` (documentados).
25
+ **Exige ao menos um filtro**: sem filtro o SGP lista os serviços de todos os clientes com senhas em
26
+ texto claro, então o SDK recusa a chamada sem filtro (`INVALID_INPUT`, `attempts: 0`, nada enviado).
27
+ Normalizado por campo: ids em texto e contadores RADIUS
28
+ viram número, `contrato_status` vem aparado; `servico_id` numérico é exigido por linha. Login,
29
+ senha, MACs, IPs (inclusive IPv6 e NAS) e ids de sessão são segredos; nome, contato e endereço são
30
+ pessoais (`getSensitivity("contracts.listServices")`). Leitura: aceita `maxRetries`. A linha online
31
+ é reconstruída da forma mascarada de um contrato real (só tipos).
32
+ - `occurrences.list(input?)`: lista ocorrências com suas ordens de serviço
33
+ (`POST /api/ura/ocorrencia/list/`, form). É o caminho do chamado até a OS: `ordens_servicos[].id`
34
+ (o `numero` da ocorrência é o `ocorrencia` da OS). Uma página, com a `paginacao` do SGP (`total`
35
+ nunca calculado; sem `total` numérico é `INVALID_RESPONSE`). **Exige ao menos um filtro**: sem
36
+ filtro o SGP lista as ocorrências de todos os clientes (até 1000), então o SDK recusa a chamada sem
37
+ filtro (`INVALID_INPUT`, `attempts: 0`, nada enviado); `offset` e `limit` sozinhos não contam. `contrato: 0` e texto vazio em filtro são recusados localmente. Nome, texto livre e
38
+ responsáveis são dados pessoais (`getSensitivity("occurrences.list")`). Leitura: aceita
39
+ `maxRetries`.
40
+ - `maintenance.list()`: novo grupo `maintenance` (SGP "manutenção"), `GET /api/ura/manutencao/list/`,
41
+ sem filtro. Devolve **todos** os avisos, inclusive os inativos: o consumidor filtra por `ativa` e
42
+ pelo período. Array do SGP, sem envelope; sem dado de cliente. Leitura: aceita `maxRetries`.
43
+ - `coverage.checkAddress(input)`: viabilidade por endereço (`POST /api/ura/viabilidade/`, form) com
44
+ os filtros do SGP (`cep`, `cidade`, `logradouro`, `bairro`, `numero_inicial`, `numero_final`; ao
45
+ menos um). Devolve o objeto do SGP como veio (observado `{ viabilidade: true }` e `{}`): **sem a
46
+ chave `viabilidade` é desconhecido, nunca "sem cobertura"**, e a resposta de "sem cobertura" nunca
47
+ foi observada. O endereço é dado pessoal. Leitura: aceita `maxRetries`.
48
+ - `workOrders.get(input)`: uma OS por id (`POST /api/os/list/id/{id}`, form, sem barra final). O SGP
49
+ devolve um objeto plano de 72 campos (OS, cliente, contrato, endereço, plano e serviço), mantido
50
+ como veio; só `os_id` é exigido. **Traz senhas em texto claro**: `servico_password`,
51
+ `wan_password`, `wifi_password` e `wifi_password_5` (e login, MAC, IP do serviço e IP do NAS) são
52
+ segredos; nome, e-mail, contatos, endereço, texto livre e responsáveis são pessoais
53
+ (`getSensitivity("workOrders.get")`). Leitura: aceita `maxRetries`.
54
+ - `workOrders.listComments(input)`: comentários de uma OS (`GET /api/os/{id}/comentario/list/`).
55
+ Array do SGP, sem envelope. Só `[]` foi observado: o formato dos itens vem da coleção do
56
+ fornecedor. Leitura: aceita `maxRetries`.
57
+ - `coverage.listGateways()`: lista os gateways de mapa do provedor (`GET /api/ura/listgatewaymapa`).
58
+ O `id` de cada um é o `gateway` que `coverage.check` recebe, então não é mais preciso `request()`
59
+ para descobri-lo. Leitura: aceita `maxRetries`.
60
+ - `services.addGeneric(input)` e `services.removeGeneric(input)`: serviço extra do tipo genérico
61
+ (`POST /api/servico/generico` e `DELETE /api/servico/generico/{servico_id}`), a rota que a TSMX
62
+ recomenda. Escritas, nunca repetidas. `planobase_id` é `PlanId` de um plano criado como genérico no
63
+ painel. Só uma exchange foi observada: o 400 de plano de outro tipo, que vira `REJECTED` com
64
+ `reason: "PLAN_NOT_GENERIC"` e `effect: "none"` (corpo reconstruído). O sucesso das duas rotas é
65
+ apenas documentado: `data` é o objeto JSON do SGP, sem campo assumido. Remover o único serviço do
66
+ contrato é recusado pelo SGP segundo o fornecedor, sem código de motivo por falta de corpo
67
+ observado.
68
+ - `occurrences.create`: a recusa com HTTP 200 e `status: 0` cujo `msg` é "Data e hora de agendamento
69
+ inválida, formato esperado: AAAA-MM-DD HH:MM" (agendamento com segundos; captura bruta, observada uma
70
+ vez, cliente sintético) agora é `REJECTED` com `reason: "INVALID_SCHEDULE"` e `effect: "none"` (a
71
+ releitura de `occurrences.list({ contrato })` achou o mesmo total antes e depois: nada foi criado). Só
72
+ casa com `status` igual ao número 0 e `msg` em texto que começa por "Data e hora de agendamento
73
+ inválida" (aparado, sem diferenciar maiúsculas); qualquer outra recusa `status: 0` segue `REJECTED`
74
+ sem `reason` e com `effect: "unknown"`. O JSDoc passa a dizer que `data_hora_agendamento` tem de ser
75
+ exatamente `AAAA-MM-DD HH:MM` (o SDK não confere o formato: quem decide é o SGP) e registra o que se
76
+ observou: ocorrência sem `motivoos` é aceita e o SGP cria a OS mesmo assim, `status_encerrado: 1`
77
+ encerra ocorrência e OS, e encerrar uma OS de instalação (`motivoos` 10) não altera o status do
78
+ contrato. A fixture da recusa deixa de ser reconstruída.
79
+
80
+ ### Alterado
81
+
82
+ - **Mudança de comportamento (versus beta.1): `cpfcnpj` canônico nas escritas que gravam o
83
+ documento**, `preRegistrations.create` e `crm.customers.create` (as leituras não mudam). O SDK
84
+ tira `.`, `-`, `/` e espaços e envia o documento na máscara da base: em `personType: "F"`, exatamente 11
85
+ dígitos viram `999.999.999-99`; em `"J"`, exatamente 14 alfanuméricos (12 letras ou dígitos e 2
86
+ dígitos, a regra da Receita) viram `AA.AAA.AAA/AAAA-99`, com as letras em maiúsculas. Entrada já na
87
+ máscara segue igual. **Qualquer outra entrada é recusada localmente**: `INVALID_INPUT`,
88
+ `attempts: 0`, `effect: "none"`, nada enviado, e `error.issues` traz `["cpfcnpj: expected ..."]`
89
+ (só o caminho e o formato esperado, nunca o valor). Isso inclui tamanho errado, caracteres
90
+ inválidos, letras em CPF ou nas 2 últimas posições do CNPJ, o tipo trocado (CNPJ em `F`, CPF em `J`)
91
+ e texto vazio. Só o formato: sem dígitos verificadores. Motivo: o pré-cadastro grava o documento
92
+ como recebido e o CRM compara com a forma formatada; um CPF só com dígitos escapou da checagem de
93
+ duplicidade e criou outro cliente. `error.issues` agora também aparece em `INVALID_INPUT` (só neste
94
+ caso). Ver MIGRATION, seção 8.
95
+ - `preRegistrations.create`: o sucesso agora tipa os ids que o SGP devolveu numa captura
96
+ bruta (um cliente sintético): `precadastro_id` (`number`), `new_cliente_id` (`CustomerId`) e
97
+ `new_contrato_id` (`number`), todos opcionais e normalizados como os demais (texto numérico vira
98
+ número, `""` vira `null`, texto formatado sai de `data` com `warning`). **Não são invariantes**: os
99
+ registros mais antigos listam só `message` e o decoder da 1.x descartava chaves desconhecidas, então
100
+ não se sabe se os ids já eram enviados antes (a fixture só com `message` é reconstruída e fica como
101
+ segundo caso, documentado), e o SDK nunca inventa um id ausente; sem eles, releia por `customers.list({ cpfcnpj })`, como antes. Chaves
102
+ desconhecidas seguem preservadas. Versus beta.1 essas três chaves eram `unknown`: ver MIGRATION,
103
+ seção 8.
104
+ - `crm.customers.create`: o 400 `errors.cpfcnpj` "Já existe um cliente com o CPF informado." (documento já cadastrado, captura bruta de um cliente sintético) agora é `REJECTED` com `reason: "DOCUMENT_ALREADY_REGISTERED"` (o mesmo de `preRegistrations.create`) e `effect: "none"`; qualquer outro erro de campo segue sem `reason`. O JSDoc foi corrigido: o SGP recusa documento já cadastrado em qualquer status (o status 6 inclusive); a exceção observada é um cliente com o documento gravado sem formatação (ex.: pré-cadastro só com dígitos), não detectado e duplicado; leia `customers.list({ cpfcnpj })` antes.
105
+ - **Mudança de comportamento (versus beta.1): `invoices.list`**. `titulos[].valor`, `valorDesconto`,
106
+ `valorCorrigido`, `valorPago` e `valorPagoParcial` já eram `number | null` na beta.1; o tipo não muda.
107
+ Um `""` em `valor` (o SGP manda texto vazio quando não há valor) saía de `data` com um `warning` e agora
108
+ chega como `null`, nunca `0`. Ver MIGRATION, seção 8.
109
+ - `crm.customers.create` e `preRegistrations.create`: o 400 de CPF com dígitos verificadores errados
110
+ (captura bruta de um cliente sintético, documento fictício) agora é `REJECTED` com
111
+ `reason: "DOCUMENT_INVALID"` e `effect: "none"` (releitura: nada foi criado). No CRM o corpo é
112
+ `{"errors":{"cpfcnpj":"CPF inválido."}}`; no pré-cadastro é `{"message":"CPF inválido","error":"Requisição
113
+ inválida."}`, uma forma nova com `message` e `error` (`error.sgp` traz o `error`; o `message` fica em
114
+ `response.body`). Só o texto observado casa, sem diferenciar maiúsculas; qualquer outro 400 segue sem
115
+ `reason`. O SDK continua conferindo só o formato do documento: os dígitos verificadores do CNPJ nunca
116
+ foram testados.
117
+ - `preRegistrations.create`: o 400 `{"message":"Login <login> já existe","error":"Requisição inválida."}`
118
+ (login PPPoE já em uso; captura bruta, observada uma vez, cliente sintético) agora é `REJECTED` com
119
+ `reason: "LOGIN_ALREADY_REGISTERED"` e `effect: "none"` (releituras por documento e por login: nada foi
120
+ criado). Só o corpo observado casa (exatamente as chaves `message` e `error`, ambas texto; `message`
121
+ começa com `Login `, traz um login não vazio e termina com ` já existe`, sem diferenciar maiúsculas);
122
+ qualquer outro 400 segue sem `reason`. O login que o `message` repete nunca é copiado para um campo do
123
+ erro: `error.sgp` traz só o `error`, e o `message` fica em `response.body`. O JSDoc recomenda
124
+ `customers.list({ login })` antes de criar.
125
+ - `customers.list`: novo filtro `login` (documentado pelo fornecedor), que casa o login PPPoE exato
126
+ (observado ao vivo: o login inteiro lista o cliente; um prefixo e um login inexistente listam nada). O
127
+ `login` é segredo em `getSensitivity("customers.list")`.
128
+ - **Quebra de tipo (versus 2.0.0-beta.1): normalização por campo**. Onde o
129
+ SGP manda o mesmo campo com tipo variável ou em texto, `data` traz um só; `""` e `null` viram `null`,
130
+ texto formatado nunca é adivinhado (campo sai de `data` com `warning`; o original fica em
131
+ `response.body`). Nomes inalterados. Campos: `coverage.check` `lat`, `lng`, `distancia`, `id` (`number | null`; antes `number | string`);
132
+ `services.addTv` e `services.removeTv` `clientecontrato_id` (`number | null`, antes `string`);
133
+ `workOrders.get` `servico_id`, `contrato_status_id` e `os_setor_id` (`number | null`) e
134
+ `contrato_status` (aparado); `preRegistrations.listPlans` `gateway.gateway_id` e `gateway.id`
135
+ (`number | null`). Ver MIGRATION, seção 8.
136
+ - `occurrences.create`: a nota de que a ligação ocorrência para OS "não foi confirmada" foi
137
+ substituída pelo que se observou: `occurrences.list` devolve a OS da ocorrência. Que o `protocolo`
138
+ do `create` é igual ao `numero` do `list` segue sem captura bruta.
139
+ - `contracts.listStatusReasons`: a fixture agora é uma captura bruta ao vivo (status, content-type e
140
+ corpo) no lugar da reconstruída; a ordem dos ids é a do SGP (1, 2, 4, 3, 5, 6, 7). COMPATIBILITY:
141
+ Observado.
142
+ - `scripts/probe.ts`: a checagem da whitelist do token virou `GET /api/ura/tecnicos` (sem a barra
143
+ final, que o SGP recusa com 403 `TOKEN_ROUTE_NOT_ALLOWED`; com a barra a rota responde 200). Uma
144
+ leitura, que não muda dado mesmo que a rota venha a ser liberada.
145
+ - COMPATIBILITY e README: seção "Limitações confirmadas pelo fornecedor (TSMX, 06/11)" (sem API para
146
+ segundo contrato em cliente definitivo, telefonia, extras de internet e multimídia, "Alterar Titular";
147
+ extras pela API só TV e genérico; documentação oficial só no Postman; sem rota de correspondência
148
+ entre os dois catálogos de plano). As linhas PJ (`/J`) de `preRegistrations.create` e
149
+ `crm.customers.create` passam a "Confirmado pelo fornecedor, não testado". CNPJ alfanumérico: os
150
+ filtros `cpfcnpj` de leitura o aceitam (200 em `customers.list` e `invoices.list`, o 400 de sempre
151
+ em `crm.contracts.list`, sem rejeição de formato; observado ao vivo, sem nenhum achado porque
152
+ nenhum cliente tem esse CNPJ); a criação de PJ com ele não foi testada.
153
+
154
+ ## 2.0.0-beta.1 (2026-11-06)
155
+
156
+ Redesenho completo como gateway de integração do SGP. Quebra de contrato sem aliases:
157
+ veja o [guia de migração](MIGRATION.md). Publicada na tag `next` do npm; `latest` continua na 1.2.0
158
+ até a 2.0.0 estável.
159
+
160
+ ### Mudanças incompatíveis
161
+
162
+ - **Superfície nova, em inglês e agrupada por conceito do SGP**: `customers`, `crm.customers`,
163
+ `crm.contracts`, `preRegistrations`, `contracts`, `services`, `plans`, `occurrences`, `workOrders`,
164
+ `invoices`, `documents`, `coverage`, `catalogs`, `experimental.annotations` e `request()`. Os
165
+ namespaces `ura`, `os`, `precadastro`, `crm.*` antigos e `suporte` foram removidos; os campos dos
166
+ dados mantêm os nomes do SGP. Parâmetros posicionais (`clienteId`, `file`) viraram campos da
167
+ entrada.
168
+ - **Configuração**: `createSgpClient({ baseUrl, credentials: { kind: "token", token, app }, ... })`.
169
+ Configuração inválida **lança** `InvalidConfigurationError` na criação; `baseUrl` precisa ser uma
170
+ origem (sem caminho).
171
+ - **Modelo de resultado**: `{ ok, data, response, attempts, warnings? }` ou
172
+ `{ ok: false, error, response, attempts }`, no lugar de `success`/`error.status`/`error.detail`/
173
+ `error.endpoint`. `response` traz status, headers (credenciais redigidas) e corpo (`json`, `text`,
174
+ `empty`, `unavailable` ou `withheld`), ou `null` quando não houve resposta.
175
+ - **Códigos de erro**: `INVALID_INPUT`, `UNAUTHORIZED`, `FORBIDDEN`, `RATE_LIMITED`,
176
+ `ROUTE_NOT_ALLOWED` (restrição local do SDK: `request()` na rota de anotações), `NOT_FOUND`, `REJECTED`, `UPSTREAM`, `NETWORK`, `TIMEOUT`,
177
+ `ABORTED`, `INVALID_RESPONSE`. Saem `VALIDATION`, `METHOD_NOT_ALLOWED`, `SCHEMA_MISMATCH` e
178
+ `UNKNOWN`; o antigo `ROUTE_NOT_ALLOWED` virou `FORBIDDEN` com `reason: "TOKEN_ROUTE_NOT_ALLOWED"`.
179
+ - **Efeito de escritas**: `outcome` (`not_applied`/`unknown`) virou `error.effect` (`none`/`unknown`),
180
+ presente em toda escrita. `"none"` agora exige evidência (recusa local, cancelamento antes do envio,
181
+ erro de campo ou de negócio do SGP, ou recusa conhecida da operação); um 4xx de formato não
182
+ reconhecido é `"unknown"`.
183
+ - **Fidelidade**: chaves desconhecidas são preservadas em todos os níveis (o 1.x as descartava) e os
184
+ arrays que chegavam como `[{}]` agora chegam inteiros. Um item que não pode ser interpretado é
185
+ `INVALID_RESPONSE`. Campos secundários com tipo inesperado saem de `data` com `warnings`; o original
186
+ fica em `response.body`.
187
+ - **`coverage.check` não converte mais** `lat`, `lng` e `distancia` para número.
188
+ - **Entradas estritas**: chaves desconhecidas são recusadas localmente (`INVALID_INPUT`). Só os campos
189
+ que o fornecedor marca como obrigatórios são obrigatórios.
190
+ - **IDs com marca**: `CustomerId`, `PlanId` e `PreRegistrationPlanId` (tipos de compilação); os dois
191
+ catálogos de planos não se misturam.
192
+ - **Dados sensíveis**: `omitSensitive` e as tuplas `*SecretKeys`/`*PersonalKeys` foram substituídos por
193
+ `getSensitivity`, `pick` (allowlist) e `toSafeLog`.
194
+ - **Constantes renomeadas**: `formaCobrancaCodigo` para `BILLING_METHOD`, `modoAquisicao` para
195
+ `ACQUISITION_MODE`, `crmStatus` para `CRM_STATUS`.
196
+ - **Anotações**: `ura.listAnnotations` virou `experimental.annotations.list`. O corpo da resposta é
197
+ retido (`withheld`), cada anotação traz `raw`, identidade de outro cliente em qualquer profundidade
198
+ falha a chamada, e `request()` recusa a rota.
199
+ - Todos os exports `*Definition`, `*KeyFields` e `*ItemPath` foram removidos (a definição de uma
200
+ operação é interna).
201
+
202
+ ### Adicionado
203
+
204
+ - Operações novas: `preRegistrations.create` (PF/PJ, endereço em campos soltos com os nomes do SGP) e `preRegistrations.listPlans`,
205
+ `contracts.update`, `contracts.updateStatus`, `contracts.listStatusReasons`, `services.changePlan`,
206
+ `services.addTv`, `services.removeTv`, `crm.contracts.createByCpfCnpj` e `crm.contracts.list`.
207
+ - `request()`: chamada bruta a uma rota que nenhum método cobre, com a mesma autenticação, prazo,
208
+ cancelamento e hooks (só caminhos sob `/api/`, sem redirecionamento, sem retry, `effect: "unknown"`
209
+ depois do envio).
210
+ - Por chamada: `abortSignal`, `timeoutMs` (um orçamento de tempo por chamada lógica) e `maxRetries`.
211
+ `TIMEOUT` (prazo do SDK) distinto de `ABORTED` (seu sinal).
212
+ - Retry opt-in para leituras (`maxRetries`, padrão 0), só em falhas transitórias, com backoff,
213
+ jitter e `Retry-After`. Escritas e `request()` nunca são repetidos. `attempts` em todo resultado.
214
+ - Hooks opcionais e síncronos (`onRequest`, `onResponse`, `onError`) só com metadados; um hook que
215
+ falha não altera o resultado (registro em `diagnostics`).
216
+ - `error.reason` por operação com evidência (`DOCUMENT_ALREADY_REGISTERED`, `STATUS_NOT_CHANGED`,
217
+ `CUSTOMER_NOT_IN_CRM`, `CONTRACT_NOT_ACTIVE`, `TOKEN_ROUTE_NOT_ALLOWED`) e `error.sgp` com os campos de
218
+ erro do SGP como vieram.
219
+ - Constantes `CONTRACT_STATUS` e `SERVICE_TYPE`; `ACQUISITION_MODE` compartilhada entre CRM e
220
+ pré-cadastro.
221
+ - Documentação em português: README, [MIGRATION.md](MIGRATION.md),
222
+ [COMPATIBILITY.md](COMPATIBILITY.md) e exemplos (`examples/` do repositório) compilados e executados na CI.
223
+ - CI no GitHub Actions e verificações do pacote construído (tipos de consumidor em NodeNext e Bundler,
224
+ smoke em Node e Bun, conteúdo do `npm pack`).
225
+
226
+ ### Em aberto antes da 2.0.0 estável
227
+
228
+ - Testes unitários também no Node (hoje no Bun; o Node é coberto pelo smoke do pacote construído).
229
+ - Rotas e recusas ainda reconstruídas ou não observadas: veja [COMPATIBILITY.md](COMPATIBILITY.md).
230
+
231
+ ## 1.x
232
+
233
+ Congelada. A última versão é a 1.2.0; as versões 1.0.0 a 1.2.0 estão nas tags `v1.0.0` a `v1.2.0` do
234
+ repositório.
@@ -0,0 +1,231 @@
1
+ # Matriz de compatibilidade
2
+
3
+ O que foi **provado**, e como, para cada operação do sgp-sdk 2.0. Serve para saber até onde confiar
4
+ no contrato de uma operação antes de usá-la em produção.
5
+
6
+ - **Instância:** uma instância de produção de um provedor. Nenhuma outra foi usada.
7
+ - **Versão do SGP:** **desconhecida**. Nenhuma observação foi associada a uma versão do SGP, e
8
+ comportamento pode mudar entre versões.
9
+ - **Dados:** as escritas observadas usaram clientes, contratos e serviços de teste sintéticos criados
10
+ para a verificação. Esta matriz não traz hosts, credenciais nem identificadores reais, e omite ids.
11
+ - **Datas:** `AAAA-MM-DD` (UTC, a data do identificador da execução de verificação). Capturas feitas
12
+ para a série 1.x aparecem como "1.x" com a data em que a fixture entrou no repositório
13
+ (2026-10-30 a 2026-10-31); a data exata da captura não foi registrada (não verificado).
14
+
15
+ ## Níveis de evidência
16
+
17
+ | Nível | Significa |
18
+ | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
19
+ | **Observado (bruta)** | Resposta real da instância, com status, headers e corpo registrados como foram recebidos. |
20
+ | **Observado (corpo)** | Resposta real da instância, mas só o corpo foi registrado (mascarado); os headers da fixture são reconstruídos. |
21
+ | **Confirmado pelo fornecedor, não testado** | O suporte da TSMX afirmou por escrito o comportamento (06/11); nunca foi exercitado nesta instância. |
22
+ | **Documentado** | Descrito pelo fornecedor (coleção de requisições ou suporte), **nunca visto** numa resposta real. |
23
+ | **Reconstruído** | Corpo ou texto reconstruído de relatos (prosa de verificações), do contrato da série 1.x ou de testes, **sem captura**. O formato real pode diferir. |
24
+
25
+ Quando a resposta real difere do esperado, o SDK falha de forma segura: `INVALID_RESPONSE`, e numa
26
+ escrita `effect: "unknown"`. Um nível "Observado" prova **um** comportamento, uma vez ou poucas;
27
+ não é uma garantia de contrato do SGP. A prova de "nada foi aplicado" é uma releitura do estado, não
28
+ uma garantia.
29
+
30
+ As fixtures que alimentam os testes estão em `fixtures/` do repositório (fora do pacote npm), cada uma
31
+ com a fonte (`source`) e o nível de evidência (`evidence`) no próprio arquivo. A data da execução de
32
+ verificação aparece na fonte quando existe; as fixtures derivadas da série 1.x não têm data, e o
33
+ cenário é o caminho do arquivo (`<grupo>/<operação>/<caso>`).
34
+
35
+ ## Matriz por operação
36
+
37
+ Cada linha é um cenário. As colunas "Data" e "Evidência" valem para aquele cenário.
38
+
39
+ ### `customers`
40
+
41
+ | Operação | Cenário | Evidência | Data | Observação |
42
+ | ------------------ | -------------------------------------------------------------------------------------------- | ----------------- | ---------- | ----------------------------------------------------------------------------------------------- |
43
+ | `customers.list` | Página de um cliente de teste, com contratos e serviços aninhados | Observado (corpo) | 2026-11-05 | Os aninhados chegam inteiros (nada de `[{}]`) |
44
+ | `customers.list` | Filtro `login`: o login exato lista o cliente; um prefixo e um login inexistente listam nada | Observado (corpo) | 2026-11-06 | Filtro documentado pelo fornecedor; casa o login PPPoE exato (três leituras, cliente sintético) |
45
+ | `customers.search` | Busca por contrato, um candidato | Observado (corpo) | 1.x | Corpo mascarado |
46
+ | `customers.search` | Sem resultado (`contratos: []`) | Reconstruído | n/d | Vem de um teste da 1.x |
47
+
48
+ ### `crm`
49
+
50
+ | Operação | Cenário | Evidência | Data | Observação |
51
+ | ------------------------------- | ------------------------------------------------------------------- | --------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
52
+ | `crm.customers.create` | PF (`/F`): 200 com `cliente_id` e `message` | Observado (bruta) | 2026-11-05 | Com um CPF que já existia, o SGP criou **outro** cliente com o mesmo documento (ambos listados por `customers.list`) |
53
+ | `crm.customers.create` | PF: 400 CPF já no CRM (`DOCUMENT_ALREADY_REGISTERED`) | Observado (bruta) | 2026-11-06 | Documento já cadastrado (cliente sintético), em qualquer status (o status 6 também recusa; um cliente definitivo que nunca passou pelo CRM também); releitura: nada foi criado. Exceção: documento gravado sem formatação (pré-cadastro só com dígitos) não é detectado (linha acima) |
54
+ | `crm.customers.create` | PF: 400 CPF com dígitos verificadores errados (`DOCUMENT_INVALID`) | Observado (bruta) | 2026-11-06 | `{"errors":{"cpfcnpj":"CPF inválido."}}` (cliente sintético, documento fictício); releitura: nada foi criado. Dígitos verificadores de CNPJ: nunca testados (veja Lacunas conhecidas) |
55
+ | `crm.customers.create` | PF: o CRM grava o CPF formatado (`999.999.999-99`) | Observado (bruta) | 2026-11-06 | Cliente sintético: um CPF enviado só com dígitos voltou formatado em `customers.list`; o 400 de duplicidade compara com a forma formatada. O SDK envia sempre a máscara canônica (veja Notas das capturas) |
56
+ | `crm.customers.create` | PF: 400 de validação de campo (nome com dígitos) | Observado (bruta) | 2026-11-05 | Releitura: nada foi criado |
57
+ | `crm.customers.create` | PJ (`/J`) | Confirmado pelo fornecedor, não testado | 06/11 (TSMX) | Funciona como o `/F`, trocam só os dados da empresa; nunca exercitado. CNPJ alfanumérico em criação: não testado (veja Lacunas conhecidas) |
58
+ | `crm.customers.updateStatus` | Status 6 ("Contrato realizado no SGP"): 200 | Observado (bruta) | 2026-11-06 | Também 200 com `codigo` 6 em cliente CRM sem contratos. Os demais códigos de `CRM_STATUS` nunca foram enviados |
59
+ | `crm.customers.updateStatus` | Cliente que já saiu do CRM: 404 `{error:"Cadastro não encontrado"}` | Reconstruído | 1.x | `NOT_FOUND`, `effect: "none"` |
60
+ | `crm.contracts.create` | Por id: 200 com `clientecontrato`, `message`, `cliente` | Observado (bruta) | 2026-11-06 | O contrato apareceu em `contracts.list` depois |
61
+ | `crm.contracts.create` | Por id: 400 por falta de `nas` | Observado (bruta) | 2026-11-06 | Nada foi criado |
62
+ | `crm.contracts.create` | Por id: 400 "Não encontrado cliente CRM" (`CUSTOMER_NOT_IN_CRM`) | Reconstruído | 2026-11-05 | Texto vindo da prosa do relato; a releitura mostrou nenhum contrato adicionado |
63
+ | `crm.contracts.create` | `modoaquisicao` 2 recusado | Observado (sem fixture) | 2026-10-31 | A documentação do fornecedor lista 1 e 2; o SDK aceita só 0 e 1 |
64
+ | `crm.contracts.create` | `login`, `senha`, `modoaquisicao` exigidos para serviço de Internet | Documentado | n/d | Só a falta de `nas` foi observada |
65
+ | `crm.contracts.createByCpfCnpj` | Sucesso (200) | Documentado | n/d | **Nenhum sucesso foi observado nesta rota**; assume-se o formato da rota por id |
66
+ | `crm.contracts.createByCpfCnpj` | 400 "Não encontrado cliente CRM" (`CUSTOMER_NOT_IN_CRM`) | Observado (bruta) | 2026-11-05 | Releitura: nada foi adicionado. Só CPF foi exercitado (CNPJ não) |
67
+ | `crm.contracts.list` | 200 com contratos | Observado (bruta) | 2026-11-06 | Valores pessoais mascarados |
68
+ | `crm.contracts.list` | 200 sem contratos | Observado (bruta) | 2026-11-05 | |
69
+ | `crm.contracts.list` | 400 para documento que não é cliente CRM | Observado (bruta) | 2026-11-05 | `REJECTED`, sem `reason` |
70
+
71
+ ### `preRegistrations`
72
+
73
+ | Operação | Cenário | Evidência | Data | Observação |
74
+ | ------------------------------ | ----------------------------------------------------------------------------------------- | --------------------------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
75
+ | `preRegistrations.create` | Sucesso (`/F`): 200 com `precadastro_id`, `new_cliente_id`, `new_contrato_id` e `message` | Observado (bruta) | 2026-11-06 | Cliente sintético; a releitura (`customers.list` pelo CPF) trouxe o mesmo cliente e o mesmo contrato. Os ids são opcionais: os registros mais antigos listam só `message`, e o decoder da 1.x descartava chaves desconhecidas, então não se sabe se os ids já eram enviados antes |
76
+ | `preRegistrations.create` | Sucesso (`/F`): 200 só com `message` | Documentado | 2026-11-05 | O texto do corpo é o exemplo do fornecedor; ao vivo só o status 200 e a chave `message` foram vistos (o decoder da 1.x descartava chaves desconhecidas) |
77
+ | `preRegistrations.create` | PF: 400 CPF com dígitos verificadores errados (`DOCUMENT_INVALID`) | Observado (bruta) | 2026-11-06 | `{"message":"CPF inválido","error":"Requisição inválida."}`: forma nova, com `message` e `error`; `error.sgp` traz só o `error` e o `message` fica em `response.body`. Cliente sintético, documento fictício; releitura: nada foi criado. Dígitos verificadores de CNPJ: nunca testados |
78
+ | `preRegistrations.create` | PF: 400 login PPPoE já em uso (`LOGIN_ALREADY_REGISTERED`) | Observado (bruta) | 2026-11-06 | `{"message":"Login <login> já existe","error":"Requisição inválida."}`: o `message` repete o login enviado; `error.sgp` traz só o `error`, e o `message` fica em `response.body`. Uma única captura (cliente sintético, login de outro cliente sintético); releituras por documento e por login: nada foi criado |
79
+ | `preRegistrations.create` | CPF existente: 402 (`DOCUMENT_ALREADY_REGISTERED`) | Observado (bruta) | 2026-11-05 | Releitura: nenhum cliente nem contrato adicionado |
80
+ | `preRegistrations.create` | CPF formatado (`999.999.999-99`) gravado como enviado | Observado (bruta) | 2026-11-06 | Cliente sintético: `customers.list` devolveu o CPF idêntico ao enviado, e o `crm.customers.create` seguinte foi recusado (400 de duplicidade). Um CPF só com dígitos (captura anterior) escapou dessa checagem. O SDK agora envia sempre a máscara canônica |
81
+ | `preRegistrations.create` | PJ (`/J`) | Confirmado pelo fornecedor, não testado | 06/11 (TSMX) | Funciona como o `/F`, trocam só os dados da empresa; nunca exercitado. CNPJ alfanumérico em criação: não testado (veja Lacunas conhecidas) |
82
+ | `preRegistrations.listPlans` | Lista de planos do pré-cadastro | Observado (corpo) | 2026-11-05 | Requisição multipart, sem barra final |
83
+ | `preRegistrations.listDueDays` | Lista de vencimentos | Observado (corpo) | 1.x | |
84
+ | `preRegistrations.listSellers` | Lista de vendedores | Observado (corpo) | 1.x | |
85
+
86
+ ### `contracts`
87
+
88
+ | Operação | Cenário | Evidência | Data | Observação |
89
+ | ----------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------ | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
90
+ | `contracts.listServices` | Contrato sintético offline: 3 linhas (2 internet, 1 TV) | Observado (bruta) | 2026-11-06 | Status, content-type e corpo. Senha e login do serviço em claro (trocados na fixture), IP do NAS trocado por endereço de documentação; ids como texto, `contrato_status` com espaço, `servico_online` booleano só nas linhas de internet |
91
+ | `contracts.listServices` | Contrato sem serviço: `[]` | Observado (bruta) | 2026-11-06 | Status, content-type e corpo |
92
+ | `contracts.listServices` | Linha de serviço ONLINE com sessão RADIUS (`rad_*`) | Reconstruído | n/d | Nenhum serviço sintético estava online; chaves e tipos vêm da forma mascarada de um contrato real (só tipos), valores inventados, sem headers |
93
+ | `contracts.checkConnection` | Contrato sintético suspenso, sem `status_all`: 200 `[]` | Observado (bruta) | 2026-11-06 | Rodado pela descoberta, não pelo SDK. Ocorrências antes e depois iguais (nenhuma manutenção ativa) |
94
+ | `contracts.checkConnection` | Mesmo contrato com `status_all: 1`: array, um item por serviço de internet, `status` 2 | Observado (bruta) | 2026-11-06 | `login` trocado na fixture. Nenhuma ocorrência aberta (sem manutenção ativa) |
95
+ | `contracts.checkConnection` | Contrato ativo sem `status_all`: objeto único com `status` 1 e `msg` "Serviço Online" | Reconstruído | n/d | Forma mascarada de um contrato real (só chaves e tipos); o SDK embrulha o objeto em array |
96
+ | `contracts.checkConnection` | Em manutenção ativa o SGP abre uma ocorrência por padrão, mesmo sem `protocolo_ura` | Documentado | n/d | Nunca testado: por isso a operação é uma escrita; `telefone`, `status_filter` e `uracontato` também nunca foram enviados |
97
+ | `contracts.list` | Filtro por contrato: array com um resumo | Observado (corpo) | 1.x | |
98
+ | `contracts.updateStatus` | Suspenso para Ativo: 200 com `resposta` | Observado (bruta) | 2026-11-05 | A releitura confirmou Ativo |
99
+ | `contracts.updateStatus` | Ativo para Suspenso: 200 com `resposta` | Observado (bruta) | 2026-11-05 | A releitura confirmou Suspenso |
100
+ | `contracts.updateStatus` | Inativo para Ativo: 200 | Observado, corpo não capturado | n/d | Confirmado por releitura |
101
+ | `contracts.updateStatus` | Ativo para Inativo: 400 `{error:"Status de Contrato não alterado."}` (`STATUS_NOT_CHANGED`) | Reconstruído | 2026-11-05 | Status e texto vêm do relato do suporte; a releitura mostrou o contrato ainda Ativo (o painel não oferece Inativo a partir de Ativo) |
102
+ | `contracts.updateStatus` | Status 6 ("Novo") | Documentado | n/d | Nunca observado; significado e transições desconhecidos |
103
+ | `contracts.updateStatus` | Status 3, 5 e 7 | Documentado | n/d | Vêm do painel do SGP; nunca enviados |
104
+ | `contracts.update` | `forma_cobranca`: 200 com `resposta.alterados` | Observado (bruta) | 2026-11-05 | A leitura seguinte mostrou a nova forma. Os demais campos documentados pelo fornecedor não são aceitos pelo SDK |
105
+ | `contracts.listStatusReasons` | Lista de motivos (`[{ id, motivo }]`) | Observado (bruta) | 2026-11-06 | Leitura ao vivo: 7 motivos, ids 1 a 7 na ordem 1, 2, 4, 3, 5, 6, 7. Registrados status, content-type e corpo; os demais headers não. A lista é configuração do provedor |
106
+
107
+ ### `services`
108
+
109
+ | Operação | Cenário | Evidência | Data | Observação |
110
+ | ------------------------ | ---------------------------------------------------------------- | ----------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
111
+ | `services.changePlan` | 200 `{ msg }` (ida e volta de plano) | Observado (bruta) | 2026-11-05 | Observado duas vezes, corpo idêntico; só `servico_tipo` 1 enviado |
112
+ | `services.addTv` | 201 com `servico_id` | Observado (bruta) | 2026-11-05 | |
113
+ | `services.addTv` | 400 contrato não ativo (`CONTRACT_NOT_ACTIVE`) | Reconstruído | 2026-11-05 | Texto vindo da prosa do relato (o formato `errors` como array não está provado em captura bruta); a releitura mostrou nada adicionado |
114
+ | `services.removeTv` | 200 `{ msg, clientecontrato_id, servico_id }` (DELETE multipart) | Observado (bruta) | 2026-11-05 | Remoção do serviço criado na mesma execução |
115
+ | `services.addGeneric` | 400 plano não é do tipo genérico (`PLAN_NOT_GENERIC`) | Reconstruído | 2026-11-05 | A recusa foi vista ao vivo (plano do tipo internet, nada adicionado na releitura), mas só o texto foi guardado; o formato `{ error }` vem do mapa de rotas validadas, sem captura bruta |
116
+ | `services.addGeneric` | Sucesso (qualquer 2xx com objeto JSON) | Documentado | n/d | Nunca observado: o provedor não tem plano genérico e planos não são criados para teste. Nenhum campo da resposta é assumido |
117
+ | `services.removeGeneric` | Sucesso (qualquer 2xx com objeto JSON) | Documentado | n/d | Nunca observado: nenhum serviço genérico foi criado. O fornecedor documenta que a rota não remove o único serviço do contrato; essa recusa nunca foi vista |
118
+
119
+ ### `plans`, `invoices`, `workOrders`, `occurrences`
120
+
121
+ | Operação | Cenário | Evidência | Data | Observação |
122
+ | ------------------------- | --------------------------------------------------------------------------------------------------- | ----------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
123
+ | `plans.list` | Catálogo de planos | Observado (corpo) | 2026-11-05 | |
124
+ | `invoices.list` | Página vazia de um cliente sem títulos | Observado (corpo) | 2026-11-05 | |
125
+ | `invoices.list` | Página com títulos | Reconstruído | n/d | Nunca houve captura de uma página populada: chaves e tipos seguem a fixture da 1.x, os valores são inventados |
126
+ | `workOrders.list` | Filtro por contrato | Observado (corpo) | 2026-11-05 | |
127
+ | `workOrders.list` | Primeira página de várias (`limit` 1, `total` maior que `parcial`) | Observado (corpo) | 1.x | |
128
+ | `workOrders.get` | Objeto plano de 72 campos de uma OS aberta | Observado (bruta) | 2026-11-06 | Status, content-type e corpo. Senha do serviço em texto claro (trocada na fixture); `wifi_*` e `wan_*` vieram vazios; MAC e IP nulos; `servico_id` e `contrato_status_id` são texto, `contrato_status` tem espaço à esquerda (a SDK os normaliza: números e texto aparado). Cliente de teste sintético |
129
+ | `workOrders.get` | 403 antes da rota estar no token | Observado (bruta) | 2026-11-06 | `FORBIDDEN` com `TOKEN_ROUTE_NOT_ALLOWED`; sem fixture, o teste usa o corpo em linha |
130
+ | `workOrders.get` | Resposta como array de um elemento; Wi-Fi, WAN, MAC e IP preenchidos; OS finalizada | Documentado | n/d | Só na coleção do fornecedor; o SGP mandou o objeto e nunca devolveu esses valores. Um array é `INVALID_RESPONSE` |
131
+ | `workOrders.listComments` | OS sem comentários: `[]` | Observado (bruta) | 2026-11-06 | Status, content-type e corpo |
132
+ | `workOrders.listComments` | 403 antes da rota estar no token | Observado (bruta) | 2026-11-06 | Mesmo `detail` do 403 de `workOrders.get`; sem fixture |
133
+ | `workOrders.listComments` | Itens (`comentario_id`, `comentario`, `usuario`, `data_cadastro`, `checklist_*`, `usuario_setores`) | Documentado | n/d | Só na coleção do fornecedor; nenhum comentário foi visto. Exigido do item: ser objeto |
134
+ | `workOrders.listReasons` | Lista de motivos | Observado (corpo) | 1.x | `id` e `codigo` distintos |
135
+ | `occurrences.create` | Sucesso: `status` 1, `id`, `protocolo` | Observado (corpo) | 1.x | Corpo mascarado; a data da resposta não foi registrada. Nenhuma captura bruta cruzou o `protocolo` com o `numero` de `occurrences.list` |
136
+ | `occurrences.create` | Recusa: HTTP 200 com `status` 0 e `msg` do formato de `data_hora_agendamento` (`INVALID_SCHEDULE`) | Observado (bruta) | 2026-11-06 | Agendamento com segundos (`2026-11-13 09:00:00`): `{"msg":"Data e hora de agendamento inválida, formato esperado: AAAA-MM-DD HH:MM","status":0,...}`, `effect: "none"`. Uma captura (cliente sintético); a releitura de `occurrences.list({ contrato })` achou o mesmo total antes e depois: nada foi criado. Casa só o início do `msg`, sem diferenciar maiúsculas |
137
+ | `occurrences.create` | Recusa: HTTP 200 com `status` 0 e outro `msg` | Reconstruído | n/d | Vem do contrato da 1.x; sem captura nem releitura, por isso `REJECTED` sem `reason` e `effect: "unknown"` |
138
+ | `occurrences.create` | Ocorrência sem `motivoos` (sem agendamento), `status_encerrado: 1` | Observado | 2026-11-06 | Aceita; o SGP criou também a OS, e `status_encerrado: 1` encerrou ocorrência e OS. Uma vez, por releitura (cliente sintético) |
139
+ | `occurrences.create` | Encerrar uma OS de instalação (`motivoos` 10) com `status_encerrado: 1` | Observado | 2026-11-06 | O contrato (inativo) continuou inativo: encerrar a OS não o ativou. Uma vez, por releitura (cliente sintético) |
140
+ | `occurrences.list` | Filtro por contrato (`contrato` e `limit`): ocorrência com sua OS | Observado (bruta) | 2026-11-06 | Status, content-type e corpo. A OS vem em `ordens_servicos[].id`; o `numero` da ocorrência é o `ocorrencia` da OS (vistos uma vez). Cliente de teste sintético |
141
+ | `occurrences.list` | Contrato sem ocorrência: `total` 0 e lista vazia | Observado (bruta) | 2026-11-06 | |
142
+ | `occurrences.list` | 401 antes da rota estar no token | Observado (bruta) | 2026-11-06 | `UNAUTHORIZED` com `detail`, não o 403 das outras rotas de suporte. Captura no run 2026-11-06T19-14-48; sem fixture, o teste usa o corpo em linha |
143
+ | `occurrences.list` | Filtros `ocorrencia`, `status`, `contrato_status`, datas | Documentado | n/d | Coleção do fornecedor; nunca enviados. Os filtros `pop`, `tipo` (arrays) e `hora_*` não são oferecidos |
144
+ | `occurrences.list` | Chamada sem filtro | Documentado | n/d | O SGP lista as ocorrências de todos os clientes (até 1000); nunca executada, de propósito. O SDK recusa a chamada sem filtro (`INVALID_INPUT`, nada enviado); `offset` e `limit` não contam como filtro |
145
+ | `occurrences.list` | `comentarios` com itens | Documentado | n/d | Sempre vazio na captura; o item (`comentario`, `usuario`, `data_cadastro`) só vem da coleção do fornecedor. `servico` e `protocolos_externos` também vieram vazios e a coleção não os cita: conteúdo desconhecido |
146
+ | `occurrences.listTypes` | Lista de tipos | Observado (corpo) | 1.x | `classificacoes` e `setores` sempre vazios nas capturas |
147
+ | `occurrences.listMethods` | Lista de métodos | Observado (corpo) | 1.x | |
148
+
149
+ ### `documents`, `coverage`, `catalogs`, `experimental`
150
+
151
+ | Operação | Cenário | Evidência | Data | Observação |
152
+ | ------------------------------- | ------------------------------------------------------------------------------- | ----------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
153
+ | `documents.add` | Sucesso: 2xx com corpo vazio | Reconstruído | n/d | Vem do contrato da 1.x e de seus testes; sem captura dos headers |
154
+ | `documents.list` | Lista de documentos | Observado (corpo) | 1.x | |
155
+ | `coverage.check` | Gateway SGPMaps; Geogrid; sem CTO no raio | Observado (corpo) | 1.x | Geogrid manda números como texto (a SDK os converte em números) |
156
+ | `coverage.checkAddress` | `cep` sozinho: `{ viabilidade: true }` | Observado (bruta) | 2026-11-06 | Status, content-type e corpo; o mesmo corpo na execução das 19:14 |
157
+ | `coverage.checkAddress` | `cidade` sozinha: `{}` | Observado (bruta) | 2026-11-06 | 200 sem a chave `viabilidade`: resposta "não sei", nunca `false` |
158
+ | `coverage.checkAddress` | Endereço sem cobertura | Documentado | n/d | **Nunca observado**: a coleção do fornecedor só mostra `true`. O que o SGP manda nesse caso é desconhecido; o SDK repassa o que vier |
159
+ | `coverage.checkAddress` | Combinação de filtros; `logradouro`, `bairro`, `numero_inicial`, `numero_final` | Documentado | n/d | Coleção do fornecedor ("use um dos filtros"); nunca enviados |
160
+ | `maintenance.list` | Três avisos, todos com `ativa: false` | Observado (bruta) | 2026-11-06 | Status, content-type e corpo; o mesmo corpo na execução das 19:14. Sem filtro: devolve também os inativos. As listas `pops`, `nas`, `aps`, `sources`, `switches`, `olts`, `oltpons`, `ctos` e `vlans` vieram vazias |
161
+ | `maintenance.list` | Aviso ativo; itens das listas de elementos afetados | Documentado | n/d | Só a coleção do fornecedor (que mostra `ativa` e `enviar_sms` como `1`; o SGP mandou booleanos). Nunca visto |
162
+ | `coverage.listGateways` | Lista de gateways (Geogrid e SGPMaps) | Observado (corpo) | 2.0 | Captura bruta ao vivo (status, content-type e corpo); lista vazia e 403 de token sem a rota não foram observados |
163
+ | `catalogs.listPops` | Lista de POPs | Observado (corpo) | 1.x | |
164
+ | `catalogs.listNas` | Lista de NAS | Observado (corpo) | 1.x | |
165
+ | `catalogs.listBillingProfiles` | Lista de portadores | Observado (corpo) | 1.x | |
166
+ | `experimental.annotations.list` | Resposta com anotações de vários clientes | Reconstruído | n/d | Corpo sintético no formato observado; os tipos dos campos foram contados ao vivo, sem persistir nenhuma anotação. Que o SGP ignora os filtros `cliente_id` e `contrato_id` foi observado ao vivo, mas sem captura positiva do cliente de teste |
167
+
168
+ ### Comportamentos transversais
169
+
170
+ | Comportamento | Evidência | Observação |
171
+ | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | --------------------------------------------------------------------------- |
172
+ | 403 "credenciais ... não foram fornecidas" (rota fora do token, barra final errada) e 403/401 "credenciais incorretas" | Observado (registro de decisão do projeto, 2026-11-05) | Corpo bruto não anexado ao repositório (não verificado) |
173
+ | Erros `errors` como array ("Ocorreu um erro: ...") são o invólucro genérico do SGP | Observado (registro de decisão do projeto) | Por isso ficam em `effect: "unknown"` salvo recusa conhecida da operação |
174
+ | `RATE_LIMITED` (429), limites de taxa do SGP | Nunca observado | O ajuste de retry é estimativa |
175
+ | 502 com corpo HTML como `UPSTREAM` | Coberto por teste | Se foi visto numa resposta real: não verificado |
176
+ | `request()` | Sem evidência própria | Depende da rota chamada: nenhum sucesso ou recusa de negócio é interpretado |
177
+
178
+ ## Limitações confirmadas pelo fornecedor (TSMX, 06/11)
179
+
180
+ Afirmações do suporte da TSMX (Erick Silva, 05/11; Natanael Azevedo, 06/11) sobre o que a API do
181
+ SGP **não** faz, mais o que nós mesmos constatamos (marcado como tal). O SDK não as testa nem contorna.
182
+
183
+ - **Segundo contrato em cliente definitivo:** sem API. "No momento não seria possível adicionar um
184
+ segundo contrato em cadastros definitivos via API" e "Não é possível adicionar um segundo ponto de
185
+ internet via api". As rotas testadas confirmam: pré-cadastro com CPF existente é recusado (402) e a
186
+ rota CRM só enxerga clientes do CRM (400).
187
+ - **Telefonia:** sem API. "No momento não temos end-points para adicionar serviços de telefonia".
188
+ - **Serviços extras:** pela API só TV (`services.addTv`) e genérico (`services.addGeneric`).
189
+ Internet adicional (como locação de roteador, que é plano do tipo internet) e multimídia: só pelo painel.
190
+ - **"Alterar Titular" e migração de contrato:** sem rota de API. Constatação nossa: nenhuma rota na
191
+ coleção e 404 em todas as variantes sondadas; a resposta da TSMX ("No momento não seria possível")
192
+ provavelmente se refere a isso, mas não foi dada a essa pergunta de forma explícita.
193
+ - **Documentação oficial:** só a coleção do Postman ("No momento a disponibilização seria apenas via
194
+ postman"); não há Swagger/OpenAPI.
195
+ - **Planos dos dois catálogos:** os IDs do pré-cadastro e do cliente definitivo são diferentes, e a
196
+ TSMX não indicou rota que devolva a correspondência (nem há uma na coleção); o consumidor casa pela
197
+ descrição.
198
+
199
+ ## Lacunas conhecidas
200
+
201
+ - Formato do documento nas escritas que o gravam: o SDK envia CPF (`F`) ou CNPJ (`J`) na máscara
202
+ canônica e recusa o resto localmente, sem validar dígitos verificadores. O SGP valida os do CPF
203
+ (400 `DOCUMENT_INVALID`, observado ao vivo); os dígitos verificadores de um CNPJ nunca foram
204
+ testados, então não se sabe se o SGP os confere. A máscara do CNPJ
205
+ alfanumérico (`AA.AAA.AAA/AAAA-99`, maiúsculas) segue a Receita, mas nenhuma criação com ele foi
206
+ testada: o SGP pode recusá-lo ou gravá-lo de outro jeito. Os filtros de leitura (`cpfcnpj`) não são
207
+ alterados: `customers.list` casa a forma só com dígitos e a formatada nos dois sentidos, e não casa
208
+ documento parcial (observado ao vivo, 06/11).
209
+ - Só PF e CPF foram exercitados em `preRegistrations.create` e `crm.customers.create`. PJ e CNPJ
210
+ só têm a confirmação do fornecedor (TSMX, 06/11), sem teste. CNPJ alfanumérico: os filtros
211
+ `cpfcnpj` de leitura **aceitam** o formato (observado ao vivo em 06/11, só leitura, com o exemplo
212
+ da Receita em três formas: com máscara, sem máscara e em minúsculas): `customers.list` e
213
+ `invoices.list` responderam 200 com `total` 0 em todas, igual ao controle numérico, e
214
+ `crm.contracts.list` respondeu o 400 "Não encontrado cliente CRM com documento ..." em todas (o
215
+ SGP formata o documento numérico na mensagem e devolve o alfanumérico exatamente como foi
216
+ enviado). Um **achado** nunca foi observado (constatação registrada no repositório, 06/11); a
217
+ **criação** de PJ com CNPJ alfanumérico não foi testada. O texto do `CUSTOMER_NOT_IN_CRM` de
218
+ `crm.contracts.createByCpfCnpj` só reconhece documento numérico. Para os tipos `E`/`EJ` o
219
+ fornecedor não tem exemplo que funcione; só via `request()`, sem prova.
220
+ - O sucesso de `crm.contracts.createByCpfCnpj` nunca foi visto.
221
+ - Recusas que são apenas reconstruídas: `STATUS_NOT_CHANGED`, `CUSTOMER_NOT_IN_CRM` por id,
222
+ `CONTRACT_NOT_ACTIVE`, o 404 de `crm.customers.updateStatus` (só o corpo veio de uma captura da
223
+ 1.x), a recusa `status: 0` de `occurrences.create` com outro `msg` que não o do agendamento e o corpo vazio de `documents.add`. Se o SGP mandar outra forma, o efeito cai em `"unknown"`.
224
+ - Não existe rota de API para um segundo contrato de um cliente definitivo nem para extras de
225
+ serviço além de TV e genérico (roteador, multimídia, telefonia): o fornecedor confirmou (veja
226
+ [Limitações confirmadas pelo fornecedor](#limitações-confirmadas-pelo-fornecedor-tsmx-0611)); hoje,
227
+ só pelo painel do SGP.
228
+ - `experimental.annotations.list` depende do SGP corrigir o filtro no lado dele, e o isolamento local
229
+ é uma heurística por padrão de chave: dados de outro cliente sob chaves que não são de identidade
230
+ não seriam detectados.
231
+ - Limites de taxa do SGP e a igualdade entre o `protocolo` de `occurrences.create` e o `numero` de `occurrences.list` não foram confirmados.