diff --git a/src/cliente/indice.teste.ts b/src/cliente/indice.teste.ts
index 7720e38..73a1812 100644
--- a/src/cliente/indice.teste.ts
+++ b/src/cliente/indice.teste.ts
@@ -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('cancelamento'),
+ 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();
+ }
+});
diff --git a/src/cliente/indice.ts b/src/cliente/indice.ts
index 910365d..0799df7 100644
--- a/src/cliente/indice.ts
+++ b/src/cliente/indice.ts
@@ -62,8 +62,13 @@ export interface ClienteSefin {
consultarDps(idDps: string): Promise;
/** POST /nfse/{chave}/eventos - registra um evento (ex.: cancelamento) já assinado. */
registrarEvento(chaveAcesso: string, pedRegXmlAssinado: string): Promise;
- /** GET /nfse/{chave}/eventos - lista os eventos registrados para a NFS-e. */
- listarEventos(chaveAcesso: string): Promise;
+ /**
+ * 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;
/**
* 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
@@ -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) {
diff --git a/src/danfse/desenho.ts b/src/danfse/desenho.ts
index de4897b..29cc9ed 100644
--- a/src/danfse/desenho.ts
+++ b/src/danfse/desenho.ts
@@ -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. */