@nfewizard/shared 1.0.2 → 1.1.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.
Files changed (30) hide show
  1. package/.rollup.cache/usr/projetos/nfewizard/nfewizard-io-idle-old/packages/shared/dist/src/adapters/SchemaLoader.js +1 -0
  2. package/.rollup.cache/usr/projetos/nfewizard/nfewizard-io-idle-old/packages/shared/dist/src/adapters/SchemaLoader.js.map +1 -1
  3. package/.rollup.cache/usr/projetos/nfewizard/nfewizard-io-idle-old/packages/shared/dist/src/base/BaseNFe.d.ts +1 -1
  4. package/.rollup.cache/usr/projetos/nfewizard/nfewizard-io-idle-old/packages/shared/dist/src/base/GerarConsulta.js +1 -0
  5. package/.rollup.cache/usr/projetos/nfewizard/nfewizard-io-idle-old/packages/shared/dist/src/base/GerarConsulta.js.map +1 -1
  6. package/.rollup.cache/usr/projetos/nfewizard/nfewizard-io-idle-old/packages/shared/dist/src/environment/LoadCertificate.d.ts +5 -0
  7. package/.rollup.cache/usr/projetos/nfewizard/nfewizard-io-idle-old/packages/shared/dist/src/environment/LoadCertificate.js +30 -9
  8. package/.rollup.cache/usr/projetos/nfewizard/nfewizard-io-idle-old/packages/shared/dist/src/environment/LoadCertificate.js.map +1 -1
  9. package/.rollup.cache/usr/projetos/nfewizard/nfewizard-io-idle-old/packages/shared/dist/src/utils/XmlParser.d.ts +65 -0
  10. package/.rollup.cache/usr/projetos/nfewizard/nfewizard-io-idle-old/packages/shared/dist/src/utils/XmlParser.js +189 -0
  11. package/.rollup.cache/usr/projetos/nfewizard/nfewizard-io-idle-old/packages/shared/dist/src/utils/XmlParser.js.map +1 -1
  12. package/.rollup.cache/usr/projetos/nfewizard/nfewizard-io-idle-old/packages/shared/dist/src/utils/index.d.ts +2 -0
  13. package/.rollup.cache/usr/projetos/nfewizard/nfewizard-io-idle-old/packages/shared/dist/src/utils/index.js +1 -0
  14. package/.rollup.cache/usr/projetos/nfewizard/nfewizard-io-idle-old/packages/shared/dist/src/utils/index.js.map +1 -1
  15. package/.rollup.cache/usr/projetos/nfewizard/nfewizard-io-idle-old/packages/shared/dist/src/utils/validateSchema.d.ts +96 -0
  16. package/.rollup.cache/usr/projetos/nfewizard/nfewizard-io-idle-old/packages/shared/dist/src/utils/validateSchema.js +252 -0
  17. package/.rollup.cache/usr/projetos/nfewizard/nfewizard-io-idle-old/packages/shared/dist/src/utils/validateSchema.js.map +1 -0
  18. package/.rollup.cache/usr/projetos/nfewizard/nfewizard-io-idle-old/packages/shared/dist/tsconfig.tsbuildinfo +1 -1
  19. package/README.md +47 -1
  20. package/dist/index.cjs +470 -9
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.mjs +470 -10
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/src/base/BaseNFe.d.ts +1 -1
  25. package/dist/src/environment/LoadCertificate.d.ts +5 -0
  26. package/dist/src/utils/XmlParser.d.ts +65 -0
  27. package/dist/src/utils/index.d.ts +2 -0
  28. package/dist/src/utils/validateSchema.d.ts +96 -0
  29. package/dist/tsconfig.tsbuildinfo +1 -1
  30. package/package.json +1 -1
package/README.md CHANGED
@@ -15,9 +15,55 @@ pnpm add @nfewizard/shared
15
15
  - ✅ **XmlBuilder** - Assinatura digital e geração de SOAP
16
16
  - ✅ **HttpClient** - Cliente HTTP configurado para SEFAZ
17
17
  - ✅ **LoadCertificate** - Carregamento de certificados A1
18
- - ✅ **SchemaLoader** - Validação XSD
18
+ - ✅ **SchemaLoader** - Mapeamento de schemas XSD por operação fiscal
19
+ - ✅ **NFE_SchemaValidate** - Validação de XML contra schema XSD com relatório humanizado
19
20
  - ✅ **Utilities** - Funções auxiliares (validações, formatações)
20
21
 
