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
77 changes: 77 additions & 0 deletions docs/pt-br/utilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,83 @@ getNfeKeyInfo('35170458716523000119620010000000121000123450');
getNfeKeyInfo('invalid'); // null
```

## Chave de NFS-e

### isValidNfseKey

Verifica se a chave de acesso de uma NFS-e nacional, a Nota Fiscal de Serviço eletrônica do Sistema Nacional NFS-e, é válida.

- A chave é um bloco único de 50 caracteres, `Cód.Mun.(7) Amb.Ger.(1) Tipo de Inscrição Federal(1) Inscrição Federal(14) nNFSe(13) AAMM(4) Cód.Num.(9) DV(1)`, todos dígitos exceto um CNPJ alfanumérico na Inscrição Federal.
- O literal `NFS` que o atributo `Id` de `infNFSe` coloca antes da chave é retirado, junto com os espaços nas extremidades.
- A chave não tem máscara impressa, já que o DANFSe a imprime em um único bloco, então, diferente do `isValidNfeKey`, um separador em qualquer ponto dela é rejeitado em vez de removido.
- O código do município precisa começar com um código IBGE de UF; ele não é consultado na tabela do IBGE.
- O `ambGer` precisa ser `1` (o sistema do município) ou `2` (o Sistema Nacional NFS-e), e o tipo de inscrição `1` (um CPF, preenchido com `000` à esquerda) ou `2` (um CNPJ, numérico ou alfanumérico), com um CPF ou CNPJ cujos próprios dígitos verificadores sejam válidos. Letras só são aceitas em um CNPJ, e minúsculas são lidas como maiúsculas, como o `isValidCnpj` com `{ version: 2 }` as lê.
- O `nNFSe` não pode ser todo de zeros e o mês precisa estar entre 01 e 12.
- O dígito verificador é um módulo 11 sobre os 49 primeiros caracteres, pesos de 2 a 9 ciclando a partir da direita, em que resto 0 ou 1 dá 0. Uma letra vale o seu código ASCII menos 48 (`A` vale 17): nenhum documento da NFS-e diz isso, então a regra vem por analogia com a chave da NF-e da Nota Técnica Conjunta 2025.001 e com os próprios dígitos verificadores do CNPJ.
- As letras seguem o `TSIdNFSe` do pacote de esquemas de 27/07/2026, nas posições da Inscrição Federal (10 a 23).
- Os modelos municipais de NFS-e que não são o padrão nacional estão fora do escopo.

```javascript
import { isValidNfseKey } from '@brazilian-utils/brazilian-utils';

isValidNfseKey('35503082258716523000119000000000001226011357924683'); // true (emitente com CNPJ, SP)
isValidNfseKey('NFS35503082258716523000119000000000001226011357924683'); // true (prefixo Id do XML)
isValidNfseKey('43149021100040364478829000000000105725120484407255'); // true (emitente com CPF, RS)
isValidNfseKey('35503082212ABC34501DE35000000000001226091357924682'); // true (emitente com CNPJ alfanumérico)
isValidNfseKey('35503082258716523000119000000000001226011357924684'); // false (dígito verificador)
isValidNfseKey('3550308 2 2 58716523000119 0000000000012 2601 135792468 3'); // false (a chave não tem máscara)
```

### parseNfseKey

Remove tudo o que não é dígito ou letra de um CNPJ alfanumérico da chave de acesso de uma NFS-e nacional e limita o resultado a 50 caracteres.

- As letras ficam em maiúsculas, como faz o `parseCnpj` com `{ version: 2 }`, e as letras antes do primeiro dígito são descartadas, inclusive o prefixo `NFS` do atributo `Id` do XML, já que a chave começa com dígitos. O `isValidNfseKey` verifica se as letras que restam estão em um CNPJ.

- Essa é a forma em que o leiaute guarda a chave e a que o DANFSe imprime, um bloco único, e por isso não existe `formatNfseKey`.

```javascript
import { parseNfseKey } from '@brazilian-utils/brazilian-utils';

parseNfseKey('NFS35503082258716523000119000000000001226011357924683');
// '35503082258716523000119000000000001226011357924683'

parseNfseKey('3550308 2 2 58716523000119 0000000000012 2601 135792468 3');
// '35503082258716523000119000000000001226011357924683'

