From a0fe042f7373b28d2573a91ec02d23c71a7ed903 Mon Sep 17 00:00:00 2001 From: Hyan Mandian <5044101+hyanmandian@users.noreply.github.com> Date: Sat, 12 Sep 2026 21:43:23 -0300 Subject: [PATCH 01/15] ci(links): skip the slow sintegra, sirc and alepe hosts in the link check --- .lycheeignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.lycheeignore b/.lycheeignore index cf6dea77..61344308 100644 --- a/.lycheeignore +++ b/.lycheeignore @@ -8,3 +8,7 @@ planalto\.gov\.br confaz\.fazenda\.gov\.br bcb\.gov\.br alerj\.rj\.gov\.br +sintegra\.gov\.br +sirc\.gov\.br +legis\.alepe\.pe\.gov\.br +sped\.rfb\.gov\.br From 273a1432e913939ce0e5f4c2c3f1e7e2b3c54f6c Mon Sep 17 00:00:00 2001 From: Hyan Mandian <5044101+hyanmandian@users.noreply.github.com> Date: Sat, 12 Sep 2026 20:54:36 -0300 Subject: [PATCH 02/15] =?UTF-8?q?fix(cpf):=20use=20the=20Receita=20Federal?= =?UTF-8?q?=20regi=C3=A3o=20fiscal=20digit=20for=20MS=20and=20MT?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/generate-cpf/constants.ts | 4 ++-- src/generate-cpf/generate-cpf.test.ts | 7 +++++++ src/generate-cpf/generate-cpf.ts | 1 + 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/generate-cpf/constants.ts b/src/generate-cpf/constants.ts index 515f044e..758b1f5f 100644 --- a/src/generate-cpf/constants.ts +++ b/src/generate-cpf/constants.ts @@ -13,8 +13,8 @@ export const STATE_CODES: Record = { ES: "7", GO: "1", MA: "3", - MT: "5", - MS: "5", + MT: "1", + MS: "1", MG: "6", PR: "9", PB: "4", diff --git a/src/generate-cpf/generate-cpf.test.ts b/src/generate-cpf/generate-cpf.test.ts index 6bb2dfdf..8b92d810 100644 --- a/src/generate-cpf/generate-cpf.test.ts +++ b/src/generate-cpf/generate-cpf.test.ts @@ -55,6 +55,13 @@ describe("generateCpf", () => { } }); + test("should embed the 1st região fiscal digit for the states the Receita Federal groups there", () => { + expect(STATE_CODES.MS).toBe("1"); + expect(STATE_CODES.MT).toBe("1"); + expect(generateCpf("MS")[8]).toBe("1"); + expect(generateCpf("MT")[8]).toBe("1"); + }); + test("should fall back to a random digit instead of looking up an unknown state code", () => { // @ts-expect-error: intentionally invalid input const cpf = generateCpf("XX"); diff --git a/src/generate-cpf/generate-cpf.ts b/src/generate-cpf/generate-cpf.ts index 8746cd2c..115a1e10 100644 --- a/src/generate-cpf/generate-cpf.ts +++ b/src/generate-cpf/generate-cpf.ts @@ -29,6 +29,7 @@ const calculateCheckDigit = (base: string, weight: number): string => { * ``` * * @see Official: https://www.gov.br/receitafederal/pt-br/assuntos/meu-cpf + * @see Official: https://www.gov.br/receitafederal/pt-br/assuntos/educacao-fiscal/educacao_fiscal/folhetos-orientativos/cadastros-dig.pdf * @see Based on: https://github.com/brazilian-utils/brutils-python/blob/main/brutils/cpf.py */ export const generateCpf = (state?: StateCode): string => { From f6b1df25519eef9244756eb1277994ff13152bc7 Mon Sep 17 00:00:00 2001 From: Hyan Mandian <5044101+hyanmandian@users.noreply.github.com> Date: Sat, 12 Sep 2026 20:54:36 -0300 Subject: [PATCH 03/15] fix(passport): never throw on hostile objects and delegate formatPassport to parsePassport --- src/format-passport/format-passport.test.ts | 3 ++- src/format-passport/format-passport.ts | 11 ++++------- src/is-valid-passport/is-valid-passport.test.ts | 4 ++++ src/is-valid-passport/is-valid-passport.ts | 5 +++-- src/parse-passport/parse-passport.test.ts | 7 +++++-- src/parse-passport/parse-passport.ts | 9 ++++----- 6 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/format-passport/format-passport.test.ts b/src/format-passport/format-passport.test.ts index 0bde5c79..b7b347e4 100644 --- a/src/format-passport/format-passport.test.ts +++ b/src/format-passport/format-passport.test.ts @@ -50,7 +50,8 @@ describe("formatPassport", () => { // @ts-expect-error: intentionally invalid input expect(formatPassport()).toBe(""); // @ts-expect-error: intentionally invalid input - expect(formatPassport(123)).toBe(""); + expect(formatPassport(123_456)).toBe(""); + expect(formatPassport(Object.create(null))).toBe(""); }); }); diff --git a/src/format-passport/format-passport.ts b/src/format-passport/format-passport.ts index add73258..5fd38f11 100644 --- a/src/format-passport/format-passport.ts +++ b/src/format-passport/format-passport.ts @@ -1,12 +1,12 @@ -import { PASSPORT_LENGTH } from "../_internals/constants/passport"; -import { sanitizeToAlphanumeric } from "../_internals/sanitize-to-alphanumeric/sanitize-to-alphanumeric"; +import { parsePassport } from "../parse-passport/parse-passport"; /** * Formats a Brazilian passport number for display. * Converts to uppercase and removes all non-alphanumeric characters. * * @param {string} passport - A Brazilian passport number (any case, possibly with symbols). - * @returns {string} The formatted passport number (uppercase, no symbols), or an empty string if invalid. + * @returns {string} The uppercased, symbol-free value capped to 8 characters, or an empty + * string for a non-string input (a number is never a passport number: the series is two letters). * * @example * formatPassport("ab123456") // "AB123456" @@ -15,7 +15,4 @@ import { sanitizeToAlphanumeric } from "../_internals/sanitize-to-alphanumeric/s * * @see Official: https://www.gov.br/pf/pt-br/assuntos/passaporte */ -export const formatPassport = (passport: string): string => { - if (typeof passport !== "string") return ""; - return sanitizeToAlphanumeric(passport).slice(0, PASSPORT_LENGTH); -}; +export const formatPassport = (passport: string): string => parsePassport(passport); diff --git a/src/is-valid-passport/is-valid-passport.test.ts b/src/is-valid-passport/is-valid-passport.test.ts index fdfd3909..7b8fb3a3 100644 --- a/src/is-valid-passport/is-valid-passport.test.ts +++ b/src/is-valid-passport/is-valid-passport.test.ts @@ -30,6 +30,10 @@ describe("isValidPassport", () => { expect(isValidPassport({})).toBe(false); }); + test("when passport is an object without a prototype", () => { + expect(isValidPassport(Object.create(null))).toBe(false); + }); + test("when passport length is different from 8", () => { expect(isValidPassport("1")).toBe(false); }); diff --git a/src/is-valid-passport/is-valid-passport.ts b/src/is-valid-passport/is-valid-passport.ts index 5458659e..188c9119 100644 --- a/src/is-valid-passport/is-valid-passport.ts +++ b/src/is-valid-passport/is-valid-passport.ts @@ -1,4 +1,3 @@ -import { isNullish } from "../_internals/is-nullish/is-nullish"; import { sanitizeToAlphanumeric } from "../_internals/sanitize-to-alphanumeric/sanitize-to-alphanumeric"; import { PASSPORT_REGEX } from "./constants"; @@ -10,6 +9,8 @@ import { PASSPORT_REGEX } from "./constants"; * sanitization performed by `formatPassport`/`parsePassport`. * This function does not verify if the input is a real passport number, * as there are no checksums for the Brazilian passport. + * A number is accepted for symmetry with `formatPassport`/`parsePassport` but is never valid: + * the decimal form of a number never starts with the two letters a passport number needs. * * @param {string|number} passport - The string containing the passport number to be checked. * @returns {boolean} True if the passport number is valid (2 letters followed by 6 digits). @@ -24,7 +25,7 @@ import { PASSPORT_REGEX } from "./constants"; * @see Official: https://www.gov.br/pf/pt-br/assuntos/passaporte */ export const isValidPassport = (passport: string | number): boolean => { - if (isNullish(passport)) return false; + if (typeof passport !== "string") return false; return PASSPORT_REGEX.test(sanitizeToAlphanumeric(passport)); }; diff --git a/src/parse-passport/parse-passport.test.ts b/src/parse-passport/parse-passport.test.ts index 4e44a03f..2a287e82 100644 --- a/src/parse-passport/parse-passport.test.ts +++ b/src/parse-passport/parse-passport.test.ts @@ -34,9 +34,12 @@ describe("parsePassport", () => { expect(parsePassport("AB123456789")).toBe("AB123456"); }); - test("when it is a non-string value", () => { - // @ts-expect-error not a string + test("when it is not a string", () => { + // @ts-expect-error: intentionally invalid input expect(parsePassport(null)).toBe(""); + // @ts-expect-error: intentionally invalid input + expect(parsePassport(123_456)).toBe(""); + expect(parsePassport(Object.create(null))).toBe(""); }); }); diff --git a/src/parse-passport/parse-passport.ts b/src/parse-passport/parse-passport.ts index 1eb9affc..88971fb4 100644 --- a/src/parse-passport/parse-passport.ts +++ b/src/parse-passport/parse-passport.ts @@ -5,7 +5,8 @@ import { sanitizeToAlphanumeric } from "../_internals/sanitize-to-alphanumeric/s * Removes non-alphanumeric characters from a passport number, uppercases it, and caps it to 8 characters. * * @param {string} passport - The string containing a passport number. - * @returns {string} The normalized passport number. + * @returns {string} The normalized passport number, or an empty string when the value is not + * a string (a number is never a passport number: the series is two letters). * * @example * parsePassport("Ab123456") // "AB123456" @@ -14,7 +15,5 @@ import { sanitizeToAlphanumeric } from "../_internals/sanitize-to-alphanumeric/s * * @see Official: https://www.gov.br/pf/pt-br/assuntos/passaporte */ -export const parsePassport = (passport: string): string => { - if (typeof passport !== "string") return ""; - return sanitizeToAlphanumeric(passport).slice(0, PASSPORT_LENGTH); -}; +export const parsePassport = (passport: string): string => + typeof passport === "string" ? sanitizeToAlphanumeric(passport).slice(0, PASSPORT_LENGTH) : ""; From 284bc5e0d9616fd87a2311d5ec83daf2e657c7c1 Mon Sep 17 00:00:00 2001 From: Hyan Mandian <5044101+hyanmandian@users.noreply.github.com> Date: Sat, 12 Sep 2026 20:54:36 -0300 Subject: [PATCH 04/15] refactor(format): keep the 2.3.0 rule that any truthy pad option pads --- src/_internals/format/format.test.ts | 7 +++++++ src/_internals/format/format.ts | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/_internals/format/format.test.ts b/src/_internals/format/format.test.ts index f8a573a0..303e8c7f 100644 --- a/src/_internals/format/format.test.ts +++ b/src/_internals/format/format.test.ts @@ -56,6 +56,13 @@ describe("format", () => { expect(format({ value: "", pattern: "***.000.000-**" })).toBe(""); }); + it("should pad for any truthy pad value and skip padding for any falsy one", () => { + // @ts-expect-error: intentionally invalid input + expect(format({ value: "12", pattern: "00-00-00", pad: 1 })).toBe("00-00-12"); + // @ts-expect-error: intentionally invalid input + expect(format({ value: "12", pattern: "00-00-00", pad: 0 })).toBe("12"); + }); + it("should count * as a slot when padding", () => { expect(format({ value: "123", pattern: "***.000.000-**", pad: true })).toBe("***.000.001-**"); }); diff --git a/src/_internals/format/format.ts b/src/_internals/format/format.ts index c720a849..f1b91542 100644 --- a/src/_internals/format/format.ts +++ b/src/_internals/format/format.ts @@ -36,7 +36,7 @@ export const format = ({ pad, value, pattern }: FormatParams): string => { let valueIndex = 0; let paddedValue = value; - if (pad === true) { + if (pad ?? false) { const separatorsLength = pattern.replaceAll(/[0*]/g, "").length; paddedValue = value.padStart(pattern.length - separatorsLength, "0"); } From 8b659237a9adea54be3316f1924d5bc8bbdce15c Mon Sep 17 00:00:00 2001 From: Hyan Mandian <5044101+hyanmandian@users.noreply.github.com> Date: Sat, 12 Sep 2026 20:54:36 -0300 Subject: [PATCH 05/15] fix(iban): accept every account type letter of the SFN dictionary --- src/_internals/constants/iban.ts | 12 ++++++++---- src/format-iban/format-iban.ts | 3 ++- src/is-valid-iban/is-valid-iban.test.ts | 13 +++++++++++-- src/is-valid-iban/is-valid-iban.ts | 6 ++++-- src/parse-iban/parse-iban.test.ts | 23 ++++++++++++++++++++--- src/parse-iban/parse-iban.ts | 23 +++++++++++++++-------- 6 files changed, 60 insertions(+), 20 deletions(-) diff --git a/src/_internals/constants/iban.ts b/src/_internals/constants/iban.ts index f22d0984..c8b736c9 100644 --- a/src/_internals/constants/iban.ts +++ b/src/_internals/constants/iban.ts @@ -1,11 +1,15 @@ /** * Layout of a Brazilian IBAN: `BR` + 2 ISO 7064 MOD 97-10 check digits + 8 digit ISPB (the * institution's Identificador do Sistema de Pagamentos Brasileiro, not the 3 digit COMPE code) - * + 5 digit branch (agência) + 10 digit account (conta) + 1 letter account type (`C` for - * conta corrente, `P` for conta poupança) + 1 alphanumeric owner indicator = 29 characters. + * + 5 digit branch (agência) + 10 digit account (conta) + 1 letter account type + 1 + * alphanumeric owner indicator = 29 characters. The registry pattern `BR2!n8!n5!n10!n1!a1!c` + * allows any letter as the account type, drawn from the "Dicionário de Tipos" of the Catálogo + * de Mensagens e de Arquivos do SFN; `C` (conta corrente) and `P` (conta poupança) are the + * usual values. * Only Brazilian IBANs follow this layout; every other ISO 13616 country has its own. - * @see Official: https://www.bcb.gov.br/estabilidadefinanceira/exibenormativo?tipo=Circular&numero=3625 Circular BCB nº 3.625/2013 (Diretrizes de Implementação do IBAN no Brasil) + * @see Official: https://www.bcb.gov.br/pre/normativos/circ/2013/pdf/circ_3625_v1_O.pdf Circular BCB nº 3.625/2013 + * @see Official: https://www.bcb.gov.br/content/estabilidadefinanceira/Documents/sistema_pagamentos_brasileiro/IBAN-Guidelines_%20port.pdf Diretrizes de Implementação do IBAN no Brasil */ export const BR_IBAN_LENGTH = 29; -export const BR_IBAN_REGEX = /^BR\d{2}\d{8}\d{5}\d{10}[CP][A-Z0-9]$/; +export const BR_IBAN_REGEX = /^BR\d{2}\d{8}\d{5}\d{10}[A-Z][A-Z0-9]$/; diff --git a/src/format-iban/format-iban.ts b/src/format-iban/format-iban.ts index 164cad33..5cb5ae88 100644 --- a/src/format-iban/format-iban.ts +++ b/src/format-iban/format-iban.ts @@ -22,7 +22,8 @@ import { GROUP_SIZE } from "./constants"; * formatIban("BR1500000000000010932840814P2EXTRA"); // "BR15 0000 0000 0000 1093 2840 814P 2" * ``` * - * @see Official: https://www.bcb.gov.br/estabilidadefinanceira/exibenormativo?tipo=Circular&numero=3625 Circular BCB nº 3.625/2013 (Diretrizes de Implementação do IBAN no Brasil) + * @see Official: https://www.bcb.gov.br/pre/normativos/circ/2013/pdf/circ_3625_v1_O.pdf Circular BCB nº 3.625/2013 + * @see Official: https://www.bcb.gov.br/content/estabilidadefinanceira/Documents/sistema_pagamentos_brasileiro/IBAN-Guidelines_%20port.pdf Diretrizes de Implementação do IBAN no Brasil */ export const formatIban = (value: string): string => { if (typeof value !== "string") return ""; diff --git a/src/is-valid-iban/is-valid-iban.test.ts b/src/is-valid-iban/is-valid-iban.test.ts index 620e8717..76d4f776 100644 --- a/src/is-valid-iban/is-valid-iban.test.ts +++ b/src/is-valid-iban/is-valid-iban.test.ts @@ -30,6 +30,11 @@ describe("isValidIban", () => { expect(isValidIban("BR3860701190000010000012345C1")).toBe(true); }); + test("for a valid IBAN whose account type is a letter other than C or P", () => { + expect(isValidIban("BR5400000000000010932840814D2")).toBe(true); + expect(isValidIban("BR7800000000000010932840814S2")).toBe(true); + }); + test("for a valid IBAN whose owner indicator is the letter A (the LETTER_CODE_A boundary)", () => { expect(isValidIban("BR4500000000000010000012345CA")).toBe(true); }); @@ -56,7 +61,11 @@ describe("isValidIban", () => { expect(isValidIban("BR1500000000000010932840814P2000")).toBe(false); }); - test("when the account type is not C or P", () => { + test("when the account type is not a letter", () => { + expect(isValidIban("BR150000000000001093284081412")).toBe(false); + }); + + test("when the account type letter does not match the check digits", () => { expect(isValidIban("BR1500000000000010932840814X2")).toBe(false); }); @@ -106,7 +115,7 @@ describe("isValidIban", () => { }); describe("properties", () => { - const bodies = fc.stringMatching(/^[0-9]{23}[CP][A-Z0-9]$/); + const bodies = fc.stringMatching(/^[0-9]{23}[A-Z][A-Z0-9]$/); test("should accept exactly one pair of check digits for any account", () => { fc.assert( diff --git a/src/is-valid-iban/is-valid-iban.ts b/src/is-valid-iban/is-valid-iban.ts index cc08b06a..41fdf247 100644 --- a/src/is-valid-iban/is-valid-iban.ts +++ b/src/is-valid-iban/is-valid-iban.ts @@ -37,8 +37,10 @@ const hasValidCheckDigits = (iban: string): boolean => { * isValidIban("DE89370400440532013000"); // false (non Brazilian IBAN) * ``` * - * @see Official: https://www.bcb.gov.br/estabilidadefinanceira/exibenormativo?tipo=Circular&numero=3625 Circular BCB nº 3.625/2013 (Diretrizes de Implementação do IBAN no Brasil) - * @see Official: https://www.iso.org/standard/81090.html ISO/IEC 7064 (MOD 97-10 check digit algorithm) + * @see Official: https://www.bcb.gov.br/pre/normativos/circ/2013/pdf/circ_3625_v1_O.pdf Circular BCB nº 3.625/2013 + * @see Official: https://www.bcb.gov.br/content/estabilidadefinanceira/Documents/sistema_pagamentos_brasileiro/IBAN-Guidelines_%20port.pdf Diretrizes de Implementação do IBAN no Brasil + * @see Official: https://www.iso.org/standard/81090.html ISO 13616-1:2020 (IBAN structure) + * @see Official: https://www.iso.org/standard/31531.html ISO/IEC 7064:2003 (MOD 97-10 check digit algorithm) * @see Based on: https://www.iban.com/structure Used to cross check the Brazil IBAN example. */ export const isValidIban = (value: string): boolean => { diff --git a/src/parse-iban/parse-iban.test.ts b/src/parse-iban/parse-iban.test.ts index f209de8d..6963c9e2 100644 --- a/src/parse-iban/parse-iban.test.ts +++ b/src/parse-iban/parse-iban.test.ts @@ -76,6 +76,18 @@ describe("parseIban", () => { owner: "2", }); }); + + test("for a valid IBAN with an account type letter other than C or P", () => { + expect(parseIban("BR5400000000000010932840814D2")).toEqual({ + countryCode: "BR", + checkDigits: "54", + bankIspb: "00000000", + branch: "00001", + account: "0932840814", + accountType: "D", + owner: "2", + }); + }); }); describe("should return null", () => { @@ -95,7 +107,11 @@ describe("parseIban", () => { expect(parseIban("BR1500000000000010932840814P2000")).toBeNull(); }); - test("when the account type is not C or P", () => { + test("when the account type is not a letter", () => { + expect(parseIban("BR150000000000001093284081412")).toBeNull(); + }); + + test("when the account type letter does not match the check digits", () => { expect(parseIban("BR1500000000000010932840814X2")).toBeNull(); }); @@ -124,6 +140,7 @@ describe("parseIban", () => { "BR1500000000000010932840814P2", "BR3860701190000010000012345C1", "BR1460746948000020001234567P2", + "BR5400000000000010932840814D2", ]; for (const iban of IBANS) { @@ -142,7 +159,7 @@ describe("parseIban", () => { }); describe("properties", () => { - const bodies = fc.stringMatching(/^[0-9]{23}[CP][A-Z0-9]$/); + const bodies = fc.stringMatching(/^[0-9]{23}[A-Z][A-Z0-9]$/); test("should split an IBAN into fields that spell it back", () => { fc.assert( @@ -187,7 +204,7 @@ describe("parseIban types", () => { bankIspb: string; branch: string; account: string; - accountType: "C" | "P"; + accountType: string; owner: string; }>(); }); diff --git a/src/parse-iban/parse-iban.ts b/src/parse-iban/parse-iban.ts index 787e866b..60b5dd5a 100644 --- a/src/parse-iban/parse-iban.ts +++ b/src/parse-iban/parse-iban.ts @@ -13,8 +13,12 @@ export type Iban = { branch: string; /** The 10 digit account (conta) number, zero-padded. */ account: string; - /** The account type: `"C"` for conta corrente, `"P"` for conta poupança. */ - accountType: "C" | "P"; + /** + * The 1 letter account type, as published in the "Dicionário de Tipos" of the Catálogo de + * Mensagens e de Arquivos do SFN. `"C"` (conta corrente) and `"P"` (conta poupança) are the + * usual values, but any letter is allowed. + */ + accountType: string; /** The 1 character alphanumeric owner indicator, distinguishing co-owners of the same account. */ owner: string; }; @@ -37,9 +41,10 @@ const ACCOUNT_TYPE_END = ACCOUNT_END + ACCOUNT_TYPE_LENGTH; * Parses a Brazilian IBAN (International Bank Account Number) into its fields. * * The 29 character Brazilian IBAN is laid out as 2 (country code, always `BR`) + 2 (ISO 7064 - * MOD 97-10 check digits) + 8 (ISPB) + 5 (branch) + 10 (account) + 1 (account type, `C` or `P`) - * + 1 (owner indicator). Only Brazilian IBANs are supported: the field layout of the other ISO - * 13616 countries is out of scope, so a well-formed non `BR` IBAN also returns `null`. + * MOD 97-10 check digits) + 8 (ISPB) + 5 (branch) + 10 (account) + 1 (account type, any letter, + * usually `C` for conta corrente or `P` for conta poupança) + 1 (owner indicator). Only + * Brazilian IBANs are supported: the field layout of the other ISO 13616 countries is out of + * scope, so a well-formed non `BR` IBAN also returns `null`. * * Accepts the same input forms as `isValidIban` (grouping spaces, lowercase) and returns `null` * whenever `isValidIban` would return `false`. @@ -65,8 +70,10 @@ const ACCOUNT_TYPE_END = ACCOUNT_END + ACCOUNT_TYPE_LENGTH; * parseIban("BR1500000000000010932840814P3"); // null (bad check digits) * ``` * - * @see Official: https://www.bcb.gov.br/estabilidadefinanceira/exibenormativo?tipo=Circular&numero=3625 Circular BCB nº 3.625/2013 (Diretrizes de Implementação do IBAN no Brasil) - * @see Official: https://www.iso.org/standard/81090.html ISO/IEC 7064 (MOD 97-10 check digit algorithm) + * @see Official: https://www.bcb.gov.br/pre/normativos/circ/2013/pdf/circ_3625_v1_O.pdf Circular BCB nº 3.625/2013 + * @see Official: https://www.bcb.gov.br/content/estabilidadefinanceira/Documents/sistema_pagamentos_brasileiro/IBAN-Guidelines_%20port.pdf Diretrizes de Implementação do IBAN no Brasil + * @see Official: https://www.iso.org/standard/81090.html ISO 13616-1:2020 (IBAN structure) + * @see Official: https://www.iso.org/standard/31531.html ISO/IEC 7064:2003 (MOD 97-10 check digit algorithm) */ export const parseIban = (value: string): Iban | null => { if (!isValidIban(value)) return null; @@ -79,7 +86,7 @@ export const parseIban = (value: string): Iban | null => { bankIspb: sanitized.slice(CHECK_DIGITS_END, ISPB_END), branch: sanitized.slice(ISPB_END, BRANCH_END), account: sanitized.slice(BRANCH_END, ACCOUNT_END), - accountType: sanitized.charAt(ACCOUNT_END) === "C" ? "C" : "P", + accountType: sanitized.charAt(ACCOUNT_END), owner: sanitized.slice(ACCOUNT_TYPE_END), }; }; From 828b7dca8e650d4a4ff29974bcd161302a05a87d Mon Sep 17 00:00:00 2001 From: Hyan Mandian <5044101+hyanmandian@users.noreply.github.com> Date: Sat, 12 Sep 2026 20:54:37 -0300 Subject: [PATCH 06/15] fix(pix): read only mobile numbers as phone keys --- .../generate-pix-payload.test.ts | 6 ++--- .../generate-pix-payload.ts | 9 ++++++-- src/is-valid-pix-key/is-valid-pix-key.test.ts | 7 +++++- src/is-valid-pix-key/is-valid-pix-key.ts | 9 ++++---- .../is-valid-pix-payload.ts | 7 ++++-- src/parse-pix-key/parse-pix-key.test.ts | 23 +++++++++++++------ src/parse-pix-key/parse-pix-key.ts | 10 ++++---- src/parse-pix-payload/parse-pix-payload.ts | 11 ++++++--- 8 files changed, 56 insertions(+), 26 deletions(-) diff --git a/src/generate-pix-payload/generate-pix-payload.test.ts b/src/generate-pix-payload/generate-pix-payload.test.ts index 4b0bfd2c..eecc19e7 100644 --- a/src/generate-pix-payload/generate-pix-payload.test.ts +++ b/src/generate-pix-payload/generate-pix-payload.test.ts @@ -333,14 +333,14 @@ describe("generatePixPayload", () => { expect(parsePixPayload(payload ?? "")?.description).toBe("y".repeat(62)); }); - test("truncating the description to what a phone key leaves", () => { + test("truncating the description to what a mobile phone key leaves", () => { const payload = generatePixPayload({ ...BASE, - key: "1130000000", + key: "11987654321", description: "y".repeat(90), }); - expect(parsePixPayload(payload ?? "")?.description).toBe("y".repeat(60)); + expect(parsePixPayload(payload ?? "")?.description).toBe("y".repeat(59)); }); test("leaving room for the description on a long key", () => { diff --git a/src/generate-pix-payload/generate-pix-payload.ts b/src/generate-pix-payload/generate-pix-payload.ts index 900f8a65..6a6b7fab 100644 --- a/src/generate-pix-payload/generate-pix-payload.ts +++ b/src/generate-pix-payload/generate-pix-payload.ts @@ -133,6 +133,10 @@ const resolveFormattedAmount = ( * template within its 99 character limit together with the `br.gov.bcb.pix` GUI. `parsePixPayload` * already parses both shapes, so `parsePixPayload(generatePixPayload({ url, ... }))` round-trips. * + * Payloads that carry the location in an Unreserved Template (IDs 80 to 99), as the "QR Code + * composto" of Pix Automático (Pix recorrente) does, are out of scope: the location is always + * written in the "Merchant Account Information" template. + * * The merchant name, the merchant city and the description are folded to printable ASCII * (accents are dropped) and truncated to the lengths the BR Code allows, the description to * whatever is left of the 99 characters the "Merchant Account Information" template holds. @@ -169,9 +173,10 @@ const resolveFormattedAmount = ( * generatePixPayload({ key: "123.456.789-09", url: "pix.example.com/qr/v2/1234", merchantName: "Fulano", merchantCity: "Brasília" }); // null (both key and url) * ``` * + * @see Official: https://www.bcb.gov.br/content/estabilidadefinanceira/spb_docs/ManualBRCode.pdf * @see Official: https://www.bcb.gov.br/content/estabilidadefinanceira/pix/Regulamento_Pix/II_ManualdePadroesparaIniciacaodoPix.pdf - * @see Based on: https://github.com/bacen/pix-api Pix (SPI) OpenAPI spec. - * @see Based on: https://github.com/bacen/pix-dict-api DICT OpenAPI spec. + * @see Official: https://github.com/bacen/pix-api Pix (SPI) OpenAPI spec. + * @see Official: https://github.com/bacen/pix-dict-api DICT OpenAPI spec. */ export const generatePixPayload = (params: GeneratePixPayloadParams): string | null => { if (isNullish(params) || typeof params !== "object") return null; diff --git a/src/is-valid-pix-key/is-valid-pix-key.test.ts b/src/is-valid-pix-key/is-valid-pix-key.test.ts index c8fa5512..01d560ba 100644 --- a/src/is-valid-pix-key/is-valid-pix-key.test.ts +++ b/src/is-valid-pix-key/is-valid-pix-key.test.ts @@ -38,6 +38,11 @@ describe("isValidPixKey", () => { expect(isValidPixKey([])).toBe(false); }); + test("when the phone is a landline, since the manual registers a mobile number", () => { + expect(isValidPixKey("(11) 3000-0000")).toBe(false); + expect(isValidPixKey("1130000000")).toBe(false); + }); + test("when it is not a key of any accepted kind", () => { expect(isValidPixKey("chave pix")).toBe(false); expect(isValidPixKey("11257245286")).toBe(false); @@ -60,7 +65,7 @@ describe("isValidPixKey", () => { expect(isValidPixKey("fulano_da_silva.recebedor@example.com")).toBe(true); }); - test("for a phone", () => { + test("for a mobile phone", () => { expect(isValidPixKey("+5561912345678")).toBe(true); expect(isValidPixKey("(11) 98765-4321")).toBe(true); }); diff --git a/src/is-valid-pix-key/is-valid-pix-key.ts b/src/is-valid-pix-key/is-valid-pix-key.ts index 41da0881..a49b0935 100644 --- a/src/is-valid-pix-key/is-valid-pix-key.ts +++ b/src/is-valid-pix-key/is-valid-pix-key.ts @@ -10,8 +10,9 @@ export type IsValidPixKeyOptions = { * Validates a Pix key (chave Pix) against the DICT key formats. * * A value is valid when `parsePixKey` recognizes it as a CPF, a CNPJ, an e-mail address, a - * Brazilian phone number or a random key (EVP), and when that kind is listed in - * `options.accept`. + * Brazilian mobile phone number or a random key (EVP), and when that kind is listed in + * `options.accept`. The manual registers a "número de telefone celular", so a landline is not + * a valid phone key. * * @param {string} value - The Pix key to validate. * @param {IsValidPixKeyOptions} [options] - Optional validation options. @@ -29,9 +30,9 @@ export type IsValidPixKeyOptions = { * ``` * * @see Official: https://www.bcb.gov.br/content/estabilidadefinanceira/pix/Regulamento_Pix/II_ManualdePadroesparaIniciacaodoPix.pdf - * @see Based on: https://github.com/bacen/pix-dict-api DICT (Diretório de Identificadores de + * @see Official: https://github.com/bacen/pix-dict-api DICT (Diretório de Identificadores de * Contas Transacionais) OpenAPI spec, key format reference. - * @see Based on: https://github.com/bacen/pix-api Pix (SPI) OpenAPI spec. + * @see Official: https://github.com/bacen/pix-api Pix (SPI) OpenAPI spec. */ export const isValidPixKey = (value: string, options?: IsValidPixKeyOptions): boolean => { const key = parsePixKey(value); diff --git a/src/is-valid-pix-payload/is-valid-pix-payload.ts b/src/is-valid-pix-payload/is-valid-pix-payload.ts index 37b09cad..1af45ec7 100644 --- a/src/is-valid-pix-payload/is-valid-pix-payload.ts +++ b/src/is-valid-pix-payload/is-valid-pix-payload.ts @@ -15,6 +15,9 @@ import { parsePixPayload } from "../parse-pix-payload/parse-pix-payload"; * can be generated with a key that is not (or is no longer) registered, so use `isValidPixKey` * when that matters. * + * Payloads that carry the location in an Unreserved Template (IDs 80 to 99), as the "QR Code + * composto" of Pix Automático (Pix recorrente) does, are out of scope and reported as invalid. + * * @param {string} value - The BR Code payload to validate. * @returns {boolean} True if the payload is a valid Pix BR Code, false otherwise. * @@ -30,7 +33,7 @@ import { parsePixPayload } from "../parse-pix-payload/parse-pix-payload"; * * @see Official: https://www.bcb.gov.br/content/estabilidadefinanceira/spb_docs/ManualBRCode.pdf * @see Official: https://www.bcb.gov.br/content/estabilidadefinanceira/pix/Regulamento_Pix/II_ManualdePadroesparaIniciacaodoPix.pdf - * @see Based on: https://github.com/bacen/pix-api Pix (SPI) OpenAPI spec. - * @see Based on: https://github.com/bacen/pix-dict-api DICT OpenAPI spec. + * @see Official: https://github.com/bacen/pix-api Pix (SPI) OpenAPI spec. + * @see Official: https://github.com/bacen/pix-dict-api DICT OpenAPI spec. */ export const isValidPixPayload = (value: string): boolean => parsePixPayload(value) !== null; diff --git a/src/parse-pix-key/parse-pix-key.test.ts b/src/parse-pix-key/parse-pix-key.test.ts index 2c4cc298..404a863e 100644 --- a/src/parse-pix-key/parse-pix-key.test.ts +++ b/src/parse-pix-key/parse-pix-key.test.ts @@ -84,6 +84,12 @@ describe("parsePixKey", () => { expect(parsePixKey("(00) 98765-4321")).toBeNull(); }); + test("when the phone is a landline, since the manual registers a mobile number", () => { + expect(parsePixKey("(11) 3000-0000")).toBeNull(); + expect(parsePixKey("+551130000000")).toBeNull(); + expect(parsePixKey("1130000000")).toBeNull(); + }); + test("when it is free text", () => { expect(parsePixKey("chave pix")).toBeNull(); expect(parsePixKey("---")).toBeNull(); @@ -134,8 +140,15 @@ describe("parsePixKey", () => { expect(parsePixKey("00.551.760/8718-13")).toEqual({ type: "cnpj", value: "00551760871813" }); }); - test("should still read a 0055 prefixed value that is not a valid CNPJ as a phone", () => { - expect(parsePixKey("00551133334444")).toEqual({ type: "phone", value: "+551133334444" }); + test("should still read a 0055 prefixed mobile number as a phone", () => { + expect(parsePixKey("005511987654321")).toEqual({ + type: "phone", + value: "+5511987654321", + }); + }); + + test("should return null for a 0055 prefixed value that is neither a valid CNPJ nor a mobile number", () => { + expect(parsePixKey("00551133334444")).toBeNull(); }); }); @@ -196,13 +209,9 @@ describe("parsePixKey", () => { }); }); - test("when it is a landline", () => { - expect(parsePixKey("(11) 3000-0000")).toEqual({ type: "phone", value: "+551130000000" }); - }); - test("and never exceed the 14 characters of the E.164 form", () => { for (let index = 0; index < 200; index++) { - const key = parsePixKey(`+55${generatePhone()}`); + const key = parsePixKey(`+55${generatePhone("mobile")}`); expect(key?.type).toBe("phone"); expect(key?.value.length).toBeLessThanOrEqual(14); diff --git a/src/parse-pix-key/parse-pix-key.ts b/src/parse-pix-key/parse-pix-key.ts index ecf713a7..4e83513f 100644 --- a/src/parse-pix-key/parse-pix-key.ts +++ b/src/parse-pix-key/parse-pix-key.ts @@ -29,7 +29,9 @@ export type PixKey = { * - `cnpj`: 14 characters, no mask, uppercase for the alphanumeric format; * - `email`: trimmed and lowercased, at most 77 characters; * - `phone`: E.164, `+55` followed by the DDD and the subscriber number, so at most 14 - * characters. Masked, bare and `+55` prefixed inputs are all accepted; + * characters. The manual registers a "número de telefone celular", so only mobile numbers + * are recognized; a landline is not a Pix key. Masked, bare and `+55` prefixed inputs are + * all accepted; * - `evp`: the random key, a lowercase UUID version 4. * * A value with a valid CNPJ check digit is read as a CNPJ, even when it starts with `0055` @@ -53,9 +55,9 @@ export type PixKey = { * ``` * * @see Official: https://www.bcb.gov.br/content/estabilidadefinanceira/pix/Regulamento_Pix/II_ManualdePadroesparaIniciacaodoPix.pdf - * @see Based on: https://github.com/bacen/pix-dict-api DICT (Diretório de Identificadores de + * @see Official: https://github.com/bacen/pix-dict-api DICT (Diretório de Identificadores de * Contas Transacionais) OpenAPI spec, key format reference. - * @see Based on: https://github.com/bacen/pix-api Pix (SPI) OpenAPI spec. + * @see Official: https://github.com/bacen/pix-api Pix (SPI) OpenAPI spec. */ export const parsePixKey = (value: string): PixKey | null => { if (typeof value !== "string") return null; @@ -76,7 +78,7 @@ export const parsePixKey = (value: string): PixKey | null => { } const national = normalizePhone(trimmed); - const phone: PixKey | null = isValidPhone(national) + const phone: PixKey | null = isValidPhone(national, { accept: ["mobile"] }) ? { type: "phone", value: `+${PHONE_COUNTRY_CODE}${national}` } : null; diff --git a/src/parse-pix-payload/parse-pix-payload.ts b/src/parse-pix-payload/parse-pix-payload.ts index ba0062e9..b62107fb 100644 --- a/src/parse-pix-payload/parse-pix-payload.ts +++ b/src/parse-pix-payload/parse-pix-payload.ts @@ -189,7 +189,11 @@ const buildPixPayload = ( * optional in the EMV® specification it refers to, so it is accepted when absent. The lengths * the manual reserves for the merchant name (25), the merchant city (15) and the `txid` (25) * are generator side limits, enforced by `generatePixPayload`; payloads in the wild routinely - * overrun them, so they are not enforced here. + * overrun them, so they are not enforced here, and neither is the 77 character limit of the + * Pix key field (26-01). + * + * Payloads that carry the location in an Unreserved Template (IDs 80 to 99), as the "QR Code + * composto" of Pix Automático (Pix recorrente) does, are out of scope and rejected. * * The merchant account information must carry exactly one of a Pix key (26-01) or a PSP * location (26-25); the location is checked with the same host and path rule @@ -214,9 +218,10 @@ const buildPixPayload = ( * // } * ``` * + * @see Official: https://www.bcb.gov.br/content/estabilidadefinanceira/spb_docs/ManualBRCode.pdf * @see Official: https://www.bcb.gov.br/content/estabilidadefinanceira/pix/Regulamento_Pix/II_ManualdePadroesparaIniciacaodoPix.pdf - * @see Based on: https://github.com/bacen/pix-api Pix (SPI) OpenAPI spec. - * @see Based on: https://github.com/bacen/pix-dict-api DICT OpenAPI spec. + * @see Official: https://github.com/bacen/pix-api Pix (SPI) OpenAPI spec. + * @see Official: https://github.com/bacen/pix-dict-api DICT OpenAPI spec. */ export const parsePixPayload = (value: string): PixPayload | null => { if (typeof value !== "string") return null; From e773022566d27405187901496654b26a65a7511f Mon Sep 17 00:00:00 2001 From: Hyan Mandian <5044101+hyanmandian@users.noreply.github.com> Date: Sat, 12 Sep 2026 20:54:37 -0300 Subject: [PATCH 07/15] fix(nfe-key): accept only the assigned tpEmis values --- src/is-valid-nfe-key/is-valid-nfe-key.test.ts | 10 ++++++++++ src/is-valid-nfe-key/is-valid-nfe-key.ts | 1 + src/parse-nfe-key/constants.ts | 7 +++++++ src/parse-nfe-key/parse-nfe-key.test.ts | 11 ++++++++++- src/parse-nfe-key/parse-nfe-key.ts | 16 ++++++++++++---- 5 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/is-valid-nfe-key/is-valid-nfe-key.test.ts b/src/is-valid-nfe-key/is-valid-nfe-key.test.ts index 77fa1edf..e64a69ec 100644 --- a/src/is-valid-nfe-key/is-valid-nfe-key.test.ts +++ b/src/is-valid-nfe-key/is-valid-nfe-key.test.ts @@ -163,6 +163,16 @@ describe("isValidNfeKey", () => { key: "35200600000000000000550010000000019000000003", expected: true, }, + { + name: "tpEmis 8, a code the MOC does not assign", + key: "35170458716523000119550010000000128000123455", + expected: false, + }, + { + name: "tpEmis 9, the off-line NFC-e contingency", + key: "35170458716523000119550010000000129000123453", + expected: true, + }, ]; for (const { name, key, expected } of CASES) { diff --git a/src/is-valid-nfe-key/is-valid-nfe-key.ts b/src/is-valid-nfe-key/is-valid-nfe-key.ts index fcf2ea40..b0e0b9fc 100644 --- a/src/is-valid-nfe-key/is-valid-nfe-key.ts +++ b/src/is-valid-nfe-key/is-valid-nfe-key.ts @@ -9,6 +9,7 @@ import { parseNfeKey } from "../parse-nfe-key/parse-nfe-key"; * document's XML (e.g. `Id="NFe3517...`), which is stripped before validation. * * The key is `cUF(2) AAMM(4) CNPJ/CPF(14) mod(2) serie(3) nNF(9) tpEmis(1) cNF(8) cDV(1)`. + * `tpEmis` must be one of the codes the MOC assigns, 1 to 7 or 9; 8 is not assigned. * The check digit (`cDV`) is a modulus 11 over the first 43 digits, weights 2-9 cycling from * the right, where a remainder of 0 or 1 maps to check digit 0. * diff --git a/src/parse-nfe-key/constants.ts b/src/parse-nfe-key/constants.ts index bd6b4af6..ec8cc5e3 100644 --- a/src/parse-nfe-key/constants.ts +++ b/src/parse-nfe-key/constants.ts @@ -1,6 +1,13 @@ /** Valid `mod` (modelo do documento) values shared by every DF-e access key. */ export const VALID_MODELS = ["55", "57", "58", "65"] as const; +/** + * The `tpEmis` (forma de emissão) codes the MOC assigns: 1 normal, 2 contingência FS-IA, + * 3 contingência SCAN, 4 contingência DPEC/EPEC, 5 contingência FS-DA, 6 contingência SVC-AN, + * 7 contingência SVC-RS and 9 contingência off-line da NFC-e. 8 is not assigned. + */ +export const VALID_EMISSION_TYPES: readonly number[] = [1, 2, 3, 4, 5, 6, 7, 9]; + /** Digits, optional whitespace between groups, optional `NFe` prefix from the XML `Id` attribute. */ export const FORMAT_REGEX = /^(?:nfe)?[\d\s]+$/i; diff --git a/src/parse-nfe-key/parse-nfe-key.test.ts b/src/parse-nfe-key/parse-nfe-key.test.ts index 8c77d204..9abbbe2a 100644 --- a/src/parse-nfe-key/parse-nfe-key.test.ts +++ b/src/parse-nfe-key/parse-nfe-key.test.ts @@ -3,6 +3,7 @@ import * as fc from "fast-check"; import { IBGE_UF_CODES } from "../_internals/constants/ibge-uf-codes"; import { type StateCode } from "../_internals/constants/states"; import { describe, expect, expectTypeOf, test } from "../_internals/test/runtime"; +import { VALID_EMISSION_TYPES } from "./constants"; import { parseNfeKey, type NfeKey, type NfeKeyModel } from "./parse-nfe-key"; const KEY_SP = "35170458716523000119550010000000121000123458"; @@ -47,6 +48,10 @@ describe("parseNfeKey", () => { expect(parseNfeKey("35170458716523000119550010000000001000123457")).toBeNull(); }); + test("when tpEmis is 8, a code the MOC does not assign, even with a matching check digit", () => { + expect(parseNfeKey("35170458716523000119550010000000128000123455")).toBeNull(); + }); + test("when the access key is otherwise invalid", () => { expect(parseNfeKey("not-a-key")).toBeNull(); }); @@ -95,6 +100,10 @@ describe("parseNfeKey", () => { expect(parseNfeKey(KEY_CPF_PADDED)?.taxId).toHaveLength(14); }); + test("for tpEmis 9, the off-line NFC-e contingency, same shape as the SP key with the tpEmis field changed and the check digit recalculated", () => { + expect(parseNfeKey("35170458716523000119550010000000129000123453")?.emissionType).toBe(9); + }); + test("for every other DF-e model (CT-e, MDF-e, NFC-e), same shape as the SP key with the model field changed and the check digit recalculated", () => { expect(parseNfeKey("35170458716523000119570010000000121000123455")?.model).toBe("57"); expect(parseNfeKey("35170458716523000119580010000000121000123459")?.model).toBe("58"); @@ -111,7 +120,7 @@ describe("parseNfeKey", () => { fc.constantFrom("55", "57", "58", "65"), fc.stringMatching(/^[0-9]{3}$/), fc.integer({ min: 1, max: 999_999_999 }), - fc.integer({ min: 1, max: 9 }), + fc.constantFrom(...VALID_EMISSION_TYPES), fc.stringMatching(/^[0-9]{8}$/), ); diff --git a/src/parse-nfe-key/parse-nfe-key.ts b/src/parse-nfe-key/parse-nfe-key.ts index 6a6de14c..a580d987 100644 --- a/src/parse-nfe-key/parse-nfe-key.ts +++ b/src/parse-nfe-key/parse-nfe-key.ts @@ -3,7 +3,14 @@ import { NFE_KEY_LENGTH } from "../_internals/constants/nfe-key"; import { type StateCode } from "../_internals/constants/states"; import { mod11 } from "../_internals/mod11/mod11"; import { sanitizeToDigits } from "../_internals/sanitize-to-digits/sanitize-to-digits"; -import { ABSENT_NUMBER, FORMAT_REGEX, NUMBER_END, NUMBER_START, VALID_MODELS } from "./constants"; +import { + ABSENT_NUMBER, + FORMAT_REGEX, + NUMBER_END, + NUMBER_START, + VALID_EMISSION_TYPES, + VALID_MODELS, +} from "./constants"; /** The document models a DF-e access key can carry: `"55"` NF-e, `"57"` CT-e, `"58"` MDF-e and `"65"` NFC-e. */ export type NfeKeyModel = "55" | "57" | "58" | "65"; @@ -24,7 +31,7 @@ export type NfeKey = { series: number; /** Document number, 1 to 999999999. */ number: number; - /** Emission type code (tpEmis), 1 to 9. */ + /** Emission type code (tpEmis): 1 to 7 or 9, the codes the MOC assigns (8 is not one of them). */ emissionType: number; /** The 8 digit numeric code (cNF) drawn by the issuer. */ code: string; @@ -38,7 +45,8 @@ export type NfeKey = { * Covers every document that shares the same 44 digit layout: NF-e (modelo 55), NFC-e * (modelo 65), CT-e (modelo 57) and MDF-e (modelo 58). Accepts the same input forms as * `isValidNfeKey` (whitespace mask, `NFe` XML `Id` prefix) and returns `null` when the key - * is not valid. + * is not valid. The emission type (`tpEmis`) must be one of the codes the MOC assigns, 1 to 7 + * or 9; 8 is not assigned and is rejected. * * @param {string} value - The access key value to be parsed. * @returns {NfeKey | null} The parsed access key, or `null` when it is not valid. @@ -85,7 +93,7 @@ export const parseNfeKey = (value: string): NfeKey | null => { const emissionType = Number(digits[34]); - if (emissionType < 1) return null; + if (!VALID_EMISSION_TYPES.includes(emissionType)) return null; const checkDigit = Number(digits[43]); From df51b0a99e60ecd0f929157e377eead18d460b83 Mon Sep 17 00:00:00 2001 From: Hyan Mandian <5044101+hyanmandian@users.noreply.github.com> Date: Sat, 12 Sep 2026 20:54:37 -0300 Subject: [PATCH 08/15] fix(municipality): resolve a numeric IBGE code in getMunicipality --- src/get-municipality/get-municipality.test.ts | 12 ++++++++---- src/get-municipality/get-municipality.ts | 16 ++++++++++------ 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/get-municipality/get-municipality.test.ts b/src/get-municipality/get-municipality.test.ts index f53bc536..a8e0cb75 100644 --- a/src/get-municipality/get-municipality.test.ts +++ b/src/get-municipality/get-municipality.test.ts @@ -77,11 +77,15 @@ describe("getMunicipality", () => { await expect(getMunicipality({ code: "3550308?x=1" })).resolves.toBeNull(); }); - it("should return null for a non-string code", async () => { + it("should return null for a code that is neither a string nor a number", async () => { // @ts-expect-error: intentionally invalid input await expect(getMunicipality({ code: null })).resolves.toBeNull(); - // @ts-expect-error: intentionally invalid input - await expect(getMunicipality({ code: 3_550_308 })).resolves.toBeNull(); + await expect(getMunicipality({ code: Object.create(null) })).resolves.toBeNull(); + }); + + it("should resolve a code given as a number", async () => { + await expect(getMunicipality({ code: 3_550_308 })).resolves.toEqual(["São Paulo", "SP"]); + await expect(getMunicipality({ code: 0 })).resolves.toBeNull(); }); it("should return null for a code with the wrong number of digits", async () => { @@ -175,7 +179,7 @@ describe("getMunicipality types", () => { expectTypeOf().toEqualTypeOf< GetMunicipalityByCodeOptions | GetMunicipalityByNameOptions >(); - expectTypeOf().toEqualTypeOf<{ code: string }>(); + expectTypeOf().toEqualTypeOf<{ code: string | number }>(); expectTypeOf().toEqualTypeOf<{ municipalityName: string; uf: string; diff --git a/src/get-municipality/get-municipality.ts b/src/get-municipality/get-municipality.ts index 5ea90a8b..91863129 100644 --- a/src/get-municipality/get-municipality.ts +++ b/src/get-municipality/get-municipality.ts @@ -1,11 +1,12 @@ import { DATA as CITIES_DATA } from "../_internals/constants/cities"; import { isNullish } from "../_internals/is-nullish/is-nullish"; +import { sanitizeToDigits } from "../_internals/sanitize-to-digits/sanitize-to-digits"; import { removeAccents } from "../remove-accents/remove-accents"; /** The `getMunicipality` query by IBGE municipality code. */ export type GetMunicipalityByCodeOptions = { - /** The 7 digit IBGE municipality code. */ - code: string; + /** The 7 digit IBGE municipality code, as a string or a number. */ + code: string | number; }; /** The `getMunicipality` query by municipality name and state code. */ @@ -27,7 +28,7 @@ let codeIndex: Map | undefined; // symmetric and cannot change which names are considered equal. const normalizeName = (value: string): string => removeAccents(value).trim().toUpperCase(); -const getMunicipalityByCode = (code: string): [string, string] | null => { +const getMunicipalityByCode = (code: string | number): [string, string] | null => { if (!codeIndex) { codeIndex = new Map(); @@ -38,9 +39,11 @@ const getMunicipalityByCode = (code: string): [string, string] | null => { } } - // `Map#get` never throws and simply misses for a key of the wrong shape or type (a malformed, - // too short/long, or non-string code), so there is no need to pre-validate `code` here first. - return codeIndex.get(code) ?? null; + if (typeof code !== "string" && typeof code !== "number") return null; + + // `Map#get` never throws and simply misses for a key of the wrong shape (a malformed, too + // short or too long code), so there is no need to pre-validate `code` any further. + return codeIndex.get(sanitizeToDigits(code)) ?? null; }; const getMunicipalityCodeByName = ({ @@ -82,6 +85,7 @@ const getMunicipalityCodeByName = ({ * @example * ```typescript * await getMunicipality({ code: "3550308" }); // ["São Paulo", "SP"] + * await getMunicipality({ code: 3550308 }); // ["São Paulo", "SP"] * await getMunicipality({ municipalityName: "sao paulo", uf: "sp" }); // "3550308" * ``` * From 5915813440572d0c3c04c8e1a195cf4ca797acfa Mon Sep 17 00:00:00 2001 From: Hyan Mandian <5044101+hyanmandian@users.noreply.github.com> Date: Sat, 12 Sep 2026 20:54:37 -0300 Subject: [PATCH 09/15] fix(registro-profissional): follow the CFC and CFP registration layouts --- .../constants.ts | 23 +++--- .../is-valid-registro-profissional.test.ts | 73 ++++++++++++++++++- .../is-valid-registro-profissional.ts | 51 ++++++++++--- 3 files changed, 125 insertions(+), 22 deletions(-) diff --git a/src/is-valid-registro-profissional/constants.ts b/src/is-valid-registro-profissional/constants.ts index 77d54280..95b759b0 100644 --- a/src/is-valid-registro-profissional/constants.ts +++ b/src/is-valid-registro-profissional/constants.ts @@ -1,21 +1,26 @@ /** * Structural format of each supported professional council registration number. * - * @see Official: https://www.oab.org.br/ Ordem dos Advogados do Brasil (OAB): "número de inscrição" + "seccional" (UF). - * @see Official: https://portal.cfm.org.br/ Conselho Federal de Medicina (CRM): registration number + UF. - * @see Official: https://cfo.org.br/ Conselho Federal de Odontologia (CRO): registration number + UF. - * @see Official: https://cfp.org.br/ Conselho Federal de Psicologia (CRP): 2 digit regional code + registration number. - * @see Official: https://cfc.org.br/ Conselho Federal de Contabilidade (CRC): UF + registration number + category (O/T) + check digit. + * The citations for these shapes live on the JSDoc of `isValidRegistroProfissional`, which is + * also where the councils that publish no number format at all are named. */ export type RegistroProfissionalCouncil = "OAB" | "CRM" | "CRO" | "CRP" | "CRC"; -export const OAB_REGEX = /^(?\d{4,6})(?[A-Z]{2})$/; - -export const CRM_REGEX = /^(?\d{4,6})(?[A-Z]{2})$/; +/** + * Registration number followed by the UF of the seccional (OAB) or of the regional (CRM), the + * same shape for both councils. + */ +export const PROFESSIONAL_NUMBER_UF_REGEX = /^(?\d{4,6})(?[A-Z]{2})$/; export const CRO_REGEX = /^(?\d{3,6})(?[A-Z]{2})$/; export const CRP_REGEX = /^(?\d{2})(?\d{4,6})$/; -export const CRC_REGEX = /^(?[A-Z]{2})(?\d{4,6})(?[OT])(?\d)$/; +export const CRC_REGEX = /^(?[A-Z]{2})(?\d{6})(?[OPT])(?\d)$/; + +/** Lowest regional code of the CFP system, CRP-01. */ +export const CRP_MIN_REGION = 1; + +/** Highest regional code of the CFP system, CRP-24. */ +export const CRP_MAX_REGION = 24; diff --git a/src/is-valid-registro-profissional/is-valid-registro-profissional.test.ts b/src/is-valid-registro-profissional/is-valid-registro-profissional.test.ts index 08b167f0..42226716 100644 --- a/src/is-valid-registro-profissional/is-valid-registro-profissional.test.ts +++ b/src/is-valid-registro-profissional/is-valid-registro-profissional.test.ts @@ -60,6 +60,26 @@ describe("isValidRegistroProfissional", () => { test("when a CRC number is missing the check digit", () => { expect(isValidRegistroProfissional("SP-123456/O", { council: "CRC" })).toBe(false); }); + + test("when a CRC number of ordem has 5 digits instead of the 6 of the Manual de Registro", () => { + expect(isValidRegistroProfissional("SP-12345/O-3", { council: "CRC" })).toBe(false); + }); + + test("when a CRC number carries a letter that is not a tipo de registro", () => { + expect(isValidRegistroProfissional("SP-123456/X-3", { council: "CRC" })).toBe(false); + }); + + test("when a CRP regional code is 00, below the CRP-01 of the CFP system", () => { + expect(isValidRegistroProfissional("00/12345", { council: "CRP" })).toBe(false); + }); + + test("when a CRP regional code is 25, above the CRP-24 of the CFP system", () => { + expect(isValidRegistroProfissional("25/12345", { council: "CRP" })).toBe(false); + }); + + test("when a CRP regional code is 99, which no Conselho Regional carries", () => { + expect(isValidRegistroProfissional("99/12345", { council: "CRP" })).toBe(false); + }); }); describe("should return true", () => { @@ -87,11 +107,27 @@ describe("isValidRegistroProfissional", () => { ); }); - test("for a valid CRC number", () => { + test("for the first regional code of the CFP system, CRP-01", () => { + expect(isValidRegistroProfissional("01/12345", { council: "CRP" })).toBe(true); + }); + + test("for the last regional code of the CFP system, CRP-24", () => { + expect(isValidRegistroProfissional("24/12345", { council: "CRP" })).toBe(true); + }); + + test("for a valid CRC number of a registro originário", () => { expect(isValidRegistroProfissional("SP-123456/O-3", { council: "CRC" })).toBe(true); }); - test("for a valid CRC number of a técnico em contabilidade", () => { + test("for DF-000001/P-7, the Manual de Registro's own example of a registro provisório", () => { + expect(isValidRegistroProfissional("DF-000001/P-7", { council: "CRC" })).toBe(true); + }); + + test("for DF-000002/O-5, the Manual de Registro's own example of a registro originário", () => { + expect(isValidRegistroProfissional("DF-000002/O-5", { council: "CRC" })).toBe(true); + }); + + test("for a valid CRC number of a registro transferido", () => { expect(isValidRegistroProfissional("RJ-654321/T-9", { council: "CRC" })).toBe(true); }); }); @@ -113,12 +149,43 @@ describe("isValidRegistroProfissional", () => { expect(isValidRegistroProfissional(`06/${number}`, { council: "CRP" })).toBe(true); expect( - isValidRegistroProfissional(`${stateCode}-${number}/O-3`, { council: "CRC" }), + isValidRegistroProfissional(`${stateCode}-${String(number).padStart(6, "0")}/O-3`, { + council: "CRC", + }), ).toBe(true); }), ); }); + test("should accept a CRP registration only for the 24 regionals of the CFP system", () => { + fc.assert( + fc.property(fc.integer({ min: 0, max: 99 }), numbers, (region, number) => { + const value = `${String(region).padStart(2, "0")}/${number}`; + const expected = region >= 1 && region <= 24; + + expect(isValidRegistroProfissional(value, { council: "CRP" })).toBe(expected); + }), + ); + }); + + test("should accept a CRC registration only with six digits of ordem", () => { + fc.assert( + fc.property( + states, + fc.integer({ min: 1, max: 9_999_999 }), + fc.constantFrom("O", "P", "T"), + (stateCode, number, category) => { + const digits = String(number); + const value = `${stateCode}-${digits}/${category}-3`; + + expect(isValidRegistroProfissional(value, { council: "CRC" })).toBe( + digits.length === 6, + ); + }, + ), + ); + }); + test("should reject a registration whose UF is not the expected one", () => { fc.assert( fc.property(states, states, numbers, (stateCode, other, number) => { diff --git a/src/is-valid-registro-profissional/is-valid-registro-profissional.ts b/src/is-valid-registro-profissional/is-valid-registro-profissional.ts index aab89bf9..d6fbd193 100644 --- a/src/is-valid-registro-profissional/is-valid-registro-profissional.ts +++ b/src/is-valid-registro-profissional/is-valid-registro-profissional.ts @@ -2,10 +2,11 @@ import { DATA, type StateCode } from "../_internals/constants/states"; import { sanitizeToAlphanumeric } from "../_internals/sanitize-to-alphanumeric/sanitize-to-alphanumeric"; import { CRC_REGEX, - CRM_REGEX, CRO_REGEX, + CRP_MAX_REGION, + CRP_MIN_REGION, CRP_REGEX, - OAB_REGEX, + PROFESSIONAL_NUMBER_UF_REGEX, type RegistroProfissionalCouncil, } from "./constants"; @@ -18,8 +19,8 @@ export type IsValidRegistroProfissionalOptions = { }; const REGEX_BY_COUNCIL: Record = { - OAB: OAB_REGEX, - CRM: CRM_REGEX, + OAB: PROFESSIONAL_NUMBER_UF_REGEX, + CRM: PROFESSIONAL_NUMBER_UF_REGEX, CRO: CRO_REGEX, CRP: CRP_REGEX, CRC: CRC_REGEX, @@ -27,6 +28,12 @@ const REGEX_BY_COUNCIL: Record = { const isKnownStateCode = (value: string): boolean => DATA.some((state) => state.code === value); +const isKnownCrpRegion = (value: string): boolean => { + const region = Number(value); + + return region >= CRP_MIN_REGION && region <= CRP_MAX_REGION; +}; + /** * Checks the structure of a professional council registration number (registro/inscrição * profissional). @@ -41,16 +48,25 @@ const isKnownStateCode = (value: string): boolean => DATA.some((state) => state. * - `"CRM"` (Conselho Regional de Medicina): 4 to 6 digits + UF, e.g. `"123456-SP"`. * - `"CRO"` (Conselho Regional de Odontologia): 3 to 6 digits + UF, e.g. `"12345/SP"`. * - `"CRP"` (Conselho Regional de Psicologia): 2 digit regional code + 4 to 6 digits, e.g. - * `"06/12345"`. The regional code is not a literal UF (some regions cover more than one - * state), so `options.stateCode` is ignored for this council. - * - `"CRC"` (Conselho Regional de Contabilidade): UF + 4 to 6 digits + category (`"O"` for - * Contador/Organização Contábil or `"T"` for Técnico em Contabilidade) + 1 check digit - * whose value is not verified, e.g. `"SP-123456/O-3"`. + * `"06/12345"`. The regional code must be one of the 24 Conselhos Regionais of the CFP + * system, CRP-01 to CRP-24. It is not a literal UF (some regions cover more than one state), + * so `options.stateCode` is ignored for this council. + * - `"CRC"` (Conselho Regional de Contabilidade): UF + 6 digits + the tipo de registro (`"O"` + * Originário, `"P"` Provisório or `"T"` Transferido) + 1 check digit whose value is not + * verified, e.g. `"SP-123456/O-3"`. The letter says nothing about the professional category: + * the Manual de Registro states that the distinction between `"O"` and `"P"` applies + * "independentemente da categoria profissional do contabilista", and `"T"` comes from the + * Resolução CFC nº 1.707/2023, art. 5º, parágrafo único, which appends it to the número do + * Registro Originário when a registration is transferred to another CRC. * * CREA (Conselho Regional de Engenharia e Agronomia) is not supported: since the 2016 national * unification (RNP) its registration number format could not be confirmed from an official, * publicly documented source. * + * Only the CRC and the CRP shapes rest on a published source. The OAB, the CFM and the CFO do + * not publish the format of the numbers their seccionais and regionais issue, so the digit + * ranges accepted for `"OAB"`, `"CRM"` and `"CRO"` are conventional rather than normative. + * * @param {string} value - The registration number to be validated. * @param {IsValidRegistroProfissionalOptions} options - The validation options. * @param {RegistroProfissionalCouncil} options.council - The issuing council. @@ -67,6 +83,19 @@ const isKnownStateCode = (value: string): boolean => DATA.some((state) => state. * isValidRegistroProfissional("SP-123456/O-3", { council: "CRC" }); // true * isValidRegistroProfissional("123456", { council: "OAB" }); // false (no UF) * ``` + * + * @see Official: https://cfc.org.br/wp-content/uploads/2018/04/1_manual_registro.pdf Manual de + * Registro do Sistema CFC/CRCs, item 1.1: the CRC registration is the sigla of the UF, six + * sequential digits, the letter of the tipo de registro and a check digit, with "UF-000001/P-7" + * and "UF-000002/O-5" as its own worked examples. + * @see Official: https://site.cfp.org.br/cfp/sistema-conselhos/conselhos-pelo-brasil/ Conselho + * Federal de Psicologia: the 24 Conselhos Regionais of the system, numbered CRP-01 to CRP-24. + * @see Based on: https://www.oab.org.br/ Ordem dos Advogados do Brasil (OAB), which publishes + * no format for the número de inscrição and the seccional. + * @see Based on: https://portal.cfm.org.br/ Conselho Federal de Medicina (CRM), which publishes + * no format for the registration number and the UF. + * @see Based on: https://cfo.org.br/ Conselho Federal de Odontologia (CRO), which publishes no + * format for the registration number and the UF. */ export const isValidRegistroProfissional = ( value: string, @@ -84,7 +113,9 @@ export const isValidRegistroProfissional = ( if (!match?.groups) return false; - const { uf } = match.groups; + const { region, uf } = match.groups; + + if (region !== undefined && !isKnownCrpRegion(region)) return false; if (uf === undefined) return true; From 862a7824bfa5492cc10a7291aa41fb6f5968e042 Mon Sep 17 00:00:00 2001 From: Hyan Mandian <5044101+hyanmandian@users.noreply.github.com> Date: Sat, 12 Sep 2026 20:54:37 -0300 Subject: [PATCH 10/15] =?UTF-8?q?fix(certidao):=20reject=20unknown=20book?= =?UTF-8?q?=20types=20and=20take=20the=20matr=C3=ADcula=20as=20a=20string?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/_internals/constants/certidao.ts | 8 ++-- src/format-certidao/format-certidao.test.ts | 14 ++++--- src/format-certidao/format-certidao.ts | 28 ++++++++------ .../is-valid-certidao.test.ts | 26 +++++++++---- src/is-valid-certidao/is-valid-certidao.ts | 38 ++++++++++--------- src/parse-certidao/constants.ts | 13 +++++-- src/parse-certidao/parse-certidao.test.ts | 9 ++++- src/parse-certidao/parse-certidao.ts | 29 ++++++++------ 8 files changed, 104 insertions(+), 61 deletions(-) diff --git a/src/_internals/constants/certidao.ts b/src/_internals/constants/certidao.ts index 99fc5ac6..a85ba8f2 100644 --- a/src/_internals/constants/certidao.ts +++ b/src/_internals/constants/certidao.ts @@ -3,10 +3,12 @@ * 6 (CNS da serventia) + 2 (acervo) + 2 (serviço) + 4 (ano) + 1 (tipo do livro) + 5 (livro) + * 3 (folha) + 7 (termo) + 2 (dígitos verificadores). * - * @see Official: https://atos.cnj.jus.br/atos/detalhar/1310 Provimento CNJ nº 3, de 17/11/2009, - * which instituted the modelo único de certidão and its 32 digit matrícula. + * @see Official: https://atos.cnj.jus.br/atos/detalhar/5243 Código Nacional de Normas da + * Corregedoria Nacional de Justiça - Foro Extrajudicial (Provimento CNJ nº 149/2023), art. 473 + * in the wording of the Provimento CN nº 182, de 17/09/2024: the in-force layout of the 32 + * digit matrícula. * @see Official: https://atos.cnj.jus.br/atos/detalhar/1311 Provimento CNJ nº 2, de 27/04/2009, - * which instituted the Código Nacional de Serventias (CNS). + * which instituted the modelos únicos de certidão and the matrícula (revoked; historical). * @see Based on: http://ghiorzi.org/DVnew.htm Worked example of the two check digits * (sums 288 and 309). * @see Based on: https://github.com/klawdyo/validation-br/blob/feat-certidao/src/certidao.ts diff --git a/src/format-certidao/format-certidao.test.ts b/src/format-certidao/format-certidao.test.ts index 96b7f4e8..38ae15e0 100644 --- a/src/format-certidao/format-certidao.test.ts +++ b/src/format-certidao/format-certidao.test.ts @@ -60,9 +60,10 @@ describe("formatCertidao", () => { }); }); - describe("should accept a number", () => { - test("for a value short enough to be an exact integer", () => { - expect(formatCertidao(104_539_015_520)).toBe("104539 01 55 20"); + describe("should refuse a number", () => { + test("because the 32 digits of a matrícula do not fit in a JavaScript number", () => { + // @ts-expect-error: intentionally invalid input + expect(formatCertidao(104_539_015_520)).toBe(""); }); }); @@ -101,7 +102,8 @@ describe("formatCertidao", () => { fc.assert( fc.property(fc.string({ unit: "grapheme" }), fc.integer(), (text, number) => { expect(typeof formatCertidao(text)).toBe("string"); - expect(typeof formatCertidao(number)).toBe("string"); + // @ts-expect-error: intentionally invalid input + expect(formatCertidao(number)).toBe(""); }), ); }); @@ -109,8 +111,8 @@ describe("formatCertidao", () => { }); describe("formatCertidao types", () => { - test("should take a string or number, optional options, and return a string", () => { - expectTypeOf(formatCertidao).parameter(0).toEqualTypeOf(); + test("should take a string, optional options, and return a string", () => { + expectTypeOf(formatCertidao).parameter(0).toEqualTypeOf(); expectTypeOf(formatCertidao).parameter(1).toEqualTypeOf(); expectTypeOf().toEqualTypeOf(); expectTypeOf(formatCertidao).returns.toEqualTypeOf(); diff --git a/src/format-certidao/format-certidao.ts b/src/format-certidao/format-certidao.ts index 1e1bbf3a..fc0910f4 100644 --- a/src/format-certidao/format-certidao.ts +++ b/src/format-certidao/format-certidao.ts @@ -1,6 +1,5 @@ import { CERTIDAO_PATTERN } from "../_internals/constants/certidao"; import { format } from "../_internals/format/format"; -import { isNullish } from "../_internals/is-nullish/is-nullish"; import { sanitizeToDigits } from "../_internals/sanitize-to-digits/sanitize-to-digits"; /** Options of `formatCertidao`. */ @@ -10,10 +9,13 @@ export type FormatCertidaoOptions = { }; /** - * Formats the matrícula of a certidão de registro civil into the printed mask of the - * Provimento, the 32 digits grouped as 6 2 2 4 1 5 3 7 2 and separated by spaces. + * Formats the matrícula of a certidão de registro civil into the printed mask of the norm, the + * 32 digits grouped as 6 2 2 4 1 5 3 7 2 and separated by spaces. * - * @param {string|number} value - The matrícula value to be formatted. It can be a string or a number. + * Only a string is accepted: the 32 digits of a matrícula are more than a JavaScript number can + * hold, so a numeric argument gives an empty string instead of the digits of a rounded value. + * + * @param {string} value - The matrícula value to be formatted. * @param {FormatCertidaoOptions} [options] - Optional formatting options. * @param {boolean} options.pad - If true, pads the value with leading zeros if necessary. * @returns {string} The formatted matrícula in the pattern "000000 00 00 0000 0 00000 000 0000000 00". @@ -30,10 +32,12 @@ export type FormatCertidaoOptions = { * // "000000 01 55 2010 1 00020 112 0000120 87" * ``` * - * @see Official: https://atos.cnj.jus.br/atos/detalhar/1310 Provimento CNJ nº 3, de 17/11/2009, - * which instituted the modelo único de certidão and its 32 digit matrícula. + * @see Official: https://atos.cnj.jus.br/atos/detalhar/5243 Código Nacional de Normas da + * Corregedoria Nacional de Justiça - Foro Extrajudicial (Provimento CNJ nº 149/2023), art. 473 + * in the wording of the Provimento CN nº 182, de 17/09/2024: the in-force layout of the 32 + * digit matrícula. * @see Official: https://atos.cnj.jus.br/atos/detalhar/1311 Provimento CNJ nº 2, de 27/04/2009, - * which instituted the Código Nacional de Serventias (CNS). + * which instituted the modelos únicos de certidão and the matrícula (revoked; historical). * @see Based on: http://ghiorzi.org/DVnew.htm Worked example of the two check digits * (sums 288 and 309). * @see Based on: https://github.com/klawdyo/validation-br/blob/feat-certidao/src/certidao.ts @@ -41,11 +45,11 @@ export type FormatCertidaoOptions = { * @see Based on: https://github.com/geekcom/validator-docs/blob/master/src/validator-docs/Rules/Certidao.php * Third reference implementation agreeing on the weights and on the remainder of 10 read as 1. */ -export const formatCertidao = (value: string | number, options?: FormatCertidaoOptions): string => - isNullish(value) - ? "" - : format({ +export const formatCertidao = (value: string, options?: FormatCertidaoOptions): string => + typeof value === "string" + ? format({ pad: options?.pad, value: sanitizeToDigits(value), pattern: CERTIDAO_PATTERN, - }); + }) + : ""; diff --git a/src/is-valid-certidao/is-valid-certidao.test.ts b/src/is-valid-certidao/is-valid-certidao.test.ts index 050cff2d..2f636a9e 100644 --- a/src/is-valid-certidao/is-valid-certidao.test.ts +++ b/src/is-valid-certidao/is-valid-certidao.test.ts @@ -61,8 +61,13 @@ describe("isValidCertidao", () => { }); test("when it is a number, which cannot carry the 32 significant digits of a matrícula", () => { + // @ts-expect-error: intentionally invalid input expect(isValidCertidao(1_045_390_155)).toBe(false); }); + + test("when the book code is 0, outside the nine books, even with matching check digits", () => { + expect(isValidCertidao("10453901552013000012021000012387")).toBe(false); + }); }); describe("should return true", () => { @@ -145,16 +150,23 @@ describe("isValidCertidao", () => { ).toBe(true); }); - test("should return true when the check digits match and accept is not given, book code 0", () => { - expect(isValidCertidao("10453901552013000012021000012387")).toBe(true); - }); - test("should return false when the book code is 0, outside the nine books of the Provimento, and accept is given", () => { expect(isValidCertidao("10453901552013000012021000012387", { accept: ["birth"] })).toBe( false, ); }); + test("should fall back to accepting every book type when accept is not an array", () => { + expect( + // @ts-expect-error: intentionally invalid input + isValidCertidao("104539 01 55 2013 1 00012 021 0000123 21", { accept: "birth" }), + ).toBe(true); + expect( + // @ts-expect-error: intentionally invalid input + isValidCertidao("104539 01 55 2013 1 00012 021 0000123 21", { accept: {} }), + ).toBe(true); + }); + test("should return false when the matrícula itself is invalid, regardless of accept", () => { expect(isValidCertidao("123456", { accept: ["birth"] })).toBe(false); }); @@ -170,7 +182,7 @@ describe("isValidCertidao", () => { }); describe("properties", () => { - const bases = fc.stringMatching(/^[0-9]{30}$/); + const bases = fc.stringMatching(/^[0-9]{14}[1-9][0-9]{15}$/); const books = fc.tuple( fc.stringMatching(/^[0-9]{14}$/), @@ -222,8 +234,8 @@ describe("isValidCertidao", () => { }); describe("isValidCertidao types", () => { - test("should take a string or number, optional options, and return a boolean", () => { - expectTypeOf(isValidCertidao).parameter(0).toEqualTypeOf(); + test("should take a string, optional options, and return a boolean", () => { + expectTypeOf(isValidCertidao).parameter(0).toEqualTypeOf(); expectTypeOf(isValidCertidao).parameter(1).toEqualTypeOf(); expectTypeOf().toEqualTypeOf(); expectTypeOf(isValidCertidao).returns.toEqualTypeOf(); diff --git a/src/is-valid-certidao/is-valid-certidao.ts b/src/is-valid-certidao/is-valid-certidao.ts index 8412692f..7ff19502 100644 --- a/src/is-valid-certidao/is-valid-certidao.ts +++ b/src/is-valid-certidao/is-valid-certidao.ts @@ -39,13 +39,16 @@ const getCheckDigit = (value: string): number => { * 0, 1, ... In both passes the check digit is the remainder itself, with a remainder of 10 read * as 1. * - * `options.accept` restricts which of the nine books (see `CertidaoType`, reused from - * `parseCertidao`) count as valid: when given, the book-type digit (fifteenth position of the - * matrícula) must map to one of the listed types, so a matrícula whose digit is `0` or greater - * than `9` (not one of the nine defined books) is also rejected. When omitted, every book type - * is accepted and the digit is not otherwise checked, matching the previous behavior. + * The book-type digit (fifteenth position of the matrícula) always has to name one of the nine + * books (see `CertidaoType`, reused from `parseCertidao`), so a matrícula whose digit is `0` is + * rejected however good its check digits are, the same way `parseCertidao` returns `null` for + * it. `options.accept` narrows that further to the listed types; when it is omitted, or when it + * is not an array, every book type is accepted. * - * @param {string|number} value - The matrícula value to be validated. + * Only a string is accepted: the 32 digits of a matrícula are more than a JavaScript number can + * hold, so a numeric argument is always rejected instead of being read as a rounded value. + * + * @param {string} value - The matrícula value to be validated. * @param {IsValidCertidaoOptions} [options] - Optional validation options. * @param {CertidaoType[]} [options.accept] - The book types to accept. Defaults to all of them. * @returns {boolean} True if the matrícula is valid, false otherwise. @@ -60,10 +63,12 @@ const getCheckDigit = (value: string): number => { * isValidCertidao("104539 01 55 2013 1 00012 021 0000123 21", { accept: ["death"] }); // false * ``` * - * @see Official: https://atos.cnj.jus.br/atos/detalhar/1310 Provimento CNJ nº 3, de 17/11/2009, - * which instituted the modelo único de certidão and its 32 digit matrícula. + * @see Official: https://atos.cnj.jus.br/atos/detalhar/5243 Código Nacional de Normas da + * Corregedoria Nacional de Justiça - Foro Extrajudicial (Provimento CNJ nº 149/2023), art. 473 + * in the wording of the Provimento CN nº 182, de 17/09/2024: the in-force layout of the 32 + * digit matrícula. * @see Official: https://atos.cnj.jus.br/atos/detalhar/1311 Provimento CNJ nº 2, de 27/04/2009, - * which instituted the Código Nacional de Serventias (CNS). + * which instituted the modelos únicos de certidão and the matrícula (revoked; historical). * @see Based on: http://ghiorzi.org/DVnew.htm Worked example of the two check digits * (sums 288 and 309). * @see Based on: https://github.com/klawdyo/validation-br/blob/feat-certidao/src/certidao.ts @@ -71,10 +76,7 @@ const getCheckDigit = (value: string): number => { * @see Based on: https://github.com/geekcom/validator-docs/blob/master/src/validator-docs/Rules/Certidao.php * Third reference implementation agreeing on the weights and on the remainder of 10 read as 1. */ -export const isValidCertidao = ( - value: string | number, - options?: IsValidCertidaoOptions, -): boolean => { +export const isValidCertidao = (value: string, options?: IsValidCertidaoOptions): boolean => { if (typeof value !== "string") return false; const digits = sanitizeToDigits(value); @@ -87,12 +89,14 @@ export const isValidCertidao = ( if (digits.slice(CERTIDAO_BASE_LENGTH) !== `${first}${second}`) return false; + const typeCode = digits.charCodeAt(14) - 48; + const type: CertidaoType | undefined = CERTIDAO_TYPES[typeCode - 1]; + + if (type === undefined) return false; + const accept = options?.accept; if (!Array.isArray(accept)) return true; - const typeCode = digits.charCodeAt(14) - 48; - const type: CertidaoType | undefined = CERTIDAO_TYPES[typeCode - 1]; - - return type !== undefined && accept.includes(type); + return accept.includes(type); }; diff --git a/src/parse-certidao/constants.ts b/src/parse-certidao/constants.ts index 40b5b157..1d682790 100644 --- a/src/parse-certidao/constants.ts +++ b/src/parse-certidao/constants.ts @@ -5,10 +5,17 @@ * (proclamas), Livro E (demais atos), Livro E desdobrado para emancipações and Livro E * desdobrado para interdições. * - * @see Official: https://atos.cnj.jus.br/atos/detalhar/1310 Provimento CNJ nº 3, de 17/11/2009, - * which instituted the modelo único de certidão and its 32 digit matrícula. + * The in-force art. 473, V of the Código Nacional de Normas da Corregedoria Nacional de Justiça + * lists only the codes 1 to 7. The codes 8 (emancipação) and 9 (interdição) come from the Anexo + * IV of the revoked Provimento CNJ nº 63/2017 and are kept because matrículas issued under it + * are still in circulation. + * + * @see Official: https://atos.cnj.jus.br/atos/detalhar/5243 Código Nacional de Normas da + * Corregedoria Nacional de Justiça - Foro Extrajudicial (Provimento CNJ nº 149/2023), art. 473 + * in the wording of the Provimento CN nº 182, de 17/09/2024: the in-force layout of the 32 + * digit matrícula. * @see Official: https://atos.cnj.jus.br/atos/detalhar/1311 Provimento CNJ nº 2, de 27/04/2009, - * which instituted the Código Nacional de Serventias (CNS). + * which instituted the modelos únicos de certidão and the matrícula (revoked; historical). * @see Based on: http://ghiorzi.org/DVnew.htm Description of the nine books and their codes. * @see Based on: https://github.com/Casilhero/brazilian-validators/blob/main/src/Support/CertidaoInfo.php * Reference implementation agreeing on the same nine books, in the same order. diff --git a/src/parse-certidao/parse-certidao.test.ts b/src/parse-certidao/parse-certidao.test.ts index 26dee308..813507fe 100644 --- a/src/parse-certidao/parse-certidao.test.ts +++ b/src/parse-certidao/parse-certidao.test.ts @@ -41,6 +41,11 @@ describe("parseCertidao", () => { test("when the book code is 0, outside the nine books of the Provimento", () => { expect(parseCertidao("10453901552013000012021000012387")).toBeNull(); }); + + test("when it is a number, which cannot carry the 32 significant digits of a matrícula", () => { + // @ts-expect-error: intentionally invalid input + expect(parseCertidao(1_045_390_155)).toBeNull(); + }); }); describe("should return the parsed matrícula", () => { @@ -169,8 +174,8 @@ describe("parseCertidao", () => { }); describe("parseCertidao types", () => { - test("should take a string or number and return a Certidao or null", () => { - expectTypeOf(parseCertidao).parameter(0).toEqualTypeOf(); + test("should take a string and return a Certidao or null", () => { + expectTypeOf(parseCertidao).parameter(0).toEqualTypeOf(); expectTypeOf(parseCertidao).returns.toEqualTypeOf(); expectTypeOf().toEqualTypeOf<{ registryCns: string; diff --git a/src/parse-certidao/parse-certidao.ts b/src/parse-certidao/parse-certidao.ts index 5165ccf1..2f12e0ac 100644 --- a/src/parse-certidao/parse-certidao.ts +++ b/src/parse-certidao/parse-certidao.ts @@ -7,6 +7,11 @@ import { CERTIDAO_TYPES } from "./constants"; * The nine books (tipo do livro) a matrícula de registro civil can point to, in the order of the * codes 1 to 9. `parseCertidao` names the book of a matrícula with one of these, and * `isValidCertidao` accepts a list of them. + * + * The in-force art. 473, V of the Código Nacional de Normas da Corregedoria Nacional de Justiça + * lists only the codes 1 to 7. The codes 8 (`"emancipation"`) and 9 (`"interdiction"`) come from + * the Anexo IV of the revoked Provimento CNJ nº 63/2017 and are kept because matrículas issued + * under it are still in circulation. */ export type CertidaoType = | "birth" @@ -47,10 +52,13 @@ export type Certidao = { * Parses the matrícula of a certidão de registro civil into its fields. * * Accepts the same input forms as `isValidCertidao` and returns `null` when the matrícula is - * not valid or when its book code is not one of the nine books defined by the Provimento, since - * an unknown book cannot be named. + * not valid, which includes a book code that is not one of the nine books defined by the + * Provimento, since an unknown book cannot be named. + * + * Only a string is accepted: the 32 digits of a matrícula are more than a JavaScript number can + * hold, so a numeric argument always gives `null` instead of being read as a rounded value. * - * @param {string|number} value - The matrícula value to be parsed. + * @param {string} value - The matrícula value to be parsed. * @returns {Certidao | null} The parsed matrícula, or `null` when it is not valid. * * @example @@ -62,10 +70,12 @@ export type Certidao = { * parseCertidao("invalid"); // null * ``` * - * @see Official: https://atos.cnj.jus.br/atos/detalhar/1310 Provimento CNJ nº 3, de 17/11/2009, - * which instituted the modelo único de certidão and its 32 digit matrícula. + * @see Official: https://atos.cnj.jus.br/atos/detalhar/5243 Código Nacional de Normas da + * Corregedoria Nacional de Justiça - Foro Extrajudicial (Provimento CNJ nº 149/2023), art. 473 + * in the wording of the Provimento CN nº 182, de 17/09/2024: the in-force layout of the 32 + * digit matrícula. * @see Official: https://atos.cnj.jus.br/atos/detalhar/1311 Provimento CNJ nº 2, de 27/04/2009, - * which instituted the Código Nacional de Serventias (CNS). + * which instituted the modelos únicos de certidão and the matrícula (revoked; historical). * @see Based on: http://ghiorzi.org/DVnew.htm Worked example of the two check digits * (sums 288 and 309). * @see Based on: https://github.com/klawdyo/validation-br/blob/feat-certidao/src/certidao.ts @@ -73,15 +83,12 @@ export type Certidao = { * @see Based on: https://github.com/geekcom/validator-docs/blob/master/src/validator-docs/Rules/Certidao.php * Third reference implementation agreeing on the weights and on the remainder of 10 read as 1. */ -export const parseCertidao = (value: string | number): Certidao | null => { +export const parseCertidao = (value: string): Certidao | null => { if (!isValidCertidao(value)) return null; const digits = sanitizeToDigits(value); const typeCode = digits.charCodeAt(14) - 48; - - const type: CertidaoType | undefined = CERTIDAO_TYPES[typeCode - 1]; - - if (type === undefined) return null; + const type = CERTIDAO_TYPES[typeCode - 1]; return { registryCns: digits.slice(0, 6), From b2795cd0e37c32d6de11a9530d78da7ca69e1cb7 Mon Sep 17 00:00:00 2001 From: Hyan Mandian <5044101+hyanmandian@users.noreply.github.com> Date: Sat, 12 Sep 2026 20:54:37 -0300 Subject: [PATCH 11/15] fix(cns): reject values that are not fifteen digits with optional separators --- src/_internals/constants/cns.ts | 9 +++++++-- src/format-cns/format-cns.ts | 2 ++ src/is-valid-cns/is-valid-cns.test.ts | 21 +++++++++++++++++++++ src/is-valid-cns/is-valid-cns.ts | 10 ++++++++-- 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/_internals/constants/cns.ts b/src/_internals/constants/cns.ts index 049941b5..3d238f5a 100644 --- a/src/_internals/constants/cns.ts +++ b/src/_internals/constants/cns.ts @@ -2,10 +2,15 @@ * CNS (Cartão Nacional de Saúde) structural constants, shared by `isValidCns` and `formatCns`. * * @see Official: https://rni-docs.anvisa.gov.br/docs/regras_gerais/validacoes/validacaoCNS/ + * @see Based on: https://integracao.esusab.ufsc.br/ledi/documentacao/regras/algoritmo_CNS.html + * e-SUS APS documentation of the same DATASUS algorithm, reachable without a browser. */ -/** Total digits of a CNS number. */ -export const CNS_LENGTH = 15; +/** + * Shape a CNS number has to be written in: the 15 digits, optionally split into the printed + * groups of 3, 4, 4 and 4 by whitespace or the usual mask characters. + */ +export const CNS_FORMAT_REGEX = /^\d{3}[\s.\-/]*\d{4}[\s.\-/]*\d{4}[\s.\-/]*\d{4}$/; /** Digits of the PIS/PASEP/NIS derived base embedded in a definitive CNS (starts with 1 or 2). */ export const CNS_DEFINITIVE_BASE_LENGTH = 11; diff --git a/src/format-cns/format-cns.ts b/src/format-cns/format-cns.ts index 803fc487..deea847b 100644 --- a/src/format-cns/format-cns.ts +++ b/src/format-cns/format-cns.ts @@ -25,6 +25,8 @@ export type FormatCnsOptions = { * ``` * * @see Official: https://rni-docs.anvisa.gov.br/docs/regras_gerais/validacoes/validacaoCNS/ + * @see Based on: https://integracao.esusab.ufsc.br/ledi/documentacao/regras/algoritmo_CNS.html + * e-SUS APS documentation of the same DATASUS algorithm, reachable without a browser. */ export const formatCns = (value: string | number, options?: FormatCnsOptions): string => isNullish(value) diff --git a/src/is-valid-cns/is-valid-cns.test.ts b/src/is-valid-cns/is-valid-cns.test.ts index 08004417..61f00a0c 100644 --- a/src/is-valid-cns/is-valid-cns.test.ts +++ b/src/is-valid-cns/is-valid-cns.test.ts @@ -86,6 +86,19 @@ describe("isValidCns", () => { test("when the first digit is not 7, 8 or 9, even though one of them appears later and the rest forms a valid provisional checksum", () => { expect(isValidCns("070000000000001")).toBe(false); }); + + test("when letters are wrapped around the digits of a valid card", () => { + expect(isValidCns("abc123456789010000")).toBe(false); + expect(isValidCns("123456789010000abc")).toBe(false); + }); + + test("when letters are mixed in between the digits of a valid card", () => { + expect(isValidCns("1a2b3c456789010000")).toBe(false); + }); + + test("when the 15 digits are grouped outside the printed 3-4-4-4 mask", () => { + expect(isValidCns("1234 5678 9010 000")).toBe(false); + }); }); describe("should return true", () => { @@ -105,6 +118,14 @@ describe("isValidCns", () => { expect(isValidCns("123 4567 8901 0000")).toBe(true); }); + test("for a definitive CNS with a dotted mask", () => { + expect(isValidCns("123.4567.8901.0000")).toBe(true); + }); + + test("for a definitive CNS with leading and trailing whitespace", () => { + expect(isValidCns(" 123456789010000 ")).toBe(true); + }); + test("for a definitive CNS whose raw check digit is 10 (base 10000000006, weighted sum 45): sum raised to 47, digit 8, suffix 001", () => { expect(isValidCns("100000000060018")).toBe(true); }); diff --git a/src/is-valid-cns/is-valid-cns.ts b/src/is-valid-cns/is-valid-cns.ts index c6dd0c7b..3e4e7b34 100644 --- a/src/is-valid-cns/is-valid-cns.ts +++ b/src/is-valid-cns/is-valid-cns.ts @@ -2,7 +2,7 @@ import { CNS_DEFINITIVE_ADJUSTED_SUFFIX, CNS_DEFINITIVE_BASE_LENGTH, CNS_DEFINITIVE_SUFFIX, - CNS_LENGTH, + CNS_FORMAT_REGEX, } from "../_internals/constants/cns"; import { generateChecksum } from "../_internals/generate-checksum/generate-checksum"; import { sanitizeToDigits } from "../_internals/sanitize-to-digits/sanitize-to-digits"; @@ -41,6 +41,10 @@ const isValidProvisional = (digits: string): boolean => * are validated by a single weighted sum (weights 15 down to 1 over all 15 digits) that must * be a multiple of 11. * + * The value has to be written as the 15 digits, optionally split into the printed groups of 3, + * 4, 4 and 4 by whitespace or the usual mask characters; anything else, a letter among the + * digits included, is rejected instead of being read past. + * * @param {string|number} value - The CNS value to be validated. * @returns {boolean} True if the CNS is valid, false otherwise. * @@ -54,13 +58,15 @@ const isValidProvisional = (digits: string): boolean => * ``` * * @see Official: https://rni-docs.anvisa.gov.br/docs/regras_gerais/validacoes/validacaoCNS/ + * @see Based on: https://integracao.esusab.ufsc.br/ledi/documentacao/regras/algoritmo_CNS.html + * e-SUS APS documentation of the same DATASUS algorithm, reachable without a browser. */ export const isValidCns = (value: string | number): boolean => { if (typeof value !== "string" && typeof value !== "number") return false; const digits = sanitizeToDigits(value); - if (digits.length !== CNS_LENGTH) return false; + if (!CNS_FORMAT_REGEX.test(String(value).trim())) return false; if (DEFINITIVE_FIRST_DIGIT_REGEX.test(digits)) return isValidDefinitive(digits); From bea8c48688a7bf108b6a9f207f1c1a81e6b1763c Mon Sep 17 00:00:00 2001 From: Hyan Mandian <5044101+hyanmandian@users.noreply.github.com> Date: Sat, 12 Sep 2026 20:54:38 -0300 Subject: [PATCH 12/15] refactor: share the CEI mask, drop the unused CAEPF length constants --- .../calculate-cei-check-digit.ts | 13 ++++++++++--- src/_internals/constants/cei.ts | 12 ++++++++++++ .../is-valid-cei-cno-number.ts | 10 ++++++++++ src/format-caepf/constants.ts | 2 -- src/format-caepf/format-caepf.ts | 2 ++ src/format-cei/constants.ts | 1 - src/format-cei/format-cei.ts | 7 +++++-- src/format-cno/constants.ts | 1 - src/format-cno/format-cno.ts | 7 +++++-- src/is-valid-caepf/constants.ts | 7 +++++-- src/is-valid-caepf/is-valid-caepf.ts | 5 +++++ src/is-valid-cei/is-valid-cei.ts | 10 ++++++++++ src/is-valid-cno/is-valid-cno.ts | 13 ++++++++++--- 13 files changed, 74 insertions(+), 16 deletions(-) delete mode 100644 src/format-cei/constants.ts delete mode 100644 src/format-cno/constants.ts diff --git a/src/_internals/calculate-cei-check-digit/calculate-cei-check-digit.ts b/src/_internals/calculate-cei-check-digit/calculate-cei-check-digit.ts index d81fd9ae..24950f1f 100644 --- a/src/_internals/calculate-cei-check-digit/calculate-cei-check-digit.ts +++ b/src/_internals/calculate-cei-check-digit/calculate-cei-check-digit.ts @@ -9,6 +9,10 @@ import { generateChecksum } from "../generate-checksum/generate-checksum"; * The tens part and the units part of that sum are added together and the check digit is the * complement of the units digit of the result to 10, with 10 mapped back to 0. * + * The Receita Federal does not publish the check digit rule of the CEI/CNO numbering, so the + * calculation follows the reference implementations cited below, cross-checked against the CNO + * open data of the Receita Federal. + * * @param {string} base - The 11 digits that precede the check digit. * @returns {number} The check digit, 0 to 9. * @@ -19,9 +23,12 @@ import { generateChecksum } from "../generate-checksum/generate-checksum"; * ``` * * @see Official: https://www.gov.br/receitafederal/pt-br/assuntos/orientacao-tributaria/cadastros/cno - * @see Official: Cadastro Nacional de Obras (CNO), dados abertos da Receita Federal: the - * 38432 works registered in Minas Gerais confirm the rule, and their check digits of 0 are - * what shows that a computed 10 maps back to 0, which neither reference implementation does. + * The registry's own page at the Receita Federal, which describes the cadastro but publishes + * neither the mask nor the check digit rule. + * @see Official: https://dados.gov.br/dados/conjuntos-dados/cadastro-nacional-de-obras-cno + * Cadastro Nacional de Obras (CNO), dados abertos da Receita Federal: the 38432 works + * registered in Minas Gerais confirm the rule, and their check digits of 0 are what shows + * that a computed 10 maps back to 0, which neither reference implementation does. * @see Based on: https://github.com/yiibr/yii2-br-validator/blob/master/src/CeiValidator.php * PHP reference implementation of the CEI check digit. * @see Based on: https://github.com/marcos-cruz/Documento/blob/master/src/Bigai.Documentos.Brasil/Cei/Cei.cs diff --git a/src/_internals/constants/cei.ts b/src/_internals/constants/cei.ts index 077ea92f..20b90d95 100644 --- a/src/_internals/constants/cei.ts +++ b/src/_internals/constants/cei.ts @@ -2,7 +2,17 @@ * Numbering shared by the CEI (Cadastro Específico do INSS) and by the CNO (Cadastro Nacional * de Obras) that replaced it: 12 digits printed as "00.000.00000/00". * + * The Receita Federal does not publish the check digit rule of the CEI/CNO numbering, so the + * calculation follows the reference implementations cited below, cross-checked against the CNO + * open data of the Receita Federal. + * * @see Official: https://www.gov.br/receitafederal/pt-br/assuntos/orientacao-tributaria/cadastros/cno + * The registry's own page at the Receita Federal, which describes the cadastro but publishes + * neither the mask nor the check digit rule. + * @see Official: https://dados.gov.br/dados/conjuntos-dados/cadastro-nacional-de-obras-cno + * Cadastro Nacional de Obras (CNO), dados abertos da Receita Federal: every one of the 38432 + * works registered in Minas Gerais passes this check, which is what ties the CNO to the CEI + * rule and where the test vectors come from. * @see Based on: https://github.com/yiibr/yii2-br-validator/blob/master/src/CeiValidator.php * PHP reference implementation of the CEI check digit. * @see Based on: https://github.com/marcos-cruz/Documento/blob/master/src/Bigai.Documentos.Brasil/Cei/Cei.cs @@ -14,3 +24,5 @@ export const CEI_BASE_LENGTH = 11; export const CEI_WEIGHTS = [7, 4, 1, 8, 5, 2, 1, 6, 3, 7, 4]; export const CEI_FORMAT_REGEX = /^\d{2}[\s.\-/]*\d{3}[\s.\-/]*\d{5}[\s.\-/]*\d{2}$/; + +export const CEI_PATTERN = "00.000.00000/00"; diff --git a/src/_internals/is-valid-cei-cno-number/is-valid-cei-cno-number.ts b/src/_internals/is-valid-cei-cno-number/is-valid-cei-cno-number.ts index 06d7fd37..54071830 100644 --- a/src/_internals/is-valid-cei-cno-number/is-valid-cei-cno-number.ts +++ b/src/_internals/is-valid-cei-cno-number/is-valid-cei-cno-number.ts @@ -12,6 +12,10 @@ import { sanitizeToDigits } from "../sanitize-to-digits/sanitize-to-digits"; * that sum to its units part and takes the complement of the units digit of the result to 10, * mapping 10 back to 0. * + * The Receita Federal does not publish the check digit rule of the CEI/CNO numbering, so the + * calculation follows the reference implementations cited below, cross-checked against the CNO + * open data of the Receita Federal. + * * @param {string|number} value - The CEI or CNO value to be validated. * @returns {boolean} True if the value is a valid CEI or CNO number, false otherwise. * @@ -25,6 +29,12 @@ import { sanitizeToDigits } from "../sanitize-to-digits/sanitize-to-digits"; * ``` * * @see Official: https://www.gov.br/receitafederal/pt-br/assuntos/orientacao-tributaria/cadastros/cno + * The registry's own page at the Receita Federal, which describes the cadastro but publishes + * neither the mask nor the check digit rule. + * @see Official: https://dados.gov.br/dados/conjuntos-dados/cadastro-nacional-de-obras-cno + * Cadastro Nacional de Obras (CNO), dados abertos da Receita Federal: every one of the 38432 + * works registered in Minas Gerais passes this check, which is what ties the CNO to the CEI + * rule and where the test vectors come from. * @see Based on: https://github.com/yiibr/yii2-br-validator/blob/master/src/CeiValidator.php * PHP reference implementation of the CEI check digit. * @see Based on: https://github.com/marcos-cruz/Documento/blob/master/src/Bigai.Documentos.Brasil/Cei/Cei.cs diff --git a/src/format-caepf/constants.ts b/src/format-caepf/constants.ts index 6e86247a..177f29e9 100644 --- a/src/format-caepf/constants.ts +++ b/src/format-caepf/constants.ts @@ -1,3 +1 @@ export const PATTERN = "000.000.000/000-00"; - -export const CAEPF_LENGTH = 14; diff --git a/src/format-caepf/format-caepf.ts b/src/format-caepf/format-caepf.ts index 24b133f8..acebbe13 100644 --- a/src/format-caepf/format-caepf.ts +++ b/src/format-caepf/format-caepf.ts @@ -31,6 +31,8 @@ export type FormatCaepfOptions = { * ``` * * @see Official: https://www.gov.br/receitafederal/pt-br/assuntos/orientacao-tributaria/cadastros/caepf + * The registry's own page at the Receita Federal, which describes the cadastro but does not + * print the mask; the mask below is the one the sources cited by `isValidCaepf` agree on. */ export const formatCaepf = (value: string | number, options?: FormatCaepfOptions): string => { if (isNullish(value)) return ""; diff --git a/src/format-cei/constants.ts b/src/format-cei/constants.ts deleted file mode 100644 index 74ec1d3b..00000000 --- a/src/format-cei/constants.ts +++ /dev/null @@ -1 +0,0 @@ -export const PATTERN = "00.000.00000/00"; diff --git a/src/format-cei/format-cei.ts b/src/format-cei/format-cei.ts index f2c8acc0..a4fc7575 100644 --- a/src/format-cei/format-cei.ts +++ b/src/format-cei/format-cei.ts @@ -1,7 +1,7 @@ +import { CEI_PATTERN } from "../_internals/constants/cei"; import { format } from "../_internals/format/format"; import { isNullish } from "../_internals/is-nullish/is-nullish"; import { sanitizeToDigits } from "../_internals/sanitize-to-digits/sanitize-to-digits"; -import { PATTERN } from "./constants"; /** Options of `formatCei`. */ export type FormatCeiOptions = { @@ -30,6 +30,9 @@ export type FormatCeiOptions = { * ``` * * @see Official: https://www.gov.br/receitafederal/pt-br/assuntos/orientacao-tributaria/cadastros/cno + * The registry's own page at the Receita Federal, which describes the cadastro but does not + * print the mask; the mask below is the one the two reference implementations of the check + * digit cited by `isValidCei` agree on. */ export const formatCei = (value: string | number, options?: FormatCeiOptions): string => { if (isNullish(value)) return ""; @@ -37,6 +40,6 @@ export const formatCei = (value: string | number, options?: FormatCeiOptions): s return format({ pad: options?.pad, value: sanitizeToDigits(value), - pattern: PATTERN, + pattern: CEI_PATTERN, }); }; diff --git a/src/format-cno/constants.ts b/src/format-cno/constants.ts deleted file mode 100644 index 74ec1d3b..00000000 --- a/src/format-cno/constants.ts +++ /dev/null @@ -1 +0,0 @@ -export const PATTERN = "00.000.00000/00"; diff --git a/src/format-cno/format-cno.ts b/src/format-cno/format-cno.ts index 43821e35..31bc1e6b 100644 --- a/src/format-cno/format-cno.ts +++ b/src/format-cno/format-cno.ts @@ -1,7 +1,7 @@ +import { CEI_PATTERN } from "../_internals/constants/cei"; import { format } from "../_internals/format/format"; import { isNullish } from "../_internals/is-nullish/is-nullish"; import { sanitizeToDigits } from "../_internals/sanitize-to-digits/sanitize-to-digits"; -import { PATTERN } from "./constants"; /** Options of `formatCno`. */ export type FormatCnoOptions = { @@ -33,6 +33,9 @@ export type FormatCnoOptions = { * ``` * * @see Official: https://www.gov.br/receitafederal/pt-br/assuntos/orientacao-tributaria/cadastros/cno + * The registry's own page at the Receita Federal, which describes the cadastro but does not + * print the mask; the mask below is the one the two reference implementations of the check + * digit cited by `isValidCei` agree on. */ export const formatCno = (value: string | number, options?: FormatCnoOptions): string => { if (isNullish(value)) return ""; @@ -40,6 +43,6 @@ export const formatCno = (value: string | number, options?: FormatCnoOptions): s return format({ pad: options?.pad, value: sanitizeToDigits(value), - pattern: PATTERN, + pattern: CEI_PATTERN, }); }; diff --git a/src/is-valid-caepf/constants.ts b/src/is-valid-caepf/constants.ts index 8aec3824..d761533d 100644 --- a/src/is-valid-caepf/constants.ts +++ b/src/is-valid-caepf/constants.ts @@ -3,15 +3,18 @@ * "000.000.000/000-00", the first 9 being the CPF base of the holder, the next 3 the sequence * of the holder's registrations and the last 2 the check digits. * + * The Receita Federal does not publish the check digit rule of the CAEPF, the shift of 12 + * included, so the calculation follows the reference implementations cited below. + * * @see Official: https://www.gov.br/receitafederal/pt-br/assuntos/orientacao-tributaria/cadastros/caepf + * The registry's own page at the Receita Federal, which describes the cadastro but publishes + * neither the 14 digit layout nor the check digit rule. * @see Based on: http://ghiorzi.org/DVnew.htm Description of the CAEPF layout and of the * shift of 12 applied to the check digit pair. * @see Based on: https://github.com/VitorLuizC/brazilian-values/blob/master/src/validators/isCAEPF.ts * Reference implementation agreeing on the weights and on the shift. */ -export const CAEPF_LENGTH = 14; - export const CAEPF_BASE_LENGTH = 12; export const CAEPF_FIRST_WEIGHTS = [6, 7, 8, 9, 2, 3, 4, 5, 6, 7, 8, 9]; diff --git a/src/is-valid-caepf/is-valid-caepf.ts b/src/is-valid-caepf/is-valid-caepf.ts index 3f1dbffd..f3679710 100644 --- a/src/is-valid-caepf/is-valid-caepf.ts +++ b/src/is-valid-caepf/is-valid-caepf.ts @@ -21,6 +21,9 @@ const getCheckDigit = (base: string, weights: number[]): number => * with a remainder of 10 read as 0. The pair is then shifted by 12, wrapping around 100, so a * CAEPF whose plain modulus 11 digits would be 72 is printed with 84. * + * The Receita Federal does not publish the check digit rule of the CAEPF, the shift of 12 + * included, so the calculation follows the reference implementations cited below. + * * @param {string|number} value - The CAEPF value to be validated. * @returns {boolean} True if the CAEPF is valid, false otherwise. * @@ -34,6 +37,8 @@ const getCheckDigit = (base: string, weights: number[]): number => * ``` * * @see Official: https://www.gov.br/receitafederal/pt-br/assuntos/orientacao-tributaria/cadastros/caepf + * The registry's own page at the Receita Federal, which describes the cadastro but publishes + * neither the 14 digit layout nor the check digit rule. * @see Based on: http://ghiorzi.org/DVnew.htm Description of the CAEPF layout and of the * shift of 12 applied to the check digit pair. * @see Based on: https://github.com/VitorLuizC/brazilian-values/blob/master/src/validators/isCAEPF.ts diff --git a/src/is-valid-cei/is-valid-cei.ts b/src/is-valid-cei/is-valid-cei.ts index d916985d..f40e8e72 100644 --- a/src/is-valid-cei/is-valid-cei.ts +++ b/src/is-valid-cei/is-valid-cei.ts @@ -10,6 +10,10 @@ import { isValidCeiCnoNumber } from "../_internals/is-valid-cei-cno-number/is-va * mapping 10 back to 0. The CEI was replaced by the CNO for construction works and by the CAEPF * for individuals, but numbers already issued keep their meaning and their check digit. * + * The Receita Federal does not publish the check digit rule of the CEI/CNO numbering, so the + * calculation follows the reference implementations cited below, cross-checked against the CNO + * open data of the Receita Federal. + * * @param {string|number} value - The CEI value to be validated. * @returns {boolean} True if the CEI is valid, false otherwise. * @@ -23,6 +27,12 @@ import { isValidCeiCnoNumber } from "../_internals/is-valid-cei-cno-number/is-va * ``` * * @see Official: https://www.gov.br/receitafederal/pt-br/assuntos/orientacao-tributaria/cadastros/cno + * The registry's own page at the Receita Federal, which describes the cadastro but publishes + * neither the mask nor the check digit rule. + * @see Official: https://dados.gov.br/dados/conjuntos-dados/cadastro-nacional-de-obras-cno + * Cadastro Nacional de Obras (CNO), dados abertos da Receita Federal: every one of the 38432 + * works registered in Minas Gerais passes this check, which is what ties the CNO to the CEI + * rule and where the test vectors come from. * @see Based on: https://github.com/yiibr/yii2-br-validator/blob/master/src/CeiValidator.php * PHP reference implementation of the CEI check digit. * @see Based on: https://github.com/marcos-cruz/Documento/blob/master/src/Bigai.Documentos.Brasil/Cei/Cei.cs diff --git a/src/is-valid-cno/is-valid-cno.ts b/src/is-valid-cno/is-valid-cno.ts index 40e45428..f49fa7d3 100644 --- a/src/is-valid-cno/is-valid-cno.ts +++ b/src/is-valid-cno/is-valid-cno.ts @@ -9,6 +9,10 @@ import { isValidCeiCnoNumber } from "../_internals/is-valid-cei-cno-number/is-va * the weights 7, 4, 1, 8, 5, 2, 1, 6, 3, 7 and 4. A work registered under a legacy CEI keeps * the same number in the CNO, so both registries validate identically. * + * The Receita Federal does not publish the check digit rule of the CEI/CNO numbering, so the + * calculation follows the reference implementations cited below, cross-checked against the CNO + * open data of the Receita Federal. + * * @param {string|number} value - The CNO value to be validated. * @returns {boolean} True if the CNO is valid, false otherwise. * @@ -22,9 +26,12 @@ import { isValidCeiCnoNumber } from "../_internals/is-valid-cei-cno-number/is-va * ``` * * @see Official: https://www.gov.br/receitafederal/pt-br/assuntos/orientacao-tributaria/cadastros/cno - * @see Official: Cadastro Nacional de Obras (CNO), dados abertos da Receita Federal: every - * one of the 38432 works registered in Minas Gerais passes this check, which is what ties - * the CNO to the CEI rule and where the test vectors come from. + * The registry's own page at the Receita Federal, which describes the cadastro but publishes + * neither the mask nor the check digit rule. + * @see Official: https://dados.gov.br/dados/conjuntos-dados/cadastro-nacional-de-obras-cno + * Cadastro Nacional de Obras (CNO), dados abertos da Receita Federal: every one of the 38432 + * works registered in Minas Gerais passes this check, which is what ties the CNO to the CEI + * rule and where the test vectors come from. * @see Based on: https://github.com/yiibr/yii2-br-validator/blob/master/src/CeiValidator.php * PHP reference implementation of the CEI check digit. */ From 413b955b4c0809eafdcb9c45a9634a02e8768200 Mon Sep 17 00:00:00 2001 From: Hyan Mandian <5044101+hyanmandian@users.noreply.github.com> Date: Sat, 12 Sep 2026 20:54:38 -0300 Subject: [PATCH 13/15] chore(data): refresh the NCM table with the codes in force --- scripts/ncm.ts | 38 ++++++++++++++++++++++++++++++++--- src/is-valid-ncm/constants.ts | 9 +++++++-- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/scripts/ncm.ts b/scripts/ncm.ts index d18471d3..45828f35 100644 --- a/scripts/ncm.ts +++ b/scripts/ncm.ts @@ -9,6 +9,7 @@ const scriptsDir = import.meta.dirname; type NcmEntry = { Codigo: string; + Data_Inicio: string; Data_Fim: string; }; @@ -21,6 +22,8 @@ const isNcmEntry = (value: unknown): value is NcmEntry => value !== null && "Codigo" in value && typeof value.Codigo === "string" && + "Data_Inicio" in value && + typeof value.Data_Inicio === "string" && "Data_Fim" in value && typeof value.Data_Fim === "string"; @@ -31,6 +34,27 @@ const isNcmResponse = (value: unknown): value is NcmResponse => Array.isArray(value.Nomenclaturas) && value.Nomenclaturas.every((entry) => isNcmEntry(entry)); +/** + * Parses a Siscomex `dd/mm/yyyy` date into a `Date` at UTC midnight. + * + * @param {string} date - A date string in `dd/mm/yyyy` format. + * @returns {Date} The parsed date. + */ +const parseBrDate = (date: string): Date => { + const [day, month, year] = date.split("/").map(Number); + return new Date(Date.UTC(year, month - 1, day)); +}; + +/** + * Whether `today` falls within `[Data_Inicio, Data_Fim]` (both inclusive). + * + * @param {NcmEntry} entry - The Siscomex NCM entry to check. + * @param {Date} today - The reference date. + * @returns {boolean} `true` when `entry` is in force on `today`. + */ +const isInForce = (entry: NcmEntry, today: Date): boolean => + parseBrDate(entry.Data_Inicio) <= today && today <= parseBrDate(entry.Data_Fim); + const main = async (): Promise => { const response = await fetchWithRetry( "https://portalunico.siscomex.gov.br/classif/api/publico/nomenclatura/download/json?perfil=PUBLICO", @@ -46,8 +70,12 @@ const main = async (): Promise => { throw new Error("Siscomex NCM payload is not a Nomenclaturas response"); } + const today = new Date( + Date.UTC(new Date().getUTCFullYear(), new Date().getUTCMonth(), new Date().getUTCDate()), + ); + const codes = json.Nomenclaturas.filter( - (entry) => entry.Data_Fim === "31/12/9999" && /^[\d.]{10}$/.test(entry.Codigo), + (entry) => /^[\d.]{10}$/.test(entry.Codigo) && isInForce(entry, today), ) .map((entry) => entry.Codigo.replaceAll(/\D/g, "")) .filter((code) => code.length === 8); @@ -57,11 +85,15 @@ const main = async (): Promise => { await writeFile( resolve(scriptsDir, "..", "./src/is-valid-ncm/constants.ts"), `/** - * Currently valid NCM (Nomenclatura Comum do Mercosul) 8 digit codes, sorted ascending. + * NCM (Nomenclatura Comum do Mercosul) 8 digit codes in force on the generation date, sorted + * ascending. A code is included when the generation date falls within its Siscomex + * \`Data_Inicio\`/\`Data_Fim\` range (both inclusive) — this also keeps codes that are valid + * today but carry a scheduled future end date, not only the ones with no end date + * (\`Data_Fim: "31/12/9999"\`). * * Generated by \`node ./scripts/ncm.ts\`. Do not edit by hand. * - * @see https://portalunico.siscomex.gov.br/classif/api/publico/nomenclatura/download/json + * @see Official: https://portalunico.siscomex.gov.br/classif/api/publico/nomenclatura/download/json */ export const NCM_CODES: readonly string[] = ${JSON.stringify(uniqueSortedCodes)}; `, diff --git a/src/is-valid-ncm/constants.ts b/src/is-valid-ncm/constants.ts index cec2446a..59bc6398 100644 --- a/src/is-valid-ncm/constants.ts +++ b/src/is-valid-ncm/constants.ts @@ -1,9 +1,13 @@ /** - * Currently valid NCM (Nomenclatura Comum do Mercosul) 8 digit codes, sorted ascending. + * NCM (Nomenclatura Comum do Mercosul) 8 digit codes in force on the generation date, sorted + * ascending. A code is included when the generation date falls within its Siscomex + * `Data_Inicio`/`Data_Fim` range (both inclusive) — this also keeps codes that are valid + * today but carry a scheduled future end date, not only the ones with no end date + * (`Data_Fim: "31/12/9999"`). * * Generated by `node ./scripts/ncm.ts`. Do not edit by hand. * - * @see https://portalunico.siscomex.gov.br/classif/api/publico/nomenclatura/download/json + * @see Official: https://portalunico.siscomex.gov.br/classif/api/publico/nomenclatura/download/json */ export const NCM_CODES: readonly string[] = [ "01012100", @@ -4900,6 +4904,7 @@ export const NCM_CODES: readonly string[] = [ "39139020", "39139030", "39139040", + "39139050", "39139060", "39139090", "39140011", From 3208d292533ef254f5aeabc22bddd8e85321c77d Mon Sep 17 00:00:00 2001 From: Hyan Mandian <5044101+hyanmandian@users.noreply.github.com> Date: Sat, 12 Sep 2026 20:54:38 -0300 Subject: [PATCH 14/15] ci(release): list dataset refreshes in the changelog --- CONTRIBUTING.md | 5 ++++- release-please-config.json | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8cedc81d..4f78283b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -291,7 +291,10 @@ There are no local release commands to run. `feat:` bumps the minor version, `fix:` bumps the patch version, and a `!` after the type/scope or a `BREAKING CHANGE:` footer bumps the major version. The release PR's description and the `CHANGELOG.md` entry it adds are generated from the commit subjects/bodies, so writing a clear, - accurately-typed commit message matters. + accurately-typed commit message matters. `release-please-config.json` maps the types to the + changelog sections: `feat`, `fix`, `perf`, `revert`, `docs`, `build`, `ci`, `deps`/`chore(deps)` + and `chore(data)` (the dataset refreshes) are listed; `chore`, `test`, `refactor` and `style` + stay hidden. 2. A maintainer reviews the release PR (version bump, changelog) and merges it. **Merging the release PR is the first confirmation.** Nothing is published yet at this point. 3. Merging tags the release and publishes a GitHub Release, which triggers the `publish` job in diff --git a/release-please-config.json b/release-please-config.json index a6c931b9..fc2cdc16 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -15,6 +15,7 @@ { "type": "ci", "section": "CI" }, { "type": "deps", "section": "Dependencies" }, { "type": "chore", "scope": "deps", "section": "Dependencies" }, + { "type": "chore", "scope": "data", "section": "Data" }, { "type": "chore", "section": "Miscellaneous", "hidden": true }, { "type": "test", "section": "Tests", "hidden": true }, { "type": "refactor", "section": "Refactoring", "hidden": true }, From 5247e22c6157a3f22347c4afb5587074501cbf6f Mon Sep 17 00:00:00 2001 From: Hyan Mandian <5044101+hyanmandian@users.noreply.github.com> Date: Sat, 12 Sep 2026 20:54:38 -0300 Subject: [PATCH 15/15] docs: cite the primary source behind every validator and lookup --- docs/llms-full.txt | 67 ++++++++------- docs/llms.txt | 18 ++-- docs/pt-br/utilities.md | 67 ++++++++------- docs/utilities.md | 67 ++++++++------- scripts/cbo.ts | 4 +- scripts/cfop.ts | 4 +- scripts/cities.ts | 2 +- scripts/cnae.ts | 4 +- scripts/states.ts | 2 +- src/_internals/constants/area-codes.ts | 11 ++- src/_internals/constants/banks.ts | 2 +- src/_internals/constants/cbo.ts | 4 +- src/_internals/constants/cfop.ts | 4 +- src/_internals/constants/cities.ts | 2 +- src/_internals/constants/cnae.ts | 4 +- src/_internals/constants/service-phone.ts | 4 +- src/_internals/constants/states.ts | 2 +- .../constants.ts | 5 +- .../convert-license-plate-to-mercosul.ts | 5 +- .../convert-number-to-words.ts | 4 +- src/format-boleto/format-boleto.ts | 9 +- src/format-cep/format-cep.ts | 1 + src/format-cnh/format-cnh.ts | 6 +- src/format-cpf/format-cpf.ts | 3 +- src/format-currency/format-currency.ts | 2 +- .../format-legal-nature.ts | 1 + src/format-pis/format-pis.ts | 4 +- .../format-processo-juridico.ts | 4 +- src/format-voter-id/format-voter-id.ts | 7 +- src/generate-boleto/generate-boleto.ts | 9 +- src/generate-cep/generate-cep.ts | 1 + src/generate-cnh/generate-cnh.ts | 6 +- src/generate-cnpj/generate-cnpj.ts | 1 + .../generate-legal-nature.ts | 1 + src/generate-pis/generate-pis.ts | 8 +- .../generate-processo-juridico.ts | 4 +- src/generate-voter-id/generate-voter-id.ts | 6 +- .../get-address-info-by-cep.ts | 4 +- src/get-area-code-info/get-area-code-info.ts | 11 ++- .../get-area-codes-by-state.ts | 7 +- src/get-bank-by-code/get-bank-by-code.ts | 2 +- src/get-bank-by-ispb/get-bank-by-ispb.ts | 2 +- src/get-banks/get-banks.ts | 2 +- src/get-boleto-info/constants.ts | 9 ++ src/get-boleto-info/get-boleto-info.ts | 10 ++- .../get-cep-info-by-address.ts | 2 +- src/get-holidays/constants.ts | 84 +++++++++---------- src/get-holidays/get-holidays.ts | 15 +++- src/get-legal-natures/get-legal-natures.ts | 1 + src/get-timezone-by-state/constants.ts | 14 ++-- .../get-timezone-by-state.ts | 12 ++- src/is-business-day/is-business-day.ts | 25 ++++-- src/is-holiday/is-holiday.ts | 14 +++- src/is-valid-boleto/is-valid-boleto.ts | 9 +- src/is-valid-cep/is-valid-cep.ts | 1 + src/is-valid-cnh/is-valid-cnh.ts | 6 +- src/is-valid-cnpj/is-valid-cnpj.ts | 4 + src/is-valid-cpf/is-valid-cpf.ts | 6 +- src/is-valid-credit-card/constants.ts | 4 +- .../is-valid-credit-card.ts | 11 ++- src/is-valid-email/is-valid-email.ts | 13 +-- src/is-valid-ie/is-valid-ie.ts | 25 ++++++ src/is-valid-legal-nature/constants.ts | 4 + .../is-valid-legal-nature.ts | 1 + .../is-valid-mobile-phone.ts | 4 + src/is-valid-pis/is-valid-pis.ts | 8 +- .../is-valid-processo-juridico.ts | 4 +- .../is-valid-service-phone.ts | 3 +- src/is-valid-vin/constants.ts | 18 ++-- src/is-valid-vin/is-valid-vin.ts | 19 +++-- src/is-valid-voter-id/is-valid-voter-id.ts | 9 +- src/parse-boleto/parse-boleto.ts | 9 +- src/parse-cep/parse-cep.ts | 1 + src/parse-cnh/parse-cnh.ts | 6 +- src/parse-cpf/parse-cpf.ts | 3 +- src/parse-legal-nature/parse-legal-nature.ts | 1 + src/parse-pis/parse-pis.ts | 4 +- .../parse-processo-juridico.ts | 4 +- src/parse-voter-id/parse-voter-id.ts | 7 +- 79 files changed, 495 insertions(+), 258 deletions(-) diff --git a/docs/llms-full.txt b/docs/llms-full.txt index 2cf83ca3..623ded59 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -283,6 +283,7 @@ Generate a valid random CPF. import { generateCpf } from '@brazilian-utils/brazilian-utils' generateCpf(); +generateCpf('SP'); // the 9th digit is 8, the SP região fiscal code ``` ### isValidCnpj @@ -344,6 +345,7 @@ Generate a valid random CNPJ. import { generateCnpj } from '@brazilian-utils/brazilian-utils' generateCnpj(); +generateCnpj(2); // alphanumeric CNPJ, e.g. 'Q0SLFMBD7VX439' ``` ### isValidBoleto @@ -412,7 +414,7 @@ getBoletoInfo('846100000005246100291102005460339004695895061080'); ### isValidPixKey -Check if a Pix key (chave Pix) is valid: a CPF, a CNPJ, an e-mail address, a Brazilian phone number or a random key (EVP), per the DICT key formats. `options.accept` (typed as `IsValidPixKeyOptions`) restricts which kinds of key are accepted; it defaults to all of them, and `[]` rejects everything. Exports the `PixKeyType` type. +Check if a Pix key (chave Pix) is valid: a CPF, a CNPJ, an e-mail address, a Brazilian mobile phone number or a random key (EVP), per the DICT key formats. The manual registers a "número de telefone celular", so a landline is not a valid phone key. `options.accept` (typed as `IsValidPixKeyOptions`) restricts which kinds of key are accepted; it defaults to all of them, and `[]` rejects everything. Exports the `PixKeyType` type. ```javascript import { isValidPixKey } from '@brazilian-utils/brazilian-utils'; @@ -421,13 +423,14 @@ isValidPixKey('123.456.789-09'); // true isValidPixKey('fulano@example.com'); // true isValidPixKey('(11) 98765-4321'); // true isValidPixKey('71c7d9be-4b85-4e43-9f1c-1f3b8b4e9a2d'); // true +isValidPixKey('(11) 3000-0000'); // false (landlines are not Pix keys) isValidPixKey('123.456.789-09', { accept: ['email', 'evp'] }); // false isValidPixKey('not a key'); // false ``` ### parsePixKey -Identifies a Pix key and normalizes it to the canonical form the DICT expects inside a BR Code: 11 digit CPF, 14 character CNPJ, lowercased e-mail, E.164 phone or lowercase UUID EVP. An 11 digit value that is valid both as a CPF and as a mobile phone is read as a CPF, unless it was written as a phone number (a `+55`/`0055` prefix or a DDD wrapped in parentheses). Returns `null` when the value is not a valid Pix key. The result is typed as `PixKey`. +Identifies a Pix key and normalizes it to the canonical form the DICT expects inside a BR Code: 11 digit CPF, 14 character CNPJ, lowercased e-mail, E.164 mobile phone (a landline is not a Pix key) or lowercase UUID EVP. An 11 digit value that is valid both as a CPF and as a mobile phone is read as a CPF, unless it was written as a phone number (a `+55`/`0055` prefix or a DDD wrapped in parentheses). Returns `null` when the value is not a valid Pix key. The result is typed as `PixKey`. ```javascript import { parsePixKey } from '@brazilian-utils/brazilian-utils'; @@ -437,13 +440,14 @@ parsePixKey('Fulano@Example.COM '); // { type: 'email', value: 'fulano@example.c parsePixKey('(11) 98765-4321'); // { type: 'phone', value: '+5511987654321' } parsePixKey('71C7D9BE-4B85-4E43-9F1C-1F3B8B4E9A2D'); // { type: 'evp', value: '71c7d9be-4b85-4e43-9f1c-1f3b8b4e9a2d' } +parsePixKey('(11) 3000-0000'); // null (a landline is not a Pix key) parsePixKey('51998259765'); // { type: 'cpf', value: '51998259765' } (also a valid phone) parsePixKey('+5551998259765'); // { type: 'phone', value: '+5551998259765' } ``` ### isValidPixPayload -Check if a Pix BR Code payload (the string behind a Pix QR Code and behind "Pix copia e cola") is valid: well-formed TLV structure, the mandatory objects present, one of the "Merchant Account Information" templates carrying the `br.gov.bcb.pix` GUI with a key or a URL, and a matching CRC-16. The key itself is not checked against the DICT formats, use `isValidPixKey` for that. +Check if a Pix BR Code payload (the string behind a Pix QR Code and behind "Pix copia e cola") is valid: well-formed TLV structure, the mandatory objects present, one of the "Merchant Account Information" templates carrying the `br.gov.bcb.pix` GUI with a key or a URL, and a matching CRC-16. The key itself is not checked against the DICT formats, use `isValidPixKey` for that. Payloads that carry the location in an Unreserved Template (IDs 80 to 99), as the "QR Code composto" of Pix Automático (Pix recorrente) does, are out of scope and reported as invalid. ```javascript import { isValidPixPayload } from '@brazilian-utils/brazilian-utils'; @@ -458,7 +462,7 @@ isValidPixPayload('00020126580014br.gov.bcb.pix...'); // false (broken CRC) ### parsePixPayload -Parses a Pix BR Code payload into its fields. The payload is validated by `isValidPixPayload` first, so a malformed structure, a broken CRC or a missing mandatory object returns `null` instead of a partial result. A static payload comes back with `key`, a dynamic one with `url`. The result is typed as `PixPayload`; `pointOfInitiation` is typed as `PixPointOfInitiation` (`"static"` or `"dynamic"`). The merchant account information must carry exactly one of a key or a `url` (checked with the same PSP location rule as `generatePixPayload`), and in a dynamic payload the amount and the `txid` are ignored, as the manual mandates. +Parses a Pix BR Code payload into its fields. The payload is validated by `isValidPixPayload` first, so a malformed structure, a broken CRC or a missing mandatory object returns `null` instead of a partial result. A static payload comes back with `key`, a dynamic one with `url`. The result is typed as `PixPayload`; `pointOfInitiation` is typed as `PixPointOfInitiation` (`"static"` or `"dynamic"`). The merchant account information must carry exactly one of a key or a `url` (checked with the same PSP location rule as `generatePixPayload`), and in a dynamic payload the amount and the `txid` are ignored, as the manual mandates. Payloads whose location lives in an Unreserved Template (IDs 80 to 99, Pix Automático) are out of scope and return `null`. ```javascript import { parsePixPayload } from '@brazilian-utils/brazilian-utils'; @@ -503,7 +507,7 @@ generatePixPayload({ merchantName: 'Fulano', merchantCity: 'Brasília' }); // nu ### isValidNfeKey -Check if a DF-e (Documento Fiscal eletrônico) access key (chave de acesso) is valid. It covers every document that shares the same 44 digit layout: NF-e (modelo 55), NFC-e (modelo 65), CT-e (modelo 57) and MDF-e (modelo 58). Accepts whitespace between digit groups (the common display mask) and the `NFe` prefix found in the `Id` attribute of the document's XML. +Check if a DF-e (Documento Fiscal eletrônico) access key (chave de acesso) is valid. It covers every document that shares the same 44 digit layout: NF-e (modelo 55), NFC-e (modelo 65), CT-e (modelo 57) and MDF-e (modelo 58). Accepts whitespace between digit groups (the common display mask) and the `NFe` prefix found in the `Id` attribute of the document's XML. The emission type (`tpEmis`) must be one of the codes the MOC assigns, 1 to 7 or 9; 8 is not assigned and makes the key invalid. ```javascript import { isValidNfeKey } from '@brazilian-utils/brazilian-utils'; @@ -512,6 +516,7 @@ isValidNfeKey('35170458716523000119550010000000121000123458'); // true (NF-e, SP isValidNfeKey('NFe35170458716523000119550010000000121000123458'); // true (XML Id prefix) isValidNfeKey('3517 0458 7165 2300 0119 5500 1000 0000 1210 0012 3458'); // true (masked) isValidNfeKey('99170458716523000119550010000000121000123458'); // false (invalid cUF) +isValidNfeKey('35170458716523000119550010000000128000123455'); // false (tpEmis 8 is not assigned) ``` ### formatNfeKey @@ -596,7 +601,7 @@ parsePhone('55987654321'); // 55987654321 (area code 55, not mistaken for the +5 ### isValidMobilePhone -Check if mobile phone number is valid. `options.version` (typed as `PhoneVersion`) controls which mobile numbering rule is enforced: `1` (default) accepts the legacy format, whose first number digit (after the DDD) may be 6, 7, 8 or 9; `2` enforces the current format, which requires 9. +Check if mobile phone number is valid. `options.version` (typed as `PhoneVersion`) controls which mobile numbering rule is enforced: `1` (default) is the pre-Resolução Anatel 749/2022 format, kept for 2.3.0 compatibility, whose first number digit (after the DDD) may be 6, 7, 8 or 9; `2` enforces only 9, a stricter subset of the resolution's art. 12 I (Serviço Móvel Pessoal). ```javascript import { isValidMobilePhone } from '@brazilian-utils/brazilian-utils'; @@ -618,7 +623,7 @@ isValidLandlinePhone('1130000000'); // true ### isValidServicePhone -Check if a phone number is a valid Brazilian service number, dialed without a DDD: the Códigos Não Geográficos `0300`, `0303`, `0500`, `0800` and `0900` (11 digits total), the abbreviated `300X`/`400X` numbers (8 digits), and the 3-digit Códigos de Acesso a Serviços de Utilidade Pública that Anatel has designated (e.g. `190`, `192`). Only the structure is checked, the number does not have to be assigned to anyone. +Check if a phone number is a valid Brazilian service number, dialed without a DDD: the Códigos Não Geográficos `0300`, `0303`, `0500`, `0800` and `0900` (11 digits total), the abbreviated `300X`/`400X` numbers (8 digits), and the 3-digit Códigos de Acesso a Serviços de Utilidade Pública that Anatel has designated (e.g. `190`, `192`; `112` and `911` are accepted too, as mobile-only aliases of `190` that Anatel lists alongside the other 3-digit codes). Only the structure is checked, the number does not have to be assigned to anyone. ```javascript import { isValidServicePhone } from '@brazilian-utils/brazilian-utils'; @@ -689,7 +694,7 @@ isValidRenavam('12345678901'); // false (invalid checksum) ### isValidPis -Check if PIS is valid. Accepts the usual mask characters and whitespace. +Check if PIS is valid. Accepts the usual mask characters (`.`, `-`, `/`, `(`, `)`, `,`, `*`) and whitespace. ```javascript import { isValidPis } from '@brazilian-utils/brazilian-utils'; @@ -916,7 +921,7 @@ getBanks(); // { code: '001', ispb: '00000000', name: 'Banco do Brasil S.A.' }, // { code: '003', ispb: '04902979', name: 'BANCO DA AMAZONIA S.A.' }, // { code: '004', ispb: '07237373', name: 'Banco do Nordeste do Brasil S.A.' }, -// ... 345 more items +// ... 460 more items // ] ``` @@ -946,7 +951,7 @@ getBankByIspb('99999999'); // null ### isValidIban -Check if a Brazilian IBAN (International Bank Account Number) is valid, per Bacen's [Diretrizes de Implementação do IBAN no Brasil](https://www.bcb.gov.br/estabilidadefinanceira/exibenormativo?tipo=Circular&numero=3625) (Circular BCB nº 3.625/2013): `BR` + 2 ISO 7064 MOD 97-10 check digits + 8 digit ISPB + 5 digit branch + 10 digit account + 1 letter account type (`C`/`P`) + 1 alphanumeric owner indicator, 29 characters total. Only Brazilian IBANs (country code `BR`) are recognized; any other country returns `false`, since this package does not carry the field layout of the other 90+ ISO 13616 countries. Accepts the usual grouping spaces and is case-insensitive. +Check if a Brazilian IBAN (International Bank Account Number) is valid, per Bacen's [Diretrizes de Implementação do IBAN no Brasil](https://www.bcb.gov.br/content/estabilidadefinanceira/Documents/sistema_pagamentos_brasileiro/IBAN-Guidelines_%20port.pdf) (Circular BCB nº 3.625/2013): `BR` + 2 ISO 7064 MOD 97-10 check digits + 8 digit ISPB + 5 digit branch + 10 digit account + 1 letter account type (any letter, usually `C` for conta corrente or `P` for conta poupança) + 1 alphanumeric owner indicator, 29 characters total. Only Brazilian IBANs (country code `BR`) are recognized; any other country returns `false`, since this package does not carry the field layout of the other 90+ ISO 13616 countries. Accepts the usual grouping spaces and is case-insensitive. ```javascript import { isValidIban } from '@brazilian-utils/brazilian-utils'; @@ -971,7 +976,7 @@ formatIban('BR15'); // 'BR15' ### parseIban -Parses a Brazilian IBAN into its fields: 2 (country code, always `BR`) + 2 (ISO 7064 MOD 97-10 check digits) + 8 (ISPB) + 5 (branch) + 10 (account) + 1 (account type, `C` or `P`) + 1 (owner indicator). Accepts the same input forms as `isValidIban` (grouping spaces, lowercase) and returns `null` whenever `isValidIban` would return `false`. The result is typed as `Iban`. +Parses a Brazilian IBAN into its fields: 2 (country code, always `BR`) + 2 (ISO 7064 MOD 97-10 check digits) + 8 (ISPB) + 5 (branch) + 10 (account) + 1 (account type, any letter, usually `C` for conta corrente or `P` for conta poupança) + 1 (owner indicator). Accepts the same input forms as `isValidIban` (grouping spaces, lowercase) and returns `null` whenever `isValidIban` would return `false`. The result is typed as `Iban`, whose `accountType` is a `string`. ```javascript import { parseIban } from '@brazilian-utils/brazilian-utils'; @@ -1252,7 +1257,7 @@ getHolidays({ year: 2024, stateCode: 'SP' }); ### isValidPassport -Check if a Brazilian passport number is valid (2 letters followed by 6 digits). The input is case-insensitive and any non-alphanumeric characters (spaces, dots, hyphens) are ignored. +Check if a Brazilian passport number is valid (2 letters followed by 6 digits). Accepts both `string` and `number` input; the input is case-insensitive and any non-alphanumeric characters (spaces, dots, hyphens) are ignored. ```javascript import { isValidPassport } from '@brazilian-utils/brazilian-utils'; @@ -1265,7 +1270,7 @@ isValidPassport('12345678'); // false ### formatPassport -Format a Brazilian passport number (uppercase, without symbols, capped to 8 characters). +Format a Brazilian passport number (uppercase, without symbols, capped to 8 characters). A non-string input returns an empty string. ```javascript import { formatPassport } from '@brazilian-utils/brazilian-utils'; @@ -1286,7 +1291,7 @@ generatePassport(); // 'RY393097' ### parsePassport -Remove all non-alphanumeric characters from a passport number, uppercase the result, and cap it to 8 characters. +Remove all non-alphanumeric characters from a passport number, uppercase the result, and cap it to 8 characters. A non-string input returns an empty string. ```javascript import { parsePassport } from '@brazilian-utils/brazilian-utils'; @@ -1532,7 +1537,7 @@ generatePis(); // '91077906857' ### getMunicipality -Get municipality information by IBGE code, or get an IBGE code from municipality name and UF. A single function handles both directions, based on whether `options` has a `code` or a `municipalityName`/`uf`. `code` must be exactly 7 digits, otherwise the function resolves to `null`. Resolution is entirely offline, from a bundled IBGE dataset: no network request is made. The municipality name match ignores accents and casing. An unknown municipality, an unknown UF or invalid input all resolve to `null`. +Get municipality information by IBGE code, or get an IBGE code from municipality name and UF. A single function handles both directions, based on whether `options` has a `code` or a `municipalityName`/`uf`. `code` accepts both `string` and `number` input and must be exactly 7 digits, otherwise the function resolves to `null`. Resolution is entirely offline, from a bundled IBGE dataset: no network request is made. The municipality name match ignores accents and casing. An unknown municipality, an unknown UF or invalid input all resolve to `null`. ```javascript import { getMunicipality } from '@brazilian-utils/brazilian-utils'; @@ -1540,6 +1545,9 @@ import { getMunicipality } from '@brazilian-utils/brazilian-utils'; await getMunicipality({ code: '3550308' }); // ['São Paulo', 'SP'] +await getMunicipality({ code: 3550308 }); +// ['São Paulo', 'SP'] + await getMunicipality({ municipalityName: 'sao paulo', uf: 'sp' }); // '3550308' @@ -1615,7 +1623,7 @@ isHoliday(); // false ### isBusinessDay -Check if a date is a Brazilian business day (dia útil). Returns `false` for Saturdays, Sundays, and Brazilian holidays returned by `getHolidays` for `value`'s local calendar day (year/month/day as read locally), the same convention used by `isHoliday`. `options.includeOptional` (part of `IsBusinessDayOptions`) defaults to `true`, so optional-type holidays (`Holiday.type === "optional"`, i.e. Carnaval and Corpus Christi) also count as non-business days, matching the Brazilian banking calendar (FEBRABAN/CMN); pass `false` to only treat statutory holidays this way. `options.stateCode` also considers that state's holidays; an unknown/invalid `stateCode` is ignored, falling back to national holidays only. A `value` that is not a valid `Date` returns `false`. Only years from 1900 through 2099 are supported, the range `getHolidays` computes; a date outside it returns `false`. +Check if a date is a Brazilian business day (dia útil). Returns `false` for Saturdays, Sundays, and Brazilian holidays returned by `getHolidays` for `value`'s local calendar day (year/month/day as read locally), the same convention used by `isHoliday`. `options.includeOptional` (part of `IsBusinessDayOptions`) defaults to `true`, so optional-type holidays (`Holiday.type === "optional"`, i.e. Carnaval and Corpus Christi) also count as non-business days; pass `false` to only treat statutory holidays this way. `options.stateCode` also considers that state's holidays; an unknown/invalid `stateCode` is ignored, falling back to national holidays only. A `value` that is not a valid `Date` returns `false`. Only years from 1900 through 2099 are supported, the range `getHolidays` computes; a date outside it returns `false`. ```javascript import { isBusinessDay } from '@brazilian-utils/brazilian-utils'; @@ -1729,7 +1737,7 @@ parseVoterId('1234 5678 8 01 91'); // '1234567880191' (13-digit SP/MG voter id) ### isValidCns -Check if a CNS (Cartão Nacional de Saúde) number is valid, the unique SUS (Sistema Único de Saúde) user identifier. Definitive cards (starting with 1 or 2) are validated with the same mod 11 weighting used for PIS numbers over an embedded 11 digit base, adjusting the base by +2 when the raw check digit computes to 10. Provisional cards (starting with 7, 8 or 9) are validated instead by a single weighted sum (weights 15 down to 1) that must be a multiple of 11. +Check if a CNS (Cartão Nacional de Saúde) number is valid, the unique SUS (Sistema Único de Saúde) user identifier. Definitive cards (starting with 1 or 2) are validated with the same mod 11 weighting used for PIS numbers over an embedded 11 digit base, adjusting the base by +2 when the raw check digit computes to 10. Provisional cards (starting with 7, 8 or 9) are validated instead by a single weighted sum (weights 15 down to 1) that must be a multiple of 11. The value has to be written as the 15 digits, optionally split into the printed groups of 3-4-4-4 by whitespace or the usual mask characters; letters among the digits are rejected instead of being read past. ```javascript import { isValidCns } from '@brazilian-utils/brazilian-utils'; @@ -1737,6 +1745,7 @@ import { isValidCns } from '@brazilian-utils/brazilian-utils'; isValidCns('123456789010000'); // true (definitive) isValidCns('700000000000005'); // true (provisional) isValidCns('12345678901'); // false (wrong length) +isValidCns('abc123456789010000'); // false (not written as a CNS) ``` ### formatCns @@ -1753,9 +1762,9 @@ formatCns('89010001', { pad: true }); // '000 0000 8901 0001' ### isValidCertidao -Check if the matrícula of a certidão de registro civil (nascimento, casamento, óbito and the other acts kept by a serventia de registro civil das pessoas naturais) is valid. The matrícula has 32 digits laid out as 6 (CNS da serventia) + 2 (acervo) + 2 (serviço) + 4 (ano) + 1 (tipo do livro) + 5 (livro) + 3 (folha) + 7 (termo) + 2 (dígitos verificadores), and both check digits are modulus 11 with weights cycling from 2 to 10 and back through 0. Accepts the usual mask characters and whitespace between/around groups. The layout and both check digits follow the [Provimento CNJ nº 3/2009](https://atos.cnj.jus.br/atos/detalhar/1310), whose CNS da serventia comes from the [Provimento CNJ nº 2/2009](https://atos.cnj.jus.br/atos/detalhar/1311), as detailed by [ghiorzi.org](http://ghiorzi.org/DVnew.htm) and implemented by [validation-br](https://github.com/klawdyo/validation-br/blob/feat-certidao/src/certidao.ts) and [validator-docs](https://github.com/geekcom/validator-docs/blob/master/src/validator-docs/Rules/Certidao.php). +Check if the matrícula of a certidão de registro civil (nascimento, casamento, óbito and the other acts kept by a serventia de registro civil das pessoas naturais) is valid. The matrícula has 32 digits laid out as 6 (CNS da serventia) + 2 (acervo) + 2 (serviço) + 4 (ano) + 1 (tipo do livro) + 5 (livro) + 3 (folha) + 7 (termo) + 2 (dígitos verificadores), and both check digits are modulus 11 with weights cycling from 2 to 10 and back through 0. Accepts the usual mask characters and whitespace between/around groups. The layout is the in-force one of [art. 473 of the Código Nacional de Normas da Corregedoria Nacional de Justiça](https://atos.cnj.jus.br/atos/detalhar/5243) (Provimento CNJ nº 149/2023, in the wording of the Provimento CN nº 182/2024); the matrícula itself was instituted by the now revoked [Provimento CNJ nº 2/2009](https://atos.cnj.jus.br/atos/detalhar/1311). The check digits are detailed by [ghiorzi.org](http://ghiorzi.org/DVnew.htm) and implemented by [validation-br](https://github.com/klawdyo/validation-br/blob/feat-certidao/src/certidao.ts) and [validator-docs](https://github.com/geekcom/validator-docs/blob/master/src/validator-docs/Rules/Certidao.php). -`options.accept` (part of `IsValidCertidaoOptions`) restricts which book types (the same `CertidaoType` returned by `parseCertidao`) count as valid; when given, the book-type digit must map to one of the listed types. Defaults to every type. +The book-type digit always has to name one of the nine book types (the same `CertidaoType` returned by `parseCertidao`), so a matrícula whose digit is `0` is rejected however good its check digits are, the same way `parseCertidao` returns `null` for it. `options.accept` (part of `IsValidCertidaoOptions`) narrows that to the listed types; it defaults to every type, and a value that is not an array falls back to that default. Only a string is accepted: the 32 digits of a matrícula are more than a JavaScript number can hold. ```javascript import { isValidCertidao } from '@brazilian-utils/brazilian-utils'; @@ -1770,7 +1779,7 @@ isValidCertidao('104539 01 55 2013 1 00012 021 0000123 21', { accept: ['death'] ### parseCertidao -Parse the matrícula of a certidão de registro civil into its fields, returning `null` when the matrícula is not valid or when its book code is not one of the nine books defined by the Provimento. The nine books and their codes are the ones defined by the [Provimento CNJ nº 3/2009](https://atos.cnj.jus.br/atos/detalhar/1310), as listed by [ghiorzi.org](http://ghiorzi.org/DVnew.htm). +Parse the matrícula of a certidão de registro civil into its fields, returning `null` when the matrícula is not valid, which includes a book code that is not one of the nine books. [Art. 473, V of the Código Nacional de Normas da Corregedoria Nacional de Justiça](https://atos.cnj.jus.br/atos/detalhar/5243) lists the codes 1 to 7; the codes 8 (emancipação) and 9 (interdição) come from Anexo IV of the revoked Provimento CNJ nº 63/2017, as listed by [ghiorzi.org](http://ghiorzi.org/DVnew.htm), and are kept because matrículas issued under it are still in circulation. Only a string is accepted: the 32 digits of a matrícula are more than a JavaScript number can hold. ```javascript import { parseCertidao } from '@brazilian-utils/brazilian-utils'; @@ -1809,7 +1818,7 @@ The `Certidao` result carries: ### formatCertidao -Format the matrícula of a certidão de registro civil into the printed mask of the Provimento, the 32 digits grouped as 6 2 2 4 1 5 3 7 2 and separated by spaces. `options.pad` (part of `FormatCertidaoOptions`) left pads the value with zeros up to 32 digits. The mask is the one printed in the [Provimento CNJ nº 3/2009](https://atos.cnj.jus.br/atos/detalhar/1310). +Format the matrícula of a certidão de registro civil into the printed mask of the Provimento, the 32 digits grouped as 6 2 2 4 1 5 3 7 2 and separated by spaces. `options.pad` (part of `FormatCertidaoOptions`) left pads the value with zeros up to 32 digits. The mask is the one of [art. 473 of the Código Nacional de Normas da Corregedoria Nacional de Justiça](https://atos.cnj.jus.br/atos/detalhar/5243). Only a string is accepted: the 32 digits of a matrícula are more than a JavaScript number can hold. ```javascript import { formatCertidao } from '@brazilian-utils/brazilian-utils'; @@ -1821,7 +1830,7 @@ formatCertidao('1552010100020112000012087', { pad: true }); // 000000 01 55 2010 ### isValidCei -Check if a CEI (Cadastro Específico do INSS) number is valid. The CEI identifies an employer with no CNPJ, such as a construction work or a rural producer: 12 digits printed as `00.000.00000/00`, the last one a check digit calculated over the 11 base digits with the weights 7, 4, 1, 8, 5, 2, 1, 6, 3, 7 and 4. Accepts the usual mask characters and whitespace between/around groups. The check digit rule is the one implemented by [yii2-br-validator](https://github.com/yiibr/yii2-br-validator/blob/master/src/CeiValidator.php) and by [Bigai.Documentos.Brasil](https://github.com/marcos-cruz/Documento/blob/master/src/Bigai.Documentos.Brasil/Cei/Cei.cs), cross-checked against the Cadastro Nacional de Obras (CNO) open dataset of the Receita Federal. +Check if a CEI (Cadastro Específico do INSS) number is valid. The CEI identifies an employer with no CNPJ, such as a construction work or a rural producer: 12 digits printed as `00.000.00000/00`, the last one a check digit calculated over the 11 base digits with the weights 7, 4, 1, 8, 5, 2, 1, 6, 3, 7 and 4. Accepts the usual mask characters and whitespace between/around groups. The Receita Federal does not publish this check digit rule, so it follows the reference implementations of [yii2-br-validator](https://github.com/yiibr/yii2-br-validator/blob/master/src/CeiValidator.php) and [Bigai.Documentos.Brasil](https://github.com/marcos-cruz/Documento/blob/master/src/Bigai.Documentos.Brasil/Cei/Cei.cs), cross-checked against the [Cadastro Nacional de Obras (CNO) open dataset](https://dados.gov.br/dados/conjuntos-dados/cadastro-nacional-de-obras-cno) of the Receita Federal. ```javascript import { isValidCei } from '@brazilian-utils/brazilian-utils'; @@ -1835,7 +1844,7 @@ isValidCei('000000000000'); // false (repeated digits) ### formatCei -Format a CEI (Cadastro Específico do INSS) number according to the official `00.000.00000/00` mask. Formats progressively, as far as the digits given go, so it can also be used as an input mask. `options.pad` (part of `FormatCeiOptions`) left pads the value with zeros up to 12 digits. +Format a CEI (Cadastro Específico do INSS) number according to the usual `00.000.00000/00` mask, the one the reference implementations of the check digit agree on (the Receita Federal does not print it). Formats progressively, as far as the digits given go, so it can also be used as an input mask. `options.pad` (part of `FormatCeiOptions`) left pads the value with zeros up to 12 digits. ```javascript import { formatCei } from '@brazilian-utils/brazilian-utils'; @@ -1847,7 +1856,7 @@ formatCei('249', { pad: true }); // 00.000.00002/49 ### isValidCno -Check if a CNO (Cadastro Nacional de Obras) number is valid. The CNO replaced the CEI for construction works and kept its numbering, so a work registered under a legacy CEI keeps the same number and both registries validate identically: 12 digits printed as `00.000.00000/00` with a check digit calculated over the 11 base digits. The rule was confirmed against the Cadastro Nacional de Obras (CNO) open dataset of the Receita Federal: every one of the 38432 works registered in Minas Gerais passes this check. +Check if a CNO (Cadastro Nacional de Obras) number is valid. The CNO replaced the CEI for construction works and kept its numbering, so a work registered under a legacy CEI keeps the same number and both registries validate identically: 12 digits printed as `00.000.00000/00` with a check digit calculated over the 11 base digits. The Receita Federal does not publish the check digit rule; it was confirmed against the [Cadastro Nacional de Obras (CNO) open dataset](https://dados.gov.br/dados/conjuntos-dados/cadastro-nacional-de-obras-cno) of the Receita Federal: every one of the 38432 works registered in Minas Gerais passes this check. ```javascript import { isValidCno } from '@brazilian-utils/brazilian-utils'; @@ -1861,7 +1870,7 @@ isValidCno('000000000000'); // false (repeated digits) ### formatCno -Format a CNO (Cadastro Nacional de Obras) number. The CNO kept the CEI's numbering, so both share the same 12 digit, `00.000.00000/00` mask. Formats progressively, as far as the digits given go, so it can also be used as an input mask. `options.pad` (part of `FormatCnoOptions`) left pads the value with zeros up to 12 digits. +Format a CNO (Cadastro Nacional de Obras) number. The CNO kept the CEI's numbering, so both share the same 12 digit, `00.000.00000/00` mask, the one the reference implementations of the check digit agree on (the Receita Federal does not print it). Formats progressively, as far as the digits given go, so it can also be used as an input mask. `options.pad` (part of `FormatCnoOptions`) left pads the value with zeros up to 12 digits. ```javascript import { formatCno } from '@brazilian-utils/brazilian-utils'; @@ -1873,7 +1882,7 @@ formatCno('979', { pad: true }); // 00.000.00009/79 ### isValidCaepf -Check if a CAEPF (Cadastro de Atividade Econômica da Pessoa Física) number is valid. The CAEPF replaced the CEI for individuals who hire employees: 14 digits printed as `000.000.000/000-00`, formed by the 9 digit CPF base of the holder, a 3 digit sequence for the holder's several registrations and 2 check digits. Both check digits use the modulus 11 of the CNPJ, and the resulting pair is then shifted by 12, wrapping around 100. The layout and the shift of 12 are described by [ghiorzi.org](http://ghiorzi.org/DVnew.htm) and implemented the same way by [brazilian-values](https://github.com/VitorLuizC/brazilian-values/blob/master/src/validators/isCAEPF.ts). +Check if a CAEPF (Cadastro de Atividade Econômica da Pessoa Física) number is valid. The CAEPF replaced the CEI for individuals who hire employees: 14 digits printed as `000.000.000/000-00`, formed by the 9 digit CPF base of the holder, a 3 digit sequence for the holder's several registrations and 2 check digits. Both check digits use the modulus 11 of the CNPJ, and the resulting pair is then shifted by 12, wrapping around 100. The Receita Federal does not publish the layout or the check digit rule: both are described by [ghiorzi.org](http://ghiorzi.org/DVnew.htm) and implemented the same way by [brazilian-values](https://github.com/VitorLuizC/brazilian-values/blob/master/src/validators/isCAEPF.ts). ```javascript import { isValidCaepf } from '@brazilian-utils/brazilian-utils'; @@ -1887,7 +1896,7 @@ isValidCaepf('00000000000000'); // false (repeated digits) ### formatCaepf -Format a CAEPF (Cadastro de Atividade Econômica da Pessoa Física) number according to the official `000.000.000/000-00` mask. Formats progressively, as far as the digits given go, so it can also be used as an input mask. `options.pad` (part of `FormatCaepfOptions`) left pads the value with zeros up to 14 digits. +Format a CAEPF (Cadastro de Atividade Econômica da Pessoa Física) number according to the usual `000.000.000/000-00` mask, the one the sources of the check digit rule agree on (the Receita Federal does not print it). Formats progressively, as far as the digits given go, so it can also be used as an input mask. `options.pad` (part of `FormatCaepfOptions`) left pads the value with zeros up to 14 digits. ```javascript import { formatCaepf } from '@brazilian-utils/brazilian-utils'; @@ -1899,7 +1908,7 @@ formatCaepf('184', { pad: true }); // 000.000.000/001-84 ### isValidRegistroProfissional -Check the structure of a professional council registration number (registro/inscrição profissional). Options are typed as `IsValidRegistroProfissionalOptions`: `options.council` picks the issuing council (`"OAB"`, `"CRM"`, `"CRO"`, `"CRP"` or `"CRC"`) and the optional `options.stateCode` checks the embedded UF (ignored for `"CRP"`, whose 2 digit prefix is a regional code, not a literal UF). This is a structural check only: digit counts and the UF are validated, but no check digit is computed, even for CRC, whose format includes one. CREA is not supported: its registration format could not be confirmed from an official, publicly documented source after the 2016 national unification (RNP). +Check the structure of a professional council registration number (registro/inscrição profissional). Options are typed as `IsValidRegistroProfissionalOptions`: `options.council` picks the issuing council (`"OAB"`, `"CRM"`, `"CRO"`, `"CRP"` or `"CRC"`) and the optional `options.stateCode` checks the embedded UF (ignored for `"CRP"`, whose 2 digit prefix is a regional code, not a literal UF). This is a structural check only: digit counts and the UF are validated, but no check digit is computed, even for CRC, whose format includes one. A CRC registration is the UF, 6 digits and the tipo de registro (`"O"` Originário, `"P"` Provisório or `"T"` Transferido, which says nothing about the professional category), as published in the [Manual de Registro do Sistema CFC/CRCs](https://cfc.org.br/wp-content/uploads/2018/04/1_manual_registro.pdf) (item 1.1) and in the Resolução CFC nº 1.707/2023. A CRP regional code has to be one of the [24 Conselhos Regionais](https://site.cfp.org.br/cfp/sistema-conselhos/conselhos-pelo-brasil/) of the CFP system, CRP-01 to CRP-24. The OAB, the CFM and the CFO publish no format for the numbers they issue, so the digit ranges accepted for `"OAB"`, `"CRM"` and `"CRO"` are conventional rather than normative. CREA is not supported: its registration format could not be confirmed from an official, publicly documented source after the 2016 national unification (RNP). ```javascript import { isValidRegistroProfissional } from '@brazilian-utils/brazilian-utils'; @@ -1912,7 +1921,7 @@ isValidRegistroProfissional('SP-123456/O-3', { council: 'CRC' }); // true ### isValidVin -Check if a VIN (Vehicle Identification Number / chassi) is valid under [ISO 3779](https://www.iso.org/standard/52200.html). Checks the length (17 characters), the excluded letters (`I`, `O`, `Q` are never valid) and the check digit at the 9th position, calculated with the ISO 3779 transliteration table and a weighted MOD 11 sum, mandatory for vehicles manufactured in or imported into Brazil under Resolução CONTRAN nº 27/1998. Case-insensitive and trims surrounding whitespace. +Check if a VIN (Vehicle Identification Number / chassi) is valid. Checks the length (17 characters), the excluded letters (`I`, `O`, `Q` are never valid; [ISO 3779:2009](https://www.iso.org/standard/52200.html) structure) and the check digit at the 9th position, with the check digit and transliteration computed per [49 CFR 565.15](https://www.ecfr.gov/current/title-49/section-565.15). That check digit is a North-American requirement (49 CFR 565.15 / SAE J853): Resolução CONTRAN nº 24/1998 and ABNT NBR 6066 define the Brazilian VIN structure but do not mandate it, so many Brazilian-built VINs do not carry a matching check digit. This function is therefore a North-American-style structural check, not a universal validator of Brazilian VINs. Case-insensitive and trims surrounding whitespace. ```javascript import { isValidVin } from '@brazilian-utils/brazilian-utils'; diff --git a/docs/llms.txt b/docs/llms.txt index 84389531..eedf5f22 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -28,21 +28,21 @@ const { getCities } = await import('@brazilian-utils/brazilian-utils/get-cities' - [isValidCnpj](https://brazilian-utils.com.br/utilities.md#isvalidcnpj): Check if CNPJ is valid. - [isValidCep](https://brazilian-utils.com.br/utilities.md#isvalidcep): Check if CEP (brazilian postal code) is valid. - [isValidBoleto](https://brazilian-utils.com.br/utilities.md#isvalidboleto): Check if boleto (brazilian payment method) is valid. -- [isValidPixKey](https://brazilian-utils.com.br/utilities.md#isvalidpixkey): Check if a Pix key (chave Pix) is valid: a CPF, a CNPJ, an e-mail address, a Brazilian phone number or a random key (EVP), per the DICT key formats. +- [isValidPixKey](https://brazilian-utils.com.br/utilities.md#isvalidpixkey): Check if a Pix key (chave Pix) is valid: a CPF, a CNPJ, an e-mail address, a Brazilian mobile phone number or a random key (EVP), per the DICT key formats. - [isValidPixPayload](https://brazilian-utils.com.br/utilities.md#isvalidpixpayload): Check if a Pix BR Code payload (the string behind a Pix QR Code and behind "Pix copia e cola") is valid: well-formed TLV structure, the mandatory objects present, one of the "Merchant Account Information" templates carrying the `br.gov.bcb.pix` GUI with a key or a URL, and a matching CRC-16. - [isValidNfeKey](https://brazilian-utils.com.br/utilities.md#isvalidnfekey): Check if a DF-e (Documento Fiscal eletrônico) access key (chave de acesso) is valid. - [isValidEmail](https://brazilian-utils.com.br/utilities.md#isvalidemail): Check if email is valid. - [isValidPhone](https://brazilian-utils.com.br/utilities.md#isvalidphone): Check if phone number (mobile or landline) is valid. - [isValidMobilePhone](https://brazilian-utils.com.br/utilities.md#isvalidmobilephone): Check if mobile phone number is valid. - [isValidLandlinePhone](https://brazilian-utils.com.br/utilities.md#isvalidlandlinephone): Check if landline phone number is valid. -- [isValidServicePhone](https://brazilian-utils.com.br/utilities.md#isvalidservicephone): Check if a phone number is a valid Brazilian service number, dialed without a DDD: the Códigos Não Geográficos `0300`, `0303`, `0500`, `0800` and `0900` (11 digits total), the abbreviated `300X`/`400X` numbers (8 digits), and the 3-digit Códigos de Acesso a Serviços de Utilidade Pública that Anatel has designated (e.g. `190`, `192`). +- [isValidServicePhone](https://brazilian-utils.com.br/utilities.md#isvalidservicephone): Check if a phone number is a valid Brazilian service number, dialed without a DDD: the Códigos Não Geográficos `0300`, `0303`, `0500`, `0800` and `0900` (11 digits total), the abbreviated `300X`/`400X` numbers (8 digits), and the 3-digit Códigos de Acesso a Serviços de Utilidade Pública that Anatel has designated (e.g. `190`, `192`; `112` and `911` are accepted too, as mobile-only aliases of `190` that Anatel lists alongside the other 3-digit codes). - [isValidLicensePlate](https://brazilian-utils.com.br/utilities.md#isvalidlicenseplate): Check if license plate is valid. - [isValidRenavam](https://brazilian-utils.com.br/utilities.md#isvalidrenavam): Check if RENAVAM (Registro Nacional de Veículos Automotores) is valid. - [isValidPis](https://brazilian-utils.com.br/utilities.md#isvalidpis): Check if PIS is valid. - [isValidProcessoJuridico](https://brazilian-utils.com.br/utilities.md#isvalidprocessojuridico): Validate the processo jurídico number according to CNJ's definition. - [isValidIe](https://brazilian-utils.com.br/utilities.md#isvalidie): Check if inscrição estadual (state registration) is valid. - [isValidBankAccount](https://brazilian-utils.com.br/utilities.md#isvalidbankaccount): Check if a Brazilian bank account is valid. -- [isValidIban](https://brazilian-utils.com.br/utilities.md#isvalidiban): Check if a Brazilian IBAN (International Bank Account Number) is valid, per Bacen's Diretrizes de Implementação do IBAN no Brasil (Circular BCB nº 3.625/2013): `BR` + 2 ISO 7064 MOD 97-10 check digits + 8 digit ISPB + 5 digit branch + 10 digit account + 1 letter account type (`C`/`P`) + 1 alphanumeric owner indicator, 29 characters total. +- [isValidIban](https://brazilian-utils.com.br/utilities.md#isvalidiban): Check if a Brazilian IBAN (International Bank Account Number) is valid, per Bacen's Diretrizes de Implementação do IBAN no Brasil (Circular BCB nº 3.625/2013): `BR` + 2 ISO 7064 MOD 97-10 check digits + 8 digit ISPB + 5 digit branch + 10 digit account + 1 letter account type (any letter, usually `C` for conta corrente or `P` for conta poupança) + 1 alphanumeric owner indicator, 29 characters total. - [isValidCreditCard](https://brazilian-utils.com.br/utilities.md#isvalidcreditcard): Check if a payment card number is valid using the Luhn algorithm (ISO/IEC 7812-1). - [isValidPassport](https://brazilian-utils.com.br/utilities.md#isvalidpassport): Check if a Brazilian passport number is valid (2 letters followed by 6 digits). - [isValidCnh](https://brazilian-utils.com.br/utilities.md#isvalidcnh): Check if CNH is valid. @@ -54,7 +54,7 @@ const { getCities } = await import('@brazilian-utils/brazilian-utils/get-cities' - [isValidCno](https://brazilian-utils.com.br/utilities.md#isvalidcno): Check if a CNO (Cadastro Nacional de Obras) number is valid. - [isValidCaepf](https://brazilian-utils.com.br/utilities.md#isvalidcaepf): Check if a CAEPF (Cadastro de Atividade Econômica da Pessoa Física) number is valid. - [isValidRegistroProfissional](https://brazilian-utils.com.br/utilities.md#isvalidregistroprofissional): Check the structure of a professional council registration number (registro/inscrição profissional). -- [isValidVin](https://brazilian-utils.com.br/utilities.md#isvalidvin): Check if a VIN (Vehicle Identification Number / chassi) is valid under ISO 3779. +- [isValidVin](https://brazilian-utils.com.br/utilities.md#isvalidvin): Check if a VIN (Vehicle Identification Number / chassi) is valid. - [isValidCbo](https://brazilian-utils.com.br/utilities.md#isvalidcbo): Check if a CBO (Classificação Brasileira de Ocupações) code exists in the MTE occupation table. - [isValidCnae](https://brazilian-utils.com.br/utilities.md#isvalidcnae): Check if a CNAE (Classificação Nacional de Atividades Econômicas) subclass code exists in the CNAE 2.3 table published by IBGE. - [isValidNcm](https://brazilian-utils.com.br/utilities.md#isvalidncm): Check if an NCM (Nomenclatura Comum do Mercosul) code exists in the current table published by Siscomex/MDIC. @@ -81,9 +81,9 @@ const { getCities } = await import('@brazilian-utils/brazilian-utils/get-cities' - [formatVoterId](https://brazilian-utils.com.br/utilities.md#formatvoterid): Format a voter ID number. - [formatCns](https://brazilian-utils.com.br/utilities.md#formatcns): Format a CNS (Cartão Nacional de Saúde) number into the common display groups of 3-4-4-4 digits separated by spaces. - [formatCertidao](https://brazilian-utils.com.br/utilities.md#formatcertidao): Format the matrícula of a certidão de registro civil into the printed mask of the Provimento, the 32 digits grouped as 6 2 2 4 1 5 3 7 2 and separated by spaces. -- [formatCei](https://brazilian-utils.com.br/utilities.md#formatcei): Format a CEI (Cadastro Específico do INSS) number according to the official `00.000.00000/00` mask. +- [formatCei](https://brazilian-utils.com.br/utilities.md#formatcei): Format a CEI (Cadastro Específico do INSS) number according to the usual `00.000.00000/00` mask, the one the reference implementations of the check digit agree on (the Receita Federal does not print it). - [formatCno](https://brazilian-utils.com.br/utilities.md#formatcno): Format a CNO (Cadastro Nacional de Obras) number. -- [formatCaepf](https://brazilian-utils.com.br/utilities.md#formatcaepf): Format a CAEPF (Cadastro de Atividade Econômica da Pessoa Física) number according to the official `000.000.000/000-00` mask. +- [formatCaepf](https://brazilian-utils.com.br/utilities.md#formatcaepf): Format a CAEPF (Cadastro de Atividade Econômica da Pessoa Física) number according to the usual `000.000.000/000-00` mask, the one the sources of the check digit rule agree on (the Receita Federal does not print it). - [formatCnae](https://brazilian-utils.com.br/utilities.md#formatcnae): Format a CNAE (Classificação Nacional de Atividades Econômicas) subclass code. - [formatNcm](https://brazilian-utils.com.br/utilities.md#formatncm): Format an NCM (Nomenclatura Comum do Mercosul) code. @@ -92,21 +92,21 @@ const { getCities } = await import('@brazilian-utils/brazilian-utils/get-cities' - [parseCpf](https://brazilian-utils.com.br/utilities.md#parsecpf): Remove CPF formatting, keep only digits, and cap the result to 11 digits. - [parseCnpj](https://brazilian-utils.com.br/utilities.md#parsecnpj): Remove CNPJ formatting, return a normalized value, and cap the result to 14 characters. - [parseBoleto](https://brazilian-utils.com.br/utilities.md#parseboleto): Remove boleto formatting, keep only digits, and cap the result to 47 digits (48 for boleto de arrecadação). -- [parsePixKey](https://brazilian-utils.com.br/utilities.md#parsepixkey): Identifies a Pix key and normalizes it to the canonical form the DICT expects inside a BR Code: 11 digit CPF, 14 character CNPJ, lowercased e-mail, E.164 phone or lowercase UUID EVP. +- [parsePixKey](https://brazilian-utils.com.br/utilities.md#parsepixkey): Identifies a Pix key and normalizes it to the canonical form the DICT expects inside a BR Code: 11 digit CPF, 14 character CNPJ, lowercased e-mail, E.164 mobile phone (a landline is not a Pix key) or lowercase UUID EVP. - [parsePixPayload](https://brazilian-utils.com.br/utilities.md#parsepixpayload): Parses a Pix BR Code payload into its fields. - [parseNfeKey](https://brazilian-utils.com.br/utilities.md#parsenfekey): Parses a DF-e access key into its fields (state, year, month, taxId, model, series, number, emissionType, code, checkDigit). - [parsePhone](https://brazilian-utils.com.br/utilities.md#parsephone): Remove phone formatting, keep only digits, and cap the result to 11 digits. - [parsePis](https://brazilian-utils.com.br/utilities.md#parsepis): Remove PIS formatting, keep only digits, and cap the result to 11 digits. - [parseCep](https://brazilian-utils.com.br/utilities.md#parsecep): Remove CEP formatting, keep only digits, and cap the result to 8 digits. - [parseProcessoJuridico](https://brazilian-utils.com.br/utilities.md#parseprocessojuridico): Remove processo jurídico formatting, keep only digits, and cap the result to 20 digits. -- [parseIban](https://brazilian-utils.com.br/utilities.md#parseiban): Parses a Brazilian IBAN into its fields: 2 (country code, always `BR`) + 2 (ISO 7064 MOD 97-10 check digits) + 8 (ISPB) + 5 (branch) + 10 (account) + 1 (account type, `C` or `P`) + 1 (owner indicator). +- [parseIban](https://brazilian-utils.com.br/utilities.md#parseiban): Parses a Brazilian IBAN into its fields: 2 (country code, always `BR`) + 2 (ISO 7064 MOD 97-10 check digits) + 8 (ISPB) + 5 (branch) + 10 (account) + 1 (account type, any letter, usually `C` for conta corrente or `P` for conta poupança) + 1 (owner indicator). - [parseCurrency](https://brazilian-utils.com.br/utilities.md#parsecurrency): Transforms a string to an integer or float format. - [parsePassport](https://brazilian-utils.com.br/utilities.md#parsepassport): Remove all non-alphanumeric characters from a passport number, uppercase the result, and cap it to 8 characters. - [parseCnh](https://brazilian-utils.com.br/utilities.md#parsecnh): Remove CNH formatting, keep only digits, and cap the result to 11 digits. - [parseLegalNature](https://brazilian-utils.com.br/utilities.md#parselegalnature): Remove legal nature formatting, keep only digits, and cap the result to 4 digits. - [parseLicensePlate](https://brazilian-utils.com.br/utilities.md#parselicenseplate): Remove separators from a license plate, normalize it to uppercase, and cap it to 7 characters. - [parseVoterId](https://brazilian-utils.com.br/utilities.md#parsevoterid): Remove voter ID formatting, keep only digits, and cap the result to 12 digits (13 when the UF digits identify São Paulo or Minas Gerais). -- [parseCertidao](https://brazilian-utils.com.br/utilities.md#parsecertidao): Parse the matrícula of a certidão de registro civil into its fields, returning `null` when the matrícula is not valid or when its book code is not one of the nine books defined by the Provimento. +- [parseCertidao](https://brazilian-utils.com.br/utilities.md#parsecertidao): Parse the matrícula of a certidão de registro civil into its fields, returning `null` when the matrícula is not valid, which includes a book code that is not one of the nine books. ## Generators (generate*) diff --git a/docs/pt-br/utilities.md b/docs/pt-br/utilities.md index 6506c7ee..8e9f08d0 100644 --- a/docs/pt-br/utilities.md +++ b/docs/pt-br/utilities.md @@ -45,6 +45,7 @@ Gera um CPF válido aleatório. import { generateCpf } from '@brazilian-utils/brazilian-utils' generateCpf(); +generateCpf('SP'); // o 9º dígito é 8, o código da região fiscal de SP ``` ## isValidCnpj @@ -106,6 +107,7 @@ Gera um CNPJ válido aleatório. import { generateCnpj } from '@brazilian-utils/brazilian-utils' generateCnpj(); +generateCnpj(2); // CNPJ alfanumérico, ex. 'Q0SLFMBD7VX439' ``` ## isValidBoleto @@ -174,7 +176,7 @@ getBoletoInfo('846100000005246100291102005460339004695895061080'); ## isValidPixKey -Valida se uma chave Pix é válida: um CPF, um CNPJ, um e-mail, um telefone brasileiro ou uma chave aleatória (EVP), conforme os formatos de chave do DICT. `options.accept` (tipado como `IsValidPixKeyOptions`) restringe quais tipos de chave são aceitos; o padrão é aceitar todos, e `[]` rejeita todos. Exporta o tipo `PixKeyType`. +Valida se uma chave Pix é válida: um CPF, um CNPJ, um e-mail, um telefone celular brasileiro ou uma chave aleatória (EVP), conforme os formatos de chave do DICT. O manual registra um "número de telefone celular", então um telefone fixo não é uma chave Pix válida. `options.accept` (tipado como `IsValidPixKeyOptions`) restringe quais tipos de chave são aceitos; o padrão é aceitar todos, e `[]` rejeita todos. Exporta o tipo `PixKeyType`. ```javascript import { isValidPixKey } from '@brazilian-utils/brazilian-utils'; @@ -183,13 +185,14 @@ isValidPixKey('123.456.789-09'); // true isValidPixKey('fulano@example.com'); // true isValidPixKey('(11) 98765-4321'); // true isValidPixKey('71c7d9be-4b85-4e43-9f1c-1f3b8b4e9a2d'); // true +isValidPixKey('(11) 3000-0000'); // false (telefone fixo não é chave Pix) isValidPixKey('123.456.789-09', { accept: ['email', 'evp'] }); // false isValidPixKey('not a key'); // false ``` ## parsePixKey -Identifica uma chave Pix e a normaliza para a forma canônica que o DICT espera dentro do BR Code: CPF com 11 dígitos, CNPJ com 14 caracteres, e-mail em minúsculas, telefone em E.164 ou UUID em minúsculas (EVP). Um valor de 11 dígitos válido tanto como CPF quanto como celular é lido como CPF, a menos que tenha sido escrito como telefone (prefixo `+55`/`0055` ou DDD entre parênteses). Retorna `null` quando o valor não é uma chave Pix válida. O resultado é tipado como `PixKey`. +Identifica uma chave Pix e a normaliza para a forma canônica que o DICT espera dentro do BR Code: CPF com 11 dígitos, CNPJ com 14 caracteres, e-mail em minúsculas, telefone celular em E.164 (um telefone fixo não é chave Pix) ou UUID em minúsculas (EVP). Um valor de 11 dígitos válido tanto como CPF quanto como celular é lido como CPF, a menos que tenha sido escrito como telefone (prefixo `+55`/`0055` ou DDD entre parênteses). Retorna `null` quando o valor não é uma chave Pix válida. O resultado é tipado como `PixKey`. ```javascript import { parsePixKey } from '@brazilian-utils/brazilian-utils'; @@ -199,13 +202,14 @@ parsePixKey('Fulano@Example.COM '); // { type: 'email', value: 'fulano@example.c parsePixKey('(11) 98765-4321'); // { type: 'phone', value: '+5511987654321' } parsePixKey('71C7D9BE-4B85-4E43-9F1C-1F3B8B4E9A2D'); // { type: 'evp', value: '71c7d9be-4b85-4e43-9f1c-1f3b8b4e9a2d' } +parsePixKey('(11) 3000-0000'); // null (telefone fixo não é chave Pix) parsePixKey('51998259765'); // { type: 'cpf', value: '51998259765' } (também é um telefone válido) parsePixKey('+5551998259765'); // { type: 'phone', value: '+5551998259765' } ``` ## isValidPixPayload -Valida se um payload de BR Code Pix (a string por trás de um QR Code Pix e do "Pix copia e cola") é válido: estrutura TLV bem formada, objetos obrigatórios presentes, um dos templates "Merchant Account Information" carregando o GUI `br.gov.bcb.pix` junto com uma chave ou uma URL, e um CRC-16 que confere. A chave em si não é validada contra os formatos do DICT, use `isValidPixKey` para isso. +Valida se um payload de BR Code Pix (a string por trás de um QR Code Pix e do "Pix copia e cola") é válido: estrutura TLV bem formada, objetos obrigatórios presentes, um dos templates "Merchant Account Information" carregando o GUI `br.gov.bcb.pix` junto com uma chave ou uma URL, e um CRC-16 que confere. A chave em si não é validada contra os formatos do DICT, use `isValidPixKey` para isso. Payloads que trazem a localização em um Unreserved Template (IDs 80 a 99), como o "QR Code composto" do Pix Automático (Pix recorrente), estão fora de escopo e são considerados inválidos. ```javascript import { isValidPixPayload } from '@brazilian-utils/brazilian-utils'; @@ -220,7 +224,7 @@ isValidPixPayload('00020126580014br.gov.bcb.pix...'); // false (CRC quebrado) ## parsePixPayload -Interpreta um payload de BR Code Pix e retorna seus campos. O payload é validado pelo `isValidPixPayload` primeiro, então uma estrutura malformada, um CRC quebrado ou um objeto obrigatório ausente retornam `null` em vez de um resultado parcial. Um payload estático vem com `key`, um dinâmico com `url`. O resultado é tipado como `PixPayload`; `pointOfInitiation` é tipado como `PixPointOfInitiation` (`"static"` ou `"dynamic"`). As informações da conta do recebedor devem trazer exatamente uma chave ou uma `url` (verificada com a mesma regra de localização de PSP do `generatePixPayload`), e em um payload dinâmico o valor e o `txid` são ignorados, como o manual determina. +Interpreta um payload de BR Code Pix e retorna seus campos. O payload é validado pelo `isValidPixPayload` primeiro, então uma estrutura malformada, um CRC quebrado ou um objeto obrigatório ausente retornam `null` em vez de um resultado parcial. Um payload estático vem com `key`, um dinâmico com `url`. O resultado é tipado como `PixPayload`; `pointOfInitiation` é tipado como `PixPointOfInitiation` (`"static"` ou `"dynamic"`). As informações da conta do recebedor devem trazer exatamente uma chave ou uma `url` (verificada com a mesma regra de localização de PSP do `generatePixPayload`), e em um payload dinâmico o valor e o `txid` são ignorados, como o manual determina. Payloads cuja localização fica em um Unreserved Template (IDs 80 a 99, Pix Automático) estão fora de escopo e retornam `null`. ```javascript import { parsePixPayload } from '@brazilian-utils/brazilian-utils'; @@ -265,7 +269,7 @@ generatePixPayload({ merchantName: 'Fulano', merchantCity: 'Brasília' }); // nu ## isValidNfeKey -Valida se uma chave de acesso de DF-e (Documento Fiscal eletrônico) é válida. Cobre todos os documentos que compartilham o mesmo layout de 44 dígitos: NF-e (modelo 55), NFC-e (modelo 65), CT-e (modelo 57) e MDF-e (modelo 58). Aceita espaços entre os grupos de dígitos (a máscara de exibição usual) e o prefixo `NFe` encontrado no atributo `Id` do XML do documento. +Valida se uma chave de acesso de DF-e (Documento Fiscal eletrônico) é válida. Cobre todos os documentos que compartilham o mesmo layout de 44 dígitos: NF-e (modelo 55), NFC-e (modelo 65), CT-e (modelo 57) e MDF-e (modelo 58). Aceita espaços entre os grupos de dígitos (a máscara de exibição usual) e o prefixo `NFe` encontrado no atributo `Id` do XML do documento. A forma de emissão (`tpEmis`) precisa ser um dos códigos atribuídos pelo MOC, de 1 a 7 ou 9; o 8 não é atribuído e torna a chave inválida. ```javascript import { isValidNfeKey } from '@brazilian-utils/brazilian-utils'; @@ -274,6 +278,7 @@ isValidNfeKey('35170458716523000119550010000000121000123458'); // true (NF-e, SP isValidNfeKey('NFe35170458716523000119550010000000121000123458'); // true (prefixo Id do XML) isValidNfeKey('3517 0458 7165 2300 0119 5500 1000 0000 1210 0012 3458'); // true (com máscara) isValidNfeKey('99170458716523000119550010000000121000123458'); // false (cUF inválido) +isValidNfeKey('35170458716523000119550010000000128000123455'); // false (tpEmis 8 não é atribuído) ``` ## formatNfeKey @@ -358,7 +363,7 @@ parsePhone('55987654321'); // 55987654321 (DDD 55, não confundido com o código ## isValidMobilePhone -Valida se o número de telefone celular é válido. `options.version` (tipado como `PhoneVersion`) controla qual regra de numeração celular é aplicada: `1` (padrão) aceita o formato antigo, cujo primeiro dígito do número (após o DDD) pode ser 6, 7, 8 ou 9; `2` exige o formato atual, que requer 9. +Valida se o número de telefone celular é válido. `options.version` (tipado como `PhoneVersion`) controla qual regra de numeração celular é aplicada: `1` (padrão) é o formato anterior à Resolução Anatel 749/2022, mantido por compatibilidade com a 2.3.0, cujo primeiro dígito do número (após o DDD) pode ser 6, 7, 8 ou 9; `2` exige apenas 9, um subconjunto mais restrito do art. 12 I da resolução (Serviço Móvel Pessoal). ```javascript import { isValidMobilePhone } from '@brazilian-utils/brazilian-utils'; @@ -380,7 +385,7 @@ isValidLandlinePhone('1130000000'); // true ## isValidServicePhone -Valida se um número de telefone é um número de serviço brasileiro válido, discado sem DDD: os Códigos Não Geográficos `0300`, `0303`, `0500`, `0800` e `0900` (11 dígitos no total), os números abreviados `300X`/`400X` (8 dígitos), e os códigos de 3 dígitos dos Códigos de Acesso a Serviços de Utilidade Pública designados pela Anatel (ex.: `190`, `192`). Apenas a estrutura é verificada, o número não precisa estar atribuído a ninguém. +Valida se um número de telefone é um número de serviço brasileiro válido, discado sem DDD: os Códigos Não Geográficos `0300`, `0303`, `0500`, `0800` e `0900` (11 dígitos no total), os números abreviados `300X`/`400X` (8 dígitos), e os códigos de 3 dígitos dos Códigos de Acesso a Serviços de Utilidade Pública designados pela Anatel (ex.: `190`, `192`; `112` e `911` também são aceitos, como aliases exclusivos de celular do `190` que a Anatel lista junto aos demais códigos de 3 dígitos). Apenas a estrutura é verificada, o número não precisa estar atribuído a ninguém. ```javascript import { isValidServicePhone } from '@brazilian-utils/brazilian-utils'; @@ -451,7 +456,7 @@ isValidRenavam('12345678901'); // false (checksum inválido) ## isValidPis -Valida se o PIS é válido. Aceita os caracteres de máscara usuais e espaços em branco. +Valida se o PIS é válido. Aceita os caracteres de máscara usuais (`.`, `-`, `/`, `(`, `)`, `,`, `*`) e espaços em branco. ```javascript import { isValidPis } from '@brazilian-utils/brazilian-utils'; @@ -678,7 +683,7 @@ getBanks(); // { code: '001', ispb: '00000000', name: 'Banco do Brasil S.A.' }, // { code: '003', ispb: '04902979', name: 'BANCO DA AMAZONIA S.A.' }, // { code: '004', ispb: '07237373', name: 'Banco do Nordeste do Brasil S.A.' }, -// ... mais 345 itens +// ... mais 460 itens // ] ``` @@ -708,7 +713,7 @@ getBankByIspb('99999999'); // null ## isValidIban -Valida se um IBAN (International Bank Account Number) brasileiro é válido, conforme as [Diretrizes de Implementação do IBAN no Brasil](https://www.bcb.gov.br/estabilidadefinanceira/exibenormativo?tipo=Circular&numero=3625) do Bacen (Circular BCB nº 3.625/2013): `BR` + 2 dígitos verificadores ISO 7064 MOD 97-10 + 8 dígitos de ISPB + 5 dígitos de agência + 10 dígitos de conta + 1 letra de tipo de conta (`C`/`P`) + 1 caractere alfanumérico de titularidade, totalizando 29 caracteres. Somente IBANs brasileiros (código de país `BR`) são reconhecidos; qualquer outro país retorna `false`, já que este pacote não conhece o layout de campos dos outros mais de 90 países da ISO 13616. Aceita os espaços de agrupamento usuais e não diferencia maiúsculas de minúsculas. +Valida se um IBAN (International Bank Account Number) brasileiro é válido, conforme as [Diretrizes de Implementação do IBAN no Brasil](https://www.bcb.gov.br/content/estabilidadefinanceira/Documents/sistema_pagamentos_brasileiro/IBAN-Guidelines_%20port.pdf) do Bacen (Circular BCB nº 3.625/2013): `BR` + 2 dígitos verificadores ISO 7064 MOD 97-10 + 8 dígitos de ISPB + 5 dígitos de agência + 10 dígitos de conta + 1 letra de tipo de conta (qualquer letra, normalmente `C` para conta corrente ou `P` para conta poupança) + 1 caractere alfanumérico de titularidade, totalizando 29 caracteres. Somente IBANs brasileiros (código de país `BR`) são reconhecidos; qualquer outro país retorna `false`, já que este pacote não conhece o layout de campos dos outros mais de 90 países da ISO 13616. Aceita os espaços de agrupamento usuais e não diferencia maiúsculas de minúsculas. ```javascript import { isValidIban } from '@brazilian-utils/brazilian-utils'; @@ -733,7 +738,7 @@ formatIban('BR15'); // 'BR15' ## parseIban -Interpreta um IBAN brasileiro em seus campos: 2 (código do país, sempre `BR`) + 2 (dígitos verificadores ISO 7064 MOD 97-10) + 8 (ISPB) + 5 (agência) + 10 (conta) + 1 (tipo de conta, `C` ou `P`) + 1 (indicador do titular). Aceita as mesmas formas de entrada que `isValidIban` (espaços de agrupamento, minúsculas) e retorna `null` sempre que `isValidIban` retornaria `false`. O resultado é tipado como `Iban`. +Interpreta um IBAN brasileiro em seus campos: 2 (código do país, sempre `BR`) + 2 (dígitos verificadores ISO 7064 MOD 97-10) + 8 (ISPB) + 5 (agência) + 10 (conta) + 1 (tipo de conta, qualquer letra, normalmente `C` para conta corrente ou `P` para conta poupança) + 1 (indicador do titular). Aceita as mesmas formas de entrada que `isValidIban` (espaços de agrupamento, minúsculas) e retorna `null` sempre que `isValidIban` retornaria `false`. O resultado é tipado como `Iban`, cujo `accountType` é uma `string`. ```javascript import { parseIban } from '@brazilian-utils/brazilian-utils'; @@ -1014,7 +1019,7 @@ getHolidays({ year: 2024, stateCode: 'SP' }); ## isValidPassport -Verifica se um número de passaporte brasileiro é válido (2 letras seguidas de 6 dígitos). A entrada é case-insensitive e caracteres não alfanuméricos (espaços, pontos, hífens) são ignorados. +Verifica se um número de passaporte brasileiro é válido (2 letras seguidas de 6 dígitos). Aceita tanto `string` quanto `number`; a entrada é case-insensitive e caracteres não alfanuméricos (espaços, pontos, hífens) são ignorados. ```javascript import { isValidPassport } from '@brazilian-utils/brazilian-utils'; @@ -1027,7 +1032,7 @@ isValidPassport('12345678'); // false ## formatPassport -Formata um número de passaporte brasileiro (maiúsculas, sem símbolos, limitado a 8 caracteres). +Formata um número de passaporte brasileiro (maiúsculas, sem símbolos, limitado a 8 caracteres). Uma entrada que não seja `string` retorna uma string vazia. ```javascript import { formatPassport } from '@brazilian-utils/brazilian-utils'; @@ -1048,7 +1053,7 @@ generatePassport(); // 'RY393097' ## parsePassport -Remove todos os caracteres não alfanuméricos de um número de passaporte, converte para maiúsculas e limita o resultado a 8 caracteres. +Remove todos os caracteres não alfanuméricos de um número de passaporte, converte para maiúsculas e limita o resultado a 8 caracteres. Uma entrada que não seja `string` retorna uma string vazia. ```javascript import { parsePassport } from '@brazilian-utils/brazilian-utils'; @@ -1294,7 +1299,7 @@ generatePis(); // '91077906857' ## getMunicipality -Busca informações de município por código IBGE, ou obtém o código IBGE a partir do nome do município e UF. Uma única função cobre as duas direções, dependendo se `options` tem `code` ou `municipalityName`/`uf`. `code` deve ter exatamente 7 dígitos, caso contrário a função resolve para `null`. A resolução é totalmente offline, a partir de um dataset do IBGE embutido na biblioteca: nenhuma requisição de rede é feita. A comparação do nome do município ignora acentos e diferenças entre maiúsculas/minúsculas. Um município desconhecido, uma UF desconhecida ou uma entrada inválida resolvem para `null`. +Busca informações de município por código IBGE, ou obtém o código IBGE a partir do nome do município e UF. Uma única função cobre as duas direções, dependendo se `options` tem `code` ou `municipalityName`/`uf`. `code` aceita tanto `string` quanto `number` e deve ter exatamente 7 dígitos, caso contrário a função resolve para `null`. A resolução é totalmente offline, a partir de um dataset do IBGE embutido na biblioteca: nenhuma requisição de rede é feita. A comparação do nome do município ignora acentos e diferenças entre maiúsculas/minúsculas. Um município desconhecido, uma UF desconhecida ou uma entrada inválida resolvem para `null`. ```javascript import { getMunicipality } from '@brazilian-utils/brazilian-utils'; @@ -1302,6 +1307,9 @@ import { getMunicipality } from '@brazilian-utils/brazilian-utils'; await getMunicipality({ code: '3550308' }); // ['São Paulo', 'SP'] +await getMunicipality({ code: 3550308 }); +// ['São Paulo', 'SP'] + await getMunicipality({ municipalityName: 'sao paulo', uf: 'sp' }); // '3550308' @@ -1377,7 +1385,7 @@ isHoliday(); // false ## isBusinessDay -Verifica se uma data é um dia útil no Brasil. Retorna `false` para sábados, domingos e feriados brasileiros retornados por `getHolidays` para a data local de `value` (ano/mês/dia lidos localmente), a mesma convenção usada por `isHoliday`. `options.includeOptional` (parte de `IsBusinessDayOptions`) tem valor padrão `true`, então feriados do tipo opcional (`Holiday.type === "optional"`, ou seja, Carnaval e Corpus Christi) também contam como dias não úteis, seguindo o calendário bancário brasileiro (FEBRABAN/CMN); passe `false` para considerar apenas os feriados estatutários. `options.stateCode` também considera os feriados daquele estado; um `stateCode` desconhecido/inválido é ignorado, retornando apenas os feriados nacionais. Um `value` que não é um `Date` válido retorna `false`. Só os anos de 1900 a 2099 são suportados, o intervalo que `getHolidays` calcula; uma data fora dele retorna `false`. +Verifica se uma data é um dia útil no Brasil. Retorna `false` para sábados, domingos e feriados brasileiros retornados por `getHolidays` para a data local de `value` (ano/mês/dia lidos localmente), a mesma convenção usada por `isHoliday`. `options.includeOptional` (parte de `IsBusinessDayOptions`) tem valor padrão `true`, então feriados do tipo opcional (`Holiday.type === "optional"`, ou seja, Carnaval e Corpus Christi) também contam como dias não úteis; passe `false` para considerar apenas os feriados estatutários. `options.stateCode` também considera os feriados daquele estado; um `stateCode` desconhecido/inválido é ignorado, retornando apenas os feriados nacionais. Um `value` que não é um `Date` válido retorna `false`. Só os anos de 1900 a 2099 são suportados, o intervalo que `getHolidays` calcula; uma data fora dele retorna `false`. ```javascript import { isBusinessDay } from '@brazilian-utils/brazilian-utils'; @@ -1491,7 +1499,7 @@ parseVoterId('1234 5678 8 01 91'); // '1234567880191' (título de 13 dígitos SP ## isValidCns -Verifica se um número de CNS (Cartão Nacional de Saúde) é válido, o identificador único do usuário do SUS (Sistema Único de Saúde). Cartões definitivos (iniciados em 1 ou 2) são validados com a mesma ponderação módulo 11 usada no PIS sobre uma base de 11 dígitos embutida, ajustando a base em +2 quando o dígito verificador bruto resulta em 10. Cartões provisórios (iniciados em 7, 8 ou 9) são validados por uma soma ponderada única (pesos de 15 a 1) que deve ser múltipla de 11. +Verifica se um número de CNS (Cartão Nacional de Saúde) é válido, o identificador único do usuário do SUS (Sistema Único de Saúde). Cartões definitivos (iniciados em 1 ou 2) são validados com a mesma ponderação módulo 11 usada no PIS sobre uma base de 11 dígitos embutida, ajustando a base em +2 quando o dígito verificador bruto resulta em 10. Cartões provisórios (iniciados em 7, 8 ou 9) são validados por uma soma ponderada única (pesos de 15 a 1) que deve ser múltipla de 11. O valor precisa vir escrito como os 15 dígitos, opcionalmente separados nos grupos impressos de 3-4-4-4 por espaços ou pelos caracteres de máscara usuais; letras no meio dos dígitos são rejeitadas em vez de ignoradas. ```javascript import { isValidCns } from '@brazilian-utils/brazilian-utils'; @@ -1499,6 +1507,7 @@ import { isValidCns } from '@brazilian-utils/brazilian-utils'; isValidCns('123456789010000'); // true (definitivo) isValidCns('700000000000005'); // true (provisório) isValidCns('12345678901'); // false (tamanho inválido) +isValidCns('abc123456789010000'); // false (não escrito como um CNS) ``` ## formatCns @@ -1515,9 +1524,9 @@ formatCns('89010001', { pad: true }); // '000 0000 8901 0001' ## isValidCertidao -Verifica se a matrícula de uma certidão de registro civil (nascimento, casamento, óbito e os demais atos mantidos por uma serventia de registro civil das pessoas naturais) é válida. A matrícula tem 32 dígitos distribuídos em 6 (CNS da serventia) + 2 (acervo) + 2 (serviço) + 4 (ano) + 1 (tipo do livro) + 5 (livro) + 3 (folha) + 7 (termo) + 2 (dígitos verificadores), e os dois dígitos verificadores usam módulo 11 com pesos ciclando de 2 a 10 e voltando por 0. Aceita os caracteres de máscara usuais e espaços entre e ao redor dos grupos. O layout e os dois dígitos verificadores seguem o [Provimento CNJ nº 3/2009](https://atos.cnj.jus.br/atos/detalhar/1310), cujo CNS da serventia vem do [Provimento CNJ nº 2/2009](https://atos.cnj.jus.br/atos/detalhar/1311), detalhado em [ghiorzi.org](http://ghiorzi.org/DVnew.htm) e implementado pelo [validation-br](https://github.com/klawdyo/validation-br/blob/feat-certidao/src/certidao.ts) e pelo [validator-docs](https://github.com/geekcom/validator-docs/blob/master/src/validator-docs/Rules/Certidao.php). +Verifica se a matrícula de uma certidão de registro civil (nascimento, casamento, óbito e os demais atos mantidos por uma serventia de registro civil das pessoas naturais) é válida. A matrícula tem 32 dígitos distribuídos em 6 (CNS da serventia) + 2 (acervo) + 2 (serviço) + 4 (ano) + 1 (tipo do livro) + 5 (livro) + 3 (folha) + 7 (termo) + 2 (dígitos verificadores), e os dois dígitos verificadores usam módulo 11 com pesos ciclando de 2 a 10 e voltando por 0. Aceita os caracteres de máscara usuais e espaços entre e ao redor dos grupos. O layout é o em vigor do [art. 473 do Código Nacional de Normas da Corregedoria Nacional de Justiça](https://atos.cnj.jus.br/atos/detalhar/5243) (Provimento CNJ nº 149/2023, na redação do Provimento CN nº 182/2024); a própria matrícula foi instituída pelo já revogado [Provimento CNJ nº 2/2009](https://atos.cnj.jus.br/atos/detalhar/1311). Os dígitos verificadores estão detalhados em [ghiorzi.org](http://ghiorzi.org/DVnew.htm) e implementado pelo [validation-br](https://github.com/klawdyo/validation-br/blob/feat-certidao/src/certidao.ts) e pelo [validator-docs](https://github.com/geekcom/validator-docs/blob/master/src/validator-docs/Rules/Certidao.php). -`options.accept` (parte de `IsValidCertidaoOptions`) restringe quais tipos de livro (o mesmo `CertidaoType` retornado por `parseCertidao`) contam como válidos; quando informado, o dígito do tipo de livro precisa corresponder a um dos tipos listados. O padrão é aceitar todos os tipos. +O dígito do tipo de livro sempre precisa nomear um dos nove tipos de livro (o mesmo `CertidaoType` retornado por `parseCertidao`), então uma matrícula cujo dígito é `0` é rejeitada por mais que os dígitos verificadores confiram, do mesmo jeito que `parseCertidao` devolve `null` para ela. `options.accept` (parte de `IsValidCertidaoOptions`) restringe ainda mais aos tipos listados; o padrão é aceitar todos os tipos, e um valor que não seja um array volta para esse padrão. Só uma string é aceita: os 32 dígitos de uma matrícula são mais do que um número JavaScript comporta. ```javascript import { isValidCertidao } from '@brazilian-utils/brazilian-utils'; @@ -1532,7 +1541,7 @@ isValidCertidao('104539 01 55 2013 1 00012 021 0000123 21', { accept: ['death'] ## parseCertidao -Extrai os campos da matrícula de uma certidão de registro civil, retornando `null` quando a matrícula é inválida ou quando o código do livro não é um dos nove livros definidos pelo Provimento. Os nove livros e seus códigos são os definidos pelo [Provimento CNJ nº 3/2009](https://atos.cnj.jus.br/atos/detalhar/1310), conforme listados em [ghiorzi.org](http://ghiorzi.org/DVnew.htm). +Extrai os campos da matrícula de uma certidão de registro civil, retornando `null` quando a matrícula é inválida, o que inclui um código de livro que não é um dos nove livros. O [art. 473, V do Código Nacional de Normas da Corregedoria Nacional de Justiça](https://atos.cnj.jus.br/atos/detalhar/5243) lista os códigos de 1 a 7; os códigos 8 (emancipação) e 9 (interdição) vêm do Anexo IV do revogado Provimento CNJ nº 63/2017, conforme listados em [ghiorzi.org](http://ghiorzi.org/DVnew.htm), e são mantidos porque matrículas emitidas sob ele ainda circulam. Só uma string é aceita: os 32 dígitos de uma matrícula são mais do que um número JavaScript comporta. ```javascript import { parseCertidao } from '@brazilian-utils/brazilian-utils'; @@ -1571,7 +1580,7 @@ O resultado `Certidao` traz: ## formatCertidao -Formata a matrícula de uma certidão de registro civil na máscara impressa do Provimento, os 32 dígitos agrupados em 6 2 2 4 1 5 3 7 2 e separados por espaços. `options.pad` (parte de `FormatCertidaoOptions`) preenche o valor com zeros à esquerda até 32 dígitos. A máscara é a impressa no [Provimento CNJ nº 3/2009](https://atos.cnj.jus.br/atos/detalhar/1310). +Formata a matrícula de uma certidão de registro civil na máscara impressa do Provimento, os 32 dígitos agrupados em 6 2 2 4 1 5 3 7 2 e separados por espaços. `options.pad` (parte de `FormatCertidaoOptions`) preenche o valor com zeros à esquerda até 32 dígitos. A máscara é a do [art. 473 do Código Nacional de Normas da Corregedoria Nacional de Justiça](https://atos.cnj.jus.br/atos/detalhar/5243). Só uma string é aceita: os 32 dígitos de uma matrícula são mais do que um número JavaScript comporta. ```javascript import { formatCertidao } from '@brazilian-utils/brazilian-utils'; @@ -1583,7 +1592,7 @@ formatCertidao('1552010100020112000012087', { pad: true }); // 000000 01 55 2010 ## isValidCei -Verifica se um número de CEI (Cadastro Específico do INSS) é válido. O CEI identifica o empregador sem CNPJ, como uma obra ou um produtor rural: 12 dígitos impressos como `00.000.00000/00`, sendo o último um dígito verificador calculado sobre os 11 dígitos da base com os pesos 7, 4, 1, 8, 5, 2, 1, 6, 3, 7 e 4. Aceita os caracteres de máscara usuais e espaços entre e ao redor dos grupos. A regra do dígito verificador é a implementada pelo [yii2-br-validator](https://github.com/yiibr/yii2-br-validator/blob/master/src/CeiValidator.php) e pelo [Bigai.Documentos.Brasil](https://github.com/marcos-cruz/Documento/blob/master/src/Bigai.Documentos.Brasil/Cei/Cei.cs), conferida contra os dados abertos do Cadastro Nacional de Obras (CNO) da Receita Federal. +Verifica se um número de CEI (Cadastro Específico do INSS) é válido. O CEI identifica o empregador sem CNPJ, como uma obra ou um produtor rural: 12 dígitos impressos como `00.000.00000/00`, sendo o último um dígito verificador calculado sobre os 11 dígitos da base com os pesos 7, 4, 1, 8, 5, 2, 1, 6, 3, 7 e 4. Aceita os caracteres de máscara usuais e espaços entre e ao redor dos grupos. A Receita Federal não publica essa regra de dígito verificador, então ela segue as implementações de referência do [yii2-br-validator](https://github.com/yiibr/yii2-br-validator/blob/master/src/CeiValidator.php) e do [Bigai.Documentos.Brasil](https://github.com/marcos-cruz/Documento/blob/master/src/Bigai.Documentos.Brasil/Cei/Cei.cs), conferida contra os [dados abertos do Cadastro Nacional de Obras (CNO)](https://dados.gov.br/dados/conjuntos-dados/cadastro-nacional-de-obras-cno) da Receita Federal. ```javascript import { isValidCei } from '@brazilian-utils/brazilian-utils'; @@ -1597,7 +1606,7 @@ isValidCei('000000000000'); // false (dígitos repetidos) ## formatCei -Formata um número de CEI (Cadastro Específico do INSS) na máscara oficial `00.000.00000/00`. Formata progressivamente, até onde os dígitos informados alcançarem, então também pode ser usada como máscara de digitação. `options.pad` (parte de `FormatCeiOptions`) preenche a esquerda com zeros até 12 dígitos. +Formata um número de CEI (Cadastro Específico do INSS) na máscara usual `00.000.00000/00`, a mesma em que as implementações de referência do dígito verificador concordam (a Receita Federal não a publica). Formata progressivamente, até onde os dígitos informados alcançarem, então também pode ser usada como máscara de digitação. `options.pad` (parte de `FormatCeiOptions`) preenche a esquerda com zeros até 12 dígitos. ```javascript import { formatCei } from '@brazilian-utils/brazilian-utils'; @@ -1609,7 +1618,7 @@ formatCei('249', { pad: true }); // 00.000.00002/49 ## isValidCno -Verifica se um número de CNO (Cadastro Nacional de Obras) é válido. O CNO substituiu o CEI para obras e manteve a mesma numeração, então uma obra registrada sob um CEI antigo conserva o número e os dois cadastros são validados do mesmo jeito: 12 dígitos impressos como `00.000.00000/00`, com o dígito verificador calculado sobre os 11 dígitos da base. A regra foi confirmada contra os dados abertos do Cadastro Nacional de Obras (CNO) da Receita Federal: todas as 38432 obras registradas em Minas Gerais passam nesta verificação. +Verifica se um número de CNO (Cadastro Nacional de Obras) é válido. O CNO substituiu o CEI para obras e manteve a mesma numeração, então uma obra registrada sob um CEI antigo conserva o número e os dois cadastros são validados do mesmo jeito: 12 dígitos impressos como `00.000.00000/00`, com o dígito verificador calculado sobre os 11 dígitos da base. A Receita Federal não publica a regra do dígito verificador; ela foi confirmada contra os [dados abertos do Cadastro Nacional de Obras (CNO)](https://dados.gov.br/dados/conjuntos-dados/cadastro-nacional-de-obras-cno) da Receita Federal: todas as 38432 obras registradas em Minas Gerais passam nesta verificação. ```javascript import { isValidCno } from '@brazilian-utils/brazilian-utils'; @@ -1623,7 +1632,7 @@ isValidCno('000000000000'); // false (dígitos repetidos) ## formatCno -Formata um número de CNO (Cadastro Nacional de Obras). O CNO manteve a numeração do CEI, então os dois compartilham a mesma máscara de 12 dígitos, `00.000.00000/00`. Formata progressivamente, até onde os dígitos informados alcançarem, então também pode ser usada como máscara de digitação. `options.pad` (parte de `FormatCnoOptions`) preenche a esquerda com zeros até 12 dígitos. +Formata um número de CNO (Cadastro Nacional de Obras). O CNO manteve a numeração do CEI, então os dois compartilham a mesma máscara de 12 dígitos, `00.000.00000/00`, a mesma em que as implementações de referência do dígito verificador concordam (a Receita Federal não a publica). Formata progressivamente, até onde os dígitos informados alcançarem, então também pode ser usada como máscara de digitação. `options.pad` (parte de `FormatCnoOptions`) preenche a esquerda com zeros até 12 dígitos. ```javascript import { formatCno } from '@brazilian-utils/brazilian-utils'; @@ -1635,7 +1644,7 @@ formatCno('979', { pad: true }); // 00.000.00009/79 ## isValidCaepf -Verifica se um número de CAEPF (Cadastro de Atividade Econômica da Pessoa Física) é válido. O CAEPF substituiu o CEI para a pessoa física que contrata empregados: 14 dígitos impressos como `000.000.000/000-00`, formados pela base de 9 dígitos do CPF do titular, um número de ordem de 3 dígitos para os vários cadastros do mesmo titular e 2 dígitos verificadores. Os dois dígitos usam o módulo 11 do CNPJ e o par resultante é somado a 12, com retorno a zero acima de 99. O layout e a soma de 12 estão descritos em [ghiorzi.org](http://ghiorzi.org/DVnew.htm) e são implementados do mesmo jeito pelo [brazilian-values](https://github.com/VitorLuizC/brazilian-values/blob/master/src/validators/isCAEPF.ts). +Verifica se um número de CAEPF (Cadastro de Atividade Econômica da Pessoa Física) é válido. O CAEPF substituiu o CEI para a pessoa física que contrata empregados: 14 dígitos impressos como `000.000.000/000-00`, formados pela base de 9 dígitos do CPF do titular, um número de ordem de 3 dígitos para os vários cadastros do mesmo titular e 2 dígitos verificadores. Os dois dígitos usam o módulo 11 do CNPJ e o par resultante é somado a 12, com retorno a zero acima de 99. A Receita Federal não publica o layout nem a regra dos dígitos verificadores: os dois estão descritos em [ghiorzi.org](http://ghiorzi.org/DVnew.htm) e são implementados do mesmo jeito pelo [brazilian-values](https://github.com/VitorLuizC/brazilian-values/blob/master/src/validators/isCAEPF.ts). ```javascript import { isValidCaepf } from '@brazilian-utils/brazilian-utils'; @@ -1649,7 +1658,7 @@ isValidCaepf('00000000000000'); // false (dígitos repetidos) ## formatCaepf -Formata um número de CAEPF (Cadastro de Atividade Econômica da Pessoa Física) na máscara oficial `000.000.000/000-00`. Formata progressivamente, até onde os dígitos informados alcançarem, então também pode ser usada como máscara de digitação. `options.pad` (parte de `FormatCaepfOptions`) preenche a esquerda com zeros até 14 dígitos. +Formata um número de CAEPF (Cadastro de Atividade Econômica da Pessoa Física) na máscara usual `000.000.000/000-00`, a mesma em que as fontes da regra do dígito verificador concordam (a Receita Federal não a publica). Formata progressivamente, até onde os dígitos informados alcançarem, então também pode ser usada como máscara de digitação. `options.pad` (parte de `FormatCaepfOptions`) preenche a esquerda com zeros até 14 dígitos. ```javascript import { formatCaepf } from '@brazilian-utils/brazilian-utils'; @@ -1661,7 +1670,7 @@ formatCaepf('184', { pad: true }); // 000.000.000/001-84 ## isValidRegistroProfissional -Verifica a estrutura de um número de registro/inscrição profissional. As opções são tipadas como `IsValidRegistroProfissionalOptions`: `options.council` escolhe o conselho emissor (`"OAB"`, `"CRM"`, `"CRO"`, `"CRP"` ou `"CRC"`) e o `options.stateCode` opcional verifica a UF embutida (ignorado para `"CRP"`, cujo prefixo de 2 dígitos é um código regional, não uma UF literal). É apenas uma verificação estrutural: a quantidade de dígitos e a UF são validadas, mas nenhum dígito verificador é calculado, mesmo para o CRC, cujo formato inclui um. O CREA não é suportado: seu formato de registro não pôde ser confirmado em uma fonte oficial e publicamente documentada após a unificação nacional de 2016 (RNP). +Verifica a estrutura de um número de registro/inscrição profissional. As opções são tipadas como `IsValidRegistroProfissionalOptions`: `options.council` escolhe o conselho emissor (`"OAB"`, `"CRM"`, `"CRO"`, `"CRP"` ou `"CRC"`) e o `options.stateCode` opcional verifica a UF embutida (ignorado para `"CRP"`, cujo prefixo de 2 dígitos é um código regional, não uma UF literal). É apenas uma verificação estrutural: a quantidade de dígitos e a UF são validadas, mas nenhum dígito verificador é calculado, mesmo para o CRC, cujo formato inclui um. Um registro no CRC é a UF, 6 dígitos e o tipo de registro (`"O"` Originário, `"P"` Provisório ou `"T"` Transferido, que nada diz sobre a categoria profissional), conforme o [Manual de Registro do Sistema CFC/CRCs](https://cfc.org.br/wp-content/uploads/2018/04/1_manual_registro.pdf) (item 1.1) e a Resolução CFC nº 1.707/2023. O código regional do CRP precisa ser um dos [24 Conselhos Regionais](https://site.cfp.org.br/cfp/sistema-conselhos/conselhos-pelo-brasil/) do sistema CFP, de CRP-01 a CRP-24. A OAB, o CFM e o CFO não publicam o formato dos números que emitem, então as faixas de dígitos aceitas para `"OAB"`, `"CRM"` e `"CRO"` são convencionais, não normativas. O CREA não é suportado: seu formato de registro não pôde ser confirmado em uma fonte oficial e publicamente documentada após a unificação nacional de 2016 (RNP). ```javascript import { isValidRegistroProfissional } from '@brazilian-utils/brazilian-utils'; @@ -1674,7 +1683,7 @@ isValidRegistroProfissional('SP-123456/O-3', { council: 'CRC' }); // true ## isValidVin -Valida se um VIN (Vehicle Identification Number / chassi) é válido conforme a [ISO 3779](https://www.iso.org/standard/52200.html). Verifica o tamanho (17 caracteres), as letras excluídas (`I`, `O`, `Q` nunca são válidas) e o dígito verificador na 9ª posição, calculado com a tabela de transliteração da ISO 3779 e uma soma ponderada em módulo 11, obrigatório para veículos fabricados ou importados no Brasil conforme a Resolução CONTRAN nº 27/1998. Não diferencia maiúsculas de minúsculas e remove espaços nas extremidades. +Valida se um VIN (Vehicle Identification Number / chassi) é válido. Verifica o tamanho (17 caracteres), as letras excluídas (`I`, `O`, `Q` nunca são válidas; estrutura da [ISO 3779:2009](https://www.iso.org/standard/52200.html)) e o dígito verificador na 9ª posição, calculado e transliterado conforme o [49 CFR 565.15](https://www.ecfr.gov/current/title-49/section-565.15). Esse dígito verificador é uma exigência norte-americana (49 CFR 565.15 / SAE J853): a Resolução CONTRAN nº 24/1998 e a ABNT NBR 6066 definem a estrutura do VIN brasileiro, mas não o exigem, então muitos VINs fabricados no Brasil não possuem um dígito verificador correspondente. Esta função é, portanto, uma verificação estrutural no padrão norte-americano, não um validador universal de VINs brasileiros. Não diferencia maiúsculas de minúsculas e remove espaços nas extremidades. ```javascript import { isValidVin } from '@brazilian-utils/brazilian-utils'; diff --git a/docs/utilities.md b/docs/utilities.md index c8655b0f..6e8d9c76 100644 --- a/docs/utilities.md +++ b/docs/utilities.md @@ -45,6 +45,7 @@ Generate a valid random CPF. import { generateCpf } from '@brazilian-utils/brazilian-utils' generateCpf(); +generateCpf('SP'); // the 9th digit is 8, the SP região fiscal code ``` ## isValidCnpj @@ -106,6 +107,7 @@ Generate a valid random CNPJ. import { generateCnpj } from '@brazilian-utils/brazilian-utils' generateCnpj(); +generateCnpj(2); // alphanumeric CNPJ, e.g. 'Q0SLFMBD7VX439' ``` ## isValidBoleto @@ -174,7 +176,7 @@ getBoletoInfo('846100000005246100291102005460339004695895061080'); ## isValidPixKey -Check if a Pix key (chave Pix) is valid: a CPF, a CNPJ, an e-mail address, a Brazilian phone number or a random key (EVP), per the DICT key formats. `options.accept` (typed as `IsValidPixKeyOptions`) restricts which kinds of key are accepted; it defaults to all of them, and `[]` rejects everything. Exports the `PixKeyType` type. +Check if a Pix key (chave Pix) is valid: a CPF, a CNPJ, an e-mail address, a Brazilian mobile phone number or a random key (EVP), per the DICT key formats. The manual registers a "número de telefone celular", so a landline is not a valid phone key. `options.accept` (typed as `IsValidPixKeyOptions`) restricts which kinds of key are accepted; it defaults to all of them, and `[]` rejects everything. Exports the `PixKeyType` type. ```javascript import { isValidPixKey } from '@brazilian-utils/brazilian-utils'; @@ -183,13 +185,14 @@ isValidPixKey('123.456.789-09'); // true isValidPixKey('fulano@example.com'); // true isValidPixKey('(11) 98765-4321'); // true isValidPixKey('71c7d9be-4b85-4e43-9f1c-1f3b8b4e9a2d'); // true +isValidPixKey('(11) 3000-0000'); // false (landlines are not Pix keys) isValidPixKey('123.456.789-09', { accept: ['email', 'evp'] }); // false isValidPixKey('not a key'); // false ``` ## parsePixKey -Identifies a Pix key and normalizes it to the canonical form the DICT expects inside a BR Code: 11 digit CPF, 14 character CNPJ, lowercased e-mail, E.164 phone or lowercase UUID EVP. An 11 digit value that is valid both as a CPF and as a mobile phone is read as a CPF, unless it was written as a phone number (a `+55`/`0055` prefix or a DDD wrapped in parentheses). Returns `null` when the value is not a valid Pix key. The result is typed as `PixKey`. +Identifies a Pix key and normalizes it to the canonical form the DICT expects inside a BR Code: 11 digit CPF, 14 character CNPJ, lowercased e-mail, E.164 mobile phone (a landline is not a Pix key) or lowercase UUID EVP. An 11 digit value that is valid both as a CPF and as a mobile phone is read as a CPF, unless it was written as a phone number (a `+55`/`0055` prefix or a DDD wrapped in parentheses). Returns `null` when the value is not a valid Pix key. The result is typed as `PixKey`. ```javascript import { parsePixKey } from '@brazilian-utils/brazilian-utils'; @@ -199,13 +202,14 @@ parsePixKey('Fulano@Example.COM '); // { type: 'email', value: 'fulano@example.c parsePixKey('(11) 98765-4321'); // { type: 'phone', value: '+5511987654321' } parsePixKey('71C7D9BE-4B85-4E43-9F1C-1F3B8B4E9A2D'); // { type: 'evp', value: '71c7d9be-4b85-4e43-9f1c-1f3b8b4e9a2d' } +parsePixKey('(11) 3000-0000'); // null (a landline is not a Pix key) parsePixKey('51998259765'); // { type: 'cpf', value: '51998259765' } (also a valid phone) parsePixKey('+5551998259765'); // { type: 'phone', value: '+5551998259765' } ``` ## isValidPixPayload -Check if a Pix BR Code payload (the string behind a Pix QR Code and behind "Pix copia e cola") is valid: well-formed TLV structure, the mandatory objects present, one of the "Merchant Account Information" templates carrying the `br.gov.bcb.pix` GUI with a key or a URL, and a matching CRC-16. The key itself is not checked against the DICT formats, use `isValidPixKey` for that. +Check if a Pix BR Code payload (the string behind a Pix QR Code and behind "Pix copia e cola") is valid: well-formed TLV structure, the mandatory objects present, one of the "Merchant Account Information" templates carrying the `br.gov.bcb.pix` GUI with a key or a URL, and a matching CRC-16. The key itself is not checked against the DICT formats, use `isValidPixKey` for that. Payloads that carry the location in an Unreserved Template (IDs 80 to 99), as the "QR Code composto" of Pix Automático (Pix recorrente) does, are out of scope and reported as invalid. ```javascript import { isValidPixPayload } from '@brazilian-utils/brazilian-utils'; @@ -220,7 +224,7 @@ isValidPixPayload('00020126580014br.gov.bcb.pix...'); // false (broken CRC) ## parsePixPayload -Parses a Pix BR Code payload into its fields. The payload is validated by `isValidPixPayload` first, so a malformed structure, a broken CRC or a missing mandatory object returns `null` instead of a partial result. A static payload comes back with `key`, a dynamic one with `url`. The result is typed as `PixPayload`; `pointOfInitiation` is typed as `PixPointOfInitiation` (`"static"` or `"dynamic"`). The merchant account information must carry exactly one of a key or a `url` (checked with the same PSP location rule as `generatePixPayload`), and in a dynamic payload the amount and the `txid` are ignored, as the manual mandates. +Parses a Pix BR Code payload into its fields. The payload is validated by `isValidPixPayload` first, so a malformed structure, a broken CRC or a missing mandatory object returns `null` instead of a partial result. A static payload comes back with `key`, a dynamic one with `url`. The result is typed as `PixPayload`; `pointOfInitiation` is typed as `PixPointOfInitiation` (`"static"` or `"dynamic"`). The merchant account information must carry exactly one of a key or a `url` (checked with the same PSP location rule as `generatePixPayload`), and in a dynamic payload the amount and the `txid` are ignored, as the manual mandates. Payloads whose location lives in an Unreserved Template (IDs 80 to 99, Pix Automático) are out of scope and return `null`. ```javascript import { parsePixPayload } from '@brazilian-utils/brazilian-utils'; @@ -265,7 +269,7 @@ generatePixPayload({ merchantName: 'Fulano', merchantCity: 'Brasília' }); // nu ## isValidNfeKey -Check if a DF-e (Documento Fiscal eletrônico) access key (chave de acesso) is valid. It covers every document that shares the same 44 digit layout: NF-e (modelo 55), NFC-e (modelo 65), CT-e (modelo 57) and MDF-e (modelo 58). Accepts whitespace between digit groups (the common display mask) and the `NFe` prefix found in the `Id` attribute of the document's XML. +Check if a DF-e (Documento Fiscal eletrônico) access key (chave de acesso) is valid. It covers every document that shares the same 44 digit layout: NF-e (modelo 55), NFC-e (modelo 65), CT-e (modelo 57) and MDF-e (modelo 58). Accepts whitespace between digit groups (the common display mask) and the `NFe` prefix found in the `Id` attribute of the document's XML. The emission type (`tpEmis`) must be one of the codes the MOC assigns, 1 to 7 or 9; 8 is not assigned and makes the key invalid. ```javascript import { isValidNfeKey } from '@brazilian-utils/brazilian-utils'; @@ -274,6 +278,7 @@ isValidNfeKey('35170458716523000119550010000000121000123458'); // true (NF-e, SP isValidNfeKey('NFe35170458716523000119550010000000121000123458'); // true (XML Id prefix) isValidNfeKey('3517 0458 7165 2300 0119 5500 1000 0000 1210 0012 3458'); // true (masked) isValidNfeKey('99170458716523000119550010000000121000123458'); // false (invalid cUF) +isValidNfeKey('35170458716523000119550010000000128000123455'); // false (tpEmis 8 is not assigned) ``` ## formatNfeKey @@ -358,7 +363,7 @@ parsePhone('55987654321'); // 55987654321 (area code 55, not mistaken for the +5 ## isValidMobilePhone -Check if mobile phone number is valid. `options.version` (typed as `PhoneVersion`) controls which mobile numbering rule is enforced: `1` (default) accepts the legacy format, whose first number digit (after the DDD) may be 6, 7, 8 or 9; `2` enforces the current format, which requires 9. +Check if mobile phone number is valid. `options.version` (typed as `PhoneVersion`) controls which mobile numbering rule is enforced: `1` (default) is the pre-Resolução Anatel 749/2022 format, kept for 2.3.0 compatibility, whose first number digit (after the DDD) may be 6, 7, 8 or 9; `2` enforces only 9, a stricter subset of the resolution's art. 12 I (Serviço Móvel Pessoal). ```javascript import { isValidMobilePhone } from '@brazilian-utils/brazilian-utils'; @@ -380,7 +385,7 @@ isValidLandlinePhone('1130000000'); // true ## isValidServicePhone -Check if a phone number is a valid Brazilian service number, dialed without a DDD: the Códigos Não Geográficos `0300`, `0303`, `0500`, `0800` and `0900` (11 digits total), the abbreviated `300X`/`400X` numbers (8 digits), and the 3-digit Códigos de Acesso a Serviços de Utilidade Pública that Anatel has designated (e.g. `190`, `192`). Only the structure is checked, the number does not have to be assigned to anyone. +Check if a phone number is a valid Brazilian service number, dialed without a DDD: the Códigos Não Geográficos `0300`, `0303`, `0500`, `0800` and `0900` (11 digits total), the abbreviated `300X`/`400X` numbers (8 digits), and the 3-digit Códigos de Acesso a Serviços de Utilidade Pública that Anatel has designated (e.g. `190`, `192`; `112` and `911` are accepted too, as mobile-only aliases of `190` that Anatel lists alongside the other 3-digit codes). Only the structure is checked, the number does not have to be assigned to anyone. ```javascript import { isValidServicePhone } from '@brazilian-utils/brazilian-utils'; @@ -451,7 +456,7 @@ isValidRenavam('12345678901'); // false (invalid checksum) ## isValidPis -Check if PIS is valid. Accepts the usual mask characters and whitespace. +Check if PIS is valid. Accepts the usual mask characters (`.`, `-`, `/`, `(`, `)`, `,`, `*`) and whitespace. ```javascript import { isValidPis } from '@brazilian-utils/brazilian-utils'; @@ -678,7 +683,7 @@ getBanks(); // { code: '001', ispb: '00000000', name: 'Banco do Brasil S.A.' }, // { code: '003', ispb: '04902979', name: 'BANCO DA AMAZONIA S.A.' }, // { code: '004', ispb: '07237373', name: 'Banco do Nordeste do Brasil S.A.' }, -// ... 345 more items +// ... 460 more items // ] ``` @@ -708,7 +713,7 @@ getBankByIspb('99999999'); // null ## isValidIban -Check if a Brazilian IBAN (International Bank Account Number) is valid, per Bacen's [Diretrizes de Implementação do IBAN no Brasil](https://www.bcb.gov.br/estabilidadefinanceira/exibenormativo?tipo=Circular&numero=3625) (Circular BCB nº 3.625/2013): `BR` + 2 ISO 7064 MOD 97-10 check digits + 8 digit ISPB + 5 digit branch + 10 digit account + 1 letter account type (`C`/`P`) + 1 alphanumeric owner indicator, 29 characters total. Only Brazilian IBANs (country code `BR`) are recognized; any other country returns `false`, since this package does not carry the field layout of the other 90+ ISO 13616 countries. Accepts the usual grouping spaces and is case-insensitive. +Check if a Brazilian IBAN (International Bank Account Number) is valid, per Bacen's [Diretrizes de Implementação do IBAN no Brasil](https://www.bcb.gov.br/content/estabilidadefinanceira/Documents/sistema_pagamentos_brasileiro/IBAN-Guidelines_%20port.pdf) (Circular BCB nº 3.625/2013): `BR` + 2 ISO 7064 MOD 97-10 check digits + 8 digit ISPB + 5 digit branch + 10 digit account + 1 letter account type (any letter, usually `C` for conta corrente or `P` for conta poupança) + 1 alphanumeric owner indicator, 29 characters total. Only Brazilian IBANs (country code `BR`) are recognized; any other country returns `false`, since this package does not carry the field layout of the other 90+ ISO 13616 countries. Accepts the usual grouping spaces and is case-insensitive. ```javascript import { isValidIban } from '@brazilian-utils/brazilian-utils'; @@ -733,7 +738,7 @@ formatIban('BR15'); // 'BR15' ## parseIban -Parses a Brazilian IBAN into its fields: 2 (country code, always `BR`) + 2 (ISO 7064 MOD 97-10 check digits) + 8 (ISPB) + 5 (branch) + 10 (account) + 1 (account type, `C` or `P`) + 1 (owner indicator). Accepts the same input forms as `isValidIban` (grouping spaces, lowercase) and returns `null` whenever `isValidIban` would return `false`. The result is typed as `Iban`. +Parses a Brazilian IBAN into its fields: 2 (country code, always `BR`) + 2 (ISO 7064 MOD 97-10 check digits) + 8 (ISPB) + 5 (branch) + 10 (account) + 1 (account type, any letter, usually `C` for conta corrente or `P` for conta poupança) + 1 (owner indicator). Accepts the same input forms as `isValidIban` (grouping spaces, lowercase) and returns `null` whenever `isValidIban` would return `false`. The result is typed as `Iban`, whose `accountType` is a `string`. ```javascript import { parseIban } from '@brazilian-utils/brazilian-utils'; @@ -1014,7 +1019,7 @@ getHolidays({ year: 2024, stateCode: 'SP' }); ## isValidPassport -Check if a Brazilian passport number is valid (2 letters followed by 6 digits). The input is case-insensitive and any non-alphanumeric characters (spaces, dots, hyphens) are ignored. +Check if a Brazilian passport number is valid (2 letters followed by 6 digits). Accepts both `string` and `number` input; the input is case-insensitive and any non-alphanumeric characters (spaces, dots, hyphens) are ignored. ```javascript import { isValidPassport } from '@brazilian-utils/brazilian-utils'; @@ -1027,7 +1032,7 @@ isValidPassport('12345678'); // false ## formatPassport -Format a Brazilian passport number (uppercase, without symbols, capped to 8 characters). +Format a Brazilian passport number (uppercase, without symbols, capped to 8 characters). A non-string input returns an empty string. ```javascript import { formatPassport } from '@brazilian-utils/brazilian-utils'; @@ -1048,7 +1053,7 @@ generatePassport(); // 'RY393097' ## parsePassport -Remove all non-alphanumeric characters from a passport number, uppercase the result, and cap it to 8 characters. +Remove all non-alphanumeric characters from a passport number, uppercase the result, and cap it to 8 characters. A non-string input returns an empty string. ```javascript import { parsePassport } from '@brazilian-utils/brazilian-utils'; @@ -1294,7 +1299,7 @@ generatePis(); // '91077906857' ## getMunicipality -Get municipality information by IBGE code, or get an IBGE code from municipality name and UF. A single function handles both directions, based on whether `options` has a `code` or a `municipalityName`/`uf`. `code` must be exactly 7 digits, otherwise the function resolves to `null`. Resolution is entirely offline, from a bundled IBGE dataset: no network request is made. The municipality name match ignores accents and casing. An unknown municipality, an unknown UF or invalid input all resolve to `null`. +Get municipality information by IBGE code, or get an IBGE code from municipality name and UF. A single function handles both directions, based on whether `options` has a `code` or a `municipalityName`/`uf`. `code` accepts both `string` and `number` input and must be exactly 7 digits, otherwise the function resolves to `null`. Resolution is entirely offline, from a bundled IBGE dataset: no network request is made. The municipality name match ignores accents and casing. An unknown municipality, an unknown UF or invalid input all resolve to `null`. ```javascript import { getMunicipality } from '@brazilian-utils/brazilian-utils'; @@ -1302,6 +1307,9 @@ import { getMunicipality } from '@brazilian-utils/brazilian-utils'; await getMunicipality({ code: '3550308' }); // ['São Paulo', 'SP'] +await getMunicipality({ code: 3550308 }); +// ['São Paulo', 'SP'] + await getMunicipality({ municipalityName: 'sao paulo', uf: 'sp' }); // '3550308' @@ -1377,7 +1385,7 @@ isHoliday(); // false ## isBusinessDay -Check if a date is a Brazilian business day (dia útil). Returns `false` for Saturdays, Sundays, and Brazilian holidays returned by `getHolidays` for `value`'s local calendar day (year/month/day as read locally), the same convention used by `isHoliday`. `options.includeOptional` (part of `IsBusinessDayOptions`) defaults to `true`, so optional-type holidays (`Holiday.type === "optional"`, i.e. Carnaval and Corpus Christi) also count as non-business days, matching the Brazilian banking calendar (FEBRABAN/CMN); pass `false` to only treat statutory holidays this way. `options.stateCode` also considers that state's holidays; an unknown/invalid `stateCode` is ignored, falling back to national holidays only. A `value` that is not a valid `Date` returns `false`. Only years from 1900 through 2099 are supported, the range `getHolidays` computes; a date outside it returns `false`. +Check if a date is a Brazilian business day (dia útil). Returns `false` for Saturdays, Sundays, and Brazilian holidays returned by `getHolidays` for `value`'s local calendar day (year/month/day as read locally), the same convention used by `isHoliday`. `options.includeOptional` (part of `IsBusinessDayOptions`) defaults to `true`, so optional-type holidays (`Holiday.type === "optional"`, i.e. Carnaval and Corpus Christi) also count as non-business days; pass `false` to only treat statutory holidays this way. `options.stateCode` also considers that state's holidays; an unknown/invalid `stateCode` is ignored, falling back to national holidays only. A `value` that is not a valid `Date` returns `false`. Only years from 1900 through 2099 are supported, the range `getHolidays` computes; a date outside it returns `false`. ```javascript import { isBusinessDay } from '@brazilian-utils/brazilian-utils'; @@ -1491,7 +1499,7 @@ parseVoterId('1234 5678 8 01 91'); // '1234567880191' (13-digit SP/MG voter id) ## isValidCns -Check if a CNS (Cartão Nacional de Saúde) number is valid, the unique SUS (Sistema Único de Saúde) user identifier. Definitive cards (starting with 1 or 2) are validated with the same mod 11 weighting used for PIS numbers over an embedded 11 digit base, adjusting the base by +2 when the raw check digit computes to 10. Provisional cards (starting with 7, 8 or 9) are validated instead by a single weighted sum (weights 15 down to 1) that must be a multiple of 11. +Check if a CNS (Cartão Nacional de Saúde) number is valid, the unique SUS (Sistema Único de Saúde) user identifier. Definitive cards (starting with 1 or 2) are validated with the same mod 11 weighting used for PIS numbers over an embedded 11 digit base, adjusting the base by +2 when the raw check digit computes to 10. Provisional cards (starting with 7, 8 or 9) are validated instead by a single weighted sum (weights 15 down to 1) that must be a multiple of 11. The value has to be written as the 15 digits, optionally split into the printed groups of 3-4-4-4 by whitespace or the usual mask characters; letters among the digits are rejected instead of being read past. ```javascript import { isValidCns } from '@brazilian-utils/brazilian-utils'; @@ -1499,6 +1507,7 @@ import { isValidCns } from '@brazilian-utils/brazilian-utils'; isValidCns('123456789010000'); // true (definitive) isValidCns('700000000000005'); // true (provisional) isValidCns('12345678901'); // false (wrong length) +isValidCns('abc123456789010000'); // false (not written as a CNS) ``` ## formatCns @@ -1515,9 +1524,9 @@ formatCns('89010001', { pad: true }); // '000 0000 8901 0001' ## isValidCertidao -Check if the matrícula of a certidão de registro civil (nascimento, casamento, óbito and the other acts kept by a serventia de registro civil das pessoas naturais) is valid. The matrícula has 32 digits laid out as 6 (CNS da serventia) + 2 (acervo) + 2 (serviço) + 4 (ano) + 1 (tipo do livro) + 5 (livro) + 3 (folha) + 7 (termo) + 2 (dígitos verificadores), and both check digits are modulus 11 with weights cycling from 2 to 10 and back through 0. Accepts the usual mask characters and whitespace between/around groups. The layout and both check digits follow the [Provimento CNJ nº 3/2009](https://atos.cnj.jus.br/atos/detalhar/1310), whose CNS da serventia comes from the [Provimento CNJ nº 2/2009](https://atos.cnj.jus.br/atos/detalhar/1311), as detailed by [ghiorzi.org](http://ghiorzi.org/DVnew.htm) and implemented by [validation-br](https://github.com/klawdyo/validation-br/blob/feat-certidao/src/certidao.ts) and [validator-docs](https://github.com/geekcom/validator-docs/blob/master/src/validator-docs/Rules/Certidao.php). +Check if the matrícula of a certidão de registro civil (nascimento, casamento, óbito and the other acts kept by a serventia de registro civil das pessoas naturais) is valid. The matrícula has 32 digits laid out as 6 (CNS da serventia) + 2 (acervo) + 2 (serviço) + 4 (ano) + 1 (tipo do livro) + 5 (livro) + 3 (folha) + 7 (termo) + 2 (dígitos verificadores), and both check digits are modulus 11 with weights cycling from 2 to 10 and back through 0. Accepts the usual mask characters and whitespace between/around groups. The layout is the in-force one of [art. 473 of the Código Nacional de Normas da Corregedoria Nacional de Justiça](https://atos.cnj.jus.br/atos/detalhar/5243) (Provimento CNJ nº 149/2023, in the wording of the Provimento CN nº 182/2024); the matrícula itself was instituted by the now revoked [Provimento CNJ nº 2/2009](https://atos.cnj.jus.br/atos/detalhar/1311). The check digits are detailed by [ghiorzi.org](http://ghiorzi.org/DVnew.htm) and implemented by [validation-br](https://github.com/klawdyo/validation-br/blob/feat-certidao/src/certidao.ts) and [validator-docs](https://github.com/geekcom/validator-docs/blob/master/src/validator-docs/Rules/Certidao.php). -`options.accept` (part of `IsValidCertidaoOptions`) restricts which book types (the same `CertidaoType` returned by `parseCertidao`) count as valid; when given, the book-type digit must map to one of the listed types. Defaults to every type. +The book-type digit always has to name one of the nine book types (the same `CertidaoType` returned by `parseCertidao`), so a matrícula whose digit is `0` is rejected however good its check digits are, the same way `parseCertidao` returns `null` for it. `options.accept` (part of `IsValidCertidaoOptions`) narrows that to the listed types; it defaults to every type, and a value that is not an array falls back to that default. Only a string is accepted: the 32 digits of a matrícula are more than a JavaScript number can hold. ```javascript import { isValidCertidao } from '@brazilian-utils/brazilian-utils'; @@ -1532,7 +1541,7 @@ isValidCertidao('104539 01 55 2013 1 00012 021 0000123 21', { accept: ['death'] ## parseCertidao -Parse the matrícula of a certidão de registro civil into its fields, returning `null` when the matrícula is not valid or when its book code is not one of the nine books defined by the Provimento. The nine books and their codes are the ones defined by the [Provimento CNJ nº 3/2009](https://atos.cnj.jus.br/atos/detalhar/1310), as listed by [ghiorzi.org](http://ghiorzi.org/DVnew.htm). +Parse the matrícula of a certidão de registro civil into its fields, returning `null` when the matrícula is not valid, which includes a book code that is not one of the nine books. [Art. 473, V of the Código Nacional de Normas da Corregedoria Nacional de Justiça](https://atos.cnj.jus.br/atos/detalhar/5243) lists the codes 1 to 7; the codes 8 (emancipação) and 9 (interdição) come from Anexo IV of the revoked Provimento CNJ nº 63/2017, as listed by [ghiorzi.org](http://ghiorzi.org/DVnew.htm), and are kept because matrículas issued under it are still in circulation. Only a string is accepted: the 32 digits of a matrícula are more than a JavaScript number can hold. ```javascript import { parseCertidao } from '@brazilian-utils/brazilian-utils'; @@ -1571,7 +1580,7 @@ The `Certidao` result carries: ## formatCertidao -Format the matrícula of a certidão de registro civil into the printed mask of the Provimento, the 32 digits grouped as 6 2 2 4 1 5 3 7 2 and separated by spaces. `options.pad` (part of `FormatCertidaoOptions`) left pads the value with zeros up to 32 digits. The mask is the one printed in the [Provimento CNJ nº 3/2009](https://atos.cnj.jus.br/atos/detalhar/1310). +Format the matrícula of a certidão de registro civil into the printed mask of the Provimento, the 32 digits grouped as 6 2 2 4 1 5 3 7 2 and separated by spaces. `options.pad` (part of `FormatCertidaoOptions`) left pads the value with zeros up to 32 digits. The mask is the one of [art. 473 of the Código Nacional de Normas da Corregedoria Nacional de Justiça](https://atos.cnj.jus.br/atos/detalhar/5243). Only a string is accepted: the 32 digits of a matrícula are more than a JavaScript number can hold. ```javascript import { formatCertidao } from '@brazilian-utils/brazilian-utils'; @@ -1583,7 +1592,7 @@ formatCertidao('1552010100020112000012087', { pad: true }); // 000000 01 55 2010 ## isValidCei -Check if a CEI (Cadastro Específico do INSS) number is valid. The CEI identifies an employer with no CNPJ, such as a construction work or a rural producer: 12 digits printed as `00.000.00000/00`, the last one a check digit calculated over the 11 base digits with the weights 7, 4, 1, 8, 5, 2, 1, 6, 3, 7 and 4. Accepts the usual mask characters and whitespace between/around groups. The check digit rule is the one implemented by [yii2-br-validator](https://github.com/yiibr/yii2-br-validator/blob/master/src/CeiValidator.php) and by [Bigai.Documentos.Brasil](https://github.com/marcos-cruz/Documento/blob/master/src/Bigai.Documentos.Brasil/Cei/Cei.cs), cross-checked against the Cadastro Nacional de Obras (CNO) open dataset of the Receita Federal. +Check if a CEI (Cadastro Específico do INSS) number is valid. The CEI identifies an employer with no CNPJ, such as a construction work or a rural producer: 12 digits printed as `00.000.00000/00`, the last one a check digit calculated over the 11 base digits with the weights 7, 4, 1, 8, 5, 2, 1, 6, 3, 7 and 4. Accepts the usual mask characters and whitespace between/around groups. The Receita Federal does not publish this check digit rule, so it follows the reference implementations of [yii2-br-validator](https://github.com/yiibr/yii2-br-validator/blob/master/src/CeiValidator.php) and [Bigai.Documentos.Brasil](https://github.com/marcos-cruz/Documento/blob/master/src/Bigai.Documentos.Brasil/Cei/Cei.cs), cross-checked against the [Cadastro Nacional de Obras (CNO) open dataset](https://dados.gov.br/dados/conjuntos-dados/cadastro-nacional-de-obras-cno) of the Receita Federal. ```javascript import { isValidCei } from '@brazilian-utils/brazilian-utils'; @@ -1597,7 +1606,7 @@ isValidCei('000000000000'); // false (repeated digits) ## formatCei -Format a CEI (Cadastro Específico do INSS) number according to the official `00.000.00000/00` mask. Formats progressively, as far as the digits given go, so it can also be used as an input mask. `options.pad` (part of `FormatCeiOptions`) left pads the value with zeros up to 12 digits. +Format a CEI (Cadastro Específico do INSS) number according to the usual `00.000.00000/00` mask, the one the reference implementations of the check digit agree on (the Receita Federal does not print it). Formats progressively, as far as the digits given go, so it can also be used as an input mask. `options.pad` (part of `FormatCeiOptions`) left pads the value with zeros up to 12 digits. ```javascript import { formatCei } from '@brazilian-utils/brazilian-utils'; @@ -1609,7 +1618,7 @@ formatCei('249', { pad: true }); // 00.000.00002/49 ## isValidCno -Check if a CNO (Cadastro Nacional de Obras) number is valid. The CNO replaced the CEI for construction works and kept its numbering, so a work registered under a legacy CEI keeps the same number and both registries validate identically: 12 digits printed as `00.000.00000/00` with a check digit calculated over the 11 base digits. The rule was confirmed against the Cadastro Nacional de Obras (CNO) open dataset of the Receita Federal: every one of the 38432 works registered in Minas Gerais passes this check. +Check if a CNO (Cadastro Nacional de Obras) number is valid. The CNO replaced the CEI for construction works and kept its numbering, so a work registered under a legacy CEI keeps the same number and both registries validate identically: 12 digits printed as `00.000.00000/00` with a check digit calculated over the 11 base digits. The Receita Federal does not publish the check digit rule; it was confirmed against the [Cadastro Nacional de Obras (CNO) open dataset](https://dados.gov.br/dados/conjuntos-dados/cadastro-nacional-de-obras-cno) of the Receita Federal: every one of the 38432 works registered in Minas Gerais passes this check. ```javascript import { isValidCno } from '@brazilian-utils/brazilian-utils'; @@ -1623,7 +1632,7 @@ isValidCno('000000000000'); // false (repeated digits) ## formatCno -Format a CNO (Cadastro Nacional de Obras) number. The CNO kept the CEI's numbering, so both share the same 12 digit, `00.000.00000/00` mask. Formats progressively, as far as the digits given go, so it can also be used as an input mask. `options.pad` (part of `FormatCnoOptions`) left pads the value with zeros up to 12 digits. +Format a CNO (Cadastro Nacional de Obras) number. The CNO kept the CEI's numbering, so both share the same 12 digit, `00.000.00000/00` mask, the one the reference implementations of the check digit agree on (the Receita Federal does not print it). Formats progressively, as far as the digits given go, so it can also be used as an input mask. `options.pad` (part of `FormatCnoOptions`) left pads the value with zeros up to 12 digits. ```javascript import { formatCno } from '@brazilian-utils/brazilian-utils'; @@ -1635,7 +1644,7 @@ formatCno('979', { pad: true }); // 00.000.00009/79 ## isValidCaepf -Check if a CAEPF (Cadastro de Atividade Econômica da Pessoa Física) number is valid. The CAEPF replaced the CEI for individuals who hire employees: 14 digits printed as `000.000.000/000-00`, formed by the 9 digit CPF base of the holder, a 3 digit sequence for the holder's several registrations and 2 check digits. Both check digits use the modulus 11 of the CNPJ, and the resulting pair is then shifted by 12, wrapping around 100. The layout and the shift of 12 are described by [ghiorzi.org](http://ghiorzi.org/DVnew.htm) and implemented the same way by [brazilian-values](https://github.com/VitorLuizC/brazilian-values/blob/master/src/validators/isCAEPF.ts). +Check if a CAEPF (Cadastro de Atividade Econômica da Pessoa Física) number is valid. The CAEPF replaced the CEI for individuals who hire employees: 14 digits printed as `000.000.000/000-00`, formed by the 9 digit CPF base of the holder, a 3 digit sequence for the holder's several registrations and 2 check digits. Both check digits use the modulus 11 of the CNPJ, and the resulting pair is then shifted by 12, wrapping around 100. The Receita Federal does not publish the layout or the check digit rule: both are described by [ghiorzi.org](http://ghiorzi.org/DVnew.htm) and implemented the same way by [brazilian-values](https://github.com/VitorLuizC/brazilian-values/blob/master/src/validators/isCAEPF.ts). ```javascript import { isValidCaepf } from '@brazilian-utils/brazilian-utils'; @@ -1649,7 +1658,7 @@ isValidCaepf('00000000000000'); // false (repeated digits) ## formatCaepf -Format a CAEPF (Cadastro de Atividade Econômica da Pessoa Física) number according to the official `000.000.000/000-00` mask. Formats progressively, as far as the digits given go, so it can also be used as an input mask. `options.pad` (part of `FormatCaepfOptions`) left pads the value with zeros up to 14 digits. +Format a CAEPF (Cadastro de Atividade Econômica da Pessoa Física) number according to the usual `000.000.000/000-00` mask, the one the sources of the check digit rule agree on (the Receita Federal does not print it). Formats progressively, as far as the digits given go, so it can also be used as an input mask. `options.pad` (part of `FormatCaepfOptions`) left pads the value with zeros up to 14 digits. ```javascript import { formatCaepf } from '@brazilian-utils/brazilian-utils'; @@ -1661,7 +1670,7 @@ formatCaepf('184', { pad: true }); // 000.000.000/001-84 ## isValidRegistroProfissional -Check the structure of a professional council registration number (registro/inscrição profissional). Options are typed as `IsValidRegistroProfissionalOptions`: `options.council` picks the issuing council (`"OAB"`, `"CRM"`, `"CRO"`, `"CRP"` or `"CRC"`) and the optional `options.stateCode` checks the embedded UF (ignored for `"CRP"`, whose 2 digit prefix is a regional code, not a literal UF). This is a structural check only: digit counts and the UF are validated, but no check digit is computed, even for CRC, whose format includes one. CREA is not supported: its registration format could not be confirmed from an official, publicly documented source after the 2016 national unification (RNP). +Check the structure of a professional council registration number (registro/inscrição profissional). Options are typed as `IsValidRegistroProfissionalOptions`: `options.council` picks the issuing council (`"OAB"`, `"CRM"`, `"CRO"`, `"CRP"` or `"CRC"`) and the optional `options.stateCode` checks the embedded UF (ignored for `"CRP"`, whose 2 digit prefix is a regional code, not a literal UF). This is a structural check only: digit counts and the UF are validated, but no check digit is computed, even for CRC, whose format includes one. A CRC registration is the UF, 6 digits and the tipo de registro (`"O"` Originário, `"P"` Provisório or `"T"` Transferido, which says nothing about the professional category), as published in the [Manual de Registro do Sistema CFC/CRCs](https://cfc.org.br/wp-content/uploads/2018/04/1_manual_registro.pdf) (item 1.1) and in the Resolução CFC nº 1.707/2023. A CRP regional code has to be one of the [24 Conselhos Regionais](https://site.cfp.org.br/cfp/sistema-conselhos/conselhos-pelo-brasil/) of the CFP system, CRP-01 to CRP-24. The OAB, the CFM and the CFO publish no format for the numbers they issue, so the digit ranges accepted for `"OAB"`, `"CRM"` and `"CRO"` are conventional rather than normative. CREA is not supported: its registration format could not be confirmed from an official, publicly documented source after the 2016 national unification (RNP). ```javascript import { isValidRegistroProfissional } from '@brazilian-utils/brazilian-utils'; @@ -1674,7 +1683,7 @@ isValidRegistroProfissional('SP-123456/O-3', { council: 'CRC' }); // true ## isValidVin -Check if a VIN (Vehicle Identification Number / chassi) is valid under [ISO 3779](https://www.iso.org/standard/52200.html). Checks the length (17 characters), the excluded letters (`I`, `O`, `Q` are never valid) and the check digit at the 9th position, calculated with the ISO 3779 transliteration table and a weighted MOD 11 sum, mandatory for vehicles manufactured in or imported into Brazil under Resolução CONTRAN nº 27/1998. Case-insensitive and trims surrounding whitespace. +Check if a VIN (Vehicle Identification Number / chassi) is valid. Checks the length (17 characters), the excluded letters (`I`, `O`, `Q` are never valid; [ISO 3779:2009](https://www.iso.org/standard/52200.html) structure) and the check digit at the 9th position, with the check digit and transliteration computed per [49 CFR 565.15](https://www.ecfr.gov/current/title-49/section-565.15). That check digit is a North-American requirement (49 CFR 565.15 / SAE J853): Resolução CONTRAN nº 24/1998 and ABNT NBR 6066 define the Brazilian VIN structure but do not mandate it, so many Brazilian-built VINs do not carry a matching check digit. This function is therefore a North-American-style structural check, not a universal validator of Brazilian VINs. Case-insensitive and trims surrounding whitespace. ```javascript import { isValidVin } from '@brazilian-utils/brazilian-utils'; diff --git a/scripts/cbo.ts b/scripts/cbo.ts index e00b48fc..81b6ca75 100644 --- a/scripts/cbo.ts +++ b/scripts/cbo.ts @@ -59,8 +59,8 @@ const main = async (): Promise => { * * Generated by \`node ./scripts/cbo.ts\`. Do not edit by hand. * - * @see https://raw.githubusercontent.com/lucaashoff/lista-cbo-json/main/cbos.json - * @see http://www.mtecbo.gov.br/cbosite/pages/downloads.jsf + * @see Based on: https://raw.githubusercontent.com/lucaashoff/lista-cbo-json/main/cbos.json + * @see Official: http://www.mtecbo.gov.br/cbosite/pages/downloads.jsf */ export const CBO_TITLES: Record = ${JSON.stringify(sorted)}; `, diff --git a/scripts/cfop.ts b/scripts/cfop.ts index e2380e0f..2a543bbe 100644 --- a/scripts/cfop.ts +++ b/scripts/cfop.ts @@ -75,8 +75,8 @@ const main = async (): Promise => { * * Generated by \`node ./scripts/cfop.ts\`. Do not edit by hand. * - * @see https://raw.githubusercontent.com/jansenfelipe/cfop/master/cfop.csv - * @see https://www.confaz.fazenda.gov.br/legislacao/ajustes/2001/AJ_007_01 + * @see Based on: https://raw.githubusercontent.com/jansenfelipe/cfop/master/cfop.csv + * @see Official: https://www.confaz.fazenda.gov.br/legislacao/ajustes/2001/AJ_007_01 */ export const CFOP_TABLE: Record = ${JSON.stringify(sorted)}; `, diff --git a/scripts/cities.ts b/scripts/cities.ts index 00a54427..12d6ec71 100644 --- a/scripts/cities.ts +++ b/scripts/cities.ts @@ -113,7 +113,7 @@ const main = async (): Promise => { * \`[name, ibgeCode]\` tuple per municipality, sorted by name with \`localeCompare\` in the * "pt-BR" locale. Generated by \`scripts/cities.ts\`. * - * @see https://servicodados.ibge.gov.br/api/docs/localidades + * @see Official: https://servicodados.ibge.gov.br/api/docs/localidades */ export type Municipality = { /** The 7-digit IBGE municipality code. */ diff --git a/scripts/cnae.ts b/scripts/cnae.ts index 2a889ee7..53b5c474 100644 --- a/scripts/cnae.ts +++ b/scripts/cnae.ts @@ -51,8 +51,8 @@ const main = async (): Promise => { * * Generated by \`node ./scripts/cnae.ts\`. Do not edit by hand. * - * @see https://servicodados.ibge.gov.br/api/v2/cnae/subclasses - * @see https://concla.ibge.gov.br/classificacoes/por-tema/atividades-economicas/classificacao-nacional-de-atividades-economicas + * @see Official: https://servicodados.ibge.gov.br/api/v2/cnae/subclasses + * @see Official: https://concla.ibge.gov.br/classificacoes/por-tema/atividades-economicas/classificacao-nacional-de-atividades-economicas */ export const CNAE_SUBCLASSES: Record = ${JSON.stringify(data)}; `, diff --git a/scripts/states.ts b/scripts/states.ts index d226a107..0a94516d 100644 --- a/scripts/states.ts +++ b/scripts/states.ts @@ -92,7 +92,7 @@ export type State = { * locale. \`ibgeCode\` is the 2-digit IBGE code of the Federative Unit ("cUF"), the same code * found in the first field of every DF-e access key (chave de acesso). * - * @see https://servicodados.ibge.gov.br/api/docs/localidades + * @see Official: https://servicodados.ibge.gov.br/api/docs/localidades */ export const DATA: readonly State[] = ${JSON.stringify(states)};`, ); diff --git a/src/_internals/constants/area-codes.ts b/src/_internals/constants/area-codes.ts index faefbc3f..6cda176f 100644 --- a/src/_internals/constants/area-codes.ts +++ b/src/_internals/constants/area-codes.ts @@ -6,10 +6,13 @@ import { type StateCode } from "./states"; * second, richer literal mapping every one of those same 67 codes to its state (UF), verified * one by one against the ANATEL numbering plan reflected by the BrasilAPI DDD dataset. * - * @see Official: https://informacoes.anatel.gov.br/legislacao/resolucoes/2010/167-resolucao-553 - * (Resolução Anatel 553/2010, Plano Geral de Numeração) - * @see Based on: https://brasilapi.com.br/docs#tag/DDD BrasilAPI DDD endpoint (`GET - * /api/ddd/v1/{ddd}`), used to verify the code-to-state mapping. + * Resolução Anatel nº 749/2022, art. 15, defines the Código Nacional (area code); the gov.br + * page below lists the codes actually allocated. The BrasilAPI DDD endpoint (`GET + * /api/ddd/v1/{ddd}`) was used to verify the code-to-state mapping. + * + * @see Official: https://informacoes.anatel.gov.br/legislacao/resolucoes/2022/1641-resolucao-749 + * @see Official: https://www.gov.br/anatel/pt-br/regulado/numeracao/codigos-nacionais + * @see Based on: https://brasilapi.com.br/docs#tag/DDD */ export const VALID_AREA_CODES: readonly number[] = [ 11, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 24, 27, 28, 31, 32, 33, 34, 35, 37, 38, 41, 42, 43, diff --git a/src/_internals/constants/banks.ts b/src/_internals/constants/banks.ts index 17cfcadd..9528a21a 100644 --- a/src/_internals/constants/banks.ts +++ b/src/_internals/constants/banks.ts @@ -2,7 +2,7 @@ * Brazilian STR (Sistema de Transferência de Reservas) participants that have a compensation * code (commonly known as COMPE), published by Banco Central do Brasil. Generated by * `scripts/banks.ts`. - * @see https://www.bcb.gov.br/content/estabilidadefinanceira/str1/ParticipantesSTR.csv + * @see Official: https://www.bcb.gov.br/content/estabilidadefinanceira/str1/ParticipantesSTR.csv */ export type Bank = { /** Compensation code (COMPE), 3 digits, zero-padded. */ diff --git a/src/_internals/constants/cbo.ts b/src/_internals/constants/cbo.ts index 70e0ad39..c10017ca 100644 --- a/src/_internals/constants/cbo.ts +++ b/src/_internals/constants/cbo.ts @@ -10,8 +10,8 @@ * * Generated by `node ./scripts/cbo.ts`. Do not edit by hand. * - * @see https://raw.githubusercontent.com/lucaashoff/lista-cbo-json/main/cbos.json - * @see http://www.mtecbo.gov.br/cbosite/pages/downloads.jsf + * @see Based on: https://raw.githubusercontent.com/lucaashoff/lista-cbo-json/main/cbos.json + * @see Official: http://www.mtecbo.gov.br/cbosite/pages/downloads.jsf */ export const CBO_TITLES: Record = { "111105": "Senador", diff --git a/src/_internals/constants/cfop.ts b/src/_internals/constants/cfop.ts index e03b3302..68a9028c 100644 --- a/src/_internals/constants/cfop.ts +++ b/src/_internals/constants/cfop.ts @@ -6,8 +6,8 @@ * * Generated by `node ./scripts/cfop.ts`. Do not edit by hand. * - * @see https://raw.githubusercontent.com/jansenfelipe/cfop/master/cfop.csv - * @see https://www.confaz.fazenda.gov.br/legislacao/ajustes/2001/AJ_007_01 + * @see Based on: https://raw.githubusercontent.com/jansenfelipe/cfop/master/cfop.csv + * @see Official: https://www.confaz.fazenda.gov.br/legislacao/ajustes/2001/AJ_007_01 */ export const CFOP_TABLE: Record = { "1101": "Compra para industrialização ou produção rural", diff --git a/src/_internals/constants/cities.ts b/src/_internals/constants/cities.ts index caab70e4..2c81c6b2 100644 --- a/src/_internals/constants/cities.ts +++ b/src/_internals/constants/cities.ts @@ -5,7 +5,7 @@ import { type StateCode } from "./states"; * `[name, ibgeCode]` tuple per municipality, sorted by name with `localeCompare` in the * "pt-BR" locale. Generated by `scripts/cities.ts`. * - * @see https://servicodados.ibge.gov.br/api/docs/localidades + * @see Official: https://servicodados.ibge.gov.br/api/docs/localidades */ export type Municipality = { /** The 7-digit IBGE municipality code. */ diff --git a/src/_internals/constants/cnae.ts b/src/_internals/constants/cnae.ts index 53d21bc6..718c2240 100644 --- a/src/_internals/constants/cnae.ts +++ b/src/_internals/constants/cnae.ts @@ -4,8 +4,8 @@ * * Generated by `node ./scripts/cnae.ts`. Do not edit by hand. * - * @see https://servicodados.ibge.gov.br/api/v2/cnae/subclasses - * @see https://concla.ibge.gov.br/classificacoes/por-tema/atividades-economicas/classificacao-nacional-de-atividades-economicas + * @see Official: https://servicodados.ibge.gov.br/api/v2/cnae/subclasses + * @see Official: https://concla.ibge.gov.br/classificacoes/por-tema/atividades-economicas/classificacao-nacional-de-atividades-economicas */ export const CNAE_SUBCLASSES: Record = { "1011201": "FRIGORÍFICO - ABATE DE BOVINOS", diff --git a/src/_internals/constants/service-phone.ts b/src/_internals/constants/service-phone.ts index 2bb01f63..bf2a7eeb 100644 --- a/src/_internals/constants/service-phone.ts +++ b/src/_internals/constants/service-phone.ts @@ -17,8 +17,8 @@ * - **Código de Acesso a Serviços de Utilidade Pública (SUP)**, art. 13-14: 3 digits, with the * whole `1N₂N₁` range destined to SUP and every other 3-digit series held in reserva técnica. * Individual codes are designated one by one by Anatel Ato, so the codes below are the - * consolidated list Anatel publishes, not the full `100`-`199` range. `112` and `911` are - * mobile-only aliases of `190` and are listed by Anatel alongside the `1XX` codes. + * consolidated list Anatel publishes, not the full `100`-`199` range (see `isValidServicePhone` + * for the `112`/`911` mobile-alias note). * - **The abbreviated `300X`/`400X` numbers** (`3003-1234`, `4004-1234`) are *not* a regulatory * category at all. They are ordinary 8-digit geographic STFC user numbers (art. 11 assigns * `2`-`6` as the first digit of a fixed-line number) whose 4-digit prefix a carrier licenses diff --git a/src/_internals/constants/states.ts b/src/_internals/constants/states.ts index e592270e..ce2f5f11 100644 --- a/src/_internals/constants/states.ts +++ b/src/_internals/constants/states.ts @@ -77,7 +77,7 @@ export type State = { * locale. `ibgeCode` is the 2-digit IBGE code of the Federative Unit ("cUF"), the same code * found in the first field of every DF-e access key (chave de acesso). * - * @see https://servicodados.ibge.gov.br/api/docs/localidades + * @see Official: https://servicodados.ibge.gov.br/api/docs/localidades */ export const DATA: readonly State[] = [ { code: "AC", name: "Acre", regionCode: "N", regionName: "Norte", ibgeCode: 12 }, diff --git a/src/convert-license-plate-to-mercosul/constants.ts b/src/convert-license-plate-to-mercosul/constants.ts index 9bdf8f86..edb2c799 100644 --- a/src/convert-license-plate-to-mercosul/constants.ts +++ b/src/convert-license-plate-to-mercosul/constants.ts @@ -2,8 +2,11 @@ * Official digit to letter conversion table used to turn an old format plate's 5th character * into the Mercosul format's embedded letter (0=A, 1=B, ..., 9=J). * + * Resolução CONTRAN nº 969/2022, art. 2º § 4º. The linked DOU PDF has no annexes; the table + * above comes from Anexo II, published separately on the CONTRAN resolutions page. + * * @see Official: https://www.gov.br/transportes/pt-br/assuntos/transito/conteudo-contran/resolucoes/resolucao9692022.pdf - * Resolução CONTRAN nº 969/2022, art. 2º § 4º and Anexo II. + * @see Official: https://www.gov.br/transportes/pt-br/assuntos/transito/conteudo-contran/resolucoes */ export const DIGIT_TO_MERCOSUL_LETTER: Record = { "0": "A", diff --git a/src/convert-license-plate-to-mercosul/convert-license-plate-to-mercosul.ts b/src/convert-license-plate-to-mercosul/convert-license-plate-to-mercosul.ts index 61fa5169..bb9b9709 100644 --- a/src/convert-license-plate-to-mercosul/convert-license-plate-to-mercosul.ts +++ b/src/convert-license-plate-to-mercosul/convert-license-plate-to-mercosul.ts @@ -20,8 +20,11 @@ import { DIGIT_TO_MERCOSUL_LETTER } from "./constants"; * convertLicensePlateToMercosul("invalid"); // "" * ``` * + * Resolução CONTRAN nº 969/2022, art. 2º § 4º. The linked DOU PDF has no annexes; the digit to + * letter table comes from Anexo II, published separately on the CONTRAN resolutions page. + * * @see Official: https://www.gov.br/transportes/pt-br/assuntos/transito/conteudo-contran/resolucoes/resolucao9692022.pdf - * Resolução CONTRAN nº 969/2022, art. 2º § 4º and Anexo II. + * @see Official: https://www.gov.br/transportes/pt-br/assuntos/transito/conteudo-contran/resolucoes */ export const convertLicensePlateToMercosul = (value: string): string => { if (getFormatLicensePlate(value) !== "LLLNNNN") return ""; diff --git a/src/convert-number-to-words/convert-number-to-words.ts b/src/convert-number-to-words/convert-number-to-words.ts index 64bb557e..1e8ac3ae 100644 --- a/src/convert-number-to-words/convert-number-to-words.ts +++ b/src/convert-number-to-words/convert-number-to-words.ts @@ -42,7 +42,9 @@ export type ConvertNumberToWordsOptions = { * convertNumberToWords(NaN); // "" * ``` * - * @see Based on: https://github.com/brazilian-utils/python/blob/main/brutils/currency.py + * @see Based on: https://github.com/savoirfairelinux/num2words `brutils` itself has no dedicated + * number-to-words module (its `currency.py` delegates the Portuguese numeral text to this + * library's `pt_BR` locale); this is the reference for the numeral-word tables reproduced here. */ export const convertNumberToWords = ( value: number, diff --git a/src/format-boleto/format-boleto.ts b/src/format-boleto/format-boleto.ts index ce89d16b..f643f5be 100644 --- a/src/format-boleto/format-boleto.ts +++ b/src/format-boleto/format-boleto.ts @@ -33,10 +33,13 @@ export type FormatBoletoOptions = { * // "82630000001-1 09880010070-2 02410202400-0 00020510451-9" * ``` * + * Carta-Circular BCB nº 2.926/2000 specifies the linha digitável fields, the módulo 11 check + * digit (using 1 for remainders 0, 10 and 1) and the fator de vencimento behind the 47 digit + * cobrança bancária slip; the FEBRABAN layout index covers the arrecadação slip. + * * @see Official: https://cmsarquivos.febraban.org.br/Arquivos/documentos/PDF/Layout%20-%20C%C3%B3digo%20de%20Barras%20-%20Vers%C3%A3o%208%20-%2011_05_2026.pdf - * @see Official: https://portal.febraban.org.br/pagina/3166/33/pt-br/layout-cobranca FEBRABAN, - * "Layout Padrão de Cobrança / Especificações Técnicas para Cobrança", the cobrança bancária - * layout behind the 47 digit linha digitável and its fator de vencimento. + * @see Official: https://www.bcb.gov.br/pre/normativos/c_circ/2000/pdf/c_circ_2926_v1_O.pdf + * @see Official: https://portal.febraban.org.br/pagina/3425/33/pt-br/layout-febraban */ export const formatBoleto = (value: string | number, options?: FormatBoletoOptions): string => { if (isNullish(value)) return ""; diff --git a/src/format-cep/format-cep.ts b/src/format-cep/format-cep.ts index 5ee15c5d..34970d0c 100644 --- a/src/format-cep/format-cep.ts +++ b/src/format-cep/format-cep.ts @@ -23,6 +23,7 @@ export type FormatCepOptions = { * ``` * * @see Official: https://www.correios.com.br/enviar/precisa-de-ajuda/tudo-sobre-cep + * @see Official: https://www.correios.com.br/enviar/precisa-de-ajuda/guia-de-enderecamento/guia-de-enderecamento */ export const formatCep = (value: string | number, options?: FormatCepOptions): string => isNullish(value) diff --git a/src/format-cnh/format-cnh.ts b/src/format-cnh/format-cnh.ts index baf620f4..2086a9f8 100644 --- a/src/format-cnh/format-cnh.ts +++ b/src/format-cnh/format-cnh.ts @@ -22,7 +22,11 @@ export type FormatCnhOptions = { * formatCnh("8900", { pad: true }); // "000000089-00" * ``` * - * @see Official: https://www.planalto.gov.br/ccivil_03/leis/l9503compilado.htm + * Resolução CONTRAN nº 886/2021, art. 4º I, defines the CNH registry number as 9 characters plus + * 2 security check digits, which is the layout this mask reproduces; no official text publishes + * the check-digit weights used to compute them. + * + * @see Official: https://www.gov.br/transportes/pt-br/assuntos/transito/conteudo-contran/resolucoes/Resolucao8862021F.pdf */ export const formatCnh = (value: string | number, options?: FormatCnhOptions): string => isNullish(value) diff --git a/src/format-cpf/format-cpf.ts b/src/format-cpf/format-cpf.ts index def7520d..d949c01a 100644 --- a/src/format-cpf/format-cpf.ts +++ b/src/format-cpf/format-cpf.ts @@ -29,7 +29,8 @@ export type FormatCpfOptions = { * ``` * * @see Official: https://www.gov.br/receitafederal/pt-br/assuntos/meu-cpf - * @see Based on: https://github.com/brazilian-utils/brutils-python/blob/main/brutils/cpf.py + * @see Official: http://sped.rfb.gov.br/arquivo/show/8231 + * @see Based on: https://github.com/brazilian-utils/python/blob/main/brutils/cpf.py */ export const formatCpf = (value: string | number, options?: FormatCpfOptions): string => { if (isNullish(value)) return ""; diff --git a/src/format-currency/format-currency.ts b/src/format-currency/format-currency.ts index 0cc7c717..01fd616b 100644 --- a/src/format-currency/format-currency.ts +++ b/src/format-currency/format-currency.ts @@ -52,7 +52,7 @@ const toNumber = (value: unknown, precision: number): number => * A value that is not a finite number, such as `NaN`, `Infinity` or `-Infinity`, formats as * an empty string. * - * The precision is clamped to the `0..100` range accepted by `Intl.NumberFormat`. + * The precision is clamped to `0-20`, the range Node's `Intl.NumberFormat` accepts. * * @param {string|number} value - The value to be formatted. Can be a string or a number. * @param {FormatCurrencyOptions} [options] - Optional formatting options. diff --git a/src/format-legal-nature/format-legal-nature.ts b/src/format-legal-nature/format-legal-nature.ts index 3c75dbe3..8283f388 100644 --- a/src/format-legal-nature/format-legal-nature.ts +++ b/src/format-legal-nature/format-legal-nature.ts @@ -14,6 +14,7 @@ import { sanitizeToDigits } from "../_internals/sanitize-to-digits/sanitize-to-d * ``` * * @see Official: https://concla.ibge.gov.br/estrutura/natjur-estrutura/natureza-juridica-2021 + * @see Official: https://concla.ibge.gov.br/images/concla/documentacao/CONCLA-TNJ2021-EstruturaDetalhada.pdf */ export const formatLegalNature = (value: string | number): string => isNullish(value) diff --git a/src/format-pis/format-pis.ts b/src/format-pis/format-pis.ts index b45dbc2b..822241d6 100644 --- a/src/format-pis/format-pis.ts +++ b/src/format-pis/format-pis.ts @@ -24,7 +24,9 @@ export type FormatPisOptions = { * ``` * * @see Official: https://www.gov.br/inss/pt-br/direitos-e-deveres/inscricao-e-contribuicao/inscricao - * @see Based on: https://github.com/brazilian-utils/brutils-python/blob/main/brutils/pis.py + * @see Official: https://www.gov.br/esocial/pt-br/documentacao-tecnica/manuais/mos-manual-de-orientacao-do-esocial-vs-2-4.pdf + * @see Official: https://www.sirc.gov.br/wp-content/uploads/manual_sirc_recomendacoes_tecnicas_v7.pdf + * @see Based on: https://github.com/brazilian-utils/python/blob/main/brutils/pis.py */ export const formatPis = (value: string | number, options?: FormatPisOptions): string => isNullish(value) diff --git a/src/format-processo-juridico/format-processo-juridico.ts b/src/format-processo-juridico/format-processo-juridico.ts index 407be631..270a3422 100644 --- a/src/format-processo-juridico/format-processo-juridico.ts +++ b/src/format-processo-juridico/format-processo-juridico.ts @@ -21,7 +21,9 @@ export type FormatProcessoJuridicoOptions = { * formatProcessoJuridico("00020802520125150049"); // "0002080-25.2012.5.15.0049" * ``` * - * @see Official: https://atos.cnj.jus.br/atos/detalhar/119 Resolução CNJ nº 65/2008 + * Resolução CNJ nº 65/2008 defines this Número Único de Processo layout and its check digits. + * + * @see Official: https://atos.cnj.jus.br/atos/detalhar/119 */ export const formatProcessoJuridico = ( value: string | number, diff --git a/src/format-voter-id/format-voter-id.ts b/src/format-voter-id/format-voter-id.ts index 7f2fa08d..69fa08a0 100644 --- a/src/format-voter-id/format-voter-id.ts +++ b/src/format-voter-id/format-voter-id.ts @@ -26,7 +26,12 @@ const LENGTH = 12; * formatVoterId("1234567880191"); // "1234 5678 8 01 91" * ``` * - * @see Official: https://www.tse.jus.br/legislacao/compilada/res/2003/resolucao-no-21-538-de-14-de-outubro-de-2003 + * The 13-digit São Paulo/Minas Gerais grouping is brutils parity, not published by the TSE. A + * 14-or-more-digit input is read the same way as a 13-digit one: it is grouped as a São Paulo or + * Minas Gerais id whenever its 10th and 11th digits are "01"/"02", extra trailing digits included. + * + * @see Official: https://www.tse.jus.br/legislacao/compilada/res/2021/resolucao-no-23-659-de-26-de-outubro-de-2021 + * @see Based on: https://github.com/brazilian-utils/python/blob/main/brutils/voter_id.py */ export const formatVoterId = (value: string | number): string => { if (isNullish(value)) return ""; diff --git a/src/generate-boleto/generate-boleto.ts b/src/generate-boleto/generate-boleto.ts index 7b0cb365..194042c5 100644 --- a/src/generate-boleto/generate-boleto.ts +++ b/src/generate-boleto/generate-boleto.ts @@ -72,10 +72,13 @@ const generateArrecadacao = (): string => { * generateBoleto({ type: "arrecadacao" }); // "846100000005246100291102005460339004695895061080" * ``` * + * Carta-Circular BCB nº 2.926/2000 specifies the linha digitável fields, the módulo 11 check + * digit (using 1 for remainders 0, 10 and 1) and the fator de vencimento behind the 47 digit + * cobrança bancária slip; the FEBRABAN layout index covers the arrecadação slip. + * * @see Official: https://cmsarquivos.febraban.org.br/Arquivos/documentos/PDF/Layout%20-%20C%C3%B3digo%20de%20Barras%20-%20Vers%C3%A3o%208%20-%2011_05_2026.pdf - * @see Official: https://portal.febraban.org.br/pagina/3166/33/pt-br/layout-cobranca FEBRABAN, - * "Layout Padrão de Cobrança / Especificações Técnicas para Cobrança", the cobrança bancária - * layout behind the 47 digit linha digitável and its fator de vencimento. + * @see Official: https://www.bcb.gov.br/pre/normativos/c_circ/2000/pdf/c_circ_2926_v1_O.pdf + * @see Official: https://portal.febraban.org.br/pagina/3425/33/pt-br/layout-febraban */ export const generateBoleto = (options?: GenerateBoletoOptions): string => options?.type === "arrecadacao" ? generateArrecadacao() : generateBancario(); diff --git a/src/generate-cep/generate-cep.ts b/src/generate-cep/generate-cep.ts index 07ccf3d1..f460adcb 100644 --- a/src/generate-cep/generate-cep.ts +++ b/src/generate-cep/generate-cep.ts @@ -14,5 +14,6 @@ import { generateRandomNumber } from "../_internals/generate-random-number/gener * ``` * * @see Official: https://www.correios.com.br/enviar/precisa-de-ajuda/tudo-sobre-cep + * @see Official: https://www.correios.com.br/enviar/precisa-de-ajuda/guia-de-enderecamento/guia-de-enderecamento */ export const generateCep = (): string => generateRandomNumber(8); diff --git a/src/generate-cnh/generate-cnh.ts b/src/generate-cnh/generate-cnh.ts index 60058f2c..f92266cd 100644 --- a/src/generate-cnh/generate-cnh.ts +++ b/src/generate-cnh/generate-cnh.ts @@ -15,7 +15,11 @@ import { isRepeatedDigits } from "../_internals/is-repeated-digits/is-repeated-d * generateCnh(); // "00000000119" * ``` * - * @see Official: https://www.planalto.gov.br/ccivil_03/leis/l9503compilado.htm + * Resolução CONTRAN nº 886/2021, art. 4º I, defines the CNH registry number as 9 characters plus + * 2 security check digits, but no official text publishes the check-digit weights; the algorithm + * below follows the community reference cited as `Based on:`. + * + * @see Official: https://www.gov.br/transportes/pt-br/assuntos/transito/conteudo-contran/resolucoes/Resolucao8862021F.pdf * @see Based on: https://siga0984.wordpress.com/2019/05/01/algoritmos-validacao-de-cnh/ */ export const generateCnh = (): string => { diff --git a/src/generate-cnpj/generate-cnpj.ts b/src/generate-cnpj/generate-cnpj.ts index 4f583628..3bac42f5 100644 --- a/src/generate-cnpj/generate-cnpj.ts +++ b/src/generate-cnpj/generate-cnpj.ts @@ -74,6 +74,7 @@ const generateAlphanumericCnpj = (): string => { * * @see Official: https://www.gov.br/receitafederal/pt-br/assuntos/orientacao-tributaria/cadastros/cnpj * @see Official: https://www.gov.br/receitafederal/pt-br/centrais-de-conteudo/publicacoes/documentos-tecnicos/cnpj/manual-dv-cnpj.pdf + * @see Official: https://www.gov.br/receitafederal/pt-br/acesso-a-informacao/acoes-e-programas/programas-e-atividades/cnpj-alfanumerico */ export const generateCnpj = (version: 1 | 2 = 1): string => version === 1 ? generateNumericCnpj() : generateAlphanumericCnpj(); diff --git a/src/generate-legal-nature/generate-legal-nature.ts b/src/generate-legal-nature/generate-legal-nature.ts index 1c380229..72cce239 100644 --- a/src/generate-legal-nature/generate-legal-nature.ts +++ b/src/generate-legal-nature/generate-legal-nature.ts @@ -13,6 +13,7 @@ import { LEGAL_NATURE } from "../is-valid-legal-nature/constants"; * ``` * * @see Official: https://concla.ibge.gov.br/estrutura/natjur-estrutura/natureza-juridica-2021 + * @see Official: https://concla.ibge.gov.br/images/concla/documentacao/CONCLA-TNJ2021-EstruturaDetalhada.pdf */ export const generateLegalNature = (): string => { const legalNatureCodes = Object.keys(LEGAL_NATURE); diff --git a/src/generate-pis/generate-pis.ts b/src/generate-pis/generate-pis.ts index 960f6169..aff44f64 100644 --- a/src/generate-pis/generate-pis.ts +++ b/src/generate-pis/generate-pis.ts @@ -23,8 +23,14 @@ const calculateCheckDigit = (base: string): string => { * generatePis(); // "12056874107" * ``` * + * The eSocial MOS states the NIS must have 11 numeric digits including the check digit, and the + * SIRC technical manual confirms the check digit is verified with módulo 11; neither publishes + * the weight vector used below, which follows the community reference cited as `Based on:`. + * * @see Official: https://www.gov.br/inss/pt-br/direitos-e-deveres/inscricao-e-contribuicao/inscricao - * @see Based on: https://github.com/brazilian-utils/brutils-python/blob/main/brutils/pis.py + * @see Official: https://www.gov.br/esocial/pt-br/documentacao-tecnica/manuais/mos-manual-de-orientacao-do-esocial-vs-2-4.pdf + * @see Official: https://www.sirc.gov.br/wp-content/uploads/manual_sirc_recomendacoes_tecnicas_v7.pdf + * @see Based on: https://github.com/brazilian-utils/python/blob/main/brutils/pis.py */ export const generatePis = (): string => { let base = generateRandomNumber(10); diff --git a/src/generate-processo-juridico/generate-processo-juridico.ts b/src/generate-processo-juridico/generate-processo-juridico.ts index 54e38a0f..45050b77 100644 --- a/src/generate-processo-juridico/generate-processo-juridico.ts +++ b/src/generate-processo-juridico/generate-processo-juridico.ts @@ -38,7 +38,9 @@ const calculateCheckDigits = (base: string): string => { * generateProcessoJuridico({ year: 10000 }); // null * ``` * - * @see Official: https://atos.cnj.jus.br/atos/detalhar/119 Resolução CNJ nº 65/2008 + * Resolução CNJ nº 65/2008 defines this Número Único de Processo layout and its check digits. + * + * @see Official: https://atos.cnj.jus.br/atos/detalhar/119 */ export const generateProcessoJuridico = ( options: GenerateProcessoJuridicoOptions = {}, diff --git a/src/generate-voter-id/generate-voter-id.ts b/src/generate-voter-id/generate-voter-id.ts index 5c8632cc..57102d97 100644 --- a/src/generate-voter-id/generate-voter-id.ts +++ b/src/generate-voter-id/generate-voter-id.ts @@ -20,7 +20,11 @@ import { UF_TO_VOTER_ID_CODE } from "../is-valid-voter-id/constants"; * generateVoterId("XX" as StateCode); // falls back to "ZZ" instead of throwing * ``` * - * @see Official: https://www.tse.jus.br/legislacao/compilada/res/2003/resolucao-no-21-538-de-14-de-outubro-de-2003 + * Resolução TSE nº 23.659/2021, art. 36, parágrafo único, confirms the federative union table and + * the two-step módulo 11 structure; the weights themselves are not published by the TSE and follow + * the community reference cited as `Based on:`. + * + * @see Official: https://www.tse.jus.br/legislacao/compilada/res/2021/resolucao-no-23-659-de-26-de-outubro-de-2021 * @see Based on: https://siga0984.wordpress.com/2019/05/01/algoritmos-validacao-de-titulo-de-eleitor/ */ export const generateVoterId = ( diff --git a/src/get-address-info-by-cep/get-address-info-by-cep.ts b/src/get-address-info-by-cep/get-address-info-by-cep.ts index 28deee47..21508aa6 100644 --- a/src/get-address-info-by-cep/get-address-info-by-cep.ts +++ b/src/get-address-info-by-cep/get-address-info-by-cep.ts @@ -185,8 +185,8 @@ const providerMap: Record Promise> = * ``` * * @see Official: https://www.correios.com.br/enviar/precisa-de-ajuda/tudo-sobre-cep - * @see Based on: https://viacep.com.br/ Default `"viacep"` provider. - * @see Based on: https://brasilapi.com.br/docs#tag/CEP Default `"brasilapi"` provider. + * @see Official: https://viacep.com.br/ Default `"viacep"` provider. + * @see Official: https://brasilapi.com.br/docs#tag/CEP Default `"brasilapi"` provider. */ export const getAddressInfoByCep = async ( cep: string | number, diff --git a/src/get-area-code-info/get-area-code-info.ts b/src/get-area-code-info/get-area-code-info.ts index 7d732ded..fa293c67 100644 --- a/src/get-area-code-info/get-area-code-info.ts +++ b/src/get-area-code-info/get-area-code-info.ts @@ -23,10 +23,13 @@ export type AreaCodeInfo = { * @returns {AreaCodeInfo|null} The area code info, or `null` when `areaCode` is not one of the * 67 DDDs in use under the Plano Geral de Numeração. * - * @see Official: https://informacoes.anatel.gov.br/legislacao/resolucoes/2010/167-resolucao-553 - * (Resolução Anatel 553/2010, Plano Geral de Numeração) - * @see Based on: https://brasilapi.com.br/docs#tag/DDD BrasilAPI DDD endpoint, used to verify - * the code-to-state mapping. + * Resolução Anatel nº 749/2022, art. 15, defines the Código Nacional (area code); the gov.br + * page below lists the codes actually allocated. The BrasilAPI DDD endpoint was used to verify + * the code-to-state mapping. + * + * @see Official: https://informacoes.anatel.gov.br/legislacao/resolucoes/2022/1641-resolucao-749 + * @see Official: https://www.gov.br/anatel/pt-br/regulado/numeracao/codigos-nacionais + * @see Based on: https://brasilapi.com.br/docs#tag/DDD * * @example * ```typescript diff --git a/src/get-area-codes-by-state/get-area-codes-by-state.ts b/src/get-area-codes-by-state/get-area-codes-by-state.ts index 59f514ea..9200b7d2 100644 --- a/src/get-area-codes-by-state/get-area-codes-by-state.ts +++ b/src/get-area-codes-by-state/get-area-codes-by-state.ts @@ -19,8 +19,11 @@ import { AREA_CODE_STATES } from "../_internals/constants/area-codes"; * getAreaCodesByState("XX"); // [] * ``` * - * @see Official: https://informacoes.anatel.gov.br/legislacao/resolucoes/2010/167-resolucao-553 - * (Resolução Anatel 553/2010, Plano Geral de Numeração) + * Resolução Anatel nº 749/2022, art. 15, defines the Código Nacional (area code); the gov.br + * page below lists the codes actually allocated. + * + * @see Official: https://informacoes.anatel.gov.br/legislacao/resolucoes/2022/1641-resolucao-749 + * @see Official: https://www.gov.br/anatel/pt-br/regulado/numeracao/codigos-nacionais */ export const getAreaCodesByState = (stateCode: string): number[] => { if (typeof stateCode !== "string") return []; diff --git a/src/get-bank-by-code/get-bank-by-code.ts b/src/get-bank-by-code/get-bank-by-code.ts index add0ebda..6de94036 100644 --- a/src/get-bank-by-code/get-bank-by-code.ts +++ b/src/get-bank-by-code/get-bank-by-code.ts @@ -19,7 +19,7 @@ const CODE_LENGTH = 3; * ``` * * @see Official: https://www.bcb.gov.br/content/estabilidadefinanceira/str1/ParticipantesSTR.csv - * @see Based on: https://brasilapi.com.br/api/banks/v1 Fallback source used by the dataset + * @see Official: https://brasilapi.com.br/api/banks/v1 Fallback source used by the dataset * generator (`scripts/banks.ts`) when the Bacen CSV request fails. */ export const getBankByCode = (code: string | number): Bank | null => { diff --git a/src/get-bank-by-ispb/get-bank-by-ispb.ts b/src/get-bank-by-ispb/get-bank-by-ispb.ts index 757e7197..bb22af74 100644 --- a/src/get-bank-by-ispb/get-bank-by-ispb.ts +++ b/src/get-bank-by-ispb/get-bank-by-ispb.ts @@ -23,7 +23,7 @@ const ISPB_LENGTH = 8; * ``` * * @see Official: https://www.bcb.gov.br/content/estabilidadefinanceira/str1/ParticipantesSTR.csv - * @see Based on: https://brasilapi.com.br/api/banks/v1 Fallback source used by the dataset + * @see Official: https://brasilapi.com.br/api/banks/v1 Fallback source used by the dataset * generator (`scripts/banks.ts`) when the Bacen CSV request fails. */ export const getBankByIspb = (value: string | number): Bank | null => { diff --git a/src/get-banks/get-banks.ts b/src/get-banks/get-banks.ts index 391d13fb..345f19cf 100644 --- a/src/get-banks/get-banks.ts +++ b/src/get-banks/get-banks.ts @@ -15,7 +15,7 @@ import { BANKS, type Bank } from "../_internals/constants/banks"; * ``` * * @see Official: https://www.bcb.gov.br/content/estabilidadefinanceira/str1/ParticipantesSTR.csv - * @see Based on: https://brasilapi.com.br/api/banks/v1 Fallback source used by the dataset + * @see Official: https://brasilapi.com.br/api/banks/v1 Fallback source used by the dataset * generator (`scripts/banks.ts`) when the Bacen CSV request fails. */ export const getBanks = (): Bank[] => BANKS.map((bank) => Object.assign({}, bank)); diff --git a/src/get-boleto-info/constants.ts b/src/get-boleto-info/constants.ts index b114dfd1..57378edf 100644 --- a/src/get-boleto-info/constants.ts +++ b/src/get-boleto-info/constants.ts @@ -1,3 +1,12 @@ +/** + * The "fator de vencimento" (expiration factor) counts days since a FEBRABAN base date and + * cycles every `CYCLE_LENGTH` days once it reaches its 4-digit maximum. FEBRABAN Comunicado + * FB-009/2023 sets the current cycle boundary: the factor reached its maximum, 9999, on + * 21/02/2025 and restarted at 1000 on 22/02/2025. + * + * @see Official: https://cmsarquivos.febraban.org.br/Arquivos/documentos/PDF/Layout%20-%20C%C3%B3digo%20de%20Barras%20-%20Vers%C3%A3o%208%20-%2011_05_2026.pdf + * @see Official: https://portal.febraban.org.br/pagina/3425/33/pt-br/layout-febraban + */ export const DAY_IN_MS = 86_400_000; export const BASE_DATE_YEAR = 1997; diff --git a/src/get-boleto-info/get-boleto-info.ts b/src/get-boleto-info/get-boleto-info.ts index 86a1897f..6592c39f 100644 --- a/src/get-boleto-info/get-boleto-info.ts +++ b/src/get-boleto-info/get-boleto-info.ts @@ -98,10 +98,14 @@ export type GetBoletoInfoOptions = { * // { amount: 2461, expirationDate: null, bankCode: '', type: 'arrecadacao', segment: 4, value: 24.61, hasEffectiveValue: true } * ``` * + * Carta-Circular BCB nº 2.926/2000 specifies the linha digitável fields, the módulo 11 check + * digit (using 1 for remainders 0, 10 and 1) and the fator de vencimento behind the 47 digit + * cobrança bancária slip; the FEBRABAN layout index covers the arrecadação slip. See + * `src/get-boleto-info/constants.ts` for the fator de vencimento cycle base date and reset. + * * @see Official: https://cmsarquivos.febraban.org.br/Arquivos/documentos/PDF/Layout%20-%20C%C3%B3digo%20de%20Barras%20-%20Vers%C3%A3o%208%20-%2011_05_2026.pdf - * @see Official: https://portal.febraban.org.br/pagina/3166/33/pt-br/layout-cobranca FEBRABAN, - * "Layout Padrão de Cobrança / Especificações Técnicas para Cobrança", the cobrança bancária - * layout behind the 47 digit linha digitável and its fator de vencimento. + * @see Official: https://www.bcb.gov.br/pre/normativos/c_circ/2000/pdf/c_circ_2926_v1_O.pdf + * @see Official: https://portal.febraban.org.br/pagina/3425/33/pt-br/layout-febraban */ export const getBoletoInfo = ( value: string, diff --git a/src/get-cep-info-by-address/get-cep-info-by-address.ts b/src/get-cep-info-by-address/get-cep-info-by-address.ts index 18ba5362..0b1d2ea4 100644 --- a/src/get-cep-info-by-address/get-cep-info-by-address.ts +++ b/src/get-cep-info-by-address/get-cep-info-by-address.ts @@ -89,7 +89,7 @@ const isCepAddressInfoArray = (value: unknown): value is CepAddressInfo[] => Arr * ``` * * @see Official: https://www.correios.com.br/enviar/precisa-de-ajuda/tudo-sobre-cep - * @see Based on: https://viacep.com.br/ + * @see Official: https://viacep.com.br/ */ export const getCepInfoByAddress = async ({ federalUnit, diff --git a/src/get-holidays/constants.ts b/src/get-holidays/constants.ts index 8a9a812b..345daeb2 100644 --- a/src/get-holidays/constants.ts +++ b/src/get-holidays/constants.ts @@ -31,48 +31,48 @@ export const LEGACY_CONSCIENCIA_NEGRA_HOLIDAY_NAME = "Consciência Negra"; /** * Feriados estaduais por lei estadual, um por UF (uma UF pode ter mais de um `@see`). * - * @see https://pt.wikipedia.org/wiki/Acre Lei AC nº 1.538/2004, Dia do Evangélico - * @see https://pt.wikipedia.org/wiki/Acre Lei AC nº 1.411/2001, Dia Internacional da Mulher - * @see https://pt.wikipedia.org/wiki/Acre Lei AC nº 14/1964, Aniversário do Acre - * @see https://pt.wikipedia.org/wiki/Acre Lei AC nº 1.526/2004, Dia da Amazônia - * @see https://pt.wikipedia.org/wiki/Acre Lei AC nº 57/1965, Assinatura do Tratado de Petrópolis - * @see https://pt.wikipedia.org/wiki/Alagoas Lei AL nº 5.508/1993, São João - * @see https://pt.wikipedia.org/wiki/Alagoas Lei AL nº 5.509/1993, São Pedro - * @see https://pt.wikipedia.org/wiki/Feriados_no_Brasil Decreto AL nº 68.782/2019 (ponto facultativo), Emancipação Política de Alagoas - * @see https://pt.wikipedia.org/wiki/Feriados_no_Brasil Lei AP nº 667/2002, Dia de São José - * @see https://pt.wikipedia.org/wiki/Amap%C3%A1 Constituição Estadual do AP, Criação do Território Federal do Amapá - * @see https://pt.wikipedia.org/wiki/Dia_Nacional_de_Zumbi_e_da_Consci%C3%AAncia_Negra Lei AP nº 1.169/2007, Dia Estadual da Consciência Negra (state holiday until it became national in 2024) - * @see https://sapl.al.am.leg.br/norma/8919 Lei AM nº 25/1977, Elevação do Amazonas à categoria de Província (05/09) - * @see https://sapl.al.am.leg.br/norma/2873 Lei AM nº 84/2010, Dia da Consciência Negra (state holiday until it became national in 2024) - * @see https://www.legisweb.com.br/legislacao/?id=316229 Decreto AM de 02/02/2016 (calendário oficial), Nossa Senhora da Conceição (08/12): ponto facultativo estadual; feriado apenas no Município de Manaus (Lei Municipal nº 496/1999) - * @see https://pt.wikipedia.org/wiki/Feriados_no_Brasil Constituição Estadual da BA, Independência da Bahia - * @see https://pt.wikipedia.org/wiki/Cear%C3%A1 Constituição Estadual do CE (Data Magna), Abolição da Escravidão no Ceará - * @see https://pt.wikipedia.org/wiki/Feriados_no_Brasil Lei Distrital nº 963/1995, Dia do Evangélico (DF) - * @see https://pt.wikipedia.org/wiki/Feriados_no_Brasil Fundação de Brasília (21/4, Lei Orgânica do DF) - * @see https://pt.wikipedia.org/wiki/Esp%C3%ADrito_Santo_(estado) Lei ES nº 11.010/2019, Nossa Senhora da Penha (padroeira do estado, 8º dia após a Páscoa) - * @see https://pt.wikipedia.org/wiki/Feriados_no_Brasil Lei MA nº 2.457/1964, Adesão do Maranhão à Independência - * @see https://pt.wikipedia.org/wiki/Mato_Grosso Lei MT nº 1.587/2002, Dia da Consciência Negra (state holiday until it became national in 2024) - * @see https://pt.wikipedia.org/wiki/Feriados_no_Brasil Lei MS nº 10/1979, Criação do Estado de Mato Grosso do Sul - * @see https://pt.wikipedia.org/wiki/Feriados_no_Brasil Lei PA nº 5.999/1996, Adesão do Pará à Independência - * @see https://pt.wikipedia.org/wiki/Para%C3%ADba Lei PB nº 10.601/2015, Fundação do Estado e Dia de Nossa Senhora das Neves - * @see https://pt.wikipedia.org/wiki/Para%C3%ADba Lei PB nº 3.489/1967, art. 2º, Morte de João Pessoa - * @see https://pt.wikipedia.org/wiki/Paran%C3%A1 Lei PR nº 18.384/2014 (ponto facultativo), Emancipação Política do Paraná - * @see https://pt.wikipedia.org/wiki/Feriados_no_Brasil Lei PE nº 13.835/2009, Revolução Pernambucana - * @see https://pt.wikipedia.org/wiki/Feriados_no_Brasil Lei PI nº 176/1937, Dia do Piauí - * @see http://alerjln1.alerj.rj.gov.br/CONTLEI.NSF/c8aa0900025feef6032564ec0060dfff/1baf90ca125ff96f8325740a00776600 Lei RJ nº 5.198/2008, São Jorge - * @see http://alerjln1.alerj.rj.gov.br/CONTLEI.NSF/69d90307244602bb032567e800668618/80a541c3a5a9d63183256c7d0057bf25 Lei RJ nº 4.007/2002, Dia da Consciência Negra (state holiday until it became national in 2024; ADI 4.131 pending at the STF) - * @see https://pt.wikipedia.org/wiki/Feriados_no_Brasil Lei RN nº 8.913/2006, Mártires de Cunhaú e Uruaçu - * @see https://pt.wikipedia.org/wiki/Feriados_no_Brasil Constituição Estadual do RS, Revolução Farroupilha - * @see https://pt.wikipedia.org/wiki/Feriados_no_Brasil Lei RO nº 3.170/2013, Criação do Estado de Rondônia - * @see https://pt.wikipedia.org/wiki/Feriados_no_Brasil Constituição Estadual de RR, Criação do Estado de Roraima - * @see https://pt.wikipedia.org/wiki/Santa_Catarina Lei SC nº 16.719/2015 (consolida e revoga as Leis nº 10.306/1996 e 12.906/2004), Criação da Capitania de Santa Catarina - * @see https://pt.wikipedia.org/wiki/Santa_Catarina Lei SC nº 16.719/2015, Dia de Santa Catarina de Alexandria - * @see https://www.al.sp.gov.br/documentacao/estudos-e-manuais/feriado-9-julho/artigo.htm Lei SP nº 9.497/1997 (PL 710/1995), Revolução Constitucionalista - * @see https://www.al.sp.gov.br/repositorio/legislacao/lei/2023/lei-17746-12.09.2023.html Lei SP nº 17.746/2023, Dia da Consciência Negra (state holiday in 2023, national since 2024) - * @see https://pt.wikipedia.org/wiki/Feriados_no_Brasil Constituição Estadual de SE, Emancipação Política de Sergipe - * @see https://pt.wikipedia.org/wiki/Feriados_no_Brasil Lei TO nº 960/1998, Autonomia do Estado do Tocantins - * @see https://pt.wikipedia.org/wiki/Feriados_no_Brasil Lei TO nº 627/1993, Padroeira do Estado (Nossa Senhora da Natividade) - * @see https://pt.wikipedia.org/wiki/Feriados_no_Brasil Lei TO nº 98/1989, Criação do Estado do Tocantins + * @see Based on: https://pt.wikipedia.org/wiki/Acre Lei AC nº 1.538/2004, Dia do Evangélico + * @see Based on: https://pt.wikipedia.org/wiki/Acre Lei AC nº 1.411/2001, Dia Internacional da Mulher + * @see Based on: https://pt.wikipedia.org/wiki/Acre Lei AC nº 14/1964, Aniversário do Acre + * @see Based on: https://pt.wikipedia.org/wiki/Acre Lei AC nº 1.526/2004, Dia da Amazônia + * @see Based on: https://pt.wikipedia.org/wiki/Acre Lei AC nº 57/1965, Assinatura do Tratado de Petrópolis + * @see Based on: https://pt.wikipedia.org/wiki/Alagoas Lei AL nº 5.508/1993, São João + * @see Based on: https://pt.wikipedia.org/wiki/Alagoas Lei AL nº 5.509/1993, São Pedro + * @see Based on: https://pt.wikipedia.org/wiki/Feriados_no_Brasil Decreto AL nº 68.782/2019 (ponto facultativo), Emancipação Política de Alagoas + * @see Based on: https://pt.wikipedia.org/wiki/Feriados_no_Brasil Lei AP nº 667/2002, Dia de São José + * @see Based on: https://pt.wikipedia.org/wiki/Amap%C3%A1 Constituição Estadual do AP, Criação do Território Federal do Amapá + * @see Based on: https://pt.wikipedia.org/wiki/Dia_Nacional_de_Zumbi_e_da_Consci%C3%AAncia_Negra Lei AP nº 1.169/2007, Dia Estadual da Consciência Negra (state holiday until it became national in 2024) + * @see Official: https://sapl.al.am.leg.br/norma/8919 Lei AM nº 25/1977, Elevação do Amazonas à categoria de Província (05/09) + * @see Official: https://sapl.al.am.leg.br/norma/2873 Lei AM nº 84/2010, Dia da Consciência Negra (state holiday until it became national in 2024) + * @see Based on: https://www.legisweb.com.br/legislacao/?id=316229 Decreto AM de 02/02/2016 (calendário oficial), Nossa Senhora da Conceição (08/12): ponto facultativo estadual; feriado apenas no Município de Manaus (Lei Municipal nº 496/1999) + * @see Based on: https://pt.wikipedia.org/wiki/Feriados_no_Brasil Constituição Estadual da BA, Independência da Bahia + * @see Based on: https://pt.wikipedia.org/wiki/Cear%C3%A1 Constituição Estadual do CE (Data Magna), Abolição da Escravidão no Ceará + * @see Based on: https://pt.wikipedia.org/wiki/Feriados_no_Brasil Lei Distrital nº 963/1995, Dia do Evangélico (DF) + * @see Based on: https://pt.wikipedia.org/wiki/Feriados_no_Brasil Fundação de Brasília (21/4, Lei Orgânica do DF) + * @see Based on: https://pt.wikipedia.org/wiki/Esp%C3%ADrito_Santo_(estado) Lei ES nº 11.010/2019, Nossa Senhora da Penha (padroeira do estado, 8º dia após a Páscoa) + * @see Based on: https://pt.wikipedia.org/wiki/Feriados_no_Brasil Lei MA nº 2.457/1964, Adesão do Maranhão à Independência + * @see Official: https://www.al.mt.gov.br/norma-juridica/urn:lex:br;mato.grosso:estadual:lei.ordinaria:2002-12-27;7879 Lei MT nº 7.879/2002, Dia da Consciência Negra (state holiday until it became national in 2024) + * @see Based on: https://pt.wikipedia.org/wiki/Feriados_no_Brasil Lei MS nº 10/1979, Criação do Estado de Mato Grosso do Sul + * @see Based on: https://pt.wikipedia.org/wiki/Feriados_no_Brasil Lei PA nº 5.999/1996, Adesão do Pará à Independência + * @see Based on: https://pt.wikipedia.org/wiki/Para%C3%ADba Lei PB nº 10.601/2015, Fundação do Estado e Dia de Nossa Senhora das Neves + * @see Based on: https://pt.wikipedia.org/wiki/Para%C3%ADba Lei PB nº 3.489/1967, art. 2º, Morte de João Pessoa + * @see Based on: https://pt.wikipedia.org/wiki/Paran%C3%A1 Lei PR nº 18.384/2014 (ponto facultativo), Emancipação Política do Paraná + * @see Official: https://legis.alepe.pe.gov.br/texto.aspx?ano=2017&complemento=0&numero=16059&tipo=&tiponorma=1&url= Lei PE nº 16.059/2017, Revolução Pernambucana (Data Magna, fixed 6 March; supersedes the movable "primeiro domingo de março" date set by Lei PE nº 13.835/2009) + * @see Based on: https://pt.wikipedia.org/wiki/Feriados_no_Brasil Lei PI nº 176/1937, Dia do Piauí + * @see Official: http://alerjln1.alerj.rj.gov.br/CONTLEI.NSF/c8aa0900025feef6032564ec0060dfff/1baf90ca125ff96f8325740a00776600 Lei RJ nº 5.198/2008, São Jorge + * @see Official: http://alerjln1.alerj.rj.gov.br/CONTLEI.NSF/69d90307244602bb032567e800668618/80a541c3a5a9d63183256c7d0057bf25 Lei RJ nº 4.007/2002, Dia da Consciência Negra (state holiday until it became national in 2024; ADI 4.131 pending at the STF) + * @see Based on: https://pt.wikipedia.org/wiki/Feriados_no_Brasil Lei RN nº 8.913/2006, Mártires de Cunhaú e Uruaçu + * @see Based on: https://pt.wikipedia.org/wiki/Feriados_no_Brasil Constituição Estadual do RS, Revolução Farroupilha + * @see Based on: https://pt.wikipedia.org/wiki/Feriados_no_Brasil Lei RO nº 3.170/2013, Criação do Estado de Rondônia + * @see Based on: https://pt.wikipedia.org/wiki/Feriados_no_Brasil Constituição Estadual de RR, Criação do Estado de Roraima + * @see Based on: https://pt.wikipedia.org/wiki/Santa_Catarina Lei SC nº 16.719/2015 (consolida e revoga as Leis nº 10.306/1996 e 12.906/2004), Criação da Capitania de Santa Catarina + * @see Based on: https://pt.wikipedia.org/wiki/Santa_Catarina Lei SC nº 16.719/2015, Dia de Santa Catarina de Alexandria + * @see Official: https://www.al.sp.gov.br/documentacao/estudos-e-manuais/feriado-9-julho/artigo.htm Lei SP nº 9.497/1997 (PL 710/1995), Revolução Constitucionalista + * @see Official: https://www.al.sp.gov.br/repositorio/legislacao/lei/2023/lei-17746-12.09.2023.html Lei SP nº 17.746/2023, Dia da Consciência Negra (state holiday in 2023, national since 2024) + * @see Based on: https://pt.wikipedia.org/wiki/Feriados_no_Brasil Constituição Estadual de SE, Emancipação Política de Sergipe + * @see Based on: https://pt.wikipedia.org/wiki/Feriados_no_Brasil Lei TO nº 960/1998, Autonomia do Estado do Tocantins + * @see Based on: https://pt.wikipedia.org/wiki/Feriados_no_Brasil Lei TO nº 627/1993, Padroeira do Estado (Nossa Senhora da Natividade) + * @see Based on: https://pt.wikipedia.org/wiki/Feriados_no_Brasil Lei TO nº 98/1989, Criação do Estado do Tocantins */ export const STATE_HOLIDAYS: Partial> = { AC: [ diff --git a/src/get-holidays/get-holidays.ts b/src/get-holidays/get-holidays.ts index 32599866..90323f3c 100644 --- a/src/get-holidays/get-holidays.ts +++ b/src/get-holidays/get-holidays.ts @@ -131,8 +131,19 @@ const computeHolidays = (year: number, stateCode: StateCode | undefined): Holida * const spHolidays = getHolidays({ year: 2024, stateCode: 'SP' }); * ``` * - * @see Official: https://www.planalto.gov.br/ccivil_03/leis/l0662.htm National holidays law - * (fixed and movable national holidays). + * @see Official: https://www.planalto.gov.br/ccivil_03/leis/l0662.htm Lei 662/1949, the base + * national holidays law (Ano novo, Dia do trabalhador, Independência do Brasil, Proclamação da + * República, Natal). + * @see Official: https://www.planalto.gov.br/ccivil_03/leis/2002/l10607.htm Lei 10.607/2002, + * added Tiradentes and Finados to the national holidays. + * @see Official: https://www.planalto.gov.br/ccivil_03/leis/l6802.htm Lei 6.802/1980, declared + * Nossa Senhora Aparecida (12 October) a national holiday. + * @see Official: https://www.planalto.gov.br/ccivil_03/_ato2023-2026/2023/lei/l14759.htm Lei + * 14.759/2023, nationalized Dia da Consciência Negra (20 November) from + * `CONSCIENCIA_NEGRA_NATIONAL_SINCE_YEAR` (2024) onward. + * @see Official: https://www.planalto.gov.br/ccivil_03/leis/l9093.htm Lei 9.093/1995, the + * framework law authorizing one state civil holiday and up to four municipal religious holidays; + * the legal basis for `STATE_HOLIDAYS`. * @see Official: state holiday laws are cited individually, one `@see` per holiday, in * `src/get-holidays/constants.ts`. * @see Based on: https://pt.wikipedia.org/wiki/Feriados_no_Brasil Used as secondary evidence for diff --git a/src/get-legal-natures/get-legal-natures.ts b/src/get-legal-natures/get-legal-natures.ts index cf6ca5a2..a98b3500 100644 --- a/src/get-legal-natures/get-legal-natures.ts +++ b/src/get-legal-natures/get-legal-natures.ts @@ -11,5 +11,6 @@ import { LEGAL_NATURE } from "../is-valid-legal-nature/constants"; * ``` * * @see Official: https://concla.ibge.gov.br/estrutura/natjur-estrutura/natureza-juridica-2021 + * @see Official: https://concla.ibge.gov.br/images/concla/documentacao/CONCLA-TNJ2021-EstruturaDetalhada.pdf */ export const getLegalNatures = (): Record => ({ ...LEGAL_NATURE }); diff --git a/src/get-timezone-by-state/constants.ts b/src/get-timezone-by-state/constants.ts index f226011b..90bdf88d 100644 --- a/src/get-timezone-by-state/constants.ts +++ b/src/get-timezone-by-state/constants.ts @@ -2,12 +2,16 @@ * IANA time zone database (tzdata) name for each Brazilian state, chosen as the zone of the * state capital per the official `zone1970.tab` comments (some tzdata zones span more than * one state, e.g. `America/Sao_Paulo` also covers DF, GO, MG, ES, RJ, PR, SC and RS, and - * `America/Fortaleza` also covers MA, PI, RN and PB besides CE). Pernambuco maps to - * `America/Recife`, not `America/Noronha`: Fernando de Noronha is an archipelago district of - * PE, not a state of its own, and its distinct UTC-02:00 offset is out of scope here. + * `America/Fortaleza` also covers MA, PI, RN and PB besides CE). Pará and Amazonas each + * straddle two IANA zones themselves (`America/Belem`/`America/Santarem` and + * `America/Manaus`/`America/Eirunepe` respectively); both map here to their capital's zone + * (Belém and Manaus). Pernambuco maps to `America/Recife`, not `America/Noronha`: Fernando de + * Noronha is an archipelago district of PE, not a state of its own, and its distinct + * UTC-02:00 offset is out of scope here. * - * @see Official: https://raw.githubusercontent.com/eggert/tz/main/zone1970.tab (IANA tz - * database, `BR` rows) + * @see Official: https://www.iana.org/time-zones + * @see Based on: https://raw.githubusercontent.com/eggert/tz/main/zone1970.tab (IANA tz + * database data file, `BR` rows) * @see Based on: https://en.wikipedia.org/wiki/Time_in_Brazil Used to confirm the state * coverage of each zone. */ diff --git a/src/get-timezone-by-state/get-timezone-by-state.ts b/src/get-timezone-by-state/get-timezone-by-state.ts index f56648be..c9c3d5b2 100644 --- a/src/get-timezone-by-state/get-timezone-by-state.ts +++ b/src/get-timezone-by-state/get-timezone-by-state.ts @@ -7,15 +7,19 @@ import { STATE_TIMEZONES } from "./constants"; * * Some tzdata zones cover more than one state: `America/Sao_Paulo` also covers DF, GO, MG, ES, * RJ, PR, SC and RS besides SP, and `America/Fortaleza` also covers MA, PI, RN and PB besides - * CE. Pernambuco resolves to `America/Recife`, not `America/Noronha`: Fernando de Noronha is an - * archipelago district of PE, not a state of its own. + * CE. Pará and Amazonas each straddle two IANA zones themselves (`America/Belem`/ + * `America/Santarem` and `America/Manaus`/`America/Eirunepe` respectively); both resolve here + * to their capital's zone (Belém and Manaus). Pernambuco resolves to `America/Recife`, not + * `America/Noronha`: Fernando de Noronha is an archipelago district of PE, not a state of its + * own. * * @param {string} stateCode - The two-letter state code (sigla). * @returns {string|null} The IANA time zone name, or `null` when `stateCode` does not match * any Brazilian state. * - * @see Official: https://raw.githubusercontent.com/eggert/tz/main/zone1970.tab (IANA tz - * database, `BR` rows) + * @see Official: https://www.iana.org/time-zones + * @see Based on: https://raw.githubusercontent.com/eggert/tz/main/zone1970.tab (IANA tz + * database data file, `BR` rows) * @see Based on: https://en.wikipedia.org/wiki/Time_in_Brazil Used to confirm the state * coverage of each zone. * diff --git a/src/is-business-day/is-business-day.ts b/src/is-business-day/is-business-day.ts index b94e0edf..23ee24a5 100644 --- a/src/is-business-day/is-business-day.ts +++ b/src/is-business-day/is-business-day.ts @@ -6,7 +6,7 @@ import { getHolidays } from "../get-holidays/get-holidays"; export type IsBusinessDayOptions = { /** Two letter state code whose state holidays are also treated as non-business days (default: national holidays only). */ stateCode?: StateCode; - /** Whether optional-type holidays (`Holiday.type === "optional"`, e.g. Carnaval, Corpus Christi) count as non-business days (default: `true`, matching Brazilian banking practice, where these days are not settlement days). */ + /** Whether optional-type holidays (`Holiday.type === "optional"`, e.g. Carnaval, Corpus Christi) count as non-business days (default: `true`). */ includeOptional?: boolean; }; @@ -23,10 +23,9 @@ const WEEKEND_DAYS = new Set([0, 6]); * specific local day, for the same reason documented in `isHoliday`. * * `options.includeOptional` defaults to `true`: holidays whose `Holiday.type` is - * `"optional"` (Carnaval and Corpus Christi) are treated as non-business days, matching - * the Brazilian banking calendar (FEBRABAN/CMN), where these days are not settlement days - * even though they are not statutory holidays. Pass `false` to only treat statutory - * (`"national"` and `"state"`) holidays as non-business days. + * `"optional"` (Carnaval and Corpus Christi) are treated as non-business days even though + * they are not statutory holidays. Pass `false` to only treat statutory (`"national"` and + * `"state"`) holidays as non-business days. * * If `options.stateCode` is provided but is not a valid/known state code, it is ignored * and only national holidays are considered (same behavior as `getHolidays`/`isHoliday`). @@ -47,7 +46,7 @@ const WEEKEND_DAYS = new Set([0, 6]); * isBusinessDay(new Date(2024, 0, 2)); // true (Tuesday, not a holiday) * isBusinessDay(new Date(2024, 0, 1)); // false (Ano novo) * isBusinessDay(new Date(2024, 0, 6)); // false (Saturday) - * isBusinessDay(new Date(2024, 1, 13)); // false (Carnaval, optional holiday, banking practice) + * isBusinessDay(new Date(2024, 1, 13)); // false (Carnaval, optional holiday, counted by default) * isBusinessDay(new Date(2024, 1, 13), { includeOptional: false }); // true * isBusinessDay(new Date(2024, 6, 9), { stateCode: "SP" }); // false (Revolução Constitucionalista) * isBusinessDay(new Date(2024, 6, 9)); // true (state holiday ignored without stateCode) @@ -55,7 +54,19 @@ const WEEKEND_DAYS = new Set([0, 6]); * isBusinessDay(new Date(2100, 0, 4)); // false (a Monday, but 2100 is outside the supported range) * ``` * - * @see Official: https://www.planalto.gov.br/ccivil_03/leis/l0662.htm + * The underlying holidays are the ones `getHolidays` computes; see its JSDoc (and + * `src/get-holidays/constants.ts` for state holidays) for the full set of laws behind them. + * + * @see Official: https://www.planalto.gov.br/ccivil_03/leis/l0662.htm Lei 662/1949, the base + * national holidays law. + * @see Official: https://www.planalto.gov.br/ccivil_03/leis/2002/l10607.htm Lei 10.607/2002, + * added Tiradentes and Finados. + * @see Official: https://www.planalto.gov.br/ccivil_03/leis/l6802.htm Lei 6.802/1980, declared + * Nossa Senhora Aparecida a national holiday. + * @see Official: https://www.planalto.gov.br/ccivil_03/_ato2023-2026/2023/lei/l14759.htm Lei + * 14.759/2023, nationalized Dia da Consciência Negra from 2024. + * @see Official: https://www.planalto.gov.br/ccivil_03/leis/l9093.htm Lei 9.093/1995, the + * framework law authorizing state and municipal holidays. */ export const isBusinessDay = (value: Date, options?: IsBusinessDayOptions): boolean => { if (!(value instanceof Date) || Number.isNaN(value.getTime())) return false; diff --git a/src/is-holiday/is-holiday.ts b/src/is-holiday/is-holiday.ts index d971f96a..b8f5b278 100644 --- a/src/is-holiday/is-holiday.ts +++ b/src/is-holiday/is-holiday.ts @@ -37,7 +37,19 @@ export type IsHolidayOptions = { * isHoliday(); // false * ``` * - * @see Official: https://www.planalto.gov.br/ccivil_03/leis/l0662.htm + * The underlying national holidays are the ones `getHolidays` computes; see its JSDoc (and + * `src/get-holidays/constants.ts` for state holidays) for the full set of laws behind them. + * + * @see Official: https://www.planalto.gov.br/ccivil_03/leis/l0662.htm Lei 662/1949, the base + * national holidays law. + * @see Official: https://www.planalto.gov.br/ccivil_03/leis/2002/l10607.htm Lei 10.607/2002, + * added Tiradentes and Finados. + * @see Official: https://www.planalto.gov.br/ccivil_03/leis/l6802.htm Lei 6.802/1980, declared + * Nossa Senhora Aparecida a national holiday. + * @see Official: https://www.planalto.gov.br/ccivil_03/_ato2023-2026/2023/lei/l14759.htm Lei + * 14.759/2023, nationalized Dia da Consciência Negra from 2024. + * @see Official: https://www.planalto.gov.br/ccivil_03/leis/l9093.htm Lei 9.093/1995, the + * framework law authorizing state and municipal holidays. */ export const isHoliday = (options?: IsHolidayOptions): boolean => { if (isNullish(options) || typeof options !== "object") { diff --git a/src/is-valid-boleto/is-valid-boleto.ts b/src/is-valid-boleto/is-valid-boleto.ts index 2ac8785e..56f148f8 100644 --- a/src/is-valid-boleto/is-valid-boleto.ts +++ b/src/is-valid-boleto/is-valid-boleto.ts @@ -47,10 +47,13 @@ const isValidCheckDigit = (boleto: string): boolean => { * isValidBoleto("846100000005246100291102005460339004695895061080"); // true (arrecadação) * ``` * + * Carta-Circular BCB nº 2.926/2000 specifies the linha digitável fields, the módulo 11 check + * digit (using 1 for remainders 0, 10 and 1) and the fator de vencimento behind the 47 digit + * cobrança bancária slip; the FEBRABAN layout index covers the arrecadação slip. + * * @see Official: https://cmsarquivos.febraban.org.br/Arquivos/documentos/PDF/Layout%20-%20C%C3%B3digo%20de%20Barras%20-%20Vers%C3%A3o%208%20-%2011_05_2026.pdf - * @see Official: https://portal.febraban.org.br/pagina/3166/33/pt-br/layout-cobranca FEBRABAN, - * "Layout Padrão de Cobrança / Especificações Técnicas para Cobrança", the cobrança bancária - * layout behind the 47 digit linha digitável and its fator de vencimento. + * @see Official: https://www.bcb.gov.br/pre/normativos/c_circ/2000/pdf/c_circ_2926_v1_O.pdf + * @see Official: https://portal.febraban.org.br/pagina/3425/33/pt-br/layout-febraban */ export const isValidBoleto = (value: string): boolean => { if (typeof value !== "string") return false; diff --git a/src/is-valid-cep/is-valid-cep.ts b/src/is-valid-cep/is-valid-cep.ts index 69a5d299..8791ab1f 100644 --- a/src/is-valid-cep/is-valid-cep.ts +++ b/src/is-valid-cep/is-valid-cep.ts @@ -22,6 +22,7 @@ const CEP_REGEX = /^\d{8}$/; * ``` * * @see Official: https://www.correios.com.br/enviar/precisa-de-ajuda/tudo-sobre-cep + * @see Official: https://www.correios.com.br/enviar/precisa-de-ajuda/guia-de-enderecamento/guia-de-enderecamento */ export const isValidCep = (cep: string | number): boolean => { if (typeof cep !== "string" && typeof cep !== "number") return false; diff --git a/src/is-valid-cnh/is-valid-cnh.ts b/src/is-valid-cnh/is-valid-cnh.ts index 4fcfa8c5..61bbfa60 100644 --- a/src/is-valid-cnh/is-valid-cnh.ts +++ b/src/is-valid-cnh/is-valid-cnh.ts @@ -17,7 +17,11 @@ import { sanitizeToDigits } from "../_internals/sanitize-to-digits/sanitize-to-d * isValidCnh("12345678901"); // false (invalid checksum) * ``` * - * @see Official: https://www.planalto.gov.br/ccivil_03/leis/l9503compilado.htm + * Resolução CONTRAN nº 886/2021, art. 4º I, defines the CNH registry number as 9 characters plus + * 2 security check digits, but no official text publishes the check-digit weights; the algorithm + * below follows the community reference cited as `Based on:`. + * + * @see Official: https://www.gov.br/transportes/pt-br/assuntos/transito/conteudo-contran/resolucoes/Resolucao8862021F.pdf * @see Based on: https://siga0984.wordpress.com/2019/05/01/algoritmos-validacao-de-cnh/ */ export const isValidCnh = (value: string): boolean => { diff --git a/src/is-valid-cnpj/is-valid-cnpj.ts b/src/is-valid-cnpj/is-valid-cnpj.ts index 67ef7a3b..50bf6049 100644 --- a/src/is-valid-cnpj/is-valid-cnpj.ts +++ b/src/is-valid-cnpj/is-valid-cnpj.ts @@ -85,6 +85,10 @@ const isValidChecksum = (cnpj: string): boolean => { * isValidCnpj("q0slfmbd7vx439", { version: 2 }); // true (case-insensitive) * ``` * + * Version 2 has no reserved-value list because the Receita Federal manual defines none for the + * alphanumeric format, so a repeated-character alphanumeric base (e.g. all `A`s) that passes the + * checksum is accepted, unlike the numeric reserved numbers rejected under version 1. + * * @see Official: https://www.gov.br/receitafederal/pt-br/assuntos/orientacao-tributaria/cadastros/cnpj * @see Official: https://www.gov.br/receitafederal/pt-br/centrais-de-conteudo/publicacoes/documentos-tecnicos/cnpj/manual-dv-cnpj.pdf * @see Official: https://www.gov.br/receitafederal/pt-br/acesso-a-informacao/acoes-e-programas/programas-e-atividades/cnpj-alfanumerico diff --git a/src/is-valid-cpf/is-valid-cpf.ts b/src/is-valid-cpf/is-valid-cpf.ts index cb1c18f1..26fa1ded 100644 --- a/src/is-valid-cpf/is-valid-cpf.ts +++ b/src/is-valid-cpf/is-valid-cpf.ts @@ -38,8 +38,12 @@ const isValidChecksum = (cpf: string): boolean => { * isValidCpf("12345678900"); // false (invalid checksum) * ``` * + * The check digit rule (`REGRA_VALIDA_CPF`) is specified, with a worked example + * (`280012389-38`), in the Receita Federal's Manual e-Financeira, Anexo II. + * * @see Official: https://www.gov.br/receitafederal/pt-br/assuntos/meu-cpf - * @see Based on: https://github.com/brazilian-utils/brutils-python/blob/main/brutils/cpf.py + * @see Official: http://sped.rfb.gov.br/arquivo/show/8231 + * @see Based on: https://github.com/brazilian-utils/python/blob/main/brutils/cpf.py */ export const isValidCpf = (cpf: string): boolean => { if (typeof cpf !== "string") return false; diff --git a/src/is-valid-credit-card/constants.ts b/src/is-valid-credit-card/constants.ts index 3ed3cbef..3cbdcb31 100644 --- a/src/is-valid-credit-card/constants.ts +++ b/src/is-valid-credit-card/constants.ts @@ -1,5 +1,5 @@ -/** Shortest digit count accepted by ISO/IEC 7812-1 issuer identification numbers. */ +/** De-facto industry minimum PAN (Primary Account Number) length (12, e.g. Maestro); ISO/IEC 7812-1 sets no such floor. */ export const MIN_LENGTH = 12; -/** Longest digit count accepted by ISO/IEC 7812-1 issuer identification numbers. */ +/** Longest PAN (Primary Account Number) length allowed by ISO/IEC 7812-1, which caps the PAN at 19 digits. */ export const MAX_LENGTH = 19; diff --git a/src/is-valid-credit-card/is-valid-credit-card.ts b/src/is-valid-credit-card/is-valid-credit-card.ts index 1e4edece..b5c50ab0 100644 --- a/src/is-valid-credit-card/is-valid-credit-card.ts +++ b/src/is-valid-credit-card/is-valid-credit-card.ts @@ -6,9 +6,9 @@ import { MAX_LENGTH, MIN_LENGTH } from "./constants"; * Validates a payment card number (crédito ou débito) using the Luhn algorithm. * * Accepts the usual mask characters (spaces and hyphens) between digits. Only checks the - * digit count (12 to 19, the range every ISO/IEC 7812-1 issuer identification number falls - * into) and the Luhn check digit; it performs no brand detection (Visa, Mastercard, Amex...), - * issuer range lookup or expiration/CVV checks. + * digit count (12 to 19: 12 is the de-facto industry minimum PAN length, e.g. Maestro, and + * ISO/IEC 7812-1 caps the PAN at 19) and the Luhn check digit; it performs no brand detection + * (Visa, Mastercard, Amex...), issuer range lookup or expiration/CVV checks. * * @param {string|number} value - The card number to be validated. * @returns {boolean} True when `value` sanitizes to 12-19 digits ending in a valid Luhn check digit. @@ -23,7 +23,10 @@ import { MAX_LENGTH, MIN_LENGTH } from "./constants"; * isValidCreditCard("123456789"); // false (too short) * ``` * - * @see Official: https://www.iso.org/standard/70484.html ISO/IEC 7812-1 (issuer identification numbers) + * ISO/IEC 7812-1 (issuer identification numbers) caps the PAN at 19 digits but sets no + * minimum; the 12-digit floor here is the de-facto industry minimum (e.g. Maestro). + * + * @see Official: https://www.iso.org/standard/70484.html */ export const isValidCreditCard = (value: string | number): boolean => { if (typeof value !== "string" && typeof value !== "number") return false; diff --git a/src/is-valid-email/is-valid-email.ts b/src/is-valid-email/is-valid-email.ts index a63d9345..c3a7b1ea 100644 --- a/src/is-valid-email/is-valid-email.ts +++ b/src/is-valid-email/is-valid-email.ts @@ -14,11 +14,14 @@ const EMAIL_REGEX = * isValidEmail("test@domain.co.uk"); // true * ``` * - * @see Based on: https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address The - * WHATWG HTML "valid e-mail address" definition, narrowed further: the local part is limited to - * letters, digits and `_'+-.`, it may not start with a dot or contain two dots in a row, and the - * domain must carry at least one dot and end in an alphabetic label of two or more letters. It is - * a practical subset, not RFC 5322: quoted local parts and address literals are rejected. + * The WHATWG HTML "valid e-mail address" definition is narrowed further: the local part is + * limited to letters, digits and `_'+-.`, it may not start with a dot or contain two dots in a + * row, and the domain must carry at least one dot and end in an alphabetic label of two or more + * letters. It is a practical subset of that WHATWG definition, not of IETF RFC 5322: quoted + * local parts and address literals are rejected. + * + * @see Official: https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address + * @see Official: https://www.rfc-editor.org/rfc/rfc5322 */ export const isValidEmail = (value: string): boolean => { if (typeof value !== "string") return false; diff --git a/src/is-valid-ie/is-valid-ie.ts b/src/is-valid-ie/is-valid-ie.ts index c6e8927c..3d9f46fe 100644 --- a/src/is-valid-ie/is-valid-ie.ts +++ b/src/is-valid-ie/is-valid-ie.ts @@ -505,6 +505,24 @@ const IE_VALIDATORS: Record = { /** * Validates a Brazilian state tax registration number (IE). * + * Per state notes, all of them deliberate and unchanged since 2.3.0: + * - DF: the SINTEGRA page is published but empty, and no SEFAZ-DF roteiro is published either, + * so DF follows the 13 digit AC rule under the prefix 07. + * - GO: the SINTEGRA page is superseded by the SEFAZ-GO roteiro, which is the source of the + * prefixes 10, 11 and 15, of the 10103105 to 10119997 range and of the dual digit + * registration 11094402. + * - RJ: the SINTEGRA page publishes only the modulus rule; the 8 digit length and the weights + * 2, 7, 6, 5, 4, 3 and 2 come from the SINTEGRA validator itself, not from the page. + * - SP: characters other than "P" and digits are rejected on purpose, a deliberate deviation + * from the Regra Geral of the SINTEGRA page, which ignores them instead. + * - AL: the tipo de empresa digit (third position) is not restricted to 0, 3, 5, 7 and 8. + * - PE: only the current 9 digit eFisco format is accepted; the old 14 digit CACEPE format + * documented on the same page is not. + * - An all zero registration is accepted for every state whose published formula yields a + * check digit of 0 for it (AM, BA with 9 digits, CE, ES, MG, MT, PB, PE, PI, PR, RJ, RS, SC, + * SE, SP and TO with 9 digits), unlike isValidCpf and isValidCnpj, which reject repeated + * digits. + * * @param {StateCode} stateCode - The state abbreviation (e.g., 'SP', 'RJ', 'MG') * @param {string} ie - The state registration number to validate * @returns {boolean} True if the state registration number is valid, false otherwise @@ -524,8 +542,13 @@ const IE_VALIDATORS: Record = { * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_BA.html * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_CE.html * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_DF.html + * The page is published but empty: it carries no format, no weights and no worked example, + * and no SEFAZ-DF roteiro is published either, so DF follows the 13 digit AC rule under the + * prefix 07. * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_ES.html * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_GO.html + * Superseded for Goiás by the SEFAZ-GO roteiro below: this page still gives the prefixes as + * 10, 11 or 20 to 29 and knows nothing of the special ranges. * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_MA.html * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_MG.html * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_MS.html @@ -536,6 +559,8 @@ const IE_VALIDATORS: Record = { * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_PI.html * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_PR.html * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_RJ.html + * Publishes only the modulus rule: the 8 digit length and the weights 2, 7, 6, 5, 4, 3 and 2 + * come from the SINTEGRA validator itself, not from this page. * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_RN.html * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_RO.html * @see Official: http://www.sintegra.gov.br/Cad_Estados/cad_RR.html diff --git a/src/is-valid-legal-nature/constants.ts b/src/is-valid-legal-nature/constants.ts index 0b8c2d34..e83e24a9 100644 --- a/src/is-valid-legal-nature/constants.ts +++ b/src/is-valid-legal-nature/constants.ts @@ -3,6 +3,10 @@ * * Generated by `node ./scripts/legal-natures.ts`. Do not edit by hand. * + * 92 of the 100 entries are the official codes from the CONCLA 2021 table; the other 8 + * (2076, 2100, 2208, 3042, 3050, 3093, 3123, 5002) are legacy codes kept for 2.3.0 + * compatibility. Code 3298 fixes an accent typo of the official PDF ("Referendária"). + * * @see https://concla.ibge.gov.br/estrutura/natjur-estrutura/natureza-juridica-2021 * @see https://concla.ibge.gov.br/images/concla/documentacao/CONCLA-TNJ2021-EstruturaDetalhada.pdf */ diff --git a/src/is-valid-legal-nature/is-valid-legal-nature.ts b/src/is-valid-legal-nature/is-valid-legal-nature.ts index 7078442a..87caded0 100644 --- a/src/is-valid-legal-nature/is-valid-legal-nature.ts +++ b/src/is-valid-legal-nature/is-valid-legal-nature.ts @@ -19,6 +19,7 @@ import { LEGAL_NATURE, MASK_REGEX } from "./constants"; * ``` * * @see Official: https://concla.ibge.gov.br/estrutura/natjur-estrutura/natureza-juridica-2021 + * @see Official: https://concla.ibge.gov.br/images/concla/documentacao/CONCLA-TNJ2021-EstruturaDetalhada.pdf */ export const isValidLegalNature = (code: string): boolean => { if (typeof code !== "string") return false; diff --git a/src/is-valid-mobile-phone/is-valid-mobile-phone.ts b/src/is-valid-mobile-phone/is-valid-mobile-phone.ts index 76e0d40a..f04e6271 100644 --- a/src/is-valid-mobile-phone/is-valid-mobile-phone.ts +++ b/src/is-valid-mobile-phone/is-valid-mobile-phone.ts @@ -47,6 +47,10 @@ const isValidMobileFirstNumber = (value: string, version?: PhoneVersion): boolea * isValidMobilePhone("+55 11 98765-4321"); // true * ``` * + * `version: 1` (the default) is the pre-Resolução 749/2022 rule, which also accepts a leading + * 6, kept for 2.3.0 compatibility. `version: 2` enforces only 9, a stricter subset of the + * resolution's art. 12 I, which places 7, 8 and 9 in Serviço Móvel Pessoal (SMP). + * * @see Official: https://informacoes.anatel.gov.br/legislacao/resolucoes/2022/1641-resolucao-749 */ export const isValidMobilePhone = (value: string, options?: IsValidMobilePhoneOptions): boolean => { diff --git a/src/is-valid-pis/is-valid-pis.ts b/src/is-valid-pis/is-valid-pis.ts index 50b5325f..ae596f25 100644 --- a/src/is-valid-pis/is-valid-pis.ts +++ b/src/is-valid-pis/is-valid-pis.ts @@ -18,8 +18,14 @@ import { RESERVED_NUMBERS } from "./constants"; * isValidPis("00000000000"); // false (reserved number) * ``` * + * The eSocial MOS states the NIS must have 11 numeric digits including the check digit, and the + * SIRC technical manual confirms the check digit is verified with módulo 11; neither publishes + * the weight vector used below, which follows the community reference cited as `Based on:`. + * * @see Official: https://www.gov.br/inss/pt-br/direitos-e-deveres/inscricao-e-contribuicao/inscricao - * @see Based on: https://github.com/brazilian-utils/brutils-python/blob/main/brutils/pis.py + * @see Official: https://www.gov.br/esocial/pt-br/documentacao-tecnica/manuais/mos-manual-de-orientacao-do-esocial-vs-2-4.pdf + * @see Official: https://www.sirc.gov.br/wp-content/uploads/manual_sirc_recomendacoes_tecnicas_v7.pdf + * @see Based on: https://github.com/brazilian-utils/python/blob/main/brutils/pis.py */ export const isValidPis = (pis: string): boolean => { if (typeof pis !== "string") return false; diff --git a/src/is-valid-processo-juridico/is-valid-processo-juridico.ts b/src/is-valid-processo-juridico/is-valid-processo-juridico.ts index 87bf6752..f04c984d 100644 --- a/src/is-valid-processo-juridico/is-valid-processo-juridico.ts +++ b/src/is-valid-processo-juridico/is-valid-processo-juridico.ts @@ -48,7 +48,9 @@ const verifyCheckDigit = (value: string): boolean => { * isValidProcessoJuridico("0002080-25.2012.5.15.0049"); // true * ``` * - * @see Official: https://atos.cnj.jus.br/atos/detalhar/119 Resolução CNJ nº 65/2008 + * Resolução CNJ nº 65/2008 defines this Número Único de Processo layout and its check digits. + * + * @see Official: https://atos.cnj.jus.br/atos/detalhar/119 */ export const isValidProcessoJuridico = (value: string): boolean => { if (typeof value !== "string") return false; diff --git a/src/is-valid-service-phone/is-valid-service-phone.ts b/src/is-valid-service-phone/is-valid-service-phone.ts index 75432e65..e8cd9ba3 100644 --- a/src/is-valid-service-phone/is-valid-service-phone.ts +++ b/src/is-valid-service-phone/is-valid-service-phone.ts @@ -25,7 +25,8 @@ const UTILITY_CODES: readonly string[] = SERVICE_PHONE_UTILITY_CODES; * - the abbreviated `300X` and `400X` numbers, followed by 4 digits, e.g. `3003-1234`. Anatel * publishes no allocation for these, so the accepted roots are the conventional ones; * - the 3-digit Códigos de Acesso a Serviços de Utilidade Pública that Anatel has designated, - * e.g. `190` and `192`. Undesignated codes in the `1XX` range are rejected. + * e.g. `190` and `192`. Undesignated codes in the `1XX` range are rejected. `112` and `911` + * are accepted too: Anatel lists them alongside the `1XX` codes as mobile-only aliases of `190`. * * Only the structure is checked: the number does not have to be assigned to anyone, and the * `0500` rule that encodes a donation amount in the last two digits is not enforced. diff --git a/src/is-valid-vin/constants.ts b/src/is-valid-vin/constants.ts index afd1e525..35726df7 100644 --- a/src/is-valid-vin/constants.ts +++ b/src/is-valid-vin/constants.ts @@ -1,12 +1,14 @@ /** - * ISO 3779 layout of a VIN (Vehicle Identification Number / chassi): 17 characters, excluding - * the letters `I`, `O` and `Q` (dropped to avoid confusion with `1` and `0`), with a check - * digit at the 9th position. Resolução CONTRAN nº 27/1998 requires the same transliteration - * table and weighted MOD 11 check digit algorithm used across the Americas (SAE J853 / NHTSA - * 49 CFR 565.15) for vehicles manufactured in or imported into Brazil. - * @see Official: https://www.iso.org/standard/52200.html ISO 3779:2009 (VIN content and structure) - * @see Based on: https://vpic.nhtsa.dot.gov/api/ NHTSA vPIC VIN decoding API and WMI table, used - * as a reference for the transliteration/weights across the Americas. + * VIN (Vehicle Identification Number / chassi) layout: 17 characters, excluding the letters + * `I`, `O` and `Q` (dropped to avoid confusion with `1` and `0`), per ISO 3779:2009 structure. + * The check digit at the 9th position, its transliteration table and its weighted MOD 11 + * algorithm are a North-American requirement (49 CFR 565.15 / SAE J853), not something + * Resolução CONTRAN nº 24/1998 or ABNT NBR 6066 — which define the Brazilian VIN structure — + * mandate; many Brazilian-built VINs do not carry a matching check digit. + * @see Official: https://www.iso.org/standard/52200.html + * @see Official: https://www.ecfr.gov/current/title-49/section-565.15 + * @see Official: https://www.gov.br/transportes/pt-br/assuntos/transito/conteudo-Senatran/resolucoes-contran + * @see Based on: https://vpic.nhtsa.dot.gov/api/ */ export const VIN_LENGTH = 17; diff --git a/src/is-valid-vin/is-valid-vin.ts b/src/is-valid-vin/is-valid-vin.ts index db1959fb..28fb5239 100644 --- a/src/is-valid-vin/is-valid-vin.ts +++ b/src/is-valid-vin/is-valid-vin.ts @@ -7,12 +7,15 @@ import { } from "./constants"; /** - * Validates a VIN (Vehicle Identification Number / chassi) under ISO 3779. + * Validates a VIN (Vehicle Identification Number / chassi). * - * Checks the length (17 characters), the excluded letters (`I`, `O`, `Q` are never valid) and - * the check digit at the 9th position, calculated with the ISO 3779 transliteration table and - * a weighted MOD 11 sum, mandatory for vehicles manufactured in or imported into Brazil under - * Resolução CONTRAN nº 27/1998. Case-insensitive and trims surrounding whitespace. + * Checks the length (17 characters), the excluded letters (`I`, `O`, `Q` are never valid; ISO + * 3779:2009 structure) and the check digit at the 9th position, with the check digit and + * transliteration computed per 49 CFR 565.15. That 9th-position check digit is a North-American + * requirement (49 CFR 565.15 / SAE J853): Resolução CONTRAN nº 24/1998 and ABNT NBR 6066 define + * the Brazilian VIN structure but do not mandate it, so many Brazilian-built VINs do not carry + * a matching check digit. This function is therefore a North-American-style structural check, + * not a universal validator of Brazilian VINs. Case-insensitive and trims surrounding whitespace. * * @param {string} value - The VIN to be validated. * @returns {boolean} True when `value` is a 17 character VIN with a matching check digit. @@ -27,8 +30,10 @@ import { * isValidVin("1HGCM82633A00435"); // false (16 characters) * ``` * - * @see Official: https://www.iso.org/standard/52200.html ISO 3779:2009 (VIN content and structure) - * @see Based on: https://vpic.nhtsa.dot.gov/api/ NHTSA vPIC VIN decoding API and WMI table. + * @see Official: https://www.iso.org/standard/52200.html + * @see Official: https://www.ecfr.gov/current/title-49/section-565.15 + * @see Official: https://www.gov.br/transportes/pt-br/assuntos/transito/conteudo-Senatran/resolucoes-contran + * @see Based on: https://vpic.nhtsa.dot.gov/api/ */ export const isValidVin = (value: string): boolean => { if (typeof value !== "string") return false; diff --git a/src/is-valid-voter-id/is-valid-voter-id.ts b/src/is-valid-voter-id/is-valid-voter-id.ts index d702afbc..a81b4236 100644 --- a/src/is-valid-voter-id/is-valid-voter-id.ts +++ b/src/is-valid-voter-id/is-valid-voter-id.ts @@ -27,9 +27,14 @@ const isValidLength = (value: string): boolean => { * isValidVoterId("123456780124"); // false (invalid checksum) * ``` * - * @see Official: https://www.tse.jus.br/legislacao/compilada/res/2003/resolucao-no-21-538-de-14-de-outubro-de-2003 + * Resolução TSE nº 23.659/2021, art. 36, parágrafo único, confirms the federative union table and + * the two-step módulo 11 structure ("até 12 algarismos"). The weights used in each step and the + * 13-digit São Paulo/Minas Gerais ids are brutils parity, not published by the TSE — siga0984 uses + * a different 9-digit rule for the sequential number. + * + * @see Official: https://www.tse.jus.br/legislacao/compilada/res/2021/resolucao-no-23-659-de-26-de-outubro-de-2021 * @see Based on: https://siga0984.wordpress.com/2019/05/01/algoritmos-validacao-de-titulo-de-eleitor/ - * @see Based on: https://github.com/brazilian-utils/brutils-python/blob/main/brutils/voter_id.py (13-digit São Paulo and Minas Gerais ids) + * @see Based on: https://github.com/brazilian-utils/python/blob/main/brutils/voter_id.py */ export const isValidVoterId = (value: string): boolean => { if (typeof value !== "string") return false; diff --git a/src/parse-boleto/parse-boleto.ts b/src/parse-boleto/parse-boleto.ts index 6ce52c82..caf9008f 100644 --- a/src/parse-boleto/parse-boleto.ts +++ b/src/parse-boleto/parse-boleto.ts @@ -21,10 +21,13 @@ import { sanitizeToDigits } from "../_internals/sanitize-to-digits/sanitize-to-d * // "826300000011098800100702024102024000000205104519" * ``` * + * Carta-Circular BCB nº 2.926/2000 specifies the linha digitável fields, the módulo 11 check + * digit (using 1 for remainders 0, 10 and 1) and the fator de vencimento behind the 47 digit + * cobrança bancária slip; the FEBRABAN layout index covers the arrecadação slip. + * * @see Official: https://cmsarquivos.febraban.org.br/Arquivos/documentos/PDF/Layout%20-%20C%C3%B3digo%20de%20Barras%20-%20Vers%C3%A3o%208%20-%2011_05_2026.pdf - * @see Official: https://portal.febraban.org.br/pagina/3166/33/pt-br/layout-cobranca FEBRABAN, - * "Layout Padrão de Cobrança / Especificações Técnicas para Cobrança", the cobrança bancária - * layout behind the 47 digit linha digitável and its fator de vencimento. + * @see Official: https://www.bcb.gov.br/pre/normativos/c_circ/2000/pdf/c_circ_2926_v1_O.pdf + * @see Official: https://portal.febraban.org.br/pagina/3425/33/pt-br/layout-febraban */ export const parseBoleto = (value: string | number): string => { if (isNullish(value)) return ""; diff --git a/src/parse-cep/parse-cep.ts b/src/parse-cep/parse-cep.ts index afe04391..490b51ae 100644 --- a/src/parse-cep/parse-cep.ts +++ b/src/parse-cep/parse-cep.ts @@ -14,6 +14,7 @@ import { sanitizeToDigits } from "../_internals/sanitize-to-digits/sanitize-to-d * ``` * * @see Official: https://www.correios.com.br/enviar/precisa-de-ajuda/tudo-sobre-cep + * @see Official: https://www.correios.com.br/enviar/precisa-de-ajuda/guia-de-enderecamento/guia-de-enderecamento */ export const parseCep = (value: string | number): string => isNullish(value) ? "" : sanitizeToDigits(value).slice(0, CEP_LENGTH); diff --git a/src/parse-cnh/parse-cnh.ts b/src/parse-cnh/parse-cnh.ts index 08dc3351..03d6cb94 100644 --- a/src/parse-cnh/parse-cnh.ts +++ b/src/parse-cnh/parse-cnh.ts @@ -13,7 +13,11 @@ import { LENGTH } from "./constants"; * parseCnh("123456789-00"); // "12345678900" * ``` * - * @see Official: https://www.planalto.gov.br/ccivil_03/leis/l9503compilado.htm + * Resolução CONTRAN nº 886/2021, art. 4º I, defines the CNH registry number as 9 characters plus + * 2 security check digits, which is the layout this parser caps at; no official text publishes + * the check-digit weights used to compute them. + * + * @see Official: https://www.gov.br/transportes/pt-br/assuntos/transito/conteudo-contran/resolucoes/Resolucao8862021F.pdf */ export const parseCnh = (value: string | number): string => isNullish(value) ? "" : sanitizeToDigits(value).slice(0, LENGTH); diff --git a/src/parse-cpf/parse-cpf.ts b/src/parse-cpf/parse-cpf.ts index 263aba85..00d8079d 100644 --- a/src/parse-cpf/parse-cpf.ts +++ b/src/parse-cpf/parse-cpf.ts @@ -14,7 +14,8 @@ import { sanitizeToDigits } from "../_internals/sanitize-to-digits/sanitize-to-d * ``` * * @see Official: https://www.gov.br/receitafederal/pt-br/assuntos/meu-cpf - * @see Based on: https://github.com/brazilian-utils/brutils-python/blob/main/brutils/cpf.py + * @see Official: http://sped.rfb.gov.br/arquivo/show/8231 + * @see Based on: https://github.com/brazilian-utils/python/blob/main/brutils/cpf.py */ export const parseCpf = (value: string | number): string => isNullish(value) ? "" : sanitizeToDigits(value).slice(0, CPF_LENGTH); diff --git a/src/parse-legal-nature/parse-legal-nature.ts b/src/parse-legal-nature/parse-legal-nature.ts index c607573f..d1745002 100644 --- a/src/parse-legal-nature/parse-legal-nature.ts +++ b/src/parse-legal-nature/parse-legal-nature.ts @@ -14,6 +14,7 @@ import { LENGTH } from "./constants"; * ``` * * @see Official: https://concla.ibge.gov.br/estrutura/natjur-estrutura/natureza-juridica-2021 + * @see Official: https://concla.ibge.gov.br/images/concla/documentacao/CONCLA-TNJ2021-EstruturaDetalhada.pdf */ export const parseLegalNature = (value: string | number): string => isNullish(value) ? "" : sanitizeToDigits(value).slice(0, LENGTH); diff --git a/src/parse-pis/parse-pis.ts b/src/parse-pis/parse-pis.ts index 799fb473..38dc580a 100644 --- a/src/parse-pis/parse-pis.ts +++ b/src/parse-pis/parse-pis.ts @@ -14,7 +14,9 @@ import { sanitizeToDigits } from "../_internals/sanitize-to-digits/sanitize-to-d * ``` * * @see Official: https://www.gov.br/inss/pt-br/direitos-e-deveres/inscricao-e-contribuicao/inscricao - * @see Based on: https://github.com/brazilian-utils/brutils-python/blob/main/brutils/pis.py + * @see Official: https://www.gov.br/esocial/pt-br/documentacao-tecnica/manuais/mos-manual-de-orientacao-do-esocial-vs-2-4.pdf + * @see Official: https://www.sirc.gov.br/wp-content/uploads/manual_sirc_recomendacoes_tecnicas_v7.pdf + * @see Based on: https://github.com/brazilian-utils/python/blob/main/brutils/pis.py */ export const parsePis = (value: string | number): string => isNullish(value) ? "" : sanitizeToDigits(value).slice(0, PIS_LENGTH); diff --git a/src/parse-processo-juridico/parse-processo-juridico.ts b/src/parse-processo-juridico/parse-processo-juridico.ts index 205f7cc4..7642be59 100644 --- a/src/parse-processo-juridico/parse-processo-juridico.ts +++ b/src/parse-processo-juridico/parse-processo-juridico.ts @@ -13,7 +13,9 @@ import { sanitizeToDigits } from "../_internals/sanitize-to-digits/sanitize-to-d * parseProcessoJuridico("0002080-25.2026.5.15.0049"); // "00020802520265150049" * ``` * - * @see Official: https://atos.cnj.jus.br/atos/detalhar/119 Resolução CNJ nº 65/2008 + * Resolução CNJ nº 65/2008 defines this Número Único de Processo layout and its check digits. + * + * @see Official: https://atos.cnj.jus.br/atos/detalhar/119 */ export const parseProcessoJuridico = (value: string | number): string => isNullish(value) ? "" : sanitizeToDigits(value).slice(0, PROCESSO_JURIDICO_LENGTH); diff --git a/src/parse-voter-id/parse-voter-id.ts b/src/parse-voter-id/parse-voter-id.ts index 88ad5d39..4d1ed719 100644 --- a/src/parse-voter-id/parse-voter-id.ts +++ b/src/parse-voter-id/parse-voter-id.ts @@ -19,7 +19,12 @@ import { EXTENDED_LENGTH, LENGTH } from "./constants"; * parseVoterId("1234 5678 8 01 91"); // "1234567880191" * ``` * - * @see Official: https://www.tse.jus.br/legislacao/compilada/res/2003/resolucao-no-21-538-de-14-de-outubro-de-2003 + * The 13-digit São Paulo/Minas Gerais cap is brutils parity, not published by the TSE. A + * 14-or-more-digit input whose 10th and 11th digits are "01"/"02" is read as a 13-digit São Paulo + * or Minas Gerais id and capped at 13 digits, discarding anything past that. + * + * @see Official: https://www.tse.jus.br/legislacao/compilada/res/2021/resolucao-no-23-659-de-26-de-outubro-de-2021 + * @see Based on: https://github.com/brazilian-utils/python/blob/main/brutils/voter_id.py */ export const parseVoterId = (value: string | number): string => { if (isNullish(value)) return "";