22
+ ## NFE_SchemaValidate
23
+
24
+ Função pública que valida um XML fiscal contra o schema XSD oficial, disponível para uso direto ou via facade (`NFeWizard.NFE_SchemaValidate`, `NFSe.NFSe_SchemaValidate`).
25
+
26
+ ```typescript
27
+ import { NFE_SchemaValidate, SchemaValidationResult } from '@nfewizard/shared';
28
+
29
+ const result: SchemaValidationResult = await NFE_SchemaValidate(
30
+ xmlString,
31
+ 'NFeAutorizacao',
32
+ { validator: 'validateSchemaJsBased' } // opcional
33
+ );
34
+
35
+ // result.success — boolean
36
+ // result.message — mensagem-resumo humanizada
37
+ // result.errors[] — SchemaValidationIssue[]: { raw, humanized, element?, line?, column?, expected? }
38
+ // result.report — string multilinha estilo SEFAZ-RS
39
+ // result.tableRows — Array pronto para console.table
40
+ // result.schema — arquivo XSD utilizado
41
+ // result.metodo — SchemaValidateMethod informado
42
+ ```
43
+
44
+ ### Métodos disponíveis (`SchemaValidateMethod`)
45
+
46
+ | Valor | Schema XSD |
47
+ |-------|-----------|
48
+ | `'NFeAutorizacao'` / `'NFEAutorizacao'` | `enviNFe_v4.00.xsd` (envelope adicionado automaticamente se ausente) |
49
+ | `'NFEStatusServico'` | `consStatServ_v4.00.xsd` |
50
+ | `'NFEConsultaProtocolo'` | `consSitNFe_v4.00.xsd` |
51
+ | `'RecepcaoEvento'` | `envEvento_v1.00.xsd` |
52
+ | `'NFeDistribuicaoDFe'` | `distDFeInt_v1.01.xsd` |
53
+ | `'NFEInutilizacao'` | `inutNFe_v4.00.xsd` |
54
+ | `'NFERetAutorizacao'` | `consReciNFe_v4.00.xsd` |
55
+ | `'CTeDistribuicaoDFe'` | `cte/distDFeInt_v1.00.xsd` |
56
+ | `'NFSe_Autorizacao'` | `nfse/DPS_v1.01.xsd` |
57
+ | `'NFSe_Consulta'` / `'NFSe_Distribuicao'` | `nfse/NFSe_v1.01.xsd` |
58
+ | `'NFSe_Eventos'` | `nfse/pedRegEvento_v1.01.xsd` |
59
+ | `'NFSe_ParametrosMunicipais'` | — (API REST, sem XSD) |
60
+
61
+ ### Seleção do validador
62
+
63
+ 1. `options.validator` — escolha explícita do caller.
64
+ 2. `options.environment.getConfig().lib?.useForSchemaValidation` — valor do config da lib.
65
+ 3. `'validateSchemaJsBased'` — padrão (sem JDK).
66
+
21
67
  ## Licença
22
68
 
23
69
  GPL-3.0
package/dist/index.cjs CHANGED
@@ -456,6 +456,195 @@ class XmlParser {
456
456
  }
457
457
  return jsonBody;
458
458
  }
