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
44 changes: 44 additions & 0 deletions src/cliente/indice.teste.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,3 +204,47 @@ test('consultarConvenio monta o caminho com o codigo do municipio', async () =>
await fechar();
}
});

// listarEventos vive no ADN (nao no SEFIN Nacional): a rota POST
// /nfse/{chave}/eventos so serve pra registrar evento, GET nela devolve 405.
// A leitura por chave e um endpoint separado do ADN Contribuinte.
test('listarEventos busca no ADN, nao no SEFIN Nacional', async () => {
const { urlBase, fechar } = await subirServidorDeTeste((req, _corpo, res) => {
assert.equal(req.method, 'GET');
assert.equal(req.url, '/contribuintes/NFSe/' + '1'.repeat(50) + '/Eventos');
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(
JSON.stringify({
StatusProcessamento: 'DOCUMENTOS_LOCALIZADOS',
LoteDFe: [
{
NSU: 238,
ChaveAcesso: '1'.repeat(50),
TipoDocumento: 'EVENTO',
TipoEvento: 'CANCELAMENTO',
ArquivoXml: compactarGZipBase64('<evento>cancelamento</evento>'),
DataHoraGeracao: '2026-07-01T23:00:02.04',
},
],
Alertas: [],
Erros: [],
TipoAmbiente: 'HOMOLOGACAO',
DataHoraProcessamento: '2026-07-01T23:01:00-03:00',
})
);
});

try {
const cliente = criarClienteSefin({
ambiente: 'homologacao',
certificado: clienteDeTeste,
urlBaseAdn: urlBase,
agenteOpcoes: { rejectUnauthorized: false },
});
const resultado = await cliente.listarEventos('1'.repeat(50));
assert.equal(resultado.documentos[0]?.tipoDocumento, 'EVENTO');
assert.equal(resultado.documentos[0]?.tipoEvento, 'CANCELAMENTO');
} finally {
await fechar();
}
});
14 changes: 10 additions & 4 deletions src/cliente/indice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,13 @@ export interface ClienteSefin {
consultarDps(idDps: string): Promise<RespostaSefin>;
/** POST /nfse/{chave}/eventos - registra um evento (ex.: cancelamento) já assinado. */
registrarEvento(chaveAcesso: string, pedRegXmlAssinado: string): Promise<RespostaSefin>;
/** GET /nfse/{chave}/eventos - lista os eventos registrados para a NFS-e. */
listarEventos(chaveAcesso: string): Promise<RespostaSefin>;
/**
* GET /contribuintes/NFSe/{chave}/Eventos no ADN - lista os eventos (ex.:
* cancelamento, substituição) registrados para a chave de acesso. Não
* confundir com `registrarEvento`: aquele é POST no SEFIN Nacional (só
* aceita escrita, devolve 405 em GET); este é leitura, e vive no ADN.
*/
listarEventos(chaveAcesso: string): Promise<LoteDistribuicaoNsu>;
/**
* GET /contribuintes/DFe/{nsu} no ADN - baixa o próximo lote de documentos
* fiscais (até 50) a partir do NSU informado. Use `0` para sincronizar
Expand Down Expand Up @@ -185,8 +190,9 @@ export function criarClienteSefin(opcoes: OpcoesClienteSefin): ClienteSefin {
});
},

listarEventos(chaveAcesso) {
return requisitar('GET', urlBase, `nfse/${chaveAcesso}/eventos`);
async listarEventos(chaveAcesso) {
const { corpo } = await requisitar('GET', urlAdn, `contribuintes/NFSe/${chaveAcesso}/Eventos`);
return normalizarLoteDistribuicao(corpo);
},

async baixarDfe(nsu, opcoesConsulta) {
Expand Down
8 changes: 7 additions & 1 deletion src/danfse/desenho.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,13 @@ const ALTURA_FAIXA = 3.4 * MM;
export type ResolvedorMunicipio = (codigoIbge: string) => { nome: string; uf: string } | undefined;

export interface OpcoesGerarDanfse {
/** Aplica marca d'água diagonal, conforme NT 008/2026 §2.5. */
/**
* Aplica marca d'água diagonal, conforme NT 008/2026 §2.5. Precisa ser
* informado explicitamente porque cancelamento/substituição nunca ficam no
* XML da própria NFS-e (o `cStat` só tem códigos de geração) - são sempre
* um documento de EVENTO separado. Quem chama essa função é responsável
* por checar os eventos (`listarEventos`/`baixarDfe`) antes de decidir.
*/
situacaoEspecial?: 'Cancelada' | 'Substituida';
resolverMunicipio?: ResolvedorMunicipio;
/** PNG/JPEG da logomarca oficial da NFS-e (item 2.4.3). Sem isso, o cabeçalho usa só texto. */
Expand Down
Loading