sgp-sdk 1.1.3 → 2.0.0-beta.1
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.
- package/CHANGELOG.md +87 -0
- package/COMPATIBILITY.md +151 -0
- package/MIGRATION.md +283 -0
- package/README.md +424 -225
- package/dist/address.d.ts +46 -0
- package/dist/annotation-selection.d.ts +32 -0
- package/dist/budget.d.ts +29 -0
- package/dist/classify.d.ts +15 -0
- package/dist/client.d.ts +662 -155
- package/dist/config.d.ts +115 -0
- package/dist/constants.d.ts +72 -0
- package/dist/effect.d.ts +22 -0
- package/dist/executor.d.ts +9 -0
- package/dist/exposure.d.ts +21 -0
- package/dist/guards.d.ts +2 -0
- package/dist/hooks.d.ts +16 -0
- package/dist/ids.d.ts +19 -0
- package/dist/index.d.ts +45 -47
- package/dist/index.js +2954 -1223
- package/dist/operation-sensitivity.d.ts +496 -0
- package/dist/operation.d.ts +139 -0
- package/dist/operations/catalogs-list-billing-profiles.d.ts +16 -0
- package/dist/operations/catalogs-list-nas.d.ts +25 -0
- package/dist/operations/catalogs-list-pops.d.ts +14 -0
- package/dist/operations/contracts-list-status-reasons.d.ts +15 -0
- package/dist/operations/contracts-list.d.ts +52 -0
- package/dist/operations/contracts-update-status.d.ts +41 -0
- package/dist/operations/contracts-update.d.ts +39 -0
- package/dist/operations/coverage-check.d.ts +42 -0
- package/dist/operations/crm-contracts-create-by-cpf-cnpj.d.ts +97 -0
- package/dist/operations/crm-contracts-create.d.ts +221 -0
- package/dist/operations/crm-contracts-list.d.ts +50 -0
- package/dist/operations/crm-customers-create.d.ts +155 -0
- package/dist/operations/crm-customers-update-status.d.ts +41 -0
- package/dist/operations/customers-list.d.ts +98 -0
- package/dist/{endpoints/ura/lookup-customer.d.ts → operations/customers-search.d.ts} +56 -46
- package/dist/operations/documents-add.d.ts +29 -0
- package/dist/operations/documents-list.d.ts +31 -0
- package/dist/operations/experimental-annotations-list.d.ts +67 -0
- package/dist/operations/invoices-list.d.ts +142 -0
- package/dist/operations/occurrences-create.d.ts +116 -0
- package/dist/operations/occurrences-list-methods.d.ts +14 -0
- package/dist/operations/occurrences-list-types.d.ts +20 -0
- package/dist/{endpoints/ura/list-plans.d.ts → operations/plans-list.d.ts} +33 -23
- package/dist/operations/pre-registrations-create.d.ts +253 -0
- package/dist/operations/pre-registrations-list-due-days.d.ts +15 -0
- package/dist/operations/pre-registrations-list-plans.d.ts +24 -0
- package/dist/operations/pre-registrations-list-sellers.d.ts +16 -0
- package/dist/operations/services-add-tv.d.ts +47 -0
- package/dist/operations/services-change-plan.d.ts +40 -0
- package/dist/operations/services-remove-tv.d.ts +29 -0
- package/dist/operations/work-orders-list-reasons.d.ts +20 -0
- package/dist/operations/work-orders-list.d.ts +81 -0
- package/dist/path.d.ts +13 -0
- package/dist/raw-request.d.ts +26 -0
- package/dist/request.d.ts +28 -0
- package/dist/result.d.ts +111 -43
- package/dist/retry.d.ts +34 -0
- package/dist/sensitivity.d.ts +89 -4
- package/dist/tolerant.d.ts +21 -0
- package/dist/validation.d.ts +3 -0
- package/package.json +6 -3
- package/dist/endpoint.d.ts +0 -66
- package/dist/endpoints/crm/address.d.ts +0 -20
- package/dist/endpoints/crm/create-contract.d.ts +0 -109
- package/dist/endpoints/crm/create-customer.d.ts +0 -65
- package/dist/endpoints/crm/update-customer-status.d.ts +0 -43
- package/dist/endpoints/os/list-occurrence-reasons.d.ts +0 -24
- package/dist/endpoints/os/list-occurrence-types.d.ts +0 -25
- package/dist/endpoints/precadastro/list-due-days.d.ts +0 -19
- package/dist/endpoints/precadastro/list-sellers.d.ts +0 -21
- package/dist/endpoints/suporte/add-customer-document.d.ts +0 -24
- package/dist/endpoints/suporte/list-customer-documents.d.ts +0 -25
- package/dist/endpoints/ura/check-coverage.d.ts +0 -46
- package/dist/endpoints/ura/list-clients.d.ts +0 -58
- package/dist/endpoints/ura/list-contracts.d.ts +0 -45
- package/dist/endpoints/ura/list-invoices.d.ts +0 -107
- package/dist/endpoints/ura/list-nas.d.ts +0 -29
- package/dist/endpoints/ura/list-occurrence-methods.d.ts +0 -19
- package/dist/endpoints/ura/list-payment-carriers.d.ts +0 -20
- package/dist/endpoints/ura/list-pops.d.ts +0 -19
- package/dist/endpoints/ura/list-service-orders.d.ts +0 -64
- package/dist/endpoints/ura/open-ticket.d.ts +0 -81
- package/dist/errors.d.ts +0 -59
- package/dist/schema/parse.d.ts +0 -19
- package/dist/schema/paths.d.ts +0 -38
- package/dist/transport.d.ts +0 -65
package/README.md
CHANGED
|
@@ -1,262 +1,461 @@
|
|
|
1
1
|
# sgp-sdk
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
application decides what to do with the data.
|
|
3
|
+
Cliente TypeScript não oficial para a API HTTP do SGP, o ERP de provedores de internet da TSMX.
|
|
4
|
+
Sem vínculo com a TSMX.
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
- Methods never throw for network, HTTP or schema failures; they return a result object.
|
|
6
|
+
> **Versão 2.0.** Esta documentação descreve a 2.0 (em preparação, ainda não publicada). A série
|
|
7
|
+
> 1.x está congelada e depreciada: não recebe mais mudanças. Para migrar, leia
|
|
8
|
+
> [MIGRATION.md](MIGRATION.md).
|
|
11
9
|
|
|
12
|
-
##
|
|
10
|
+
## O que é e o que não é
|
|
11
|
+
|
|
12
|
+
O sgp-sdk é um **gateway de integração**: encapsula o transporte e a semântica conhecida de cada
|
|
13
|
+
operação do SGP, preserva todos os dados que recebe, devolve falhas com códigos estáveis e diz
|
|
14
|
+
explicitamente o que sabe, e o que não sabe, sobre o efeito de uma escrita.
|
|
15
|
+
|
|
16
|
+
Ele **não é** uma camada de política. Quem decide autorização, orquestração de vários passos,
|
|
17
|
+
recuperação e o que chega a um usuário ou a um agente é a sua aplicação. O SDK também não
|
|
18
|
+
renomeia campos (`cpfcnpj`, `planobase_id` e os demais chegam e saem com os nomes do SGP) e não
|
|
19
|
+
inventa valores: ausente, `null` e `""` chegam como o SGP mandou.
|
|
20
|
+
|
|
21
|
+
- **Somente servidor.** O cliente carrega o token do SGP; navegadores não são suportados.
|
|
22
|
+
- **ESM**, Node >= 22 e Bun. A única dependência de runtime é `zod`. Os testes unitários rodam no
|
|
23
|
+
Bun; o Node é coberto pelo teste de fumaça do pacote construído.
|
|
24
|
+
- Nenhum método lança exceção por falha esperada (input inválido, rede, timeout, cancelamento,
|
|
25
|
+
recusa do SGP, resposta incompatível): tudo volta no resultado. Só `createSgpClient` lança, para
|
|
26
|
+
configuração inválida (`InvalidConfigurationError`), além de bugs internos.
|
|
27
|
+
- Tudo fora de `experimental.*` segue SemVer.
|
|
28
|
+
|
|
29
|
+
Instância de SGP: a versão do SGP em que cada operação foi comprovada é **desconhecida**. O que foi
|
|
30
|
+
provado, onde e quando está em [COMPATIBILITY.md](COMPATIBILITY.md).
|
|
31
|
+
|
|
32
|
+
## Instalação
|
|
13
33
|
|
|
14
34
|
```sh
|
|
15
35
|
bun add sgp-sdk
|
|
16
|
-
#
|
|
36
|
+
# ou
|
|
17
37
|
npm install sgp-sdk
|
|
18
38
|
```
|
|
19
39
|
|
|
20
|
-
|
|
21
|
-
`node16` / `nodenext`: the emitted `.d.ts` files carry explicit `.js` extensions.
|
|
40
|
+
Os tipos resolvem com `moduleResolution` `bundler` e `node16`/`nodenext`.
|
|
22
41
|
|
|
23
|
-
##
|
|
42
|
+
## Quickstart
|
|
24
43
|
|
|
25
44
|
```ts
|
|
26
|
-
import {
|
|
45
|
+
import { CONTRACT_STATUS, createSgpClient } from "sgp-sdk";
|
|
27
46
|
|
|
28
47
|
const sgp = createSgpClient({
|
|
29
|
-
baseUrl: "https://sgp.example.com",
|
|
30
|
-
token: "<token
|
|
31
|
-
app: "<app name>",
|
|
32
|
-
// timeoutMs: 20_000, // default
|
|
33
|
-
// fetch: fakeFetch, // default: the global fetch (inject a fake in tests)
|
|
48
|
+
baseUrl: "https://sgp.example.com", // origem da instância, sem caminho
|
|
49
|
+
credentials: { kind: "token", token: "<token>", app: "<nome do app>" }, // carregue como preferir
|
|
34
50
|
});
|
|
35
51
|
|
|
36
|
-
|
|
37
|
-
|
|
52
|
+
// Uma leitura: `ok` estreita o resultado.
|
|
53
|
+
const plans = await sgp.plans.list();
|
|
54
|
+
if (plans.ok) {
|
|
38
55
|
console.log(plans.data.planos.length, plans.warnings ?? []);
|
|
39
56
|
} else {
|
|
40
|
-
console.error(plans.error.code, plans.
|
|
57
|
+
console.error(plans.error.code, plans.response?.status);
|
|
41
58
|
}
|
|
42
59
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
60
|
+
// Uma escrita: em caso de falha, `error.effect` diz o que fazer.
|
|
61
|
+
const change = await sgp.contracts.updateStatus({ contrato: 1001, status: CONTRACT_STATUS.active });
|
|
62
|
+
if (change.ok) {
|
|
63
|
+
console.log(change.data.resposta.contrato_status);
|
|
64
|
+
} else if (change.error.effect === "none") {
|
|
65
|
+
// Nada foi aplicado: corrigir a entrada e chamar de novo não duplica nada.
|
|
66
|
+
console.error(change.error.code, change.error.reason);
|
|
67
|
+
} else {
|
|
68
|
+
// "unknown": o SGP pode ter aplicado. Leia o estado (contracts.list) antes de enviar de novo.
|
|
49
69
|
}
|
|
50
70
|
```
|
|
51
71
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
| `ura.listPaymentCarriers()` | `GET /api/ura/portador/` |
|
|
69
|
-
| `ura.checkCoverage(params)` | `GET /api/ura/viabilidadeinstalacao` |
|
|
70
|
-
| `ura.listPlans()` | `GET /api/ura/consultaplano/` |
|
|
71
|
-
| `ura.listOccurrenceMethods()` | `GET /api/ura/ocorrencia/metodo/list/` |
|
|
72
|
-
| `ura.openTicket(params)` | `POST /api/ura/chamado/` |
|
|
73
|
-
| `os.listOccurrenceTypes()` | `GET /api/os/ocorrencia/tipo/list/` |
|
|
74
|
-
| `os.listOccurrenceReasons()` | `GET /api/os/ocorrencia/motivo/list/` |
|
|
75
|
-
| `precadastro.listDueDays()` | `POST /api/precadastro/vencimento/list` |
|
|
76
|
-
| `precadastro.listSellers()` | `POST /api/precadastro/vendedor/list` |
|
|
77
|
-
| `crm.createIndividualCustomer(params)` | `POST /api/crm/cliente/F` |
|
|
78
|
-
| `crm.createCompanyCustomer(params)` | `POST /api/crm/cliente/J` |
|
|
79
|
-
| `crm.createContract(clienteId, params)` | `POST /api/crm/cliente/{cliente_id}/contratos/` |
|
|
80
|
-
| `crm.updateCustomerStatus(clienteId, params)` | `POST /api/crm/cliente/{cliente_id}/status/` |
|
|
81
|
-
| `suporte.addCustomerDocument(clienteId, file, params?)` | `PUT /api/suporte/cliente/{cliente_id}/documento/add/` |
|
|
82
|
-
| `suporte.listCustomerDocuments(clienteId)` | `GET /api/suporte/cliente/{cliente_id}/documento/list/` |
|
|
83
|
-
|
|
84
|
-
Every route must be allowed on the consumer's token in SGP (System > Tools > Admin Panel >
|
|
85
|
-
Tokens). A route outside the whitelist returns `ROUTE_NOT_ALLOWED`. Paging is the API's own:
|
|
86
|
-
`offset` and `limit`; the SDK never pages for you.
|
|
87
|
-
|
|
88
|
-
`ura.checkCoverage({ gateway, raio, coordenada })` checks installation viability: the answer is keyed
|
|
89
|
-
by the gateway's name (data-defined, e.g. `SGPMaps`) and holds the CTOs within `raio` meters or the
|
|
90
|
-
message `"Localização inviável"`. The coordinate (`"lat,lng"`) comes from the consumer; the SDK does
|
|
91
|
-
not geocode. `lat`, `lng` and `distancia` are always numbers (Geogrid sends them as numeric strings
|
|
92
|
-
and the SDK converts them). A malformed gateway answer, such as a CTO with a non-numeric `lat`, is
|
|
93
|
-
`SCHEMA_MISMATCH`.
|
|
94
|
-
|
|
95
|
-
## Result and error contract
|
|
96
|
-
|
|
97
|
-
Every method returns `SgpResult<T>`:
|
|
72
|
+
O SDK nunca lê o ambiente: as credenciais vêm de você. Exemplos completos e executados no CI, contra
|
|
73
|
+
um `fetch` falso, estão em `examples/` do repositório: `quickstart.ts`,
|
|
74
|
+
`write-with-effect.ts`,
|
|
75
|
+
`sensitive-logging.ts` e `request-raw.ts` (fora do pacote npm).
|
|
76
|
+
Para rodar um deles: `SGP_BASE_URL=... SGP_TOKEN=... SGP_APP=... bun examples/quickstart.ts`.
|
|
77
|
+
|
|
78
|
+
Cada rota precisa estar liberada no token do SGP, e a barra final conta (as rotas da
|
|
79
|
+
[referência](#referência-de-operações) estão exatamente como o SDK as chama). Rota fora da lista do
|
|
80
|
+
token volta como `FORBIDDEN` com `reason: "TOKEN_ROUTE_NOT_ALLOWED"`.
|
|
81
|
+
|
|
82
|
+
Nas rotas `GET`, `token` e `app` vão na query string; nas demais, no corpo. Uma camada de `fetch`,
|
|
83
|
+
proxy ou tracing que você injete enxerga isso.
|
|
84
|
+
|
|
85
|
+
## O modelo de resultado
|
|
86
|
+
|
|
87
|
+
Toda operação devolve `SgpResult<T>`:
|
|
98
88
|
|
|
99
89
|
```ts
|
|
100
90
|
type SgpResult<T> =
|
|
101
|
-
| {
|
|
102
|
-
|
|
91
|
+
| {
|
|
92
|
+
ok: true;
|
|
93
|
+
data: T;
|
|
94
|
+
response: CapturedResponse;
|
|
95
|
+
attempts: number;
|
|
96
|
+
warnings?: SchemaWarning[];
|
|
97
|
+
diagnostics?: Diagnostic[];
|
|
98
|
+
}
|
|
99
|
+
| {
|
|
100
|
+
ok: false;
|
|
101
|
+
error: SgpError;
|
|
102
|
+
response: CapturedResponse | null;
|
|
103
|
+
attempts: number;
|
|
104
|
+
diagnostics?: Diagnostic[];
|
|
105
|
+
};
|
|
103
106
|
|
|
104
107
|
interface SgpError {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
code: SgpErrorCode; // estável: ramifique por ele, nunca por `message`
|
|
109
|
+
message: string; // escrita pelo SDK, em inglês
|
|
110
|
+
operation: string; // ex.: "customers.list"
|
|
111
|
+
reason?: string; // código específico da operação, só com evidência
|
|
112
|
+
effect?: "none" | "unknown"; // só em escritas
|
|
113
|
+
sgp?: { detail?: unknown; msg?: unknown; error?: unknown; errors?: unknown }; // campos de erro do SGP, como vieram
|
|
114
|
+
issues?: readonly string[]; // em INVALID_RESPONSE: caminhos que quebraram o contrato, nunca valores
|
|
110
115
|
}
|
|
116
|
+
```
|
|
111
117
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
118
|
+
### Códigos
|
|
119
|
+
|
|
120
|
+
| Código | Quando |
|
|
121
|
+
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
122
|
+
| `INVALID_INPUT` | Entrada local inválida (campos, chaves desconhecidas, parâmetros de caminho, opções da chamada). Nada é enviado: `attempts: 0`, `response: null` |
|
|
123
|
+
| `UNAUTHORIZED` | HTTP 401, ou 403 cujo `detail` diz que as credenciais estão incorretas |
|
|
124
|
+
| `FORBIDDEN` | Demais 403. Com `reason: "TOKEN_ROUTE_NOT_ALLOWED"` quando o `detail` diz que as credenciais "não foram fornecidas" (rota fora da lista do token, ou barra final errada) |
|
|
125
|
+
| `RATE_LIMITED` | HTTP 429 (nunca observado na instância) |
|
|
126
|
+
| `ROUTE_NOT_ALLOWED` | Restrição local do SDK: hoje só `request()` na rota de anotações (use `experimental.annotations.list`). Nada é enviado; nunca mapeia o 405 do HTTP |
|
|
127
|
+
| `NOT_FOUND` | HTTP 404 (rota ou recurso; o significado específico, só via `reason`) |
|
|
128
|
+
| `REJECTED` | Demais 4xx (400, 402, 405, 409, 422...), inclusive validação de campo do SGP, e recusas conhecidas que o SGP devolve com HTTP 200 |
|
|
129
|
+
| `UPSTREAM` | HTTP 5xx |
|
|
130
|
+
| `NETWORK` | Falha de transporte (DNS, conexão, TLS, corpo interrompido) |
|
|
131
|
+
| `TIMEOUT` | O prazo do SDK expirou (`timeoutMs`) |
|
|
132
|
+
| `ABORTED` | O seu `abortSignal` cancelou |
|
|
133
|
+
| `INVALID_RESPONSE` | 2xx fora do contrato do método, ou status que o método não aceita (3xx: redirecionamentos nunca são seguidos) |
|
|
134
|
+
|
|
135
|
+
O status HTTP recebido sempre decide o código; um corpo ilegível não apaga um status já recebido
|
|
136
|
+
(vira `response.body.kind === "unavailable"`).
|
|
137
|
+
|
|
138
|
+
### `reason`
|
|
139
|
+
|
|
140
|
+
Código específico de uma operação, **só** onde há evidência por operação; nunca um mapa global de
|
|
141
|
+
status. Os existentes:
|
|
142
|
+
|
|
143
|
+
| `reason` | Operação | Situação |
|
|
144
|
+
| ----------------------------- | ------------------------------------------ | -------------------------------------- |
|
|
145
|
+
| `TOKEN_ROUTE_NOT_ALLOWED` | qualquer (com `FORBIDDEN`) | 403 "credenciais não foram fornecidas" |
|
|
146
|
+
| `DOCUMENT_ALREADY_REGISTERED` | `preRegistrations.create` | 402, CPF já cadastrado |
|
|
147
|
+
| `STATUS_NOT_CHANGED` | `contracts.updateStatus` | 400 "Status de Contrato não alterado." |
|
|
148
|
+
| `CUSTOMER_NOT_IN_CRM` | `crm.contracts.create`, `.createByCpfCnpj` | 400 "Não encontrado cliente CRM ..." |
|
|
149
|
+
| `CONTRACT_NOT_ACTIVE` | `services.addTv` | 400, contrato não ativo |
|
|
150
|
+
|
|
151
|
+
Sem `reason`, a falha continua útil: `code`, `effect`, `sgp` e `response.body` dizem tudo o que o
|
|
152
|
+
SGP disse.
|
|
153
|
+
|
|
154
|
+
### `effect`: o que se sabe sobre uma escrita
|
|
155
|
+
|
|
156
|
+
Só escritas têm `error.effect`; leituras não têm. O código sozinho não determina o efeito (e "leitura"
|
|
157
|
+
é propriedade da operação conhecida, nunca do verbo HTTP).
|
|
158
|
+
|
|
159
|
+
- **`"none"`**: o SDK consegue afirmar que a mudança pedida **não** foi aplicada. Acontece em recusa
|
|
160
|
+
local (`INVALID_INPUT`), cancelamento antes do envio, recusa conhecida da operação (tabela de
|
|
161
|
+
`reason` acima) e 4xx do SGP com formato de erro de campo ou de negócio (`errors` como objeto,
|
|
162
|
+
`error` ou `detail` como texto). Corrigir a entrada e chamar de novo é seguro.
|
|
163
|
+
- **`"unknown"`**: o SGP pode ter aplicado. Vale para timeout, falha de rede, cancelamento depois do
|
|
164
|
+
envio, 5xx, 3xx, 2xx fora do contrato, 4xx com corpo ilegível ou de formato não reconhecido, a
|
|
165
|
+
recusa com HTTP 200 de `occurrences.create` (`status: 0`) e o "invólucro genérico" do SGP
|
|
166
|
+
(`errors` como **array**, "Ocorreu um erro: ..."), salvo quando a operação o declara como
|
|
167
|
+
recusa conhecida.
|
|
168
|
+
|
|
169
|
+
Uma observação de "nada foi aplicado" é registrada como observação com seu método de verificação
|
|
170
|
+
(releitura), não como garantia.
|
|
171
|
+
|
|
172
|
+
**Diante de `"unknown"`, não reenvie às cegas.** Releia o estado e só então decida:
|
|
173
|
+
|
|
174
|
+
| Escrita que ficou em `unknown` | Leitura que verifica |
|
|
175
|
+
| ------------------------------------------------------------ | ------------------------------------------------------------------------------ |
|
|
176
|
+
| `crm.customers.create` | `customers.search` ou `customers.list` pelo documento |
|
|
177
|
+
| `crm.contracts.create`, `crm.contracts.createByCpfCnpj` | `crm.contracts.list` (por documento) |
|
|
178
|
+
| `crm.customers.updateStatus` | ler o cliente antes de reenviar |
|
|
179
|
+
| `contracts.updateStatus`, `contracts.update` | `contracts.list({ contrato })` |
|
|
180
|
+
| `preRegistrations.create` | `customers.list({ cpfcnpj })` (a resposta de sucesso não traz ids) |
|
|
181
|
+
| `occurrences.create` | `workOrders.list({ contrato })`; com `desativa_os: 1` não há OS para consultar |
|
|
182
|
+
| `services.changePlan`, `services.addTv`, `services.removeTv` | `customers.list` (serviços do contrato) |
|
|
183
|
+
| `documents.add` | `documents.list` |
|
|
184
|
+
|
|
185
|
+
### `sgp`, `response`, `warnings`, `attempts`
|
|
186
|
+
|
|
187
|
+
- **`error.sgp`** projeta os campos de erro do SGP (`detail`, `msg`, `error`, `errors`) com os
|
|
188
|
+
valores como vieram. O corpo inteiro continua em `response.body`.
|
|
189
|
+
- **`response`** é a resposta HTTP bruta: `{ status, headers, body }`, com nomes de header em
|
|
190
|
+
minúsculas. `body` é um valor discriminado: `{ kind: "json", value }` (o valor pode ser `null`),
|
|
191
|
+
`{ kind: "text", text }`, `{ kind: "empty" }`, `{ kind: "unavailable", reason }` (o corpo começou e
|
|
192
|
+
não foi lido até o fim) ou `{ kind: "withheld" }` (veja [leituras com escopo](#leituras-com-escopo)).
|
|
193
|
+
É o corpo já decodificado, não os bytes. `response` é `null` quando nenhuma resposta HTTP existiu.
|
|
194
|
+
- **`warnings`** aparece quando um campo **secundário** veio com tipo inesperado: o campo sai de
|
|
195
|
+
`data`, e `{ path, dropped, expected, received }` registra o caminho e os nomes dos tipos (nunca
|
|
196
|
+
valores). O valor original continua intacto em `response.body`. Os campos **estruturais** de cada
|
|
197
|
+
operação (ids e o que a navegação precisa) são validados com rigor: se falharem, o resultado é
|
|
198
|
+
`INVALID_RESPONSE`.
|
|
199
|
+
- **`attempts`** conta as requisições que o SDK iniciou: 0 quando recusou localmente, 1 na primeira.
|
|
200
|
+
- **Fidelidade.** Campos conhecidos são tipados; os desconhecidos chegam em `data` em todos os níveis
|
|
201
|
+
(raiz, aninhados, arrays), sem serem descartados. Um item que o SDK não consegue interpretar faz a
|
|
202
|
+
resposta falhar com `INVALID_RESPONSE`; nunca vira `{}` nem `[]`.
|
|
203
|
+
- **Paginação.** `customers.list`, `workOrders.list` e `invoices.list` devolvem **uma página**, com
|
|
204
|
+
a `paginacao` do SGP como veio (`total` nunca é calculado pelo SDK). Para a próxima página, passe
|
|
205
|
+
`offset`. Listas sem paginação mantêm o formato do SGP (array ou objeto), sem envelope.
|
|
206
|
+
- **Credenciais nos headers.** Os headers capturados nunca carregam o token nem o app: o valor de
|
|
207
|
+
qualquer parâmetro `token`/`app` é trocado por `<redacted>`, e o valor exato das credenciais (puro
|
|
208
|
+
ou codificado) também é trocado quando tem 8 caracteres ou mais. **Corpos não são alterados.**
|
|
209
|
+
|
|
210
|
+
## Tempo, cancelamento e retry
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
const controller = new AbortController();
|
|
214
|
+
const result = await sgp.customers.list(
|
|
215
|
+
{ limit: 20 },
|
|
216
|
+
{
|
|
217
|
+
timeoutMs: 5_000, // prazo desta chamada (padrão do cliente: 20 000 ms)
|
|
218
|
+
abortSignal: controller.signal,
|
|
219
|
+
maxRetries: 2, // só leituras
|
|
220
|
+
},
|
|
221
|
+
);
|
|
117
222
|
```
|
|
118
223
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
`
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
`
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
- `personal`: CPF/CNPJ, phone, e-mail, address, birth date, customer name, free text typed by
|
|
215
|
-
operators.
|
|
216
|
-
|
|
217
|
-
Tuple names follow the item: `clientSecretKeys` / `clientPersonalKeys`,
|
|
218
|
-
`customerContractSecretKeys` / `customerContractPersonalKeys`, and so on (see the package exports).
|
|
219
|
-
`omitSensitive(item, keys)` returns a shallow copy without those keys and removes them from the
|
|
220
|
-
type, so a leak becomes a compile error:
|
|
224
|
+
- **Um único orçamento de tempo por chamada lógica**: tentativas, esperas e leitura do corpo. Nunca
|
|
225
|
+
reinicia por tentativa. `timeoutMs` por chamada substitui o padrão do cliente (é padrão, não teto).
|
|
226
|
+
Deve ser um número positivo, finito, até `MAX_TIMEOUT_MS`.
|
|
227
|
+
- **`TIMEOUT`** é o prazo do SDK; **`ABORTED`** é o seu `abortSignal`. Se o sinal já estiver
|
|
228
|
+
cancelado quando você chama, nada é enviado (`effect: "none"` numa escrita); cancelado depois do
|
|
229
|
+
envio, `effect: "unknown"`.
|
|
230
|
+
- **Retry é opt-in** (`maxRetries`, padrão 0 = nenhuma nova tentativa), vale **só para leituras**
|
|
231
|
+
e só para falhas transitórias: `RATE_LIMITED`, `NETWORK` e `UPSTREAM` com HTTP 502, 503 ou 504.
|
|
232
|
+
Há espera exponencial com jitter (base de 500 ms, teto de 10 s) e `Retry-After` (segundos ou data
|
|
233
|
+
HTTP) é respeitado como piso, nunca encurtado; se a espera não cabe no orçamento, a chamada
|
|
234
|
+
termina com a última falha. `TIMEOUT`, `ABORTED`, `INVALID_*`, `UNAUTHORIZED`, `FORBIDDEN`,
|
|
235
|
+
`NOT_FOUND` e `REJECTED` nunca são repetidos.
|
|
236
|
+
- **Escritas nunca são repetidas pelo SDK**, e `request()` também não, qualquer que seja
|
|
237
|
+
`maxRetries`. Conferir o `attempts` mostra quantas requisições partiram.
|
|
238
|
+
|
|
239
|
+
## Hooks
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
const sgp = createSgpClient({
|
|
243
|
+
baseUrl,
|
|
244
|
+
credentials,
|
|
245
|
+
hooks: {
|
|
246
|
+
onRequest: (m) => log.debug({ op: m.operation, attempt: m.attempt }),
|
|
247
|
+
onResponse: (m) => log.info({ op: m.operation, status: m.status, ms: m.durationMs }),
|
|
248
|
+
onError: (m) => log.warn({ op: m.operation, code: m.code }),
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Opcionais, **síncronos** e só para observabilidade. Recebem metadados: `callId` (liga as tentativas de
|
|
254
|
+
uma chamada), `attempt` (1, 2, ...), `operation`, `method`, `pathTemplate` (omitido quando
|
|
255
|
+
desconhecido, como em `request()`) e, conforme o hook, `status`, `code` e `durationMs`. Nunca
|
|
256
|
+
recebem credenciais, URLs nem corpos. `onResponse` roda por tentativa quando os headers chegam;
|
|
257
|
+
`onError`, por tentativa que falhou.
|
|
258
|
+
|
|
259
|
+
Um hook que lança (ou devolve uma Promise, que quebra o contrato síncrono) **nunca altera o
|
|
260
|
+
resultado**: uma escrita confirmada continua confirmada. O fato aparece em `diagnostics`
|
|
261
|
+
(`{ hook, occurrence }`), sem o valor lançado.
|
|
262
|
+
|
|
263
|
+
## Dados sensíveis
|
|
264
|
+
|
|
265
|
+
Fidelidade significa que os dados do SGP chegam inteiros, e o SGP mistura credenciais (logins e senhas
|
|
266
|
+
de Central e PPPoE, Wi-Fi, MAC, IP) e dados pessoais de clientes nas respostas. **A sua aplicação
|
|
267
|
+
projeta o que sai do processo.**
|
|
268
|
+
|
|
269
|
+
- **`getSensitivity("customers.search")`** devolve, por operação, as chaves secretas e pessoais da
|
|
270
|
+
entrada (`input`) e da resposta (`output`). Serve para saber o que remover. Uma chave que não está
|
|
271
|
+
listada **não é segura**: o SGP acrescenta campos sem aviso.
|
|
272
|
+
- **`pick(origem, allowlist)`** copia só os caminhos nomeados (`"clientes[].contratos[].id"`;
|
|
273
|
+
`"[].id"` para array na raiz). Chave não nomeada nunca sai. Use para tudo o que vai a logs,
|
|
274
|
+
ferramentas, MCP ou agentes.
|
|
275
|
+
- **`toSafeLog(result)`** devolve a versão para log de um resultado: `ok`, `attempts`, `diagnostics`,
|
|
276
|
+
`warnings` (caminhos e nomes de tipo), status HTTP e, do erro, `code`, `message`, `operation`,
|
|
277
|
+
`reason`, `effect`, `issues` e os **nomes** dos campos do SGP que vieram. Sem `data`, sem `response.body`, sem headers, sem o texto de `error.sgp`
|
|
278
|
+
(que costuma repetir o que você enviou).
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
logger.info(toSafeLog(result)); // nunca `logger.info(result)`
|
|
282
|
+
const forTheAgent = pick(result.data, ["contratos[].contratoId", "contratos[].clienteId"]);
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
**Nunca registre o resultado cru.** `console.log(result)`, `structuredClone(result)`, um logger que
|
|
286
|
+
percorre o objeto ou uma cópia feita antes de `toSafeLog` enxergam tudo. E os corpos em `response.body`
|
|
287
|
+
**não são redigidos**: podem repetir credenciais ou dados pessoais que o SGP devolveu, e o texto de
|
|
288
|
+
erro do SGP pode ecoar o que você enviou. Os headers são redigidos; os corpos, não.
|
|
289
|
+
|
|
290
|
+
Veja `examples/sensitive-logging.ts` no repositório.
|
|
291
|
+
|
|
292
|
+
## Leituras com escopo
|
|
293
|
+
|
|
294
|
+
Princípio: leitor tolerante na interpretação, **fronteira estrita** no isolamento. A tolerância à
|
|
295
|
+
evolução do contrato do SGP nunca alarga o que sai de uma operação.
|
|
296
|
+
|
|
297
|
+
`experimental.annotations.list` (anotações de um cliente e/ou contrato) é o caso: o SGP ignora os
|
|
298
|
+
filtros e responde com anotações de **outros** clientes. O SDK seleciona localmente, sobre a única
|
|
299
|
+
resposta recebida:
|
|
300
|
+
|
|
301
|
+
- `response.body` é `{ kind: "withheld" }` em qualquer status, `error.sgp` guarda só textos, e nenhum
|
|
302
|
+
hook recebe o corpo. Cada anotação selecionada traz `raw`, uma cópia profunda do registro como o SGP
|
|
303
|
+
mandou.
|
|
304
|
+
- Uma anotação cuja identidade (cliente ou contrato) não pode ser decidida, ou que embute identidade
|
|
305
|
+
de outro cliente em qualquer profundidade, faz a chamada inteira falhar com `INVALID_RESPONSE`
|
|
306
|
+
(`error.issues` aponta os caminhos, sem valores). Falha fechada, nunca pulo silencioso.
|
|
307
|
+
- `request()` recusa essa rota (`ROUTE_NOT_ALLOWED`), para a fronteira não ser contornada.
|
|
308
|
+
- Não é uma fronteira de autorização: autorize os ids que você passa e rode isso só em processo de
|
|
309
|
+
servidor confiável. Uma camada de `fetch` ou tracing que você injete enxerga o corpo inteiro **antes**
|
|
310
|
+
da seleção e não deve capturá-lo.
|
|
311
|
+
- É **experimental** e pode mudar de forma incompatível em uma versão menor. A completude não é
|
|
312
|
+
garantida: o SDK faz uma requisição, nunca pagina e nunca trunca; a rota não documenta paginação.
|
|
313
|
+
|
|
314
|
+
## `request()`: chamada bruta
|
|
315
|
+
|
|
316
|
+
Para uma rota que nenhum método cobre ainda, com a mesma autenticação, prazo, cancelamento e hooks.
|
|
317
|
+
**Ferramenta de desenvolvedor: nunca a exponha como ferramenta de agente nem monte `path` ou `body`
|
|
318
|
+
a partir de entrada não confiável.**
|
|
221
319
|
|
|
222
320
|
```ts
|
|
223
|
-
const
|
|
224
|
-
safe.nome; // type error: `nome` was omitted
|
|
321
|
+
const raw = await sgp.request({ method: "POST", path: "/api/ura/clientes/", body: { limit: 1 } });
|
|
225
322
|
```
|
|
226
323
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
324
|
+
- Só caminho relativo sob `/api/` da instância configurada, já na forma final (sem query, `//`,
|
|
325
|
+
`.`/`..`, `%`). Qualquer outro: `INVALID_INPUT`, `effect: "none"`, `attempts: 0`, nada enviado.
|
|
326
|
+
Parâmetros de um `GET` vão em `body` (só primitivos). `encoding`: `json` (padrão) ou `form`.
|
|
327
|
+
- Classifica só por HTTP, com o mesmo mapa de status dos métodos. **`ok: true` significa "o SGP
|
|
328
|
+
aceitou em nível HTTP" (2xx), não sucesso de negócio**: o SGP responde algumas recusas com 200,
|
|
329
|
+
então leia `data` (o corpo como veio, tipado `unknown`).
|
|
330
|
+
- Nunca segue redirecionamentos (3xx é `INVALID_RESPONSE`) e nunca repete.
|
|
331
|
+
- Depois do envio, toda falha tem `effect: "unknown"`, inclusive 4xx; o verbo nunca é tomado como
|
|
332
|
+
prova de que a chamada só lê.
|
|
333
|
+
- Não declara chaves sensíveis: trate `data` e `response` como sensíveis.
|
|
334
|
+
|
|
335
|
+
Veja `examples/request-raw.ts` no repositório.
|
|
336
|
+
|
|
337
|
+
## IDs com marca e os dois catálogos de planos
|
|
338
|
+
|
|
339
|
+
Alguns ids têm tipo "com marca" (apenas em tempo de compilação; em runtime são números):
|
|
340
|
+
|
|
341
|
+
| Tipo | De onde vem | Para onde vai |
|
|
342
|
+
| ----------------------- | ----------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
343
|
+
| `CustomerId` | `customers.list` (`clientes[].id`), `customers.search` (`clienteId`), `crm.customers.create` (`cliente_id`) | `cliente_id` de `crm.*`, `documents.*`, `customers.list`, `invoices.list` |
|
|
344
|
+
| `PlanId` | `plans.list` (`planos[].id`) | `plano` de `contracts.list`, `plano_id` de `services.changePlan` e de `crm.contracts.create`, `planobase_id` de `services.addTv` |
|
|
345
|
+
| `PreRegistrationPlanId` | `preRegistrations.listPlans` (`id`) | `plano_id` de `preRegistrations.create` |
|
|
346
|
+
|
|
347
|
+
Um cliente CRM tem o mesmo `CustomerId` do cliente. **O id de um contrato não é um `CustomerId`**, e
|
|
348
|
+
o compilador recusa um no lugar do outro e um número solto onde um id é esperado. Para marcar um
|
|
349
|
+
número em que você confia: `as CustomerId`.
|
|
350
|
+
|
|
351
|
+
**Há dois catálogos de planos, com espaços de ids diferentes**: o mesmo plano comercial pode ter outro
|
|
352
|
+
id no catálogo do pré-cadastro, e um id aceito por uma rota pode ser recusado pela outra. Os tipos
|
|
353
|
+
`PlanId` e `PreRegistrationPlanId` são mantidos separados de propósito, nos dois sentidos. A
|
|
354
|
+
conversão entre catálogos não é feita pelo SDK.
|
|
355
|
+
|
|
356
|
+
## Constantes
|
|
357
|
+
|
|
358
|
+
Códigos fixos do SGP exportados como constantes; as operações aceitam qualquer número e o SGP
|
|
359
|
+
decide.
|
|
360
|
+
|
|
361
|
+
| Constante | Valores | Evidência |
|
|
362
|
+
| ------------------ | ------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
363
|
+
| `CONTRACT_STATUS` | `active` 1, `inactive` 2, `canceled` 3, `suspended` 4, `technicalInfeasibility` 5, `new` 6, `activeReducedSpeed` 7 | 1 e 4 confirmados (200 e releitura); 2 enviado e recusado a partir de Ativo; 3, 5 e 7 do painel; 6 documentado pelo fornecedor, nunca observado |
|
|
364
|
+
| `BILLING_METHOD` | `boleto` 1, `automaticDebit` 2, `booklet` 3, `recurringCharge` 4, `pix` 6 | códigos globais fixos; o 5 não é listado (documentado para uma rota, nunca confirmado) |
|
|
365
|
+
| `ACQUISITION_MODE` | `owned` 0, `loan` 1 | o valor 2, que a documentação do fornecedor lista para o CRM, foi recusado ao vivo |
|
|
366
|
+
| `SERVICE_TYPE` | `internet` 1 | só o 1 foi enviado ao SGP |
|
|
367
|
+
| `CRM_STATUS` | `underAnalysis` 1 ... `technicalFeasibilityAnalysis` 10 | só o 6 (`contractMadeInSgp`) foi enviado ao vivo |
|
|
368
|
+
|
|
369
|
+
O SDK não guarda tabela de transições de status: quem decide o que é permitido é o SGP.
|
|
370
|
+
|
|
371
|
+
## Referência de operações
|
|
372
|
+
|
|
373
|
+
Cada método aceita `options?: CallOptions` (`abortSignal`, `timeoutMs`, `maxRetries`) como último
|
|
374
|
+
argumento. Campos de entrada e de resposta usam os nomes do SGP. Uma entrada com chave desconhecida é
|
|
375
|
+
recusada localmente (`INVALID_INPUT`); para campos que ainda não têm tipo, use `request()`. O JSDoc de
|
|
376
|
+
cada método, no tipo `SgpClient` (nos tipos publicados, `dist/client.d.ts`), é o texto de referência; aqui
|
|
377
|
+
ficam só rota, codificação, natureza, recusas conhecidas e evidência.
|
|
378
|
+
|
|
379
|
+
Legenda. **Natureza:** _leitura_ é repetível com `maxRetries`; _escrita_ nunca é repetida.
|
|
380
|
+
**Codificação:** `query` (GET, parâmetros na URL), `json`, `form` (multipart sem arquivos),
|
|
381
|
+
`form-files` (multipart com arquivo). **Evidência**, resumida (detalhes por cenário em
|
|
382
|
+
[COMPATIBILITY.md](COMPATIBILITY.md)): _observado_ (resposta real capturada), _parcial_ (sucesso
|
|
383
|
+
observado, mas recusas só documentadas ou reconstruídas, ou o inverso), _documentado_ (só a
|
|
384
|
+
documentação do fornecedor), _reconstruído_ (corpo reconstruído a partir de relatos ou testes, sem
|
|
385
|
+
captura bruta).
|
|
386
|
+
|
|
387
|
+
### `customers`
|
|
388
|
+
|
|
389
|
+
| Método | Rota e codificação | Natureza | Notas | Evidência |
|
|
390
|
+
| ------------------------- | --------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- |
|
|
391
|
+
| `customers.list(input?)` | `POST /api/ura/clientes/` · json | leitura | Uma página; `paginacao` do SGP como veio; filtros `cliente_id`, `cpfcnpj`, `cliente_nome`; contratos e serviços aninhados chegam inteiros | observado |
|
|
392
|
+
| `customers.search(input)` | `POST /api/ura/consultacliente/` · json | leitura | Devolve **candidatos** e nunca escolhe um; ao menos um critério (`cpfcnpj`, `contrato`, `nome`, `login`, `telefone`); texto vazio é recusado; sem resultado é sucesso com `contratos: []`; cada candidato traz credenciais do serviço | observado (corpo mascarado, 1.x) |
|
|
393
|
+
|
|
394
|
+
### `crm.customers` e `crm.contracts`
|
|
395
|
+
|
|
396
|
+
| Método | Rota e codificação | Natureza | Notas | Evidência |
|
|
397
|
+
| -------------------------------------- | ------------------------------------------------------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
|
|
398
|
+
| `crm.customers.create(input)` | `POST /api/crm/cliente/{F\|J}` · json | escrita | `personType` escolhe a rota; `endereco` aninhado, como o SGP o lê. **Aceita CPF já cadastrado e cria OUTRO cliente com o mesmo documento**: confira antes (`customers.search`). 400 de validação de campo: `REJECTED`, `effect: "none"` | observado (`/F`); `/J` documentado |
|
|
399
|
+
| `crm.customers.updateStatus(input)` | `POST /api/crm/cliente/{cliente_id}/status/` · json | escrita | Códigos em `CRM_STATUS`. Cliente que já saiu do CRM (ou status 6 repetido): 404 `{error:"Cadastro não encontrado"}`, `NOT_FOUND`, `effect: "none"` | observado (status 6; o 404 vem de captura da 1.x) |
|
|
400
|
+
| `crm.contracts.create(input)` | `POST /api/crm/cliente/{cliente_id}/contratos/` · json | escrita | Só para cliente ainda no funil CRM. Recusa `CUSTOMER_NOT_IN_CRM`, `effect: "none"`. `plano_id` é `PlanId`; `modoaquisicao` é `ACQUISITION_MODE` | parcial |
|
|
401
|
+
| `crm.contracts.createByCpfCnpj(input)` | `POST /api/crm/cliente/contratos/?cpfcnpj=` · json | escrita | Documento na **query** do POST. Mesma recusa `CUSTOMER_NOT_IN_CRM`. Com documento duplicado, qual cliente recebe o contrato não foi verificado. **Nenhum sucesso foi observado nesta rota** | parcial (sucesso só documentado) |
|
|
402
|
+
| `crm.contracts.list(input)` | `GET /api/crm/cliente/contratos/?cpfcnpj=` · query | leitura | Documento que não é cliente CRM: 400, `REJECTED`, sem `reason` | observado |
|
|
403
|
+
|
|
404
|
+
### `preRegistrations`
|
|
405
|
+
|
|
406
|
+
| Método | Rota e codificação | Natureza | Notas | Evidência |
|
|
407
|
+
| -------------------------------- | ----------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
|
|
408
|
+
| `preRegistrations.create(input)` | `POST /api/precadastro/{F\|J}` · form | escrita | Endereço em campos soltos no topo da entrada (`logradouro`, `numero`, ...), com os nomes do SGP. Com `precadastro_ativar: 1` vira cliente definitivo; contrato só se plano e cobrança forem suficientes. Sucesso traz só `message`, **sem ids**. CPF existente: 402, `REJECTED`, `DOCUMENT_ALREADY_REGISTERED`, `effect: "none"` | parcial (recusa observada; sucesso `{message}` do fornecedor; `/J` documentado) |
|
|
409
|
+
| `preRegistrations.listPlans()` | `POST /api/precadastro/plano/list` · form (sem barra final) | leitura | `PreRegistrationPlanId`: catálogo próprio, não é o de `plans.list` | observado |
|
|
410
|
+
| `preRegistrations.listDueDays()` | `POST /api/precadastro/vencimento/list` · json | leitura | `id` é o `vencimento_id` de `create`; `dia` é o dia do mês | observado (corpo mascarado, 1.x) |
|
|
411
|
+
| `preRegistrations.listSellers()` | `POST /api/precadastro/vendedor/list` · json | leitura | `id` é o `vendedor_id`; nome, CPF e e-mail são dados pessoais | observado (corpo mascarado, 1.x) |
|
|
412
|
+
|
|
413
|
+
### `contracts` e `services`
|
|
414
|
+
|
|
415
|
+
| Método | Rota e codificação | Natureza | Notas | Evidência |
|
|
416
|
+
| ------------------------------- | ------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ |
|
|
417
|
+
| `contracts.list(input?)` | `POST /api/ura/listacontrato/` · json | leitura | Sem paginação; array simples; sem filtro pode vir a base inteira do provedor; `plano` é `PlanId` | observado (corpo mascarado, 1.x) |
|
|
418
|
+
| `contracts.updateStatus(input)` | `POST /api/ura/contrato/status/edit/` · json | escrita | Ativar inicia a cobrança. O SGP decide as transições. Recusa `STATUS_NOT_CHANGED` (`effect: "none"`). `motivo` vem de `contracts.listStatusReasons` | parcial (200 observado; a recusa é reconstruída) |
|
|
419
|
+
| `contracts.update(input)` | `POST /api/ura/contrato/edit/` · json | escrita | Só `forma_cobranca` (`BILLING_METHOD`); devolve `resposta.alterados` como o SGP mandou. Sem recusa conhecida | observado |
|
|
420
|
+
| `contracts.listStatusReasons()` | `POST /api/ura/contrato/status/motivos/` · json | leitura | Cada provedor tem a sua lista: leia antes de passar `motivo` | reconstruído |
|
|
421
|
+
| `services.changePlan(input)` | `POST /api/suporte/service/update/{servico_id}/` · form | escrita | `action` fixa `change_plano`; `servico_tipo` obrigatório (só `1` exercitado); evento de cobrança. Sucesso traz só `msg` | observado |
|
|
422
|
+
| `services.addTv(input)` | `POST /api/servico/tv` · form | escrita | HTTP 201 com `servico_id`; só contratos ativos: recusa `CONTRACT_NOT_ACTIVE` (`effect: "none"`); `planobase_id` é `PlanId` | parcial (201 observado; a recusa é reconstruída) |
|
|
423
|
+
| `services.removeTv(input)` | `DELETE /api/servico/tv/{servico_id}` · form | escrita | O contrato vai num corpo multipart no DELETE, como o SGP espera. Sem recusa conhecida | observado |
|
|
424
|
+
|
|
425
|
+
### `plans`, `invoices`, `workOrders`, `occurrences`
|
|
426
|
+
|
|
427
|
+
| Método | Rota e codificação | Natureza | Notas | Evidência |
|
|
428
|
+
| --------------------------- | ---------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
|
|
429
|
+
| `plans.list()` | `GET /api/ura/consultaplano/` · query | leitura | Mantém o formato do SGP (`planos` e totais); `planos[].id` é `PlanId` | observado |
|
|
430
|
+
| `invoices.list(input?)` | `POST /api/ura/titulos/` · json | leitura | Uma página; `paginacao.total` ausente é `INVALID_RESPONSE`; máximo de 250 por página é do fornecedor. Traz nome, documento, links de pagamento e PIX | parcial (página vazia observada; página populada reconstruída) |
|
|
431
|
+
| `workOrders.list(input?)` | `POST /api/ura/ordemservico/list/` · json | leitura | Uma página; `ocorrencia` é texto e continua texto | observado |
|
|
432
|
+
| `workOrders.listReasons()` | `GET /api/os/ocorrencia/motivo/list/` · query | leitura | `id` e `codigo` são distintos; `occurrences.create` usa o `codigo` em `motivoos` | observado (corpo mascarado, 1.x) |
|
|
433
|
+
| `occurrences.create(input)` | `POST /api/ura/chamado/` · json | escrita | "Chamado" do SGP. Devolve `id` da **ocorrência** e `protocolo`, nunca o id da OS (leia com `workOrders.list`). Recusa com HTTP 200 e `status: 0`: `REJECTED`, `effect: "unknown"` | parcial (sucesso: corpo mascarado, 1.x; a recusa é reconstruída) |
|
|
434
|
+
| `occurrences.listTypes()` | `GET /api/os/ocorrencia/tipo/list/` · query | leitura | `id` e `codigo`; `occurrences.create` usa o `codigo` em `ocorrenciatipo` | observado (corpo mascarado, 1.x) |
|
|
435
|
+
| `occurrences.listMethods()` | `GET /api/ura/ocorrencia/metodo/list/` · query | leitura | | observado (corpo mascarado, 1.x) |
|
|
436
|
+
|
|
437
|
+
### `documents`, `coverage`, `catalogs`, `experimental`
|
|
438
|
+
|
|
439
|
+
| Método | Rota e codificação | Natureza | Notas | Evidência |
|
|
440
|
+
| -------------------------------------- | ------------------------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- |
|
|
441
|
+
| `documents.add(input)` | `PUT /api/suporte/cliente/{cliente_id}/documento/add/` · form-files | escrita | Sucesso é 2xx com corpo **vazio** (`data: null`, nada identifica o documento); qualquer outro corpo 2xx é `INVALID_RESPONSE` com `effect: "unknown"` | reconstruído |
|
|
442
|
+
| `documents.list(input)` | `GET /api/suporte/cliente/{cliente_id}/documento/list/` · query | leitura | `cliente_id` é um `CustomerId` positivo; array vazio = sem documentos | observado (corpo mascarado, 1.x) |
|
|
443
|
+
| `coverage.check(input)` | `GET /api/ura/viabilidadeinstalacao` · query (sem barra final) | leitura | `gateway`, `raio` (metros) e `coordenada` (`"lat,lng"`) obrigatórios; o SDK não geocodifica; nada é convertido (o Geogrid manda números como texto) | observado (corpo mascarado, 1.x; SGPMaps e Geogrid) |
|
|
444
|
+
| `catalogs.listPops()` | `POST /api/ura/pops/` · json | leitura | | observado (corpo mascarado, 1.x) |
|
|
445
|
+
| `catalogs.listNas()` | `POST /api/ura/nas/list/` · json | leitura | `endereco_ip` é segredo | observado (corpo mascarado, 1.x) |
|
|
446
|
+
| `catalogs.listBillingProfiles()` | `GET /api/ura/portador/` · query | leitura | "Portadores" do SGP; os ids alimentam o filtro `portador` de `invoices.list` | observado (corpo mascarado, 1.x) |
|
|
447
|
+
| `experimental.annotations.list(input)` | `GET /api/ura/cliente/anotacao/list/` · query | leitura | **Experimental.** Seleção local, corpo retido. Veja [leituras com escopo](#leituras-com-escopo) | reconstruído (corpo sintético; tipos de campo observados) |
|
|
448
|
+
|
|
449
|
+
Todas as leituras acima aceitam `maxRetries`. `request(input, options?)` está descrita
|
|
450
|
+
[acima](#request-chamada-bruta).
|
|
451
|
+
|
|
452
|
+
## Migração do 1.x
|
|
453
|
+
|
|
454
|
+
A 2.0 é uma quebra de contrato (resultado, códigos, nomes e comportamento), sem aliases. O mapa de
|
|
455
|
+
cada método do 1.x e as mudanças de comportamento estão em [MIGRATION.md](MIGRATION.md); o histórico
|
|
456
|
+
de mudanças, em [CHANGELOG.md](CHANGELOG.md).
|
|
457
|
+
|
|
458
|
+
## Desenvolvimento
|
|
459
|
+
|
|
460
|
+
Veja `CONTRIBUTING.md` no repositório. Os testes usam sempre um `fetch` injetado: nenhum teste
|
|
461
|
+
toca o SGP, e a CI não usa credenciais.
|