@alijunior/acbr-api-sdk-node 2.3.4 → 2.3.6

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/README.md CHANGED
@@ -10,19 +10,29 @@
10
10
  <img alt="license" src="https://img.shields.io/npm/l/@alijunior/acbr-api-sdk-node.svg">
11
11
 
12
12
  </p>
13
+ <h3>Veja também o SDK para a Nuvem Fiscal
14
+ <a href="https://github.com/alijuniorbr/nuvemfiscal-sdk-node">
15
+ @alijunior/nuvemfiscal-sdk-node
16
+ </a>
17
+ </h3>
18
+ <br />
13
19
 
14
20
  SDK em **TypeScript/Node.js** para integração com a **Acbr API**, com tipos gerados a partir do OpenAPI e client HTTP enxuto (fetch/axios).
15
21
 
16
22
  > ✅ Destaques
17
23
  >
18
24
  > - Tipagem forte (params, body e response) **direto do OpenAPI**
19
- > - Client simples: `AcbrApiClient`
25
+ > - Client simples: `AcbrApi`
20
26
  > - Adapters: `FetchHttpClient` (nativo) e `AxiosHttpClient` (opcional)
21
- > - **Novos helpers de token**:
27
+ > - **Helpers de token**:
22
28
  > - `getClientCredentialsToken` (objeto completo do token)
23
29
  > - `getAccessTokenString` (só a string do token; wrapper)
24
30
  > - `getAcbrApiTokenForBrowser` (para **testes no browser** — não usar em produção)
25
31
 