459
+ /**
460
+ * Converte um XML de envio (`enviNFe`) ou uma `NFe` solo em uma
461
+ * estrutura JSON compatível com o tipo `NFe` esperado pelos serviços
462
+ * de Autorização de NFe/NFCe (`{ idLote, indSinc, NFe }`).
463
+ *
464
+ * Formatos aceitos:
465
+ * - Envelope `enviNFe` contendo uma ou mais `NFe`.
466
+ * - `nfeProc` (XML autorizado) — extrai a `NFe` interna.
467
+ * - `NFe` solo — empacota com defaults (`idLote=1`, `indSinc=1`).
468
+ *
469
+ * Defaults são aplicáveis somente para os campos do envelope; o
470
+ * conteúdo de `infNFe` não é alterado e segue o fluxo normal de
471
+ * geração/assinatura no service.
472
+ *
473
+ * @param xml String XML de entrada.
474
+ * @param overrides Permite sobrescrever `idLote`/`indSinc` quando o XML
475
+ * não contém envelope.
476
+ */
477
+ convertXmlEnvioNFeToJson(xml, overrides) {
478
+ logger.info('Convertendo XML de envio para JSON', {
479
+ context: 'XmlParser',
480
+ method: 'convertXmlEnvioNFeToJson',
481
+ });
482
+ const jsonData = this.parseNfeLikeXml(xml);
483
+ let envelope = this.findInObj(jsonData, 'enviNFe');
484
+ if (!envelope) {
485
+ const nfeProc = this.findInObj(jsonData, 'nfeProc');
486
+ const nfeNode = nfeProc ? this.findInObj(nfeProc, 'NFe') : this.findInObj(jsonData, 'NFe');
487
+ if (!nfeNode) {
488
+ throw new Error('XML inválido: não foi possível localizar `enviNFe` ou `NFe`.');
489
+ }
490
+ envelope = {
491
+ idLote: overrides?.idLote ?? '1',
492
+ indSinc: overrides?.indSinc ?? 1,
493
+ NFe: nfeNode,
494
+ };
495
+ }
496
+ // Normaliza CNPJ/CPF -> CNPJCPF nas NFe (formato esperado pela lib)
497
+ const nfeList = Array.isArray(envelope.NFe) ? envelope.NFe : [envelope.NFe];
498
+ nfeList.forEach((n) => this.normalizeNfeForLibFormat(n));
499
+ return {
500
+ idLote: envelope.idLote ?? overrides?.idLote ?? '1',
501
+ indSinc: Number(envelope.indSinc ?? overrides?.indSinc ?? 1),
502
+ NFe: envelope.NFe,
503
+ };
504
+ }
505
+ /**
506
+ * Converte um XML autorizado (`nfeProc`) ou uma `NFe` solo em uma
507
+ * estrutura JSON compatível com o gerador de DANFE
508
+ * (`{ NFe, protNFe? }`), além de retornar a chave de acesso quando
509
+ * disponível.
510
+ *
511
+ * @param xml String XML de entrada.
512
+ */
513
+ convertXmlNfeProcToJson(xml) {
514
+ logger.info('Convertendo nfeProc para JSON', {
515
+ context: 'XmlParser',
516
+ method: 'convertXmlNfeProcToJson',
517
+ });
518
+ const jsonData = this.parseNfeLikeXml(xml);
519
+ const nfeProc = this.findInObj(jsonData, 'nfeProc');
520
+ const NFe = nfeProc ? this.findInObj(nfeProc, 'NFe') : this.findInObj(jsonData, 'NFe');
521
+ if (!NFe) {
522
+ throw new Error('XML inválido: não foi possível localizar `NFe` ou `nfeProc`.');
523
+ }
524
+ const protNFe = nfeProc ? this.findInObj(nfeProc, 'protNFe') : this.findInObj(jsonData, 'protNFe');
525
+ const chFromProt = protNFe?.infProt?.chNFe;
526
+ const idAttr = NFe?.infNFe?.Id ?? '';
527
+ const chFromId = typeof idAttr === 'string' ? idAttr.replace(/^NFe/, '') : '';
528
+ const chave = chFromProt || chFromId || '';
529
+ const data = { NFe };
530
+ if (protNFe)
531
+ data.protNFe = protNFe;
532
+ return { data, chave };
533
+ }
534
+ /**
535
+ * Faz o parse de um XML de NFe/NFCe (envio, retorno ou solo) preservando
536
+ * atributos e elevando-os para o nível do elemento (ex.: `Id` em `infNFe`,
537
+ * `versao` em `NFe`, `nItem` em `det`). Necessário para alimentar o
538
+ * fluxo interno da lib que espera atributos como propriedades comuns.
539
+ */
540
+ parseNfeLikeXml(xml) {
541
+ const jsonAsString = convert__namespace.xml2json(xml, {
542
+ compact: true,
543
+ spaces: 2,
544
+ ignoreAttributes: false,
545
+ ignoreDeclaration: true,
546
+ trim: true,
547
+ ignoreInstruction: true,
548
+ ignoreComment: true,
549
+ ignoreCdata: true,
550
+ ignoreDoctype: true,
551
+ textFn: this.removeJsonTextAttribute,
552
+ });
553
+ const parsed = JSON.parse(jsonAsString);
554
+ this.liftAttributes(parsed);
555
+ return parsed;
556
+ }
557
+ /**
558
+ * Eleva, recursivamente, as chaves de `_attributes` para o próprio nível
559
+ * do elemento e remove o nó `_attributes`. Compatível com a saída de
560
+ * `xml-js` em modo compacto.
561
+ */
562
+ liftAttributes(node) {
563
+ if (!node || typeof node !== 'object')
564
+ return;
565
+ if (Array.isArray(node)) {
566
+ node.forEach(item => this.liftAttributes(item));
567
+ return;
568
+ }
569
+ if (node._attributes && typeof node._attributes === 'object') {
570
+ for (const attrKey of Object.keys(node._attributes)) {
571
+ if (!(attrKey in node)) {
572
+ node[attrKey] = node._attributes[attrKey];
573
+ }
574
+ }
575
+ delete node._attributes;
576
+ }
577
+ for (const key of Object.keys(node)) {
578
+ const value = node[key];
579
+ if (value && typeof value === 'object') {
580
+ this.liftAttributes(value);
581
+ }
582
+ }
583
+ }
584
+ /**
585
+ * Normaliza uma `NFe` parseada de XML para o formato interno da lib:
586
+ * - converte `CNPJ`/`CPF`/`idEstrangeiro` em `CNPJCPF` em `emit`,
587
+ * `dest`, `transp.transporta` e `NFref.refNFP`.
588
+ * - remove atributos elevados que a lib re-aplica via `$` ao montar
589
+ * o XML final (`infNFe.versao`, `det.nItem`), evitando duplicidade
590
+ * no XML gerado.
591
+ *
592
+ * Observação: `infRespTec` mantém o campo `CNPJ` original — a lib não
593
+ * unifica documento para esse nó.
594
+ */
595
+ normalizeNfeForLibFormat(nfeNode) {
596
+ const infNFe = nfeNode?.infNFe;
597
+ if (!infNFe || typeof infNFe !== 'object')
598
+ return;
599
+ // versao é re-aplicado pela lib via $ ao montar `infNFe`
600
+ delete infNFe.versao;
601
+ // O atributo `Id` no XML vem com prefixo "NFe" (ex.: "NFe4126..."),
602
+ // mas a lib espera apenas a chave de 44 dígitos em `infNFe.Id` —
603
+ // ela própria re-prefixa com "NFe" ao calcular `chaveAcesso`.
604
+ if (typeof infNFe.Id === 'string' && /^NFe\d{44}$/.test(infNFe.Id)) {
605
+ infNFe.Id = infNFe.Id.slice(3);
606
+ }
607
+ this.unifyDocFields(infNFe.emit);
608
+ this.unifyDocFields(infNFe.dest);
609
+ this.unifyDocFields(infNFe.transp?.transporta);
610
+ // nItem é re-aplicado pela lib via $ em cada `det` quando é array.
611
+ // Por isso normalizamos sempre para array (no XML, com 1 item, viraria
612
+ // objeto único e a lib não re-aplicaria nItem, quebrando a validação XSD).
613
+ const det = infNFe.det;
614
+ if (Array.isArray(det)) {
615
+ det.forEach((d) => { if (d && typeof d === 'object')
616
+ delete d.nItem; });
617
+ }
618
+ else if (det && typeof det === 'object') {
619
+ delete det.nItem;
620
+ infNFe.det = [det];
621
+ }
622
+ const NFref = infNFe.NFref;
623
+ if (Array.isArray(NFref)) {
624
+ NFref.forEach((ref) => this.unifyDocFields(ref?.refNFP));
625
+ }
626
+ else if (NFref && typeof NFref === 'object') {
627
+ this.unifyDocFields(NFref.refNFP);
628
+ }
629
+ }
630
+ /**
631
+ * Em um nó (ex.: `emit`, `dest`), unifica `CNPJ`/`CPF`/`idEstrangeiro`
632
+ * em um único campo `CNPJCPF`, removendo as chaves originais. Mantém a
633
+ * primeira não-vazia encontrada na ordem `CNPJ → CPF → idEstrangeiro`.
634
+ */
635
+ unifyDocFields(target) {
636
+ if (!target || typeof target !== 'object')
637
+ return;
638
+ if (target.CNPJCPF)
639
+ return;
640
+ const candidate = target.CNPJ || target.CPF || target.idEstrangeiro;
641
+ if (candidate) {
642
+ target.CNPJCPF = candidate;
643
+ }
644
+ delete target.CNPJ;
645
+ delete target.CPF;
646
+ delete target.idEstrangeiro;
647
+ }
459
648
  }