parseNfseKey('nfs3550308 2 2 12.abc.345/01de-35 0000000000012 2609 135792468 2');
// '35503082212ABC34501DE35000000000001226091357924682'
```

### getNfseKeyInfo

Interpreta a chave de acesso de uma NFS-e nacional e retorna seus campos, como um `NfseKeyInfo`. Aceita as mesmas formas de entrada do `isValidNfseKey`.

- Retorna `municipalityCode`, `stateCode`, `generatorEnvironment`, `taxIdType`, `taxId`, `number`, `year`, `month`, `code` e `checkDigit`.
- O `generatorEnvironment` é um `NfseKeyGeneratorEnvironment`: `1` o sistema do município, `2` o Sistema Nacional NFS-e.
- O `taxIdType` é um `NfseKeyTaxIdType`, `'cpf'` ou `'cnpj'`, e o `taxId` é o CPF de 11 dígitos, sem o `000` que o preenche na chave, ou o CNPJ de 14 caracteres, numérico ou alfanumérico, em maiúsculas.
- Retorna `null` quando a chave não é válida.

```javascript
import { getNfseKeyInfo } from '@brazilian-utils/brazilian-utils';

getNfseKeyInfo('35503082258716523000119000000000001226011357924683');
// { municipalityCode: '3550308', stateCode: 'SP', generatorEnvironment: 2, taxIdType: 'cnpj',
// taxId: '58716523000119', number: 12, year: 2026, month: 1, code: '135792468', checkDigit: 3 }

getNfseKeyInfo('43149021100040364478829000000000105725120484407255');
// { municipalityCode: '4314902', stateCode: 'RS', generatorEnvironment: 1, taxIdType: 'cpf',
// taxId: '40364478829', number: 1057, year: 2025, month: 12, code: '048440725', checkDigit: 5 }

getNfseKeyInfo('35503082212ABC34501DE35000000000001226091357924682');
// { municipalityCode: '3550308', stateCode: 'SP', generatorEnvironment: 2, taxIdType: 'cnpj',
// taxId: '12ABC34501DE35', number: 12, year: 2026, month: 9, code: '135792468', checkDigit: 2 }

getNfseKeyInfo('invalid'); // null
```

Fonte: a [documentação técnica do Sistema Nacional NFS-e](https://www.gov.br/nfse/pt-br/biblioteca/documentacao-tecnica/documentacao-atual), cujos tipos de esquema `TSIdNFSe` e `TSChaveNFSe` e o campo `NFSe/infNFSe/id` do ANEXO I definem o leiaute e as regras E1280 e E1284, o [manual da emissão por decisão administrativa ou judicial](https://www.gov.br/nfse/pt-br/biblioteca/documentacao-tecnica/documentacao-atual/manual-contribuintes-emissor-publico-api-emissao-decisao-administrativa-e-judicial.pdf), que nomeia o dígito verificador de módulo 11, a [Nota Técnica SE/CGNFS-e 008](https://www.gov.br/nfse/pt-br/biblioteca/documentacao-tecnica/rtc/nt-008-se-cgnfse-danfse-20260714-v1-02.pdf) (item 2.1.1), que imprime a chave em bloco único, os [esquemas atualizados para o CNPJ alfanumérico](https://www.gov.br/nfse/pt-br/noticias/plataforma-nfs-e-disponibiliza-novas-evolucoes-em-producao-restrita-e-divulga-cronograma-de-implantacao) (pacote v1.01-20260727, em produção desde 10/08/2026) e a [Nota Técnica Conjunta 2025.001](https://www.nfe.fazenda.gov.br/portal/exibirArquivo.aspx?conteudo=5ZkvIZt10mQ=), cuja regra de ASCII menos 48 da chave da NF-e o dígito verificador empresta.

## SUFRAMA

### isValidSuframa
Expand Down
77 changes: 77 additions & 0 deletions docs/utilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,83 @@ getNfeKeyInfo('35170458716523000119620010000000121000123450');
getNfeKeyInfo('invalid'); // null
```

## NFS-e key

### isValidNfseKey

Check if the access key (chave de acesso) of a national NFS-e, the Nota Fiscal de Serviço eletrônica of the Sistema Nacional NFS-e, is valid.

