foxnfe 1.3.0

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 ADDED
@@ -0,0 +1,236 @@
1
+ # foxnfe
2
+
3
+ SDK oficial FOX NF-e para Node.js — emissão NF-e, NFSe, cancelamento, consulta e integração MCP.
4
+
5
+ ## Requisitos
6
+
7
+ - Node.js 18+
8
+ - TypeScript 5+ (opcional, para tipagem completa)
9
+
10
+ ## Instalação
11
+
12
+ ```bash
13
+ npm install foxnfe
14
+ # ou
15
+ yarn add foxnfe
16
+ # ou
17
+ pnpm add foxnfe
18
+ ```
19
+
20
+ ## Quick Start
21
+
22
+ ```typescript
23
+ import { Client } from 'foxnfe';
24
+
25
+ const client = new Client({ tenantSlug: 'minha-empresa' });
26
+
27
+ // Autenticar
28
+ const auth = await client.login('email@empresa.com', 'senha-segura');
29
+ console.log('Token:', auth.token);
30
+
31
+ // Ou usar token existente
32
+ const client2 = new Client({ tenantSlug: 'minha-empresa', token: 'seu-token-aqui' });
33
+ // Ou via withToken (retorna nova instância)
34
+ const authed = client.withToken('seu-token-aqui');
35
+ ```
36
+
37
+ ## NF-e
38
+
39
+ ```typescript
40
+ import { Client, NfeEmitRequest } from 'foxnfe';
41
+
42
+ const client = new Client({ tenantSlug: 'minha-empresa', token: 'seu-token' });
43
+ const nfe = new Nfe(client);
44
+
45
+ // Emitir NF-e
46
+ const payload: NfeEmitRequest = {
47
+ ambiente: 2, // 2=homologação
48
+ certificate_id: 1,
49
+ tomador: {
50
+ cnpj: '12345678000190',
51
+ razao_social: 'Empresa Tomadora Ltda',
52
+ endereco: {
53
+ logradouro: 'Rua das Flores',
54
+ numero: '100',
55
+ municipio: 'São Paulo',
56
+ uf: 'SP',
57
+ cep: '01310100',
58
+ },
59
+ },
60
+ itens: [{
61
+ codigo: 'SRV001',
62
+ descricao: 'Serviço de consultoria',
63
+ cfop: '5933',
64
+ quantidade: 1,
65
+ valor_unitario: 1000.00,
66
+ valor_total: 1000.00,
67
+ }],
68
+ pagamentos: [{ forma: '01', valor: 1000.00 }],
69
+ total: 1000.00,
70
+ };
71
+
72
+ const result = await nfe.emit(payload);
73
+ console.log('NF-e ID:', result.id);
74
+
75
+ // Aguardar autorização (polling automático)
76
+ const nfeAutorizada = await nfe.waitForAuthorization(result.id);
77
+ console.log('Status:', nfeAutorizada.status); // 'authorized'
78
+
79
+ // Baixar XML
80
+ const xml = await nfe.xml(result.id);
81
+ await fs.writeFile('nfe.xml', xml);
82
+
83
+ // Baixar DANFE PDF
84
+ const pdf = await nfe.pdf(result.id);
85
+ await fs.writeFile('danfe.pdf', pdf);
86
+
87
+ // Cancelar
88
+ await nfe.cancel(result.id, { justificativa: 'Cancelamento solicitado pelo cliente' });
89
+ ```
90
+
91
+ ## NFSe
92
+
93
+ ```typescript
94
+ import { Client, Nfse, NfseEmitRequest } from 'foxnfe';
95
+
96
+ const nfse = new Nfse(client);
97
+
98
+ const payload: NfseEmitRequest = {
99
+ ambiente: 2,
100
+ certificate_id: 1,
101
+ prestador: {
102
+ cnpj: '12345678000190',
103
+ inscricao_municipal: '123456',
104
+ razao_social: 'Minha Empresa Ltda',
105
+ codigo_municipio: '3550308', // São Paulo (IBGE)
106
+ },
107
+ tomador: {
108
+ cnpj: '98765432000110',
109
+ nome: 'Cliente S.A.',
110
+ },
111
+ servico: {
112
+ codigo_tributacao_nacional: '01.01.00001',
113
+ descricao: 'Desenvolvimento de software',
114
+ data_competencia: '2026-05-01',
115
+ valor: 5000.00,
116
+ aliquota_iss: 2.0,
117
+ },
118
+ };
119
+
120
+ const result = await nfse.emit(payload);
121
+ const nfseData = await nfse.get(result.id);
122
+ console.log('Número NFSe:', nfseData.numero_nfse);
123
+
124
+ // Consultar por RPS ou chave
125
+ await nfse.consultByNumero('00000001');
126
+ await nfse.consultByChave('SP3550308202605010000000000001');
127
+
128
+ // Cancelar / Substituir
129
+ await nfse.cancel(result.id, { motivo: 'Erro nos dados' });
130
+ await nfse.substitute(result.id, { ...payload, motivo_cancelamento: 'Correção' });
131
+ ```
132
+
133
+ ## MCP (Model Context Protocol)
134
+
135
+ ```typescript
136
+ import { Mcp } from 'foxnfe';
137
+
138
+ const mcp = new Mcp(client);
139
+
140
+ // Inicializar sessão MCP
141
+ const info = await mcp.initialize();
142
+ console.log('MCP Server:', info.serverInfo.name);
143
+
144
+ // Listar tools
145
+ const { tools } = await mcp.listTools();
146
+ tools.forEach(t => console.log(`${t.name}: ${t.description}`));
147
+
148
+ // Chamar uma tool
149
+ const result = await mcp.callTool('emitir_nfe', {
150
+ ambiente: 2,
151
+ certificate_id: 1,
152
+ // ...
153
+ });
154
+
155
+ if (result.is_error) {
156
+ console.error('Tool error:', result.content[0]?.text);
157
+ } else {
158
+ console.log('Tool result:', result.content[0]?.text);
159
+ }
160
+ ```
161
+
162
+ ## 1.3.0 — eventos, rejeições, homologação, RTC, cobertura NFS-e e suporte
163
+
164
+ ```typescript
165
+ import { NfeEvents, Nfe, Nfse, Rtc, Support } from 'foxnfe';
166
+
167
+ const ev = new NfeEvents(client);
168
+ await ev.atorInteressado(15, { documento: '11222333000181' }); // 110150
169
+ await ev.insucessoEntrega(15, { dh_tentativa: '2026-09-08T10:00:00-03:00', tp_motivo: 1 }); // 110192
170
+ await ev.inutilizar({ serie: 1, numero_inicial: 10, numero_final: 12, justificativa: 'Numeração pulada por falha do ERP' });
171
+ await ev.contratos(); // catálogo (conciliação financeira, RTC…)
172
+ await ev.registrarEvento(15, 'econf', { /* campos do contrato */ });
173
+
174
+ const nfe = new Nfe(client);
175
+ await nfe.rejeicao('539'); // categoria/ação/dica
176
+ await nfe.homologacaoRun(65); // amostras XML/PDF simuladas por cenário
177
+
178
+ await new Nfse(client).coberturaMunicipio('2304400'); // driver, operações e provas
179
+ const rtc = new Rtc(client);
180
+ await rtc.verifyResolution('550e8400-e29b-41d4-a716-446655440000'); // reproducible | output_drift | version_drift
181
+ await new Support(client).createCase({ subject: 'Webhook sem entrega desde ontem', priority: 'high' });
182
+ ```
183
+
184
+ Cada método valida localmente o que pode (ids, dígitos, tamanhos, enums) e deixa a regra fiscal para a API. A consulta de NF-e traz `rejection` quando houver rejeição SEFAZ.
185
+
186
+ ## Tratamento de Erros
187
+
188
+ ```typescript
189
+ import { ApiException, AuthException, FoxNfeException } from 'foxnfe';
190
+
191
+ try {
192
+ await nfe.emit(payload);
193
+ } catch (err) {
194
+ if (err instanceof AuthException) {
195
+ // Token inválido ou expirado (401/403)
196
+ console.error('Auth error:', err.message);
197
+ } else if (err instanceof ApiException) {
198
+ // Erro da API (422, 500, etc.)
199
+ console.error(`API error ${err.statusCode}:`, err.message);
200
+ console.error('Body:', err.responseBody);
201
+ } else if (err instanceof FoxNfeException) {
202
+ // Timeout, erro de rede, etc.
203
+ console.error('SDK error:', err.message);
204
+ }
205
+ }
206
+ ```
207
+
208
+ ## Configuração avançada
209
+
210
+ ```typescript
211
+ const client = new Client({
212
+ tenantSlug: 'minha-empresa',
213
+ token: 'seu-token',
214
+ baseUrl: 'https://sandbox.centralfox.online/api/v1',
215
+ timeoutMs: 60_000,
216
+ });
217
+ ```
218
+
219
+ ## Estrutura do pacote
220
+
221
+ ```
222
+ src/
223
+ ├── index.ts # Exports públicos + createClient()
224
+ ├── client.ts # Cliente HTTP principal
225
+ ├── nfe.ts # Módulo NF-e
226
+ ├── nfse.ts # Módulo NFSe
227
+ ├── mcp.ts # Módulo MCP
228
+ ├── types.ts # Tipos TypeScript exportados
229
+ └── errors.ts # Classes de erro
230
+ ```
231
+
232
+ ## Links
233
+
234
+ - [Documentação API](https://docs.centralfox.online)
235
+ - [Portal FOX NF-e](https://foxnfe.centralfox.online)
236
+ - [Suporte](mailto:suporte@centralfox.online)
@@ -0,0 +1,34 @@
1
+ import { ClientOptions, LoginResponse } from './types.js';
2
+ export declare class Client {
3
+ private static readonly DEFAULT_BASE_URL;
4
+ readonly tenantSlug: string;
5
+ private token?;
6
+ private readonly baseUrl;
7
+ private readonly timeoutMs;
8
+ constructor(options: ClientOptions);
9
+ /** Autentica e armazena o token internamente. */
10
+ login(email: string, password: string): Promise<LoginResponse>;
11
+ /** Retorna uma nova instância com o token definido. */
12
+ withToken(token: string): Client;
13
+ logout(): Promise<void>;
14
+ me(): Promise<Record<string, unknown>>;
15
+ get<T = Record<string, unknown>>(path: string, params?: Record<string, string>): Promise<T>;
16
+ post<T = Record<string, unknown>>(path: string, body?: unknown): Promise<T>;
17
+ put<T = Record<string, unknown>>(path: string, body?: unknown): Promise<T>;
18
+ delete<T = Record<string, unknown>>(path: string): Promise<T>;
19
+ /** Retorna Buffer com conteúdo binário (XML, PDF). */
20
+ download(path: string): Promise<Buffer>;
21
+ private buildUrl;
22
+ /**
23
+ * Envio multipart (upload de XML). O Content-Type é definido pelo runtime com o
24
+ * boundary; nunca use JSON aqui.
25
+ */
26
+ upload<T = Record<string, unknown>>(path: string, file: {
27
+ field: string;
28
+ filename: string;
29
+ content: Buffer | string;
30
+ contentType?: string;
31
+ }, fields?: Record<string, string>): Promise<T>;
32
+ private fetch;
33
+ private parseJson;
34
+ }
package/dist/client.js ADDED
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Client = void 0;
4
+ const errors_js_1 = require("./errors.js");
5
+ class Client {
6
+ constructor(options) {
7
+ this.tenantSlug = options.tenantSlug;
8
+ this.token = options.token;
9
+ this.baseUrl = options.baseUrl?.replace(/\/$/, '') ?? Client.DEFAULT_BASE_URL;
10
+ this.timeoutMs = options.timeoutMs ?? 30000;
11
+ }
12
+ /** Autentica e armazena o token internamente. */
13
+ async login(email, password) {
14
+ const data = await this.post('auth/login', { email, password });
15
+ if (!data.token)
16
+ throw new errors_js_1.AuthException('Token não retornado pela API.');
17
+ this.token = data.token;
18
+ return data;
19
+ }
20
+ /** Retorna uma nova instância com o token definido. */
21
+ withToken(token) {
22
+ return new Client({
23
+ tenantSlug: this.tenantSlug,
24
+ baseUrl: this.baseUrl,
25
+ timeoutMs: this.timeoutMs,
26
+ token,
27
+ });
28
+ }
29
+ async logout() {
30
+ await this.post('auth/logout');
31
+ this.token = undefined;
32
+ }
33
+ async me() {
34
+ return this.get('auth/me');
35
+ }
36
+ // ── HTTP helpers ────────────────────────────────────────────────────────
37
+ async get(path, params) {
38
+ const url = this.buildUrl(path, params);
39
+ const res = await this.fetch(url, { method: 'GET' });
40
+ return this.parseJson(res);
41
+ }
42
+ async post(path, body) {
43
+ const url = this.buildUrl(path);
44
+ const res = await this.fetch(url, {
45
+ method: 'POST',
46
+ body: body !== undefined ? JSON.stringify(body) : undefined,
47
+ });
48
+ return this.parseJson(res);
49
+ }
50
+ async put(path, body) {
51
+ const url = this.buildUrl(path);
52
+ const res = await this.fetch(url, {
53
+ method: 'PUT',
54
+ body: body !== undefined ? JSON.stringify(body) : undefined,
55
+ });
56
+ return this.parseJson(res);
57
+ }
58
+ async delete(path) {
59
+ const url = this.buildUrl(path);
60
+ const res = await this.fetch(url, { method: 'DELETE' });
61
+ return this.parseJson(res);
62
+ }
63
+ /** Retorna Buffer com conteúdo binário (XML, PDF). */
64
+ async download(path) {
65
+ const url = this.buildUrl(path);
66
+ const res = await this.fetch(url, { method: 'GET' });
67
+ if (!res.ok) {
68
+ const body = await res.json().catch(() => ({}));
69
+ throw errors_js_1.ApiException.fromResponse(res.status, body);
70
+ }
71
+ return Buffer.from(await res.arrayBuffer());
72
+ }
73
+ buildUrl(path, params) {
74
+ const url = new URL(`${this.baseUrl}/${path}`);
75
+ if (params) {
76
+ Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
77
+ }
78
+ return url.toString();
79
+ }
80
+ /**
81
+ * Envio multipart (upload de XML). O Content-Type é definido pelo runtime com o
82
+ * boundary; nunca use JSON aqui.
83
+ */
84
+ async upload(path, file, fields = {}) {
85
+ const form = new FormData();
86
+ for (const [k, v] of Object.entries(fields))
87
+ form.set(k, v);
88
+ form.set(file.field, new Blob([file.content], { type: file.contentType ?? 'application/xml' }), file.filename);
89
+ const res = await this.fetch(this.buildUrl(path), { method: 'POST', body: form }, null);
90
+ return this.parseJson(res);
91
+ }
92
+ async fetch(url, init, contentType = 'application/json') {
93
+ const controller = new AbortController();
94
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
95
+ try {
96
+ return await fetch(url, {
97
+ ...init,
98
+ signal: controller.signal,
99
+ headers: {
100
+ ...(contentType ? { 'Content-Type': contentType } : {}),
101
+ 'Accept': 'application/json',
102
+ 'X-Tenant-ID': this.tenantSlug,
103
+ ...(this.token ? { Authorization: `Bearer ${this.token}` } : {}),
104
+ },
105
+ });
106
+ }
107
+ catch (err) {
108
+ if (err?.name === 'AbortError') {
109
+ throw new errors_js_1.FoxNfeException(`Timeout após ${this.timeoutMs}ms`);
110
+ }
111
+ throw new errors_js_1.FoxNfeException(`Erro de rede: ${err?.message ?? err}`);
112
+ }
113
+ finally {
114
+ clearTimeout(timer);
115
+ }
116
+ }
117
+ async parseJson(res) {
118
+ let body;
119
+ try {
120
+ body = await res.json();
121
+ }
122
+ catch {
123
+ throw new errors_js_1.FoxNfeException('Resposta JSON inválida.');
124
+ }
125
+ if (!res.ok) {
126
+ const err = body;
127
+ if (res.status === 401 || res.status === 403) {
128
+ throw new errors_js_1.AuthException(err.message ?? 'Não autorizado.');
129
+ }
130
+ throw errors_js_1.ApiException.fromResponse(res.status, err);
131
+ }
132
+ return body;
133
+ }
134
+ }
135
+ exports.Client = Client;
136
+ Client.DEFAULT_BASE_URL = 'https://foxnfe.centralfox.online/api/v1';
@@ -0,0 +1,38 @@
1
+ import { Client } from './client.js';
2
+ export interface ManifestacaoRequest {
3
+ chave_acesso: string;
4
+ tipo_evento: 210200 | 210210 | 210220 | 210240;
5
+ n_seq_evento?: number;
6
+ justificativa?: string;
7
+ nfe_distribuicao_id?: number;
8
+ certificate_id?: number;
9
+ ambiente?: 1 | 2;
10
+ }
11
+ /**
12
+ * NF-e recebidas (capacidade 10): distribuição DF-e, cursor NSU, captura
13
+ * automática (opt-in) e manifestação do destinatário (solicitar → aprovar →
14
+ * transmitir; `reconciliar` resolve uma transmissão ambígua por consulta real).
15
+ */
16
+ export declare class Distribuicao {
17
+ private readonly client;
18
+ constructor(client: Client);
19
+ list(params?: Record<string, string | number | undefined>): Promise<Record<string, unknown>>;
20
+ cursor(): Promise<Record<string, unknown>>;
21
+ optins(): Promise<Record<string, unknown>>;
22
+ enableOptin(certificateId: number, ambiente: 1 | 2): Promise<Record<string, unknown>>;
23
+ disableOptin(id: number): Promise<Record<string, unknown>>;
24
+ /** Captura manual (bounded); `certificate_id`/`ambiente` opcionais. */
25
+ capturar(params?: {
26
+ certificate_id?: number;
27
+ ambiente?: 1 | 2;
28
+ }): Promise<Record<string, unknown>>;
29
+ manifestacoes(params?: Record<string, string | number | undefined>): Promise<Record<string, unknown>>;
30
+ manifestacao(id: number): Promise<Record<string, unknown>>;
31
+ solicitarManifestacao(body: ManifestacaoRequest): Promise<Record<string, unknown>>;
32
+ /** Aprovação vinculada ao payload_hash devolvido na solicitação (alteração invalida). */
33
+ aprovarManifestacao(id: number, payloadHash: string): Promise<Record<string, unknown>>;
34
+ transmitirManifestacao(id: number): Promise<Record<string, unknown>>;
35
+ reconciliarManifestacao(id: number): Promise<Record<string, unknown>>;
36
+ private static q;
37
+ private static id;
38
+ }
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Distribuicao = void 0;
4
+ /**
5
+ * NF-e recebidas (capacidade 10): distribuição DF-e, cursor NSU, captura
6
+ * automática (opt-in) e manifestação do destinatário (solicitar → aprovar →
7
+ * transmitir; `reconciliar` resolve uma transmissão ambígua por consulta real).
8
+ */
9
+ class Distribuicao {
10
+ constructor(client) {
11
+ this.client = client;
12
+ }
13
+ list(params = {}) { return this.client.get('nfe-distribuicao', Distribuicao.q(params)); }
14
+ cursor() { return this.client.get('nfe-distribuicao/cursor'); }
15
+ optins() { return this.client.get('nfe-distribuicao/captura-automatica'); }
16
+ enableOptin(certificateId, ambiente) {
17
+ Distribuicao.id(certificateId, 'certificateId');
18
+ return this.client.put('nfe-distribuicao/captura-automatica', { certificate_id: certificateId, ambiente });
19
+ }
20
+ disableOptin(id) { Distribuicao.id(id, 'id'); return this.client.delete(`nfe-distribuicao/captura-automatica/${id}`); }
21
+ /** Captura manual (bounded); `certificate_id`/`ambiente` opcionais. */
22
+ capturar(params = {}) { return this.client.post('nfe-distribuicao/capturar', params); }
23
+ manifestacoes(params = {}) { return this.client.get('nfe-distribuicao/manifestacoes', Distribuicao.q(params)); }
24
+ manifestacao(id) { Distribuicao.id(id, 'id'); return this.client.get(`nfe-distribuicao/manifestacoes/${id}`); }
25
+ solicitarManifestacao(body) {
26
+ if (!/^[0-9]{6}[0-9A-Z]{12}[0-9]{26}$/.test(body.chave_acesso))
27
+ throw new TypeError('chave_acesso inválida (44 posições).');
28
+ if (![210200, 210210, 210220, 210240].includes(body.tipo_evento))
29
+ throw new TypeError('tipo_evento inválido.');
30
+ return this.client.post('nfe-distribuicao/manifestacoes', body);
31
+ }
32
+ /** Aprovação vinculada ao payload_hash devolvido na solicitação (alteração invalida). */
33
+ aprovarManifestacao(id, payloadHash) {
34
+ Distribuicao.id(id, 'id');
35
+ if (!/^[0-9a-f]{64}$/.test(payloadHash))
36
+ throw new TypeError('payloadHash deve ser sha256 hex minúsculo.');
37
+ return this.client.post(`nfe-distribuicao/manifestacoes/${id}/aprovar`, { payload_hash: payloadHash });
38
+ }
39
+ transmitirManifestacao(id) { Distribuicao.id(id, 'id'); return this.client.post(`nfe-distribuicao/manifestacoes/${id}/transmitir`); }
40
+ reconciliarManifestacao(id) { Distribuicao.id(id, 'id'); return this.client.post(`nfe-distribuicao/manifestacoes/${id}/reconciliar`); }
41
+ static q(params) {
42
+ const p = {};
43
+ for (const [k, v] of Object.entries(params))
44
+ if (v !== undefined)
45
+ p[k] = String(v);
46
+ return p;
47
+ }
48
+ static id(id, label) {
49
+ if (!Number.isInteger(id) || id < 1)
50
+ throw new TypeError(`${label} deve ser inteiro positivo.`);
51
+ }
52
+ }
53
+ exports.Distribuicao = Distribuicao;
@@ -0,0 +1,28 @@
1
+ import { Client } from './client.js';
2
+ export interface DocumentListParams {
3
+ [key: string]: string | number | undefined;
4
+ }
5
+ /**
6
+ * Central de Documentos (capacidades 14/15/16): consulta unificada (emitidas,
7
+ * recebidas, importadas), detalhe, importação de XML de terceiros com prévia
8
+ * (sem valor fiscal próprio) e exportação ZIP assíncrona.
9
+ */
10
+ export declare class Documents {
11
+ private readonly client;
12
+ constructor(client: Client);
13
+ /** Consulta unificada paginada (filtros: período, origem, tipo, status, participante…). */
14
+ list(params?: DocumentListParams): Promise<Record<string, unknown>>;
15
+ /** Detalhe por origem (`nfes`, `nfses`, `nfe_distribuicoes`, `imported_documents`) e id. */
16
+ show(source: string, id: number): Promise<Record<string, unknown>>;
17
+ /** Prévia de importação: valida e vincula SEM persistir; devolve integrity.raw_sha256 para o commit. */
18
+ importPreview(xml: Buffer | string, filename?: string): Promise<Record<string, unknown>>;
19
+ /** Importa o XML (mesmos bytes da prévia quando previewSha256 é informado). */
20
+ import(xml: Buffer | string, previewSha256?: string, filename?: string): Promise<Record<string, unknown>>;
21
+ /** Solicita exportação ZIP assíncrona com os mesmos filtros da consulta. */
22
+ exportRequest(filters?: DocumentListParams): Promise<Record<string, unknown>>;
23
+ exportStatus(requestId: string): Promise<Record<string, unknown>>;
24
+ /** ZIP com manifesto de hashes; confira a integridade antes de arquivar. */
25
+ exportDownload(requestId: string): Promise<Buffer>;
26
+ private static id;
27
+ private static uuid;
28
+ }
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Documents = void 0;
4
+ /**
5
+ * Central de Documentos (capacidades 14/15/16): consulta unificada (emitidas,
6
+ * recebidas, importadas), detalhe, importação de XML de terceiros com prévia
7
+ * (sem valor fiscal próprio) e exportação ZIP assíncrona.
8
+ */
9
+ class Documents {
10
+ constructor(client) {
11
+ this.client = client;
12
+ }
13
+ /** Consulta unificada paginada (filtros: período, origem, tipo, status, participante…). */
14
+ list(params = {}) {
15
+ const p = {};
16
+ for (const [k, v] of Object.entries(params))
17
+ if (v !== undefined)
18
+ p[k] = String(v);
19
+ return this.client.get('documents', p);
20
+ }
21
+ /** Detalhe por origem (`nfes`, `nfses`, `nfe_distribuicoes`, `imported_documents`) e id. */
22
+ show(source, id) {
23
+ if (!/^[a-z_]+$/.test(source))
24
+ throw new TypeError('source inválido.');
25
+ Documents.id(id);
26
+ return this.client.get(`documents/${source}/${id}`);
27
+ }
28
+ /** Prévia de importação: valida e vincula SEM persistir; devolve integrity.raw_sha256 para o commit. */
29
+ importPreview(xml, filename = 'nota.xml') {
30
+ return this.client.upload('documents/imports/preview', { field: 'file', filename, content: xml });
31
+ }
32
+ /** Importa o XML (mesmos bytes da prévia quando previewSha256 é informado). */
33
+ import(xml, previewSha256, filename = 'nota.xml') {
34
+ const fields = {};
35
+ if (previewSha256 !== undefined) {
36
+ if (!/^[0-9a-f]{64}$/.test(previewSha256))
37
+ throw new TypeError('previewSha256 deve ser sha256 hex minúsculo.');
38
+ fields.preview_sha256 = previewSha256;
39
+ }
40
+ return this.client.upload('documents/imports', { field: 'file', filename, content: xml }, fields);
41
+ }
42
+ /** Solicita exportação ZIP assíncrona com os mesmos filtros da consulta. */
43
+ exportRequest(filters = {}) {
44
+ return this.client.post('documents/exports', filters);
45
+ }
46
+ exportStatus(requestId) {
47
+ Documents.uuid(requestId);
48
+ return this.client.get(`documents/exports/${requestId}`);
49
+ }
50
+ /** ZIP com manifesto de hashes; confira a integridade antes de arquivar. */
51
+ exportDownload(requestId) {
52
+ Documents.uuid(requestId);
53
+ return this.client.download(`documents/exports/${requestId}/download`);
54
+ }
55
+ static id(id) {
56
+ if (!Number.isInteger(id) || id < 1)
57
+ throw new TypeError('id deve ser inteiro positivo.');
58
+ }
59
+ static uuid(v) {
60
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(v))
61
+ throw new TypeError('requestId deve ser um UUID válido.');
62
+ }
63
+ }
64
+ exports.Documents = Documents;
@@ -0,0 +1,14 @@
1
+ import { ApiErrorResponse } from './types.js';
2
+ export declare class FoxNfeException extends Error {
3
+ readonly context?: unknown | undefined;
4
+ constructor(message: string, context?: unknown | undefined);
5
+ }
6
+ export declare class ApiException extends FoxNfeException {
7
+ readonly statusCode: number;
8
+ readonly responseBody?: ApiErrorResponse | undefined;
9
+ constructor(statusCode: number, message: string, responseBody?: ApiErrorResponse | undefined);
10
+ static fromResponse(statusCode: number, body: ApiErrorResponse): ApiException;
11
+ }
12
+ export declare class AuthException extends FoxNfeException {
13
+ constructor(message: string);
14
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AuthException = exports.ApiException = exports.FoxNfeException = void 0;
4
+ class FoxNfeException extends Error {
5
+ constructor(message, context) {
6
+ super(message);
7
+ this.context = context;
8
+ this.name = 'FoxNfeException';
9
+ }
10
+ }
11
+ exports.FoxNfeException = FoxNfeException;
12
+ class ApiException extends FoxNfeException {
13
+ constructor(statusCode, message, responseBody) {
14
+ super(message, responseBody);
15
+ this.statusCode = statusCode;
16
+ this.responseBody = responseBody;
17
+ this.name = 'ApiException';
18
+ }
19
+ static fromResponse(statusCode, body) {
20
+ return new ApiException(statusCode, body.message ?? `HTTP ${statusCode}`, body);
21
+ }
22
+ }
23
+ exports.ApiException = ApiException;
24
+ class AuthException extends FoxNfeException {
25
+ constructor(message) {
26
+ super(message);
27
+ this.name = 'AuthException';
28
+ }
29
+ }
30
+ exports.AuthException = AuthException;