Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Em desenvolvimento inicial. A tabela abaixo reflete o estado real de cada módul
| --- | --- | --- |
| `certificado` | Leitura de certificado A1 (.pfx/.p12) | Feito |
| `assinatura` | Assinatura XML (XMLDSig) | Feito |
| `dps` | Tipos e serialização da DPS, incluindo o bloco IBSCBS (XML validado contra o XSD oficial) | Feito; grupo gReeRepRes (reembolso/repasse/ressarcimento de terceiros) ainda não |
| `dps` | Tipos e serialização da DPS, incluindo o bloco IBSCBS completo (XML validado contra o XSD oficial) | Feito |
| `cliente` | Cliente HTTP mTLS para o SEFIN Nacional e o ADN (produção e homologação) | Feito |
| `danfse` | Geração do DANFSe em PDF (NT 008/2026), incluindo o Canhoto | Feito |

Expand Down
33 changes: 33 additions & 0 deletions src/dps/serializacao.teste.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,24 @@ test('monta um XML de DPS com bloco IBSCBS valido contra o XSD oficial', () => {
},
imovel: { cCIB: 'AB123456' },
valores: {
gReeRepRes: [
{
dFeNacional: { tipoChaveDFe: '1', chaveDFe: '1'.repeat(50) },
fornec: { CNPJ: '11222333000181', xNome: 'Fornecedor Exemplo' },
dtEmiDoc: new Date('2026-06-01T00:00:00Z'),
dtCompDoc: new Date('2026-06-01T00:00:00Z'),
tpReeRepRes: '04',
vlrReeRepRes: 100,
},
{
docOutro: { nDoc: '123', xDoc: 'Recibo de despesa' },
dtEmiDoc: new Date('2026-06-02T00:00:00Z'),
dtCompDoc: new Date('2026-06-02T00:00:00Z'),
tpReeRepRes: '99',
xTpReeRepRes: 'Reembolso diverso',
vlrReeRepRes: 50,
},
],
trib: {
gIBSCBS: {
CST: '000',
Expand Down Expand Up @@ -154,3 +172,18 @@ test('rejeita IBSCBS.imovel sem cCIB nem end', () => {
};
assert.throws(() => montarXmlDps(dados), ErroValidacaoDps);
});

test('rejeita documento de gReeRepRes sem dFeNacional, docFiscalOutro ou docOutro', () => {
const dados = dadosDpsExemplo();
dados.IBSCBS = {
finNFSe: '0',
cIndOp: '000001',
indDest: '0',
valores: {
// @ts-expect-error -- teste propositalmente nao informa nenhuma referencia de documento
gReeRepRes: [{ dtEmiDoc: new Date(), dtCompDoc: new Date(), tpReeRepRes: '01', vlrReeRepRes: 10 }],
trib: { gIBSCBS: { CST: '000', cClassTrib: '000001' } },
},
};
assert.throws(() => montarXmlDps(dados), ErroValidacaoDps);
});
56 changes: 52 additions & 4 deletions src/dps/serializacao.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ import type {
DadosDps,
DestinatarioIbscbs,
DiferimentoIbscbs,
DocumentoReeRepRes,
Endereco,
EnderecoImovel,
FornecedorReeRepRes,
IdentificacaoDestinatarioIbscbs,
ImovelIbscbs,
InfoIbscbs,
Pessoa,
Expand Down Expand Up @@ -164,18 +167,18 @@ function xmlValores(valores: Valores): string {
);
}

function xmlIdentificacaoDest(dest: DestinatarioIbscbs): string {
function xmlIdentificacaoDest(dest: IdentificacaoDestinatarioIbscbs, contexto: string): string {
if (dest.CNPJ) return tag('CNPJ', dest.CNPJ);
if (dest.CPF) return tag('CPF', dest.CPF);
if (dest.NIF) return tag('NIF', dest.NIF);
if (dest.cNaoNIF) return tag('cNaoNIF', dest.cNaoNIF);
throw new ErroValidacaoDps('IBSCBS.dest precisa informar CNPJ, CPF, NIF ou cNaoNIF.');
throw new ErroValidacaoDps(`${contexto} precisa informar CNPJ, CPF, NIF ou cNaoNIF.`);
}

function xmlDest(dest: DestinatarioIbscbs): string {
return (
`<dest>` +
xmlIdentificacaoDest(dest) +
xmlIdentificacaoDest(dest, 'IBSCBS.dest') +
tag('xNome', dest.xNome) +
(dest.end ? `<end>${xmlEndereco(dest.end)}</end>` : '') +
tag('fone', dest.fone) +
Expand Down Expand Up @@ -233,7 +236,52 @@ function xmlRefNFSe(refs: string[]): string {
return `<gRefNFSe>${refs.map((ref) => tag('refNFSe', ref)).join('')}</gRefNFSe>`;
}

function xmlDocumentoReferenciadoReeRepRes(doc: DocumentoReeRepRes): string {
if (doc.dFeNacional) {
const d = doc.dFeNacional;
return `<dFeNacional>${tag('tipoChaveDFe', d.tipoChaveDFe)}${tag('xTipoChaveDFe', d.xTipoChaveDFe)}${tag('chaveDFe', d.chaveDFe)}</dFeNacional>`;
}
if (doc.docFiscalOutro) {
const d = doc.docFiscalOutro;
return `<docFiscalOutro>${tag('cMunDocFiscal', d.cMunDocFiscal)}${tag('nDocFiscal', d.nDocFiscal)}${tag('xDocFiscal', d.xDocFiscal)}</docFiscalOutro>`;
}
if (doc.docOutro) {
const d = doc.docOutro;
return `<docOutro>${tag('nDoc', d.nDoc)}${tag('xDoc', d.xDoc)}</docOutro>`;
}
throw new ErroValidacaoDps(
'IBSCBS.valores.gReeRepRes: cada documento precisa informar dFeNacional, docFiscalOutro ou docOutro.'
);
}

function xmlFornecedorReeRepRes(fornec: FornecedorReeRepRes): string {
return `<fornec>${xmlIdentificacaoDest(fornec, 'IBSCBS.valores.gReeRepRes.fornec')}${tag('xNome', fornec.xNome)}</fornec>`;
}

function xmlDocumentoReeRepRes(doc: DocumentoReeRepRes): string {
return (
`<documentos>` +
xmlDocumentoReferenciadoReeRepRes(doc) +
(doc.fornec ? xmlFornecedorReeRepRes(doc.fornec) : '') +
tag('dtEmiDoc', formatarData(doc.dtEmiDoc)) +
tag('dtCompDoc', formatarData(doc.dtCompDoc)) +
tag('tpReeRepRes', doc.tpReeRepRes) +
tag('xTpReeRepRes', doc.xTpReeRepRes) +
tagNum('vlrReeRepRes', doc.vlrReeRepRes) +
`</documentos>`
);
}

function xmlGReeRepRes(documentos: DocumentoReeRepRes[]): string {
return `<gReeRepRes>${documentos.map(xmlDocumentoReeRepRes).join('')}</gReeRepRes>`;
}

function xmlIbscbs(ibscbs: InfoIbscbs): string {
const gReeRepRes =
ibscbs.valores.gReeRepRes && ibscbs.valores.gReeRepRes.length > 0
? xmlGReeRepRes(ibscbs.valores.gReeRepRes)
: '';

return (
`<IBSCBS>` +
tag('finNFSe', ibscbs.finNFSe) +
Expand All @@ -245,7 +293,7 @@ function xmlIbscbs(ibscbs: InfoIbscbs): string {
tag('indDest', ibscbs.indDest) +
(ibscbs.dest ? xmlDest(ibscbs.dest) : '') +
(ibscbs.imovel ? xmlImovel(ibscbs.imovel) : '') +
`<valores><trib>${xmlSituacaoTributariaIbscbs(ibscbs.valores.trib.gIBSCBS)}</trib></valores>` +
`<valores>${gReeRepRes}<trib>${xmlSituacaoTributariaIbscbs(ibscbs.valores.trib.gIBSCBS)}</trib></valores>` +
`</IBSCBS>`
);
}
Expand Down
52 changes: 50 additions & 2 deletions src/dps/tipos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
// (sem obra, evento, comercio exterior ou dedução por documentos), valores,
// tributacao municipal e federal, e o bloco IBSCBS declarado pelo emitente
// (TCRTCInfoIBSCBS: finalidade, destinatario, imovel, situacao/classificacao
// tributaria, tributacao regular e diferimento). O grupo gReeRepRes (valores
// de reembolso/repasse/ressarcimento de terceiros) ainda nao esta tipado.
// tributaria, tributacao regular, diferimento e reembolso/repasse/ressarcimento
// de terceiros via gReeRepRes).

export type TipoAmbiente = '1' | '2';
export type TipoEmitenteDps = '1' | '2' | '3';
Expand Down Expand Up @@ -186,7 +186,55 @@ export interface SituacaoTributariaIbscbs {
gDif?: DiferimentoIbscbs;
}

export type TipoChaveDfe = '1' | '2' | '3' | '9';
export type TipoReembolsoRepasseRessarcimento = '01' | '02' | '03' | '04' | '99';

/** Documento fiscal eletrônico do Repositório Nacional (TCRTCListaDocDFe). */
export interface DocumentoFiscalEletronicoReferenciado {
tipoChaveDFe: TipoChaveDfe;
/** Obrigatório apenas quando tipoChaveDFe = "9" (Outro). */
xTipoChaveDFe?: string;
chaveDFe: string;
}

/** Documento fiscal fora do Repositório Nacional (TCRTCListaDocFiscalOutro). */
export interface DocumentoFiscalOutroReferenciado {
cMunDocFiscal: string;
nDocFiscal: string;
xDocFiscal: string;
}

/** Documento não fiscal (TCRTCListaDocOutro). */
export interface DocumentoOutroReferenciado {
nDoc: string;
xDoc: string;
}

export interface FornecedorReeRepRes extends IdentificacaoDestinatarioIbscbs {
xNome: string;
}

/**
* Documento referenciado num reembolso, repasse ou ressarcimento de valores
* já tributados por terceiros (TCRTCListaDoc) - exige exatamente uma das três
* formas de identificar o documento.
*/
export interface DocumentoReeRepRes {
dFeNacional?: DocumentoFiscalEletronicoReferenciado;
docFiscalOutro?: DocumentoFiscalOutroReferenciado;
docOutro?: DocumentoOutroReferenciado;
fornec?: FornecedorReeRepRes;
dtEmiDoc: Date;
dtCompDoc: Date;
tpReeRepRes: TipoReembolsoRepasseRessarcimento;
/** Obrigatório apenas quando tpReeRepRes = "99" (Outros). */
xTpReeRepRes?: string;
vlrReeRepRes: number;
}

export interface ValoresIbscbs {
/** Documentos de reembolso/repasse/ressarcimento (gReeRepRes/documentos), até 1000 ocorrências. */
gReeRepRes?: DocumentoReeRepRes[];
trib: {
gIBSCBS: SituacaoTributariaIbscbs;
};
Expand Down
Loading