nfewizard-io 1.0.4 → 1.1.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/README.md CHANGED
@@ -34,7 +34,7 @@ npm install nfewizard-io
34
34
 
35
35
  Este pacote fornece métodos para operações de NFe, incluindo:
36
36
 
37
- - ✅ **Autorização (Emissão de NFe)**: Submissão de Notas Fiscais Eletrônicas para autorização
37
+ - ✅ **Autorização (Emissão de NFe)**: Submissão de Notas Fiscais Eletrônicas para autorização. Aceita tanto o **objeto tipado `NFe`** quanto uma **string XML** (com ou sem o envelope `<enviNFe>`)
38
38
  - ✅ **Distribuição DFe**: Consulta e Download de documentos fiscais eletrônicos
39
39
  - ✅ **Consulta de Protocolo**: Verificação da situação da NFe na SEFAZ
40
40
  - ✅ **Inutilização**: Inutilização de números de NFe não utilizados
@@ -47,6 +47,7 @@ Este pacote fornece métodos para operações de NFe, incluindo:
47
47
  - Desconhecimento da Operação
48
48
  - EPEC (Evento Prévio de Emissão em Contingência)
49
49
  - Operação Não Realizada
50
+ - ✅ **Validação de Schema XSD (`NFE_SchemaValidate`)**: Valida qualquer XML fiscal contra o schema XSD oficial da SEFAZ, com relatório humanizado e `console.table` integrado
50
51
 
51
52
  > **⚠️ Importante**:
52
53
  > - Para **DANFE** (geração de PDF), instale separadamente: `npm install @nfewizard/danfe`