32
+ > 🤖 **Usando com assistentes de IA?** Este pacote inclui um arquivo [`llms.txt`](./llms.txt) com contexto pronto sobre como aplicar o SDK no backend e no browser. Aponte seu assistente para ele.
33
+
34
+ > 💡 Este SDK é **gêmeo** do [`@alijunior/nuvemfiscal-sdk-node`](https://github.com/alijuniorbr/nuvemfiscal-sdk-node): mesma interface, mesmos adapters e mesmos helpers de token. Se você já usa um, o outro funciona do mesmo jeito — trocando apenas o nome do client (`AcbrApi` ⇆ `NuvemFiscalApi`), o `tokenUrl` e o `API_BASE`.
35
+
26
36
  ---
27
37
 
28
38
  ## Instalação
@@ -62,6 +72,8 @@ API_BASE=https://prod.acbr.api.br/
62
72
  SCOPE=empresa conta cnpj cep nfce nfe
63
73
  ```
64
74
 
75
+ > A autenticação da Acbr API usa **Keycloak** (`auth.acbr.api.br`). O endpoint de token é o padrão OpenID Connect: `.../realms/ACBrAPI/protocol/openid-connect/token`.
76
+
65
77
  ---
66
78
 
67
79
  ## Helpers de Token
@@ -81,7 +93,7 @@ const input: ClientCredentialsInput = {
81
93
  tokenUrl: process.env.TOKEN_URL!,
82
94
  clientId: process.env.CLIENT_ID!,
83
95
  clientSecret: process.env.CLIENT_SECRET!,
84
- scope: process.env.SCOPE,
96
+ scope: process.env.SCOPE, // opcional
85
97
  authStyle: "auto", // tenta basic e cai para Body em 400/401
86
98
  };
87
99
 
@@ -100,13 +112,17 @@ const accessToken = await getAccessTokenString({
100
112
  tokenUrl: process.env.TOKEN_URL!,
101
113
  clientId: process.env.CLIENT_ID!,
102
114
  clientSecret: process.env.CLIENT_SECRET!,
103
- scope: process.env.SCOPE!,
115
+ scope: process.env.SCOPE, // opcional
104
116
  });
105
117
  ```
106
118
 
107
119
  ### 3) `getAcbrApiTokenForBrowser` (Browser – **somente testes**)
108
120
 
109
- Útil para provar fluxo no front **localmente**. **Não use em produção** (expõe o `client_secret`).
121
+ Útil para provar o fluxo **localmente**. **Não use em produção** (expõe o `client_secret`).
122
+
123
+ Assinatura: `getAcbrApiTokenForBrowser(clientId, clientSecret, tokenUrl?, scope?)`. Os parâmetros `tokenUrl` e `scope` são **opcionais** e posicionais.
124
+
125
+ > ⚠️ **Atenção (CORS):** o servidor de autenticação Keycloak da Acbr API normalmente **não libera CORS** para origens de browser. Na prática, mesmo em testes, a obtenção de token direto do navegador pode ser bloqueada. O caminho confiável é obter o token **no backend** e expô-lo ao front (veja a seção sobre browser em produção, mais abaixo).
110
126
 
111
127
  ```ts
112
128
  import { getAcbrApiTokenForBrowser } from "@alijunior/acbr-api-sdk-node";
@@ -114,8 +130,8 @@ import { getAcbrApiTokenForBrowser } from "@alijunior/acbr-api-sdk-node";
114
130
  const accessToken = await getAcbrApiTokenForBrowser(
115
131
  "CLIENT_ID",
116
132
  "CLIENT_SECRET",
117
- "https://auth.acbr.api.br/oauth/token", // default
118
- "empresa conta cnpj cep nfce nfe" // default
133
+ "https://auth.acbr.api.br/realms/ACBrAPI/protocol/openid-connect/token",
134
+ "empresa conta cnpj cep nfce nfe"
119
135
  );
120
136
  ```
121
137
 
@@ -127,7 +143,7 @@ const accessToken = await getAcbrApiTokenForBrowser(
127
143
 
128
144
  ```ts
129
145
  import {
130
- AcbrApiClient,
146
+ AcbrApi,
131
147
  FetchHttpClient,
132
148
  getAccessTokenString,
133
149
  } from "@alijunior/acbr-api-sdk-node";
@@ -136,14 +152,14 @@ const accessToken = await getAccessTokenString({
136
152
  tokenUrl: process.env.TOKEN_URL!,
137
153
  clientId: process.env.CLIENT_ID!,
138
154
  clientSecret: process.env.CLIENT_SECRET!,
139
- scope: process.env.SCOPE!,
155
+ scope: process.env.SCOPE,
140
156
  });
141
157
 
142
158
  const http = new FetchHttpClient({
143
159
  headers: { Authorization: `Bearer ${accessToken}` },
144
160
  });
145
161
 
146
- const api = new AcbrApiClient(http, process.env.API_BASE!);
162
+ const api = new AcbrApi(http, process.env.API_BASE!);
147
163
 
148
164
  // CEP
149
165
  const cep = await api.consultarCep({ Cep: "04513010" });
@@ -163,10 +179,11 @@ console.log("CNPJ:", cnpjList);
163
179
 
164
180
  ### Com `axios` (opcional)
165
181
 
182
+ O `AxiosHttpClient` recebe um objeto de configuração. Você pode passar apenas o `accessToken` (o adapter cria a instância axios internamente) **ou** fornecer uma `axiosInstance` própria.
183
+
166
184
  ```ts
167
- import axios from "axios";
168
185
  import {
169
- AcbrApiClient,
186
+ AcbrApi,
170
187
  AxiosHttpClient,
171
188
  getAccessTokenString,
172
189
  } from "@alijunior/acbr-api-sdk-node";
@@ -177,17 +194,114 @@ const accessToken = await getAccessTokenString({
177
194
  clientSecret: process.env.CLIENT_SECRET!,
178
195
  });
179
196
 
180
- const axiosInstance = axios.create({
181
- headers: { Authorization: `Bearer ${accessToken}` },
197
+ const http = new AxiosHttpClient({
198
+ accessToken,
199
+ baseURL: process.env.API_BASE!,
182
200
  });
183
201
 
184
- const http = new AxiosHttpClient(axiosInstance);
185
- const api = new AcbrApiClient(http, process.env.API_BASE!);
202
+ const api = new AcbrApi(http, process.env.API_BASE!);
186
203
 
187
204
  // exemplo
188
205
  const cep = await api.consultarCep({ Cep: "01001000" });
189
206
  ```
190
207
 
208
+ Caso prefira controlar a instância axios (interceptors, timeout, etc.), passe-a via `axiosInstance`:
209
+
210
+ ```ts
211
+ import axios from "axios";
212
+ import { AxiosHttpClient } from "@alijunior/acbr-api-sdk-node";
213
+
214
+ const axiosInstance = axios.create({
215
+ baseURL: process.env.API_BASE!,
216
+ headers: { Authorization: `Bearer ${accessToken}` },
217
+ });
218
+
219
+ const http = new AxiosHttpClient({ axiosInstance });
220
+ ```
221
+
222
+ > **Opções do construtor do `AxiosHttpClient`:** `{ accessToken?, axiosInstance?, baseURL?, headers?, timeoutMs? }`. Se `accessToken` e `axiosInstance` forem informados simultaneamente, a `axiosInstance` prevalece.
223
+
224
+ ---
225
+
226
+ ## Exemplo completo: empresa, certificado e emissão
227
+
228
+ Fluxo típico: cadastrar a empresa emitente, enviar o certificado digital, configurar a NFC-e e emitir.
229
+
230
+ ```ts
231
+ import {
232
+ AcbrApi,
233
+ FetchHttpClient,
234
+ getAccessTokenString,
235
+ type Empresa,
236
+ } from "@alijunior/acbr-api-sdk-node";
237
+
238
+ const accessToken = await getAccessTokenString({
239
+ tokenUrl: process.env.TOKEN_URL!,
240
+ clientId: process.env.CLIENT_ID!,
241
+ clientSecret: process.env.CLIENT_SECRET!,
242
+ scope: process.env.SCOPE,
243
+ });
244
+
245
+ const http = new FetchHttpClient({
246
+ headers: { Authorization: `Bearer ${accessToken}` },
247
+ });
248
+ const api = new AcbrApi(http, process.env.API_BASE!);
249
+
250
+ // 1) Cadastrar a empresa emitente
251
+ const empresa: Empresa = {
252
+ cpf_cnpj: "00000000000000", // sem máscara
253
+ nome_razao_social: "Minha Empresa LTDA",
254
+ nome_fantasia: "Minha Empresa",
255
+ email: "contato@minhaempresa.com.br",
256
+ inscricao_estadual: "ISENTO",
257
+ endereco: {
258
+ logradouro: "Rua Exemplo",
259
+ numero: "100",
260
+ bairro: "Centro",
261
+ codigo_municipio: "3550308", // código IBGE
262
+ uf: "SP",
263
+ cep: "01001000", // sem máscara
264
+ },
265
+ };
266
+
267
+ // criarEmpresa cria ou, se já existir, normalmente retorna conflito —
268
+ // trate o erro e use atualizarEmpresa quando necessário (veja Tratamento de erros).
269
+ await api.criarEmpresa(empresa);
270
+
271
+ // 2) Cadastrar o certificado digital (.pfx/.p12 em base64)
272
+ await api.cadastrarCertificadoEmpresa(
273
+ { cpf_cnpj: empresa.cpf_cnpj },
274
+ {
275
+ certificado: "BASE64_DO_PFX",
276
+ password: "senha-do-certificado",
277
+ }
278
+ );
279
+
280
+ // 3) Configurar a NFC-e (CRT + CSC da SEFAZ)
281
+ // CRT: 1=Simples Nacional, 2=SN excesso de sublimite, 3=Regime Normal, 4=MEI.
282
+ await api.alterarConfigNfce(
283
+ { cpf_cnpj: empresa.cpf_cnpj },
284
+ {
285
+ CRT: 1,
286
+ ambiente: "homologacao", // ou "producao"
287
+ sefaz: {
288
+ id_csc: 1,
289
+ csc: "CODIGO_CSC_DA_SEFAZ",
290
+ },
291
+ }
292
+ );
293
+
294
+ // 4) Consultar a empresa
295
+ const cadastrada = await api.consultarEmpresa({ cpf_cnpj: empresa.cpf_cnpj });
296
+ console.log("Empresa cadastrada:", cadastrada.nome_razao_social);
297
+
298
+ // 5) Emitir uma NFC-e (monte o pedido conforme o tipo NfePedidoEmissao)
299
+ // const nfce = await api.emitirNfce(pedido);
300
+ // console.log("Status:", nfce.status, "Chave:", nfce.chave);
301
+ ```
302
+
303
+ > Operações de exclusão (ex.: `excluirEmpresa`, `excluirCertificadoEmpresa`) retornam `void`: a API responde `204 No Content` e o adapter resolve sem corpo.
304
+
191
305
  ---
192
306
 
193
307
  ## Tratamento de erros (exemplo)
@@ -196,10 +310,11 @@ Os adapters levantam `Error` com `status` e, se presente, `body`.
196
310
 
197
311
  ```ts
198
312
  function printHttpError(e: unknown, ctx?: string) {
199
- const status = (e as any)?.status;
313
+ const status = (e as { status?: number }).status;
314
+ const body = (e as { body?: { error?: { message?: string }; message?: string } }).body;
200
315
  const msg =
201
- (e as any)?.body?.error?.message ??
202
- (e as any)?.body?.message ??
316
+ body?.error?.message ??
317
+ body?.message ??
203
318
  (e as Error)?.message ??
204
319
  String(e);
205
320
 
@@ -217,6 +332,10 @@ try {
217
332
  }
218
333
  ```
219
334
 
335
+ > Em adapters baseados em `fetch`/`axios` deste SDK, o erro expõe `status` e `body` diretamente (ex.: `err.status`), e **não** `err.response.status` (formato do axios cru). Trate uma consulta `404` como "registro não encontrado".
336
+
337
+ > **Empresa já cadastrada:** ao tentar `criarEmpresa` para um CNPJ existente, a Acbr API pode retornar um erro com código do tipo `EmpresaAlreadyExists`. Nesse caso, faça o fallback para `atualizarEmpresa`. A detecção mais segura é verificar `status === 409` **ou** `/AlreadyExists/i.test(code)`, já que o status retornado pode variar.
338
+
220
339
  ---
221
340
 
222
341
  ## Tipos úteis (reexportados)
@@ -243,20 +362,20 @@ import type {
243
362
 
244
363
  ---
245
364
 
246
- ## Download de binarios
365
+ ## Download de binários
366
+
367
+ Métodos como `baixarPdfNfe` retornam `ArrayBuffer`.
247
368
 
248
369
  ```typescript
370
+ // Node: salvar em disco
249
371
  import { writeFile } from "node:fs/promises";
250
- import { arrayBufferToNodeBuffer } from "../types/acbr-api.httpclient";
251
-
252
- // Node: para salvar o arquivo
253
372
 
254
373
  const pdf = await api.baixarPdfNfe({ id: "..." });
255
- await writeFile("nota.pdf", arrayBufferToNodeBuffer(pdf));
374
+ await writeFile("nota.pdf", Buffer.from(pdf));
256
375
  ```
257
376
 
258
377
  ```typescript
259
- // Browser: para baixar
378
+ // Browser: forçar download
260
379
 
261
380
  const ab = await api.baixarPdfNfe({ id: "..." });
262
381
  const blob = new Blob([ab], { type: "application/pdf" });
@@ -270,6 +389,14 @@ URL.revokeObjectURL(url);
270
389
 
271
390
  ---
272
391
 
392
+ ## Uso com assistentes de IA (`llms.txt`)
393
+
394
+ O pacote inclui um arquivo [`llms.txt`](./llms.txt) na raiz, seguindo a convenção [llmstxt.org](https://llmstxt.org/). Ele descreve, em formato otimizado para LLMs, as assinaturas verificadas dos helpers de token e adapters, os fluxos de uso em **backend** e **browser** (incluindo a restrição de CORS do Keycloak), e as armadilhas mais comuns.
395
+
396
+ Ao gerar código que usa este SDK com um assistente, forneça o conteúdo de `llms.txt` como contexto para obter exemplos corretos.
397
+
398
+ ---
399
+
273
400
  ## Desenvolvimento
274
401
 
275
402
  ```bash
@@ -292,4 +419,4 @@ pnpm build
292
419
 
293
420
  ## Licença
294
421
 
295
- [MIT](./LICENSE)
422
+ [MIT](./LICENSE)