- The key is one block of 50 characters, `Cód.Mun.(7) Amb.Ger.(1) Tipo de Inscrição Federal(1) Inscrição Federal(14) nNFSe(13) AAMM(4) Cód.Num.(9) DV(1)`, all digits except an alphanumeric CNPJ in the Inscrição Federal.
- The `NFS` literal the `Id` attribute of `infNFSe` puts in front of the key is stripped, with surrounding whitespace.
- The key has no printed mask, since the DANFSe prints it as a single block, so, unlike `isValidNfeKey`, a separator anywhere in it is rejected instead of being stripped.
- The municipality code must start with an IBGE UF code; it is not looked up in the IBGE table.
- `ambGer` must be `1` (the system of the municipality) or `2` (the Sistema Nacional NFS-e), and the registration type `1` (a CPF, left padded with `000`) or `2` (a CNPJ, numeric or alphanumeric), with a CPF or CNPJ whose own check digits are valid. Letters are accepted in a CNPJ only, and lower case is read as upper case, as `isValidCnpj` with `{ version: 2 }` reads it.
- `nNFSe` must not be all zeros and the month must be 01 to 12.
- The check digit is a modulus 11 over the first 49 characters, weights 2 to 9 cycling from the right, where a remainder of 0 or 1 gives 0. A letter counts as its ASCII code minus 48 (`A` is 17): no NFS-e document states it, so it is taken by analogy with the NF-e key of Nota Técnica Conjunta 2025.001 and the CNPJ's own check digits.
- The letters follow `TSIdNFSe` of the schema bundle of 2026-07-27, in the positions of the Inscrição Federal (10 to 23).
- The municipal NFS-e models that are not the national standard are out of scope.

```javascript
import { isValidNfseKey } from '@brazilian-utils/brazilian-utils';

isValidNfseKey('35503082258716523000119000000000001226011357924683'); // true (CNPJ issuer, SP)
isValidNfseKey('NFS35503082258716523000119000000000001226011357924683'); // true (XML Id prefix)
isValidNfseKey('43149021100040364478829000000000105725120484407255'); // true (CPF issuer, RS)
isValidNfseKey('35503082212ABC34501DE35000000000001226091357924682'); // true (alphanumeric CNPJ issuer)
isValidNfseKey('35503082258716523000119000000000001226011357924684'); // false (check digit)
isValidNfseKey('3550308 2 2 58716523000119 0000000000012 2601 135792468 3'); // false (the key has no mask)
```

### parseNfseKey

Remove everything but the digits and the letters of an alphanumeric CNPJ from the access key of a national NFS-e, and cap the result to 50 characters.

- Letters are upper cased, as `parseCnpj` with `{ version: 2 }` does, and the letters in front of the first digit are dropped, the `NFS` prefix of the XML `Id` attribute included, since the key opens with digits. `isValidNfseKey` checks that the letters left stand in a CNPJ.

- That is the form the leiaute stores the key in and the one the DANFSe prints, a single block, which is why there is no `formatNfseKey`.

```javascript
import { parseNfseKey } from '@brazilian-utils/brazilian-utils';

parseNfseKey('NFS35503082258716523000119000000000001226011357924683');
// '35503082258716523000119000000000001226011357924683'

parseNfseKey('3550308 2 2 58716523000119 0000000000012 2601 135792468 3');
// '35503082258716523000119000000000001226011357924683'

parseNfseKey('nfs3550308 2 2 12.abc.345/01de-35 0000000000012 2609 135792468 2');
// '35503082212ABC34501DE35000000000001226091357924682'
```

### getNfseKeyInfo

Parse the access key of a national NFS-e into its fields, as an `NfseKeyInfo`. Accepts the same input forms as `isValidNfseKey`.

- Returns `municipalityCode`, `stateCode`, `generatorEnvironment`, `taxIdType`, `taxId`, `number`, `year`, `month`, `code` and `checkDigit`.
- `generatorEnvironment` is an `NfseKeyGeneratorEnvironment`: `1` the system of the municipality, `2` the Sistema Nacional NFS-e.
- `taxIdType` is an `NfseKeyTaxIdType`, `'cpf'` or `'cnpj'`, and `taxId` is the 11 digit CPF, without the `000` that pads it in the key, or the 14 character CNPJ, numeric or alphanumeric, in upper case.
- Returns `null` when the key is not valid.

```javascript
import { getNfseKeyInfo } from '@brazilian-utils/brazilian-utils';

getNfseKeyInfo('35503082258716523000119000000000001226011357924683');
// { municipalityCode: '3550308', stateCode: 'SP', generatorEnvironment: 2, taxIdType: 'cnpj',
// taxId: '58716523000119', number: 12, year: 2026, month: 1, code: '135792468', checkDigit: 3 }

getNfseKeyInfo('43149021100040364478829000000000105725120484407255');
// { municipalityCode: '4314902', stateCode: 'RS', generatorEnvironment: 1, taxIdType: 'cpf',
// taxId: '40364478829', number: 1057, year: 2025, month: 12, code: '048440725', checkDigit: 5 }

getNfseKeyInfo('35503082212ABC34501DE35000000000001226091357924682');
// { municipalityCode: '3550308', stateCode: 'SP', generatorEnvironment: 2, taxIdType: 'cnpj',
// taxId: '12ABC34501DE35', number: 12, year: 2026, month: 9, code: '135792468', checkDigit: 2 }

getNfseKeyInfo('invalid'); // null
```

