Skip to content
Open
6 changes: 3 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ but the few rules below hold everywhere:
they call.
- `src/_internals/` holds the helpers shared between utilities (check-digit arithmetic, `mod10`/
`mod11`, TLV parsing for Pix, sanitizers). `src/_internals/constants/` holds the datasets
(banks, municipalities, CNAE, CBO, CFOP, NCM, legal natures), generated by the `scripts/`
from their official sources and refreshed by the `Update datasets` workflow through
a pull request, never edited by hand.
(banks, municipalities, CNAE, CBO, CFOP, NCM, NBS, the LC 116/2003 service list, legal natures),
generated by the `scripts/` from their official sources and refreshed by the `Update datasets`
workflow through a pull request, never edited by hand.
- Utilities are synchronous, stateless and side-effect free: input in, value out, no globals, no
environment access, no dynamic code. The two exceptions are `getAddressInfoByCep` and
`getCepInfoByAddress`, the only utilities that do I/O: they query public CEP APIs (BrasilAPI,
Expand Down
2 changes: 1 addition & 1 deletion context7.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"rules": [
"The package has zero runtime dependencies and ships as ESM plus a UMD build; nothing else needs to be installed to use it.",
"Import from the root: import { isValidCpf } from '@brazilian-utils/brazilian-utils'. Every util is also a kebab-case subpath, e.g. '@brazilian-utils/brazilian-utils/is-valid-cpf'.",
"Use the subpaths to lazy-load the dataset-backed utils (getMunicipalities, getMunicipalityByCode, getCnae, getCbo, getCfop, isValidNcm, getBanks): each one embeds a large official table.",
"Use the subpaths to lazy-load the dataset-backed utils (getMunicipalities, getMunicipalityByCode, getCnae, getCbo, getCfop, isValidNcm, getBanks, getNbs, isValidNbs, getServiceItem, isValidServiceItem): each one embeds a large official table.",
"Never import the same util from both the root and its subpath in one app: a bundler treats them as two unrelated modules and bundles the dataset twice.",
"Public functions never throw on bad input (null, undefined, wrong type): isValid* return false, format* and parse* return '', single-item getters return null, list getters return [].",
"The only utils that reject are the async getAddressInfoByCep (GetAddressInfoByCepError: NotFound, Validation, Service) and getCepInfoByAddress (GetCepInfoByAddressError: NotFound, Validation).",
Expand Down
2 changes: 2 additions & 0 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,10 @@ A few utils embed an official dataset and weigh far more than everything else co
| `isValidNcm` | NCM (Nomenclatura Comum do Mercosul) codes | 114.2 KB | 24.6 KB |
| `isValidCbo` · `getCbo` | CBO 2002 occupation titles | 119.1 KB | 30.6 KB |
| `isValidCnae` · `getCnae` | CNAE-Subclasses 2.3 | 93.9 KB | 21.2 KB |
| `isValidNbs` · `getNbs` | NBS 2.0 (Nomenclatura Brasileira de Serviços) descriptions | 81.8 KB | 13.8 KB |
| `isValidCfop` · `getCfop` | CFOP operation descriptions | 68.9 KB | 6.9 KB |
| `getBanks` · `getBankByCode` · `getBankByIspb` | Banco Central STR participants (COMPE + ISPB) | 38.3 - 38.6 KB | 9.5 - 9.7 KB |
| `isValidServiceItem` · `getServiceItem` | Service list of the Lei Complementar 116/2003 | 27.2 KB | 8.9 KB |

The root of the package is a single ESM module, so a bundler cannot split one of these datasets out of it: importing a heavy util from the root puts its whole dataset in your main bundle, and a dynamic `import()` of the root does not help. To lazy-load one, import it from its own subpath:

Expand Down
2 changes: 2 additions & 0 deletions docs/pt-br/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,10 @@ Alguns utilitários embutem uma base de dados oficial e pesam muito mais que tod
| `isValidNcm` | códigos NCM (Nomenclatura Comum do Mercosul) | 114,2 KB | 24,6 KB |
| `isValidCbo` · `getCbo` | títulos das ocupações da CBO 2002 | 119,1 KB | 30,6 KB |
| `isValidCnae` · `getCnae` | CNAE-Subclasses 2.3 | 93,9 KB | 21,2 KB |
| `isValidNbs` · `getNbs` | descrições da NBS 2.0 (Nomenclatura Brasileira de Serviços) | 81,8 KB | 13,8 KB |
| `isValidCfop` · `getCfop` | descrições das operações do CFOP | 68,9 KB | 6,9 KB |
| `getBanks` · `getBankByCode` · `getBankByIspb` | participantes do STR do Banco Central (COMPE + ISPB) | 38,3 - 38,6 KB | 9,5 - 9,7 KB |
| `isValidServiceItem` · `getServiceItem` | lista de serviços da Lei Complementar 116/2003 | 27,2 KB | 8,9 KB |

A raiz do pacote é um único módulo ESM, então o bundler não consegue separar uma dessas bases de dados dele: importar um utilitário pesado da raiz coloca a base inteira no seu bundle principal, e um `import()` dinâmico da raiz não ajuda. Para carregar sob demanda, importe do subpath próprio:

Expand Down
94 changes: 94 additions & 0 deletions docs/pt-br/utilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -2855,6 +2855,100 @@ parseNcm('8471.30.12'); // '84713012'
parseNcm('8471'); // '8471' (um código parcial é mantido como está)
```

### isValidNbs

Valida um código NBS (Nomenclatura Brasileira de Serviços, Intangíveis e Outras Operações que Produzam Variações no Patrimônio) contra a tabela oficial da NBS 2.0, o código que a NFS-e nacional leva em `cNBS`.

- O código tem 9 dígitos, impressos como `N.NNNN.NN.NN`: o algarismo 1, o capítulo, a posição, os dois níveis de subposição, o item e o subitem.
- Aceita uma string com os 9 dígitos ou com a máscara, com um único separador entre os grupos e espaços opcionais nas extremidades, ou um inteiro seguro não negativo. Qualquer outra string é rejeitada em vez de ter os dígitos pinçados.
- Só códigos completos são válidos: os títulos de capítulo (`1.01`), posição (`1.0101`) e subposição (`1.0101.1`) não classificam nada por si sós.
- O ANEXO B do Sistema Nacional NFS-e lista os mesmos 920 códigos menos três (`1.0402.29.00`, `1.0403.29.00` e `1.0904.40.00`), então um código válido aqui ainda pode ser recusado pela NFS-e.

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

isValidNbs('1.0101.11.00'); // true
isValidNbs('101011100'); // true
isValidNbs(101011100); // true
isValidNbs('1.0101'); // false (título de posição, não um código completo)
isValidNbs('1.9999.99.99'); // false
isValidNbs('1.0101abc11.00'); // false (não é uma forma documentada)
```

### formatNbs

Formata um código NBS (Nomenclatura Brasileira de Serviços) na máscara `N.NNNN.NN.NN` em que a nomenclatura o imprime. Só a estrutura muda; use `isValidNbs` para conferir um código com a tabela.

- Todo código NBS começa com 1, então, diferente do `formatNcm`, não há opção `pad`.
- No resto, mesmas regras de `formatCnae`: a máscara é aplicada até onde o valor vai, os caracteres fora dela são descartados e um número é lido como a string dos seus dígitos.

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

formatNbs('101011100'); // 1.0101.11.00
formatNbs(101011100); // 1.0101.11.00
formatNbs('10101'); // 1.0101 (mascarado até onde vai)
formatNbs('abc101011100'); // 1.0101.11.00 (só os dígitos são lidos)
```

### getNbs

Consulta um código NBS (Nomenclatura Brasileira de Serviços) e retorna a sua descrição oficial. O resultado é um registro `Nbs`: `{ code, description }`.

- Mesmas regras de `isValidNbs`. `code` são os 9 dígitos, sem a máscara. Retorna `null` quando o código é desconhecido ou o valor não está em uma forma documentada.

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

getNbs('1.0101.11.00');
// { code: '101011100', description: 'Serviços de construção de edificações residenciais de um e dois pavimentos' }

getNbs(126050000); // { code: '126050000', description: 'Serviços domésticos' }
getNbs('1.0101'); // null (título de posição, não um código completo)
getNbs('1.9999.99.99'); // null
```

Fonte: [tabela da NBS 2.0 publicada pelo MDIC](https://www.gov.br/mdic/pt-br/assuntos/sdic/comercio-e-servicos/nbs-nomenclatura-brasileira-de-servicos), aprovada pela Portaria Conjunta RFB/SCS 1.429/2018 e alterada pela Portaria Conjunta RFB/SCS 2.000/2018.

### isValidServiceItem

Verifica se um valor é um subitem em vigor da lista de serviços anexa à Lei Complementar 116/2003, a lista dos serviços sobre os quais incide o ISS.

- A lei numera o subitem como o item, um ponto e dois dígitos, de `1.01` a `40.01`.
- Aceita essa forma, o item preenchido com zero (`'01.01'`) ou os dígitos puros (`'0101'`, `'101'` ou o inteiro `101`), que são os quatro primeiros dígitos do código `cTribNac` da NFS-e nacional, com espaços opcionais nas extremidades.
- O ponto é o único separador que a lei imprime entre o item e o subitem, então, ao contrário dos códigos com máscara de agrupamento impressa (`isValidCfop`, `isValidNbs`), nada mais é aceito no lugar dele e `'1-01'` é rejeitado.
- Um número só é lido quando é um inteiro seguro não negativo, então o decimal `1.01` é rejeitado: escreva a forma com ponto como string.
- Os subitens vetados (`3.01`, `7.14`, `7.15`, `13.01` e `17.07`), os títulos de item, os códigos nacionais de 6 dígitos em que um subitem se desdobra e o item 99 da lista nacional, que não faz parte da lei, não são válidos. Códigos municipais de serviço estão fora do escopo.

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

isValidServiceItem('1.01'); // true
isValidServiceItem('01.01'); // true
isValidServiceItem('0101'); // true
isValidServiceItem(101); // true
isValidServiceItem('3.01'); // false (vetado)
isValidServiceItem('99.01'); // false (só da lista nacional, não da lei)
isValidServiceItem(1.01); // false (não é um inteiro seguro não negativo)
```

### getServiceItem

Consulta um subitem da lista de serviços anexa à Lei Complementar 116/2003 e retorna a sua descrição oficial. O resultado é um registro `ServiceItem`: `{ code, description }`.

- Mesmas regras de `isValidServiceItem`. `code` é a forma em que a lei o imprime (`'1.01'`). Retorna `null` quando o subitem é desconhecido ou o valor não está em uma forma documentada.

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

getServiceItem('1.01'); // { code: '1.01', description: 'Análise e desenvolvimento de sistemas.' }
getServiceItem('0101'); // { code: '1.01', description: 'Análise e desenvolvimento de sistemas.' }
getServiceItem('40.01'); // { code: '40.01', description: 'Obras de arte sob encomenda.' }
getServiceItem('3.01'); // null (vetado)
```

Fonte: [Lei Complementar 116/2003](https://www.planalto.gov.br/ccivil_03/leis/lcp/lcp116.htm) e a planilha `LISTA.SERV.NAC.` do [ANEXO B do Sistema Nacional NFS-e](https://www.gov.br/nfse/pt-br/biblioteca/documentacao-tecnica/documentacao-atual), a lista em vigor em formato legível por máquina.

### isValidCfop

Valida um código CFOP (Código Fiscal de Operações e Prestações) contra a tabela oficial, o Anexo II consolidado do Convênio SINIEF s/nº 1970 em vigor.
Expand Down
94 changes: 94 additions & 0 deletions docs/utilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -2855,6 +2855,100 @@ parseNcm('8471.30.12'); // '84713012'
parseNcm('8471'); // '8471' (a partial code is kept as written)
```

### isValidNbs

Check if an NBS (Nomenclatura Brasileira de Serviços, Intangíveis e Outras Operações que Produzam Variações no Patrimônio) code exists in the official NBS 2.0 table, the code the national NFS-e carries in `cNBS`.

- A code has 9 digits, printed as `N.NNNN.NN.NN`: the digit 1, the chapter, the position, the two subposition levels, the item and the subitem.
- Accepts a string with the 9 digits or with the mask, with a single separator between the groups and optional surrounding whitespace, or a non-negative safe integer. Any other string is rejected instead of having its digits picked out.
- Only complete codes are valid: the chapter (`1.01`), position (`1.0101`) and subposition (`1.0101.1`) headings classify nothing by themselves.
- The ANEXO B of the Sistema Nacional NFS-e lists the same 920 codes except three (`1.0402.29.00`, `1.0403.29.00` and `1.0904.40.00`), so a code valid here can still be refused by the NFS-e.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

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

isValidNbs('1.0101.11.00'); // true
isValidNbs('101011100'); // true
isValidNbs(101011100); // true
isValidNbs('1.0101'); // false (a position heading, not a complete code)
isValidNbs('1.9999.99.99'); // false
isValidNbs('1.0101abc11.00'); // false (not a documented form)
```

### formatNbs

Format an NBS (Nomenclatura Brasileira de Serviços) code into the `N.NNNN.NN.NN` mask the nomenclature prints. Only the structure changes; use `isValidNbs` to check a code against the table.

- Every NBS code starts with 1, so, unlike `formatNcm`, there is no `pad` option.
- Same rules as `formatCnae` otherwise: the mask is applied as far as the value goes, characters outside it are dropped, and a number is read as the string of its digits.

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

formatNbs('101011100'); // 1.0101.11.00
formatNbs(101011100); // 1.0101.11.00
formatNbs('10101'); // 1.0101 (masked as far as it goes)
formatNbs('abc101011100'); // 1.0101.11.00 (only the digits are read)
```

### getNbs

Look an NBS (Nomenclatura Brasileira de Serviços) code up and get its official description. The result is an `Nbs` record: `{ code, description }`.

- Same rules as `isValidNbs`. `code` is the 9 digits, without the mask. Returns `null` when the code is unknown or the value is not in a documented form.

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

getNbs('1.0101.11.00');
// { code: '101011100', description: 'Serviços de construção de edificações residenciais de um e dois pavimentos' }

getNbs(126050000); // { code: '126050000', description: 'Serviços domésticos' }
getNbs('1.0101'); // null (a position heading, not a complete code)
getNbs('1.9999.99.99'); // null
```

Source: [NBS 2.0 table published by the MDIC](https://www.gov.br/mdic/pt-br/assuntos/sdic/comercio-e-servicos/nbs-nomenclatura-brasileira-de-servicos), approved by the Portaria Conjunta RFB/SCS 1.429/2018 and amended by the Portaria Conjunta RFB/SCS 2.000/2018.

### isValidServiceItem

Check if a value is a subitem in force of the service list annexed to the Lei Complementar 116/2003, the list of the services the ISS is levied on.

- The law numbers a subitem as the item, a dot and two digits, `1.01` to `40.01`.
- Accepts that form, a zero padded item (`'01.01'`) or the bare digits (`'0101'`, `'101'` or the integer `101`), which are the first four digits of the `cTribNac` code of the national NFS-e, with optional surrounding whitespace.
- The dot is the only separator the law ever prints between the item and the subitem, so, unlike the codes with a printed grouping mask (`isValidCfop`, `isValidNbs`), nothing else is accepted in its place and `'1-01'` is rejected.
- A number is only read when it is a non-negative safe integer, so the float `1.01` is rejected: write the dotted form as a string.
- The vetoed subitems (`3.01`, `7.14`, `7.15`, `13.01` and `17.07`), the item headings, the 6 digit national codes a subitem is split into and item 99 of the national list, which is not part of the law, are not valid. Municipal service codes are out of scope.

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

isValidServiceItem('1.01'); // true
isValidServiceItem('01.01'); // true
isValidServiceItem('0101'); // true
isValidServiceItem(101); // true
isValidServiceItem('3.01'); // false (vetoed)
isValidServiceItem('99.01'); // false (national list only, not the law)
isValidServiceItem(1.01); // false (not a non-negative safe integer)
```

### getServiceItem

Look a subitem of the service list annexed to the Lei Complementar 116/2003 up and get its official description. The result is a `ServiceItem` record: `{ code, description }`.

- Same rules as `isValidServiceItem`. `code` is the form the law prints (`'1.01'`). Returns `null` when the subitem is unknown or the value is not in a documented form.

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

getServiceItem('1.01'); // { code: '1.01', description: 'Análise e desenvolvimento de sistemas.' }
getServiceItem('0101'); // { code: '1.01', description: 'Análise e desenvolvimento de sistemas.' }
getServiceItem('40.01'); // { code: '40.01', description: 'Obras de arte sob encomenda.' }
getServiceItem('3.01'); // null (vetoed)
```

Source: [Lei Complementar 116/2003](https://www.planalto.gov.br/ccivil_03/leis/lcp/lcp116.htm) and the sheet `LISTA.SERV.NAC.` of the [ANEXO B of the Sistema Nacional NFS-e](https://www.gov.br/nfse/pt-br/biblioteca/documentacao-tecnica/documentacao-atual), the list in force in machine readable form.

### isValidCfop

Check if a CFOP (Código Fiscal de Operações e Prestações) code exists in the official table, the consolidated Anexo II of Convênio SINIEF s/nº 1970 in force.
Expand Down
5 changes: 5 additions & 0 deletions jsr.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"./format-iban": "./src/format-iban/format-iban.ts",
"./format-legal-nature": "./src/format-legal-nature/format-legal-nature.ts",
"./format-license-plate": "./src/format-license-plate/format-license-plate.ts",
"./format-nbs": "./src/format-nbs/format-nbs.ts",
"./format-ncm": "./src/format-ncm/format-ncm.ts",
"./format-nfe-key": "./src/format-nfe-key/format-nfe-key.ts",
"./format-passport": "./src/format-passport/format-passport.ts",
Expand Down Expand Up @@ -75,12 +76,14 @@
"./get-municipalities": "./src/get-municipalities/get-municipalities.ts",
"./get-municipality": "./src/get-municipality/get-municipality.ts",
"./get-municipality-by-code": "./src/get-municipality-by-code/get-municipality-by-code.ts",
"./get-nbs": "./src/get-nbs/get-nbs.ts",
"./get-next-business-day": "./src/get-next-business-day/get-next-business-day.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-nth-business-day": "./src/get-nth-business-day/get-nth-business-day.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-service-item": "./src/get-service-item/get-service-item.ts",
"./get-state-by-cep": "./src/get-state-by-cep/get-state-by-cep.ts",
"./get-state-by-ibge-code": "./src/get-state-by-ibge-code/get-state-by-ibge-code.ts",
"./get-state-code-by-name": "./src/get-state-code-by-name/get-state-code-by-name.ts",
Expand Down Expand Up @@ -114,6 +117,7 @@
"./is-valid-legal-nature": "./src/is-valid-legal-nature/is-valid-legal-nature.ts",
"./is-valid-license-plate": "./src/is-valid-license-plate/is-valid-license-plate.ts",
"./is-valid-mobile-phone": "./src/is-valid-mobile-phone/is-valid-mobile-phone.ts",
"./is-valid-nbs": "./src/is-valid-nbs/is-valid-nbs.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",
Expand All @@ -125,6 +129,7 @@
"./is-valid-processo-juridico": "./src/is-valid-processo-juridico/is-valid-processo-juridico.ts",
"./is-valid-registro-profissional": "./src/is-valid-registro-profissional/is-valid-registro-profissional.ts",
"./is-valid-renavam": "./src/is-valid-renavam/is-valid-renavam.ts",
"./is-valid-service-item": "./src/is-valid-service-item/is-valid-service-item.ts",
"./is-valid-service-phone": "./src/is-valid-service-phone/is-valid-service-phone.ts",
"./is-valid-suframa": "./src/is-valid-suframa/is-valid-suframa.ts",
"./is-valid-vin": "./src/is-valid-vin/is-valid-vin.ts",
Expand Down
Loading
Loading