@@ -131,6 +132,22 @@ const chaveNFe: DFePorChaveNFe = {
131
132
  await nfeWizard.NFE_DistribuicaoDFePorChave(chaveNFe);
132
133
  ```
133
134
 
135
+ ### Nomes dos arquivos salvos na Distribuição DFe
136
+
137
+ Com `baixarXMLDistribuicao` habilitado, cada `docZip` é salvo com nome único para evitar sobrescrita quando existirem múltiplos documentos para a mesma chave no mesmo lote.
138
+
139
+ - Resumo (`resNFe`): `<chave>-res-<nsu>.xml`
140
+ - Documento processado (`nfeProc`): `<chave>-proc-<nsu>.xml`
141
+ - Evento (`procEventoNFe`): `<chave>-event-<tpEvento>-<nsu>.xml`
142
+
143
+ Exemplo:
144
+
145
+ ```text
146
+ 3525...1234-res-000000000000101.xml
147
+ 3525...1234-proc-000000000000102.xml
148
+ 3525...1234-event-110110-000000000000103.xml
149
+ ```
150
+
134
151
  ---
135
152
 
136
153
  ## 🚧 Requisitos
@@ -203,6 +220,79 @@ build:
203
220
 
204
221
  ---
205
222
 
223
+ ## ✨ Novidades recentes (pós 1.0.0)
224
+
225
+ ### Autorização aceita XML string
226
+
227
+ `NFE_Autorizacao` agora aceita diretamente uma string XML (além do objeto tipado), tanto com o envelope `<enviNFe>` quanto apenas com o elemento `<NFe>`:
228
+
229
+ ```typescript
230
+ // Objeto tipado (comportamento original — mantido)
231
+ await nfeWizard.NFE_Autorizacao(nfeObject);
232
+
233
+ // String XML completa (com enviNFe)
234
+ const xmlComEnvelope = '<enviNFe versao="4.00" ...>...</enviNFe>';
235
+ await nfeWizard.NFE_Autorizacao(xmlComEnvelope);
236
+
237
+ // String XML sem envelope (a lib adiciona <enviNFe> automaticamente)
238
+ const xmlSemEnvelope = '<NFe xmlns="http://www.portalfiscal.inf.br/nfe">...</NFe>';
239
+ await nfeWizard.NFE_Autorizacao(xmlSemEnvelope);
240
+ ```
241
+
242
+ ### NFE_SchemaValidate — Validação de Schema XSD
243
+
244
+ Novo método que valida qualquer XML fiscal contra o schema XSD oficial, retornando um relatório detalhado com erros humanizados:
245
+
246
+ ```typescript
247
+ import NFeWizard, { SchemaValidationResult } from 'nfewizard-io';
248
+
249
+ const result: SchemaValidationResult = await nfeWizard.NFE_SchemaValidate(
250
+ xmlString,
251
+ 'NFeAutorizacao', // SchemaValidateMethod
252
+ // 'validateSchemaJsBased' // validador opcional — usa config da lib se omitido
253
+ );
254
+
255
+ // result.success — boolean
256
+ // result.message — resumo humanizado
257
+ // result.errors[] — lista de SchemaValidationIssue com: raw, humanized, element, line, column, expected
258
+ // result.report — string multilinha estilo SEFAZ-RS (já impressa automaticamente)
259
+ // result.tableRows — prontos para console.table (já exibido automaticamente)
260
+ // result.schema — nome do arquivo XSD utilizado
261
+ ```
262
+
263
+ **Métodos (`SchemaValidateMethod`) disponíveis:**
264
+
265
+ | Valor | Schema XSD |
266
+ |-------|-----------|
267
+ | `'NFeAutorizacao'` / `'NFEAutorizacao'` | `enviNFe_v4.00.xsd` |
268
+ | `'NFEStatusServico'` | `consStatServ_v4.00.xsd` |
269
+ | `'NFEConsultaProtocolo'` | `consSitNFe_v4.00.xsd` |
270
+ | `'RecepcaoEvento'` | `envEvento_v1.00.xsd` |
271
+ | `'NFeDistribuicaoDFe'` | `distDFeInt_v1.01.xsd` |
272
+ | `'NFEInutilizacao'` | `inutNFe_v4.00.xsd` |
273
+ | `'NFERetAutorizacao'` | `consReciNFe_v4.00.xsd` |
274
+ | `'CTeDistribuicaoDFe'` | `cte/distDFeInt_v1.00.xsd` |
275
+ | `'NFSe_Autorizacao'` | `nfse/DPS_v1.01.xsd` |
276
+ | `'NFSe_Consulta'` / `'NFSe_Distribuicao'` | `nfse/NFSe_v1.01.xsd` |
277
+ | `'NFSe_Eventos'` | `nfse/pedRegEvento_v1.01.xsd` |
278
+
279
+ > **Nota**: para `NFeAutorizacao`/`NFEAutorizacao`, se o XML passado começar com `<NFe>`, o envelope `<enviNFe versao="4.00">` é adicionado automaticamente antes da validação.
280
+
281
+ **Tratamento de erro:**
282
+
283
+ ```typescript
284
+ try {
285
+ await nfeWizard.NFE_SchemaValidate(xml, 'NFeAutorizacao');
286
+ } catch (err: any) {
287
+ // err.message — mensagem resumida
288
+ // err.errors — SchemaValidationIssue[]
289
+ // err.report — relatório completo
290
+ // err.tableRows — linhas para console.table
291
+ }
292
+ ```
293
+
294
+ ---
295
+
206
296
  ## ⚙️ Configuração TypeScript
207
297
 
208
298
  ### tsconfig.json recomendado
package/dist/index.cjs CHANGED
@@ -272,6 +272,43 @@ class NFERetornoAutorizacaoService extends shared$1.BaseNFE {
272
272
  constructor(environment, utility, xmlBuilder, axios, saveFiles, gerarConsulta) {
273
273
  super(environment, utility, xmlBuilder, 'NFERetAutorizacao', axios, saveFiles, gerarConsulta);
274
274
  }
275
+ normalizeProtNFe(protNFe) {
276
+ const normalizedProtNFe = {
277
+ ...protNFe,
278
+ $: {
279
+ xmlns: 'http://www.portalfiscal.inf.br/nfe',
280
+ versao: protNFe?.$?.versao ?? '4.00',
281
+ },
282
+ };
283
+ const infProtNode = Array.isArray(normalizedProtNFe?.infProt)
284
+ ? normalizedProtNFe.infProt[0]
285
+ : normalizedProtNFe?.infProt;
286
+ if (!infProtNode) {
287
+ return normalizedProtNFe;
288
+ }
289
+ const nProt = Array.isArray(infProtNode.nProt) ? infProtNode.nProt[0] : infProtNode.nProt;
290
+ if (!nProt) {
291
+ return normalizedProtNFe;
292
+ }
293
+ const currentId = infProtNode?.$?.Id;
294
+ const infProtId = typeof currentId === 'string' && currentId.startsWith('ID')
295
+ ? currentId
296
+ : `ID${nProt}`;
297
+ const normalizedInfProtNode = {
298
+ ...infProtNode,
299
+ $: {
300
+ ...(infProtNode?.$ ?? {}),
301
+ Id: infProtId,
302
+ },
303
+ };
304
+ if (Array.isArray(normalizedProtNFe.infProt)) {
305
+ normalizedProtNFe.infProt[0] = normalizedInfProtNode;
306
+ }
307
+ else {
308
+ normalizedProtNFe.infProt = normalizedInfProtNode;
309
+ }
310
+ return normalizedProtNFe;
311
+ }
275
312
  gerarXml(data) {
276
313
  const { nfe: { ambiente } } = this.environment.getConfig();
277
314
  const xmlObject = {
@@ -350,7 +387,8 @@ class NFERetornoAutorizacaoService extends shared$1.BaseNFE {
350
387
  const formatedProtNFe = protNFe;
351
388
  const xmlCompleto = xmlNFe.find((item) => item.indexOf(formatedProtNFe[i].infProt[0].chNFe[0]) !== -1);
352
389
  if (xmlCompleto) {
353
- const protTag = this.xmlBuilder.gerarXml(protNFe[i], 'protNFe', this.metodo);
390
+ const protNFeNormalizado = this.normalizeProtNFe(protNFe[i]);
391
+ const protTag = this.xmlBuilder.gerarXml(protNFeNormalizado, 'protNFe', this.metodo);
354
392
  const xmlFinal = [xmlCompleto];
355
393
  xmlFinal.push(protTag);
356
394
  /**
@@ -705,8 +743,12 @@ class NFEAutorizacaoService extends shared$1.BaseNFE {
705
743
  let xmlRetorno = {};
706
744
  const ContentType = this.setContentType();
707
745
  try {
746
+ // Se receber XML em string, converte para o JSON do padrão esperado pela lib
747
+ const dataAsJson = typeof data === 'string'
748
+ ? new shared$1.XmlParser().convertXmlEnvioNFeToJson(data)
749
+ : data;
708
750
  // Gerando XML para consulta de Status do Serviço
709
- xmlConsulta = this.gerarXmlNFeAutorizacao(data);
751
+ xmlConsulta = this.gerarXmlNFeAutorizacao(dataAsJson);
710
752
  const { xmlFormated, agent, webServiceUrl, action } = await this.gerarConsulta.gerarConsulta(xmlConsulta, this.metodo);
711
753
  xmlConsultaSoap = xmlFormated;
712
754
  // Efetua requisição para o webservice NFEStatusServico
@@ -715,7 +757,7 @@ class NFEAutorizacaoService extends shared$1.BaseNFE {
715
757
  * Verifica se houve rejeição no processamento do lote
716
758
  */
717
759
  responseInJson = this.utility.verificaRejeicao(xmlRetorno.data, this.metodo);
718
- const retorno = await this.trataRetorno(xmlRetorno.data, data.indSinc, responseInJson);
760
+ const retorno = await this.trataRetorno(xmlRetorno.data, dataAsJson.indSinc, responseInJson);
719
761
  const xmlFinal = this.salvaArquivos(xmlConsulta, responseInJson, xmlRetorno.data, {
720
762
  xmlAutorizacao: retorno.data,
721
763
  xMotivo: retorno.message
@@ -1090,19 +1132,19 @@ class DistribuicaoHandler {
1090
1132
  /**
1091
1133
  * Métodos para tratativas do DistribuicaoDFe
1092
1134
  */
1093
- salvaArquivos(XMLDistribuicaoInJson, XMLDistribuicao, chNFe) {
1135
+ salvaArquivos(XMLDistribuicaoInJson, XMLDistribuicao, fileName) {
1094
1136
  const { pathXMLDistribuicao, baixarXMLDistribuicao, armazenarRetornoEmJSON } = this.environment.config.dfe;
1095
1137
  if (baixarXMLDistribuicao) {
1096
1138
  this.utility.salvaXML({
1097
1139
  data: XMLDistribuicao,
1098
- fileName: chNFe,
1140
+ fileName,
1099
1141
  metodo: this.metodo,
1100
1142
  path: pathXMLDistribuicao,
1101
1143
  });
1102
1144
  if (armazenarRetornoEmJSON) {
1103
1145
  this.utility.salvaJSON({
1104
1146
  data: XMLDistribuicaoInJson,
1105
- fileName: chNFe,
1147
+ fileName,
1106
1148
  metodo: this.metodo,
1107
1149
  path: pathXMLDistribuicao,
1108
1150
  });
@@ -1120,27 +1162,47 @@ class DistribuicaoHandler {
1120
1162
  throw new Error(`Erro ao descomprimir o XML: ${err}`);
1121
1163
  }
1122
1164
  const docZips = this.utility.findInObj(result, 'docZip');
1123
- docZips.forEach((docZip) => {
1165
+ docZips.forEach((docZip, index) => {
1124
1166
  const xmlString = this.decodeDocZip(docZip);
1125
1167
  const cleanedXml = this.removeSignatureTag(xmlString);
1126
1168
  const parsedResult = this.parseXml(cleanedXml);
1127
1169
  if (!parsedResult)
1128
1170
  return;
1129
- let chNFe = this.getChNFe(parsedResult);
1130
- let tipo = this.getTipo(parsedResult);
1131
- const nsu = docZip['$'].NSU;
1132
- if (parsedResult['procEventoNFe']) {
1133
- const tpEvento = this.utility.findInObj(parsedResult, 'tpEvento');
1134
- chNFe = `${chNFe}-event-${tpEvento}`;
1135
- tipo = 'event';
1136
- }
1171
+ const chNFe = this.getChNFe(parsedResult);
1172
+ const tipo = this.getTipo(parsedResult);
1173
+ const nsu = this.getDocNsu(docZip, index);
1174
+ const tpEvento = tipo === 'event' ? this.utility.findInObj(parsedResult, 'tpEvento') : '';
1175
+ const fileName = this.buildFileName(chNFe, tipo, nsu, tpEvento);
1137
1176
  const xmlDistribuicaoInJson = json.convertXmlToJson(cleanedXml, `${metodo}_${tipo}`, nsu);
1138
- this.handleResponse(xmlDistribuicaoInJson, cleanedXml, chNFe);
1139
- files.push(chNFe);
1177
+ this.handleResponse(xmlDistribuicaoInJson, cleanedXml, fileName);
1178
+ files.push(fileName);
1140
1179
  });
1141
1180
  });
1142
1181
  return files;
1143
1182
  }
1183
+ getDocNsu(docZip, index) {
1184
+ const nsu = (docZip?.$?.NSU ?? '').toString().trim();
1185
+ return nsu || `idx-${index}`;
1186
+ }
1187
+ sanitizeFileNamePart(value) {
1188
+ const normalizedValue = (value ?? '').toString().trim();
1189
+ if (!normalizedValue) {
1190
+ return '';
1191
+ }
1192
+ return normalizedValue.replace(/[^a-zA-Z0-9_.-]/g, '_');
1193
+ }
1194
+ buildFileName(chNFe, tipo, nsu, tpEvento) {
1195
+ const safeChNFe = this.sanitizeFileNamePart(chNFe) || 'sem-chave';
1196
+ const safeNsu = this.sanitizeFileNamePart(nsu) || 'sem-nsu';
1197
+ if (tipo === 'res') {
1198
+ return `${safeChNFe}-res-${safeNsu}`;
1199
+ }
1200
+ if (tipo === 'event') {
1201
+ const safeTpEvento = this.sanitizeFileNamePart(tpEvento) || 'sem-tpevento';
1202
+ return `${safeChNFe}-event-${safeTpEvento}-${safeNsu}`;
1203
+ }
1204
+ return `${safeChNFe}-proc-${safeNsu}`;
1205
+ }
1144
1206
  decodeDocZip(docZip) {
1145
1207
  shared$1.logger.info('Decodificando DocZip', {
1146
1208
  context: 'DistribuicaoHandler',
@@ -1182,8 +1244,8 @@ class DistribuicaoHandler {
1182
1244
  }
1183
1245
  return 'proc';
1184
1246
  }
1185
- handleResponse(XMLDistribuicaoInJson, XMLDistribuicao, chNFe) {
1186
- this.salvaArquivos(XMLDistribuicaoInJson, XMLDistribuicao, chNFe);
1247
+ handleResponse(XMLDistribuicaoInJson, XMLDistribuicao, fileName) {
1248
+ this.salvaArquivos(XMLDistribuicaoInJson, XMLDistribuicao, fileName);
1187
1249
  // Gera erro em caso de Rejeição
1188
1250
  const xMotivo = this.utility.findInObj(XMLDistribuicaoInJson, 'xMotivo');
1189
1251
  if (xMotivo && (xMotivo.includes('Rejeição') || xMotivo.includes('Rejeicao'))) {
@@ -1697,6 +1759,39 @@ class NFeWizardService {
1697
1759
  throw new Error(`NFE_Inutilizacao: ${error.message}`);
1698
1760
  }
1699
1761
  }
1762
+ /**
1763
+ * Valida um XML contra o schema XSD do método fiscal informado.
1764
+ * O `environment` é injetado automaticamente a partir da configuração da lib.
1765
+ *
1766
+ * @param xml - String XML a ser validada.
1767
+ * @param metodo - Nome do método/operação fiscal (ex.: `'NFeAutorizacao'`).
1768
+ * @param validator - Força um validador específico. Se omitido, usa
1769
+ * `lib.useForSchemaValidation` do config; caso não definido,
1770
+ * usa `'validateSchemaJsBased'` como padrão.
1771
+ */
1772
+ async NFE_SchemaValidate(xml, metodo, validator) {
1773
+ try {
1774
+ const response = await shared$1.NFE_SchemaValidate(xml, metodo, { validator, environment: this.environment });
1775
+ console.log(response.report);
1776
+ console.table(response.tableRows);
1777
+ return response;
1778
+ }
1779
+ catch (error) {
1780
+ // Quando o erro vem do próprio NFE_SchemaValidate, é um SchemaValidationResult enriquecido.
1781
+ if (error && typeof error === 'object' && 'errors' in error && 'report' in error) {
1782
+ console.log(error.report);
1783
+ console.table(error.tableRows);
1784
+ shared$1.logger.error('', { message: error.message, errors: error.errors }, { context: 'NFE_SchemaValidate' });
1785
+ const err = new Error(`NFE_SchemaValidate: ${error.message}`);
1786
+ err.errors = error.errors;
1787
+ err.report = error.report;
1788
+ err.tableRows = error.tableRows;
1789
+ throw err;
1790
+ }
1791
+ shared$1.logger.error('', error, { context: 'NFE_SchemaValidate' });
1792
+ throw new Error(`NFE_SchemaValidate: ${error?.message ?? error}`);
1793
+ }
1794
+ }
1700
1795
  /**
1701
1796
  * @deprecated A partir da v1.0.0, use o pacote @nfewizard/danfe
1702
1797
  */
@@ -1879,9 +1974,29 @@ class NFeWizard {
1879
1974
  /**
1880
1975
  * Autorização NFe
1881
1976
  */
1977
+ /**
1978
+ * Autoriza uma NFe.
1979
+ *
1980
+ * @param data Dados da NFe em JSON do padrão da lib (`NFe`) ou um XML
1981
+ * de envio (`enviNFe` / `NFe` solo) em string. Quando string, o XML é
1982
+ * convertido para JSON antes de seguir o fluxo normal.
1983
+ */
1882
1984
  async NFE_Autorizacao(data) {
1883
1985
  return await this.nfeWizardService.NFE_Autorizacao(data);
1884
1986
  }
1987
+ /**
1988
+ * Valida um XML contra o schema XSD do método fiscal informado.
1989
+ * O `environment` é injetado automaticamente a partir da configuração da lib.
1990
+ *
1991
+ * @param xml - String XML a ser validada.
1992
+ * @param metodo - Nome do método/operação fiscal (ex.: `'NFeAutorizacao'`).
1993
+ * @param validator - Força um validador específico. Se omitido, usa
1994
+ * `lib.useForSchemaValidation` do config; caso não definido,
1995
+ * usa `'validateSchemaJsBased'` como padrão.
1996
+ */
1997
+ async NFE_SchemaValidate(xml, metodo, validator) {
1998
+ return await this.nfeWizardService.NFE_SchemaValidate(xml, metodo, validator);
1999
+ }
1885
2000
  /**
1886
2001
  * @deprecated A partir da v1.0.0, use o pacote @nfewizard/nfce diretamente
1887
2002
  * @example
@@ -1991,6 +2106,10 @@ Object.defineProperty(exports, "LoadCertificate", {
1991
2106
  enumerable: true,
1992
2107
  get: function () { return shared$1.LoadCertificate; }
1993
2108
  });
2109
+ Object.defineProperty(exports, "NFE_SchemaValidate", {
2110
+ enumerable: true,
2111
+ get: function () { return shared$1.NFE_SchemaValidate; }
2112
+ });
1994
2113
  Object.defineProperty(exports, "SaveFiles", {
1995
2114
  enumerable: true,
1996
2115
  get: function () { return shared$1.SaveFiles; }