Source: the [technical documentation of the Sistema Nacional NFS-e](https://www.gov.br/nfse/pt-br/biblioteca/documentacao-tecnica/documentacao-atual), whose schema types `TSIdNFSe` and `TSChaveNFSe` and ANEXO I field `NFSe/infNFSe/id` define the layout and the rules E1280 and E1284, the [manual da emissão por decisão administrativa ou judicial](https://www.gov.br/nfse/pt-br/biblioteca/documentacao-tecnica/documentacao-atual/manual-contribuintes-emissor-publico-api-emissao-decisao-administrativa-e-judicial.pdf), which names the modulus 11 check digit, [Nota Técnica SE/CGNFS-e 008](https://www.gov.br/nfse/pt-br/biblioteca/documentacao-tecnica/rtc/nt-008-se-cgnfse-danfse-20260714-v1-02.pdf) (item 2.1.1), which prints the key as a single block, the [schemas updated for the alphanumeric CNPJ](https://www.gov.br/nfse/pt-br/noticias/plataforma-nfs-e-disponibiliza-novas-evolucoes-em-producao-restrita-e-divulga-cronograma-de-implantacao) (bundle v1.01-20260727, in production since 2026-08-10) and [Nota Técnica Conjunta 2025.001](https://www.nfe.fazenda.gov.br/portal/exibirArquivo.aspx?conteudo=5ZkvIZt10mQ=), whose ASCII minus 48 rule for the NF-e key the check digit borrows.

## SUFRAMA

### isValidSuframa
Expand Down
3 changes: 3 additions & 0 deletions jsr.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"./get-municipality": "./src/get-municipality/get-municipality.ts",
"./get-municipality-by-code": "./src/get-municipality-by-code/get-municipality-by-code.ts",
"./get-nfe-key-info": "./src/get-nfe-key-info/get-nfe-key-info.ts",
"./get-nfse-key-info": "./src/get-nfse-key-info/get-nfse-key-info.ts",
"./get-pix-key-info": "./src/get-pix-key-info/get-pix-key-info.ts",
"./get-pix-payload-info": "./src/get-pix-payload-info/get-pix-payload-info.ts",
"./get-state-by-cep": "./src/get-state-by-cep/get-state-by-cep.ts",
Expand Down Expand Up @@ -112,6 +113,7 @@
"./is-valid-mobile-phone": "./src/is-valid-mobile-phone/is-valid-mobile-phone.ts",
"./is-valid-ncm": "./src/is-valid-ncm/is-valid-ncm.ts",
"./is-valid-nfe-key": "./src/is-valid-nfe-key/is-valid-nfe-key.ts",
"./is-valid-nfse-key": "./src/is-valid-nfse-key/is-valid-nfse-key.ts",
"./is-valid-passport": "./src/is-valid-passport/is-valid-passport.ts",
"./is-valid-phone": "./src/is-valid-phone/is-valid-phone.ts",
"./is-valid-pis": "./src/is-valid-pis/is-valid-pis.ts",
Expand Down Expand Up @@ -143,6 +145,7 @@
"./parse-license-plate": "./src/parse-license-plate/parse-license-plate.ts",
"./parse-ncm": "./src/parse-ncm/parse-ncm.ts",
"./parse-nfe-key": "./src/parse-nfe-key/parse-nfe-key.ts",
"./parse-nfse-key": "./src/parse-nfse-key/parse-nfse-key.ts",
"./parse-passport": "./src/parse-passport/parse-passport.ts",
"./parse-phone": "./src/parse-phone/parse-phone.ts",
"./parse-pis": "./src/parse-pis/parse-pis.ts",
Expand Down
29 changes: 29 additions & 0 deletions reports/api/brazilian-utils.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,9 @@ export type GetMunicipalityParams = GetMunicipalityByCodeParams | GetMunicipalit
// @public
export const getNfeKeyInfo: (value: string) => NfeKeyInfo | null;

// @public
export const getNfseKeyInfo: (value: string) => NfseKeyInfo | null;

// @public
export const getPixKeyInfo: (value: string) => PixKeyInfo | null;

Expand Down Expand Up @@ -831,6 +834,9 @@ export const isValidNcm: (value: string | number) => boolean;
// @public
export const isValidNfeKey: (value: string) => boolean;

// @public
export const isValidNfseKey: (value: string) => boolean;

// @public
export const isValidPassport: (passport: string | number) => boolean;

Expand Down Expand Up @@ -934,6 +940,26 @@ export type NfeKeyInfo = {
// @public
export type NfeKeyModel = "55" | "57" | "58" | "62" | "63" | "64" | "65" | "66" | "67";

// @public
export type NfseKeyGeneratorEnvironment = 1 | 2;

// @public
export type NfseKeyInfo = {
municipalityCode: string;
stateCode: StateCode;
generatorEnvironment: NfseKeyGeneratorEnvironment;
taxIdType: NfseKeyTaxIdType;
taxId: string;
number: number;
year: number;
month: number;
code: string;
checkDigit: number;
};

// @public
export type NfseKeyTaxIdType = "cpf" | "cnpj";

// @public
export type NumberToWordsGender = "masculine" | "feminine";

Expand Down Expand Up @@ -1002,6 +1028,9 @@ export const parseNcm: (value: string | number) => string;
// @public
export const parseNfeKey: (value: string | number) => string;

// @public
export const parseNfseKey: (value: string | number) => string;

// @public
export const parsePassport: (passport: string) => string;

Expand Down
64 changes: 64 additions & 0 deletions src/_internals/constants/nfse-key.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* Shape of the key: 50 characters, all digits but the 14 of the "Inscrição Federal", which may
* also be the upper case letters of an alphanumeric CNPJ, optionally behind the `NFS` literal the
* `Id` attribute of `infNFSe` puts in front of it. It follows `TSIdNFSe` of
* `tiposSimples_v1.01.xsd` (bundle 20260727), `NFS[0-9]{9}[0-9A-Z]{14}[0-9]{27}`, whose letter
* window is the registration of the key structure. The key has no printed mask (the DANFSe prints it "em
* único bloco", Nota Técnica SE/CGNFS-e 008, item 2.1.1), so there is no separator to accept.
* Case-insensitive, as `isValidCnpj` with version 2 is: the callers upper case the key, which is
* the first capture group. Whether a letter may stand in the registration at all is left to the
* registration type, since only a CNPJ (type 2) can carry one.
*/
export const FORMAT_REGEX = /^(?:nfs)?(\d{9}[\dA-Z]{14}\d{27})$/i;

/**
* Characters of the key, type `TSChaveNFSe` of the leiaute. Once `isValidNfseKey` accepts a
* value, they are the last 50 characters of the trimmed value, after the optional `NFS` prefix.
*/
export const NFSE_KEY_LENGTH = 50;

/**
* The `ambGer` (ambiente gerador) codes of `TSAmbGeradorNFSe`: 1 for the system of the
* municipality (Prefeitura), 2 for the Sistema Nacional NFS-e (Sefin Nacional). In code order,
* so code `n` sits at index `n - 1`.
*/
export const GENERATOR_ENVIRONMENTS = [1, 2] as const;

/**
* The "Tipo de Inscrição Federal" codes of the key, as rule E1263 of the ANEXO I states them:
* 1 for a CPF and 2 for a CNPJ.
*/
export const TAX_ID_TYPES: Readonly<Record<string, "cpf" | "cnpj">> = { "1": "cpf", "2": "cnpj" };

/** The zeros that pad an 11 digit CPF to the 14 positions of the "Inscrição Federal" field. */
export const CPF_PADDING = "000";

/**
* A number of all zeros is not a valid `nNFSe`: the leiaute types it `TSNNFSe`, whose pattern is
* `[1-9]{1}[0-9]{0,12}`.
*/
export const ABSENT_NUMBER = "0000000000000";

/** Position of `ambGer` inside the 50 character key. */
export const GENERATOR_ENVIRONMENT_INDEX = 7;

/** Position of the "Tipo de Inscrição Federal" inside the 50 character key. */
export const TAX_ID_TYPE_INDEX = 8;

/** Start of the "Inscrição Federal" inside the 50 character key. */
export const TAX_ID_START = 9;

/** Start of the NFS-e number (`nNFSe`), which is also the end of the "Inscrição Federal". */
export const NUMBER_START = 23;

/** Start of the issue year and month (AAMM), which is also the end of the NFS-e number. */
export const YEAR_START = 36;

/** Start of the issue month, which is also the end of the issue year. */
export const MONTH_START = 38;

/** Start of the numeric code (Cód.Num.), which is also the end of the issue month. */
export const CODE_START = 40;

/** Position of the check digit (DV), which is also the end of the numeric code. */
export const CHECK_DIGIT_INDEX = 49;
Loading
Loading