460
649
 
461
650
  class BaseNFE {
@@ -765,6 +954,7 @@ class GerarConsulta {
765
954
  const config = this.environment.getConfig();
766
955
  // Capturando o schema path
767
956
  const { schemaPath } = this.utility.getSchema(metodo);
957
+ logger.info(xmlConsulta);
768
958
  // Valida Schema apenas se o schema path estiver definido
769
959
  if (schemaPath) {
770
960
  if (config.lib?.useForSchemaValidation !== 'validateSchemaJsBased') {
@@ -1015,6 +1205,7 @@ const getSchema = (metodo) => {
1015
1205
  RecepcaoEvento: `${pathSchemas}/envEvento_v1.00.xsd`,
1016
1206
  NFeDistribuicaoDFe: `${pathSchemas}/distDFeInt_v1.01.xsd`,
1017
1207
  NFEAutorizacao: `${pathSchemas}/enviNFe_v4.00.xsd`,
1208
+ NFeAutorizacao: `${pathSchemas}/enviNFe_v4.00.xsd`,
1018
1209
  NFEInutilizacao: `${pathSchemas}/inutNFe_v4.00.xsd`,
1019
1210
  NFERetAutorizacao: `${pathSchemas}/consReciNFe_v4.00.xsd`,
1020
1211
  CTeDistribuicaoDFe: `${pathSchemas}/cte/distDFeInt_v1.00.xsd`,
@@ -3678,6 +3869,254 @@ const mountICMS = (icms) => {
3678
3869
  return {};
3679
3870
  };
3680
3871
 
3872
+ /*
3873
+ * This file is part of NFeWizard-io.
3874
+ *
3875
+ * NFeWizard-io is free software: you can redistribute it and/or modify
3876
+ * it under the terms of the GNU General Public License as published by
3877
+ * the Free Software Foundation, either version 3 of the License, or
3878
+ * (at your option) any later version.
3879
+ *
3880
+ * NFeWizard-io is distributed in the hope that it will be useful,
3881
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3882
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3883
+ * GNU General Public License for more details.
3884
+ *
3885
+ * You should have received a copy of the GNU General Public License
3886
+ * along with NFeWizard-io. If not, see <https://www.gnu.org/licenses/>.
3887
+ */
3888
+ /** Remove o prefixo `{namespace}` deixando apenas o nome local do elemento. */
3889
+ function stripNamespace(name) {
3890
+ return name.replace(/^\{[^}]*\}/, '');
3891
+ }
3892
+ /**
3893
+ * Interpreta uma mensagem de erro do libxmljs2 / xsd-schema-validator e retorna
3894
+ * um `SchemaValidationIssue` com versão humanizada + metadados estruturados.
3895
+ */
3896
+ function humanizeIssue(raw, line, column) {
3897
+ const issue = { raw, humanized: raw, line, column };
3898
+ // 1) Elemento com filhos ausentes
3899
+ // Element '{ns}Nome': Missing child element(s). Expected is one of ( {ns}a, {ns}b ).
3900
+ let m = raw.match(/Element\s+'([^']+)':\s*Missing child element\(s\)\.\s*Expected is one of\s*\(\s*([^)]+)\s*\)\.?/i);
3901
+ if (m) {
3902
+ const element = stripNamespace(m[1]);
3903
+ const expected = m[2].split(',').map((s) => stripNamespace(s.trim())).filter(Boolean);
3904
+ issue.element = element;
3905
+ issue.expected = expected;
3906
+ issue.humanized = `Elemento <${element}> está incompleto. Filho ausente, esperado um de: ${expected.join(', ')}.`;
3907
+ return issue;
3908
+ }
3909
+ // 2) Elemento não esperado
3910
+ // Element '{ns}Nome': This element is not expected. Expected is one of ( {ns}a, {ns}b ).
3911
+ m = raw.match(/Element\s+'([^']+)':\s*This element is not expected\.\s*(?:Expected is one of\s*\(\s*([^)]+)\s*\)\.?)?/i);
3912
+ if (m) {
3913
+ const element = stripNamespace(m[1]);
3914
+ issue.element = element;
3915
+ if (m[2]) {
3916
+ issue.expected = m[2].split(',').map((s) => stripNamespace(s.trim())).filter(Boolean);
3917
+ issue.humanized = `Elemento <${element}> inesperado nesta posição. Esperado um de: ${issue.expected.join(', ')}.`;
3918
+ }
3919
+ else {
3920
+ issue.humanized = `Elemento <${element}> inesperado nesta posição.`;
3921
+ }
3922
+ return issue;
3923
+ }
3924
+ // 3) Atributo inválido / faltando
3925
+ // Element '{ns}Nome', attribute 'attr': ...
3926
+ m = raw.match(/Element\s+'([^']+)',\s*attribute\s+'([^']+)':\s*(.+?)\.?$/i);
3927
+ if (m) {
3928
+ const element = stripNamespace(m[1]);
3929
+ const attribute = m[2];
3930
+ const desc = m[3];
3931
+ issue.element = element;
3932
+ issue.attribute = attribute;
3933
+ issue.humanized = `Elemento <${element}> — atributo "${attribute}": ${desc}.`;
3934
+ return issue;
3935
+ }
3936
+ // 4) Valor inválido para tipo
3937
+ // Element '{ns}Nome': [facet 'pattern'] The value '...' is not accepted by the pattern '...'.
3938
+ // Element '{ns}Nome': '...' is not a valid value of the atomic type 'TString'.
3939
+ m = raw.match(/Element\s+'([^']+)':\s*(?:\[facet[^\]]*\]\s*)?(.+?)\.?$/i);
3940
+ if (m) {
3941
+ const element = stripNamespace(m[1]);
3942
+ issue.element = element;
3943
+ issue.humanized = `Elemento <${element}>: ${m[2]}.`;
3944
+ return issue;
3945
+ }
3946
+ // 5) Sem padrão reconhecido — devolve raw como humanizado
3947
+ return issue;
3948
+ }
3949
+ /** Cria um issue "interno" quando o erro não veio do schema (ex.: XML mal formado). */
3950
+ function internalIssue(raw) {
3951
+ return { raw, humanized: raw };
3952
+ }
3953
+ /**
3954
+ * Monta o relatório textual estilo SEFAZ-RS pronto para `console.log`.
3955
+ */
3956
+ function buildReport(metodo, schemaFile, errors) {
3957
+ const linhas = [];
3958
+ linhas.push('=== Resultado da Validação do Schema ===');
3959
+ linhas.push(`Tipo de Mensagem: ${metodo}`);
3960
+ linhas.push(`Schema XML: ${schemaFile}`);
3961
+ if (errors.length === 0) {
3962
+ linhas.push('Parser XML: Nenhum erro encontrado');
3963
+ linhas.push('Schema XML: Nenhum erro encontrado');
3964
+ linhas.push('Status: XML válido.');
3965
+ }
3966
+ else {
3967
+ linhas.push(`Status: ${errors.length} erro(s) de validação`);
3968
+ linhas.push('');
3969
+ errors.forEach((err, idx) => {
3970
+ const pos = err.line ? ` (linha ${err.line}${err.column ? `, col ${err.column}` : ''})` : '';
3971
+ linhas.push(` ${idx + 1}. ${err.humanized}${pos}`);
3972
+ });
3973
+ }
3974
+ linhas.push('=========================================');
3975
+ return linhas.join('\n');
3976
+ }
3977
+ /** Converte a lista de erros em linhas para `console.table`. */
3978
+ function buildTableRows(errors) {
3979
+ if (errors.length === 0) {
3980
+ return [{ '#': 1, Linha: '', Elemento: '-', Mensagem: 'XML válido.' }];
3981
+ }
3982
+ return errors.map((err, idx) => ({
3983
+ '#': idx + 1,
3984
+ Linha: err.line ?? '',
3985
+ Elemento: err.element ?? '-',
3986
+ Mensagem: err.humanized,
3987
+ }));
3988
+ }
3989
+ function schemaFileName(schemaPath) {
3990
+ if (!schemaPath)
3991
+ return '-';
3992
+ const parts = schemaPath.split(/[\\/]/);
3993
+ return parts[parts.length - 1] || schemaPath;
3994
+ }
3995
+ function makeResult(success, metodo, schemaPath, errors) {
3996
+ const schema = schemaFileName(schemaPath);
3997
+ const report = buildReport(metodo, schema, errors);
3998
+ const message = success
3999
+ ? 'XML válido.'
4000
+ : errors[0]?.humanized ?? 'Erro de validação não identificado.';
4001
+ return {
4002
+ success,
4003
+ message,
4004
+ errors,
4005
+ report,
4006
+ tableRows: buildTableRows(errors),
4007
+ metodo,
4008
+ schema,
4009
+ };
4010
+ }
4011
+ function runJsBased(xml, metodo) {
4012
+ return new Promise(async (resolve, reject) => {
4013
+ const { basePath, schemaPath } = getSchema(metodo);
4014
+ if (!schemaPath) {
4015
+ reject(makeResult(false, metodo, '', [internalIssue(`Schema XSD não encontrado para o método '${metodo}'. Verifique se o nome do método está correto.`)]));
4016
+ return;
4017
+ }
4018
+ try {
4019
+ const completeXSD = await xsdAssembler.assemble(schemaPath);
4020
+ const xmlDoc = libxmljs.parseXml(xml);
4021
+ const xsdDoc = libxmljs.parseXml(completeXSD, { baseUrl: `${basePath}/` });
4022
+ const isValid = xmlDoc.validate(xsdDoc);
4023
+ if (isValid) {
4024
+ resolve(makeResult(true, metodo, schemaPath, []));
4025
+ return;
4026
+ }
4027
+ const errors = (xmlDoc.validationErrors || []).map((e) => humanizeIssue(String(e.message ?? e).trim(), e.line, e.column));
4028
+ reject(makeResult(false, metodo, schemaPath, errors.length ? errors : [internalIssue('XML inválido (sem detalhes do validador).')]));
4029
+ }
4030
+ catch (error) {
4031
+ reject(makeResult(false, metodo, schemaPath, [internalIssue(String(error?.message ?? error))]));
4032
+ }
4033
+ });
4034
+ }
4035
+ function runJavaBased(xml, metodo) {
4036
+ return new Promise((resolve, reject) => {
4037
+ const { schemaPath } = getSchema(metodo);
4038
+ if (!schemaPath) {
4039
+ reject(makeResult(false, metodo, '', [internalIssue(`Schema XSD não encontrado para o método '${metodo}'. Verifique se o nome do método está correto.`)]));
4040
+ return;
4041
+ }
4042
+ try {
4043
+ xsdValidator.validateXML(xml, schemaPath, (err, validationResult) => {
4044
+ if (err) {
4045
+ reject(makeResult(false, metodo, schemaPath, [humanizeIssue(String(err.message ?? err))]));
4046
+ return;
4047
+ }
4048
+ if (!validationResult?.valid) {
4049
+ const msgs = validationResult?.messages ?? [];
4050
+ const errors = msgs.length
4051
+ ? msgs.map((m) => humanizeIssue(String(m)))
4052
+ : [internalIssue('XML inválido (sem detalhes do validador).')];
4053
+ reject(makeResult(false, metodo, schemaPath, errors));
4054
+ return;
4055
+ }
4056
+ resolve(makeResult(true, metodo, schemaPath, []));
4057
+ });
4058
+ }
4059
+ catch (error) {
4060
+ reject(makeResult(false, metodo, schemaPath, [internalIssue(String(error?.message ?? error))]));
4061
+ }
4062
+ });
4063
+ }
4064
+ /**
4065
+ * Valida um XML contra o schema XSD correspondente ao método fiscal informado.
4066
+ *
4067
+ * ### Seleção do validador (ordem de prioridade)
4068
+ * 1. `options.validator` — escolha explícita do caller.
4069
+ * 2. `options.environment.getConfig().lib?.useForSchemaValidation` — valor definido
4070
+ * na inicialização da lib.
4071
+ * 3. `'validateSchemaJsBased'` — padrão (sem JDK).
4072
+ *
4073
+ * @param xml - String XML a ser validada.
4074
+ * @param metodo - Nome do método/operação fiscal (ex.: `'NFeAutorizacao'`).
4075
+ * @param options - Opções opcionais: `validator` e/ou `environment`.
4076
+ *
4077
+ * @example
4078
+ * // Sem opções: usa validateSchemaJsBased
4079
+ * await NFE_SchemaValidate(xml, 'NFeAutorizacao');
4080
+ *
4081
+ * @example
4082
+ * // Passando o environment inicializado pela lib (lê useForSchemaValidation do config)
4083
+ * await NFE_SchemaValidate(xml, 'NFeAutorizacao', { environment });
4084
+ *
4085
+ * @example
4086
+ * // Forçando um validador específico
4087
+ * await NFE_SchemaValidate(xml, 'NFeAutorizacao', { validator: 'validateSchemaJavaBased' });
4088
+ */
4089
+ function NFE_SchemaValidate(xml, metodo, options) {
4090
+ let chosen = 'validateSchemaJsBased';
4091
+ if (options?.validator) {
4092
+ chosen = options.validator;
4093
+ }
4094
+ else if (options?.environment) {
4095
+ const fromConfig = options.environment.getConfig()?.lib?.useForSchemaValidation;
4096
+ if (fromConfig)
4097
+ chosen = fromConfig;
4098
+ }
4099
+ const xmlNormalizado = wrapEnviNFeIfNeeded(xml, metodo);
4100
+ return chosen === 'validateSchemaJavaBased'
4101
+ ? runJavaBased(xmlNormalizado, metodo)
4102
+ : runJsBased(xmlNormalizado, metodo);
4103
+ }
4104
+ /**
4105
+ * Para os métodos de autorização de NF-e, o schema XSD (`enviNFe_v4.00.xsd`)
4106
+ * exige que o elemento raiz seja `<enviNFe>`. Quando o caller passa apenas a
4107
+ * árvore `<NFe>`, envelopamos automaticamente em `<enviNFe>` com `idLote=1` e
4108
+ * `indSinc=1` para que a validação funcione.
4109
+ */
4110
+ function wrapEnviNFeIfNeeded(xml, metodo) {
4111
+ if (metodo !== 'NFEAutorizacao' && metodo !== 'NFeAutorizacao')
4112
+ return xml;
4113
+ const semDecl = xml.replace(/^\s*<\?xml[^?]*\?>\s*/, '').trimStart();
4114
+ if (/^<NFe[\s>]/.test(semDecl)) {
4115
+ return `<enviNFe versao="4.00" xmlns="http://www.portalfiscal.inf.br/nfe"><idLote>1</idLote><indSinc>1</indSinc>${semDecl}</enviNFe>`;
4116
+ }
4117
+ return xml;
4118
+ }
4119
+
3681
4120
  /*
3682
4121
  * This file is part of NFeWizard-io.
3683
4122
  *
@@ -3863,12 +4302,35 @@ class LoadCertificate {
3863
4302
  this.certificate = '';
3864
4303
  this.cert_key = '';
3865
4304
  }
3866
- loadCertificateWithPEM() {
4305
+ /**
4306
+ * Resolve o conteúdo binário do PFX a partir do `pathCertificado` informado em
4307
+ * config. Aceita `string` (caminho), `Buffer` ou `NodeJS.ReadableStream`.
4308
+ */
4309
+ async resolvePfxBuffer() {
4310
+ const input = this.config.dfe.pathCertificado;
4311
+ if (typeof input === 'string') {
4312
+ return fs.readFileSync(input);
4313
+ }
4314
+ if (Buffer.isBuffer(input)) {
4315
+ return input;
4316
+ }
4317
+ if (input && typeof input.pipe === 'function') {
4318
+ const stream = input;
4319
+ return await new Promise((resolve, reject) => {
4320
+ const chunks = [];
4321
+ stream.on('data', (chunk) => {
4322
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
4323
+ });
4324
+ stream.on('end', () => resolve(Buffer.concat(chunks)));
4325
+ stream.on('error', (err) => reject(err));
4326
+ });
4327
+ }
4328
+ throw new Error('pathCertificado deve ser uma string (path), Buffer ou Stream legível.');
4329
+ }
4330
+ loadCertificateWithPEM(pfxFile) {
3867
4331
  return new Promise((resolve, reject) => {
3868
4332
  try {
3869
- const pfxPath = this.config.dfe.pathCertificado;
3870
4333
  const pfxPassword = this.config.dfe.senhaCertificado;
3871
- const pfxFile = fs.readFileSync(pfxPath);
3872
4334
  const certsDir = path.resolve(baseDir, dir);
3873
4335
  // Read CA certificates if directory exists and has files
3874
4336
  let caCerts = [];
@@ -3927,13 +4389,10 @@ class LoadCertificate {
3927
4389
  }
3928
4390
  });
3929
4391
  }
3930
- loadCertificateWithNodeForge() {
4392
+ loadCertificateWithNodeForge(pfxFile) {
3931
4393
  return new Promise((resolve, reject) => {
3932
4394
  try {
3933
- const pfxPath = this.config.dfe.pathCertificado;
3934
4395
  const pfxPassword = this.config.dfe.senhaCertificado;
3935
- // Lê o arquivo PFX
3936
- const pfxFile = fs.readFileSync(pfxPath);
3937
4396
  // Decodifica o arquivo PFX (PKCS#12)
3938
4397
  const p12Asn1 = forge.asn1.fromDer(pfxFile.toString('binary'));
3939
4398
  const p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, pfxPassword);
@@ -4005,15 +4464,16 @@ class LoadCertificate {
4005
4464
  logger.info('Validando certificado', {
4006
4465
  context: 'LoadCertificate',
4007
4466
  });
4467
+ const pfxFile = await this.resolvePfxBuffer();
4008
4468
  if (this.config.lib?.useOpenSSL || this.config.lib?.useOpenSSL === undefined) {
4009
- const { agent } = await this.loadCertificateWithPEM();
4469
+ const { agent } = await this.loadCertificateWithPEM(pfxFile);
4010
4470
  return {
4011
4471
  certificate: this.certificate,
4012
4472
  cert_key: this.cert_key,
4013
4473
  agent
4014
4474
  };
4015
4475
  }
4016
- const { agent } = await this.loadCertificateWithNodeForge();
4476
+ const { agent } = await this.loadCertificateWithNodeForge(pfxFile);
4017
4477
  return {
4018
4478
  certificate: this.certificate,
4019
4479
  cert_key: this.cert_key,
@@ -4105,6 +4565,7 @@ exports.GerarConsulta = GerarConsulta;
4105
4565
  exports.HttpClientBuilder = HttpClientBuilder;
4106
4566
  exports.JsonArrayTransport = JsonArrayTransport;
4107
4567
  exports.LoadCertificate = LoadCertificate;
4568
+ exports.NFE_SchemaValidate = NFE_SchemaValidate;
4108
4569
  exports.SaveFiles = SaveFiles;
4109
4570
  exports.Utility = Utility;
4110
4571
  exports.ValidaCPFCNPJ = ValidaCPFCNPJ;