diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 00000000..76199ca6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,81 @@ +name: Bug report +description: Report something that isn't working as expected in @brazilian-utils/brazilian-utils +title: "fix: " +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to report a bug! Please fill in as much detail as possible so we can reproduce it quickly. + + - type: input + id: version + attributes: + label: Package version + description: Which version of `@brazilian-utils/brazilian-utils` are you using? + placeholder: "2.3.0" + validations: + required: true + + - type: dropdown + id: runtime + attributes: + label: Runtime + description: Where are you seeing this behavior? + multiple: true + options: + - Node.js + - Bun + - Deno + - Browser + - Other + validations: + required: true + + - type: textarea + id: description + attributes: + label: What happened? + description: A clear and concise description of the bug. + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Expected behavior + description: What did you expect to happen instead? + validations: + required: true + + - type: textarea + id: repro + attributes: + label: Minimal reproduction + description: | + A minimal code sample that reproduces the issue. Include the exact input value(s) used. + render: typescript + placeholder: | + import { isValidCpf } from "@brazilian-utils/brazilian-utils"; + + isValidCpf("..."); // expected true, got false + validations: + required: true + + - type: textarea + id: context + attributes: + label: Additional context + description: Anything else that might help (stack trace, screenshots, links to an official spec, etc). + validations: + required: false + + - type: checkboxes + id: checks + attributes: + label: Checklist + options: + - label: I searched existing issues and this hasn't been reported yet. + required: true + - label: I'm using a version listed in [SECURITY.md](../../SECURITY.md#supported-versions) as supported. + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..2493bfa9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Questions & discussions + url: https://github.com/brazilian-utils/javascript/discussions + about: Ask a question, share an idea, or discuss usage before opening an issue. + - name: Report a security vulnerability + url: https://github.com/brazilian-utils/javascript/security/advisories/new + about: Report security issues privately instead of opening a public issue. See SECURITY.md. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 00000000..6eb6eef5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,81 @@ +name: New utility / feature request +description: Propose a new Brazilian document/format utility, or an improvement to an existing one +title: "feat: " +labels: ["enhancement"] +body: + - type: markdown + attributes: + value: | + Thanks for proposing an idea! Most requests here are for a new utility covering a Brazilian + document, format or business rule (e.g. a new document validator/formatter/generator). + The more concrete detail you give, the faster this can be reviewed and implemented. + + - type: dropdown + id: type + attributes: + label: What kind of request is this? + options: + - New utility (validate/format/generate/parse something) + - Improvement to an existing utility + - Other + validations: + required: true + + - type: input + id: document + attributes: + label: Brazilian document / spec name + description: What Brazilian document, identifier or business rule is this about? + placeholder: "e.g. CNH (Carteira Nacional de Habilitação), Nota Fiscal, RENAVAM..." + validations: + required: true + + - type: input + id: source + attributes: + label: Official source / specification link + description: Link to the official government page, law, or specification describing the format and validation/check-digit algorithm. + placeholder: "https://www.gov.br/..." + validations: + required: true + + - type: textarea + id: api + attributes: + label: Expected API + description: | + What would the function signature(s) look like? Follow the existing naming convention + (`isValidX`, `formatX`, `generateX`, `parseX`) if applicable. + render: typescript + placeholder: | + isValidRenavam(value: string | number): boolean; + formatRenavam(value: string | number): string; + validations: + required: true + + - type: textarea + id: examples + attributes: + label: Example input/output + description: A few concrete examples of valid/invalid input and the expected output. + render: typescript + validations: + required: true + + - type: textarea + id: context + attributes: + label: Additional context + description: Anything else that helps explain the use case or edge cases (regional variants, check-digit algorithm details, etc). + validations: + required: false + + - type: checkboxes + id: checks + attributes: + label: Checklist + options: + - label: I searched existing issues/discussions and this hasn't been requested yet. + required: true + - label: I understand this library has zero runtime dependencies and any implementation should follow that constraint. + required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..56b68a5a --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,22 @@ +## What does this PR do? + + + +## Checklist + +- [ ] My commit/PR title follows [Conventional Commits](https://www.conventionalcommits.org/) (e.g. `feat: ...`, `fix: ...`, `docs: ...`). +- [ ] I added or updated tests covering this change (`npm test`). +- [ ] I updated the documentation if this adds/changes a utility, in **both**: + - [ ] `docs/utilities.md` (English) + - [ ] `docs/pt-br/utilities.md` (Portuguese) +- [ ] `npm check` passes locally (format, lint, types). +- [ ] This change does not introduce a breaking change, **or** I flagged it clearly below and it was discussed with maintainers beforehand. +- [ ] This change does not add any runtime dependency (this library is zero-runtime-dependency by design). + +## Breaking change? + + + +## Additional context + + diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 3f336a86..8cbbaa68 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -31,5 +31,8 @@ jobs: - name: Run checks run: vp check + - name: Check llms.txt/llms-full.txt are up to date + run: npm run build:llms && git diff --exit-code -- docs/llms.txt docs/llms-full.txt + - name: Audit dependencies run: npx --yes audit-ci@7.1.0 --high --allowlist GHSA-jmr9-qjv8-65gv GHSA-7pqw-9j4j-h8q3 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..3a67b27e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,207 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [2.3.0](https://github.com/brazilian-utils/javascript/compare/2.2.0...2.3.0) (2026-04-09) + +### Features + +- implement some extra utilities ([0b57240](https://github.com/brazilian-utils/javascript/commit/0b57240a63bc9c028986e95f2094f7cde0ebea8e)) + +### Bug Fixes + +- adjust apis ([409780d](https://github.com/brazilian-utils/javascript/commit/409780df19b40c093f1960121ab4cb524f2daaaf)) +- lint and tests ([ea10397](https://github.com/brazilian-utils/javascript/commit/ea10397132862e41b54b855cb87d906e0bb0cf98)) + +## [2.2.0](https://github.com/brazilian-utils/javascript/compare/2.1.0...2.2.0) (2026-04-07) + +### Features + +- add Brazilian passport validation utilities ([f4e7a90](https://github.com/brazilian-utils/javascript/commit/f4e7a9025891a10368f9eabf41b809f50ba7b7cf)) + +### Bug Fixes + +- adjust actions ([979d50d](https://github.com/brazilian-utils/javascript/commit/979d50d42974fd578c96ab420e9420284c4b9bac)) +- adjust actions ([a38977c](https://github.com/brazilian-utils/javascript/commit/a38977cb7b11850313346b6953dcbd8237f049e6)) +- handle edge cases ([110252b](https://github.com/brazilian-utils/javascript/commit/110252b9f47d5efb71ec01199227363748456ee1)) +- handle edge cases ([4f60290](https://github.com/brazilian-utils/javascript/commit/4f60290299da33cff54857366042cb92b60ab769)) +- handle edge cases ([4240a45](https://github.com/brazilian-utils/javascript/commit/4240a45215c471515f25ff564f29df0c09e1c9ca)) +- handle edge cases ([b0605e2](https://github.com/brazilian-utils/javascript/commit/b0605e23c88b034dc9149bd1c7fcbb57b9205058)) +- handle edge cases ([a92046a](https://github.com/brazilian-utils/javascript/commit/a92046aa57932108401ffbe1ce8cd264e86774a8)) +- handle edge cases ([2ff0380](https://github.com/brazilian-utils/javascript/commit/2ff0380eb9697b04ede386ce80fca4a7c10f2cfc)) +- lint ([da4e356](https://github.com/brazilian-utils/javascript/commit/da4e35665afd81cf8cd6ad7cae0ee1a40a322b38)) +- lint ([86e37cf](https://github.com/brazilian-utils/javascript/commit/86e37cff70020298b237eee2d9ea7cb793daca7e)) +- passport tests ([ca8c368](https://github.com/brazilian-utils/javascript/commit/ca8c368ecfe38273b94ce6c7201323cda2419f6d)) +- replace as any with as unknown as string in test ([1e3deaa](https://github.com/brazilian-utils/javascript/commit/1e3deaa846fb7a921754da9ca1b6f59bfad789a5)) + +## [2.1.0](https://github.com/brazilian-utils/javascript/compare/2.0.1...2.1.0) (2026-04-02) + +### Features + +- add missing parse functions ([d76c36e](https://github.com/brazilian-utils/javascript/commit/d76c36ef089ba40090191c046e62334e0c65e6a7)) + +## [2.0.1](https://github.com/brazilian-utils/javascript/compare/2.0.0...2.0.1) (2026-03-30) + +### Bug Fixes + +- adds test cases using the official Receita Federal example (12.ABC.345/01DE-35) to prevent future regressions ([272ece0](https://github.com/brazilian-utils/javascript/commit/272ece0a7c3de61546ece0a86504eb0f5deff179)) +- corrects sanitization to accept the entire alphabet in alphanumeric CNPJ ([009d9c0](https://github.com/brazilian-utils/javascript/commit/009d9c058b2f12a1e3102ade770f063a354b13d9)) + +## [2.0.0](https://github.com/brazilian-utils/javascript/compare/1.0.0-rc.12...2.0.0) (2026-01-21) + +### Features + +- add all formatters ([84ee6d2](https://github.com/brazilian-utils/javascript/commit/84ee6d2034d531d1de8815cfed318ef9017bb116)) +- add covenio files ([00602b7](https://github.com/brazilian-utils/javascript/commit/00602b795b5d507afcfa1cc5ce569e27ead03bc5)) +- add docs to cities data ([954e953](https://github.com/brazilian-utils/javascript/commit/954e953d7ed8c343eb3dc7539080215cbd2aac8c)) +- add public api test ([158778d](https://github.com/brazilian-utils/javascript/commit/158778dd7efaed7a1b5ff443008f8b0b1b44b297)) +- include some more utils ([8df157c](https://github.com/brazilian-utils/javascript/commit/8df157c34b465560453b87b9f21d830e6addc06c)) +- include some more utils ([c3cfe0d](https://github.com/brazilian-utils/javascript/commit/c3cfe0d279cf1df62eb375b02a3da009db68b014)) +- **is-valid-pj:** adds validation for processo juridico ([12ac657](https://github.com/brazilian-utils/javascript/commit/12ac657649049386ff7b8dfa8b1c8910aface744)) + +### Bug Fixes + +- adjust all format errors ([38e06a4](https://github.com/brazilian-utils/javascript/commit/38e06a465b94f7447b49d41f8406c258ae9e882b)) +- adjust build ([33f367f](https://github.com/brazilian-utils/javascript/commit/33f367f1f1c9f7bb4b0a808244167ca809a7ed0d)) +- adjust ci ([f5a1dd7](https://github.com/brazilian-utils/javascript/commit/f5a1dd7574899eec1ed1583b9a6cc3a65f86ec40)) +- adjust ci ([bb96db1](https://github.com/brazilian-utils/javascript/commit/bb96db197a139b43cbdf82e40bac6774220a90da)) +- adjust ci ([fcfcbe9](https://github.com/brazilian-utils/javascript/commit/fcfcbe9fcc05bb7a3997193908d3592c53d68121)) +- adjust ci ([bba7cb6](https://github.com/brazilian-utils/javascript/commit/bba7cb610d2750be4274614f859ebb53a005b9d8)) +- adjust ci ([08417a2](https://github.com/brazilian-utils/javascript/commit/08417a2ed675a3900c8aea0fa4abfef826e6414f)) +- adjust format ([97f55d7](https://github.com/brazilian-utils/javascript/commit/97f55d742b8c9e7c0c3b969cf7773b489cfaa47b)) +- adjust is-valid-cnpj and generate-cnpj ([adfdecb](https://github.com/brazilian-utils/javascript/commit/adfdecb97fb3e76c339bee767acc211a84c8d645)) +- adjust lint ([cb13d33](https://github.com/brazilian-utils/javascript/commit/cb13d3391c9391fbd6eb5391dcd39483d251db4e)) +- adjust project structure and include some more utils ([3cdb483](https://github.com/brazilian-utils/javascript/commit/3cdb483572274935c2ce36467eeb1af113ea6434)) +- adjust public api tests ([9e9cd6a](https://github.com/brazilian-utils/javascript/commit/9e9cd6a60f37b00e8e637a85a9ddfd974617ad32)) +- include some more utils ([5a0309b](https://github.com/brazilian-utils/javascript/commit/5a0309be917a81e83c2da23ab0301114fff7d421)) +- include some more utils ([f871594](https://github.com/brazilian-utils/javascript/commit/f8715941845f8fd5c68788b444f64e185e68f50a)) +- include some more utils ([e9e2c1b](https://github.com/brazilian-utils/javascript/commit/e9e2c1b9dabfed03746b94ebc285790c667ff704)) +- include some more utils ([9c9b5af](https://github.com/brazilian-utils/javascript/commit/9c9b5af2932a1b07ae293227f942cfa28dbbd0f3)) +- include some more utils ([2c0ff1a](https://github.com/brazilian-utils/javascript/commit/2c0ff1a44cdfb17c8141f61c1e385f6402fa035c)) +- include some more utils ([5553b2e](https://github.com/brazilian-utils/javascript/commit/5553b2e496e78d0f10f246d06e4fadb7aab48e4e)) +- include some more utils ([a894f40](https://github.com/brazilian-utils/javascript/commit/a894f407887c5423035544bdde4ee6c8c236f9c1)) +- include some more utils ([c85a92a](https://github.com/brazilian-utils/javascript/commit/c85a92add63f623a3d1448c40f0fa5ef5a833eb2)) +- include some more utils ([b18bb03](https://github.com/brazilian-utils/javascript/commit/b18bb0305102c3463a6f9dcbf8eff0c378e3a042)) +- include some more utils ([862b39d](https://github.com/brazilian-utils/javascript/commit/862b39d385c400f07aae70fd17c1a0b4cb5439ab)) +- include some more utils ([703a4ac](https://github.com/brazilian-utils/javascript/commit/703a4acfc3da1b3be6b0ff7849acc57f09a31b33)) +- remove unused files ([26b33cb](https://github.com/brazilian-utils/javascript/commit/26b33cbccf687828caffee31f8222421d9fb11f4)) +- unsolved conflict ([47497c7](https://github.com/brazilian-utils/javascript/commit/47497c77b5f51c07791c17fd09766f8d57c03687)) + +## [1.0.0-rc.12](https://github.com/brazilian-utils/javascript/compare/1.0.0-rc.11...1.0.0-rc.12) (2020-10-27) + +### Features + +- **licenseplate:** add a validador for brazilian and mercosul license plates ([580d961](https://github.com/brazilian-utils/javascript/commit/580d9613ed97d636f3ca717072522d05620081b3)), closes [#112](https://github.com/brazilian-utils/javascript/issues/112) + +## [1.0.0-rc.11](https://github.com/brazilian-utils/javascript/compare/1.0.0-rc.10...1.0.0-rc.11) (2020-08-25) + +### Features + +- add formatCurrency() and parseCurrency() ([b0e6994](https://github.com/brazilian-utils/javascript/commit/b0e6994ebd7613030c648bbc9150640f305bf4ca)) + +### Bug Fixes + +- **deps:** upgrade devDependency to fix security warning ([af7c353](https://github.com/brazilian-utils/javascript/commit/af7c353e97d1f9140a251a84fc13168aa2789956)) +- esm filename in package.json ([32cd815](https://github.com/brazilian-utils/javascript/commit/32cd815905af8ff850f6b4f56824288223722200)) +- let formatCurrency() more flexible and remove default param from parseCurrency() ([8a9a437](https://github.com/brazilian-utils/javascript/commit/8a9a4374d2b1e6bd33f2ccb26e6496c5f12dcd22)) +- replace array.flat with reduce to support node 10 ([a47a51a](https://github.com/brazilian-utils/javascript/commit/a47a51a1b881aaf045d9e69af4fd0b20c50dcbfe)) +- **workflow:** update workflow config ([fb449c0](https://github.com/brazilian-utils/javascript/commit/fb449c09ed0b19277f946af26e4465efe10c68be)) + +## [1.0.0-rc.10](https://github.com/brazilian-utils/javascript/compare/1.0.0-rc.9...1.0.0-rc.10) (2020-07-27) + +### Features + +- add function capitalize() ([6d915fd](https://github.com/brazilian-utils/javascript/commit/6d915fdd43a43fccfaee363a5718babf19d4ce8d)) +- atualiza cidades novas ([348ab84](https://github.com/brazilian-utils/javascript/commit/348ab841ed7740ab0a839053978c5b697bbd0e9c)) + +### Bug Fixes + +- exported the function "capitalize()" to the public API ([a16d603](https://github.com/brazilian-utils/javascript/commit/a16d60360cbd641b104bfdc7e7cb8d8ab23e73d6)) + +## [1.0.0-rc.9](https://github.com/brazilian-utils/javascript/compare/1.0.0-rc.8...1.0.0-rc.9) (2020-07-09) + +### Features + +- add functions isValidLandlinePhone() and isValidMobilePhone() ([e8fe8bd](https://github.com/brazilian-utils/javascript/commit/e8fe8bd8aef422d0cc09f09dce232e0352540842)) + +### Bug Fixes + +- applied code review ([48a81f1](https://github.com/brazilian-utils/javascript/commit/48a81f1f84b8f720773ea085dabb253b870a0fca)) +- solve all Codecov issues ([3394479](https://github.com/brazilian-utils/javascript/commit/33944795ad865e84277a46246adb5c6cfc0eb814)) + +## [1.0.0-rc.8](https://github.com/brazilian-utils/javascript/compare/1.0.0-rc.7...1.0.0-rc.8) (2020-06-17) + +### Features + +- add utility getCities ([ebb949d](https://github.com/brazilian-utils/javascript/commit/ebb949d449d9c03bcc69da2f728554f87d602a2f)) +- black lives matters ([cce58d8](https://github.com/brazilian-utils/javascript/commit/cce58d8302ec27c07992b98b13207dc77dd3694d)) +- change the way the cities are filtered. Add docs ([7f4ca6d](https://github.com/brazilian-utils/javascript/commit/7f4ca6de757feca9b02fe41bcd717b359b6dd7f0)) + +### Bug Fixes + +- add getCities to api public testes ([3f8c4be](https://github.com/brazilian-utils/javascript/commit/3f8c4bef59b4abb9e885c677587863c802f719b4)) +- adjust docs typo ([89cf335](https://github.com/brazilian-utils/javascript/commit/89cf33504392a726ff74710c9fd5dfcb5895c8fa)) +- adjust responsive docs ([bafcdc0](https://github.com/brazilian-utils/javascript/commit/bafcdc0d56a5a3c677ac9c8dba4332ac5dd2090a)) +- export getCities ([0472144](https://github.com/brazilian-utils/javascript/commit/0472144b723752029e451394ecd67cc2dc32f379)) +- update-cities script command ([bce5628](https://github.com/brazilian-utils/javascript/commit/bce56283320902a6acbc1b1d059621086f9bc5aa)) + +## [1.0.0-rc.7](https://github.com/brazilian-utils/javascript/compare/1.0.0-rc.6...1.0.0-rc.7) (2020-05-05) + +### Features + +- **utilities:** create `getStates` method ([57e3174](https://github.com/brazilian-utils/javascript/commit/57e3174b52b6fce0fad4c2a3d42527e8e2a2fe14)), closes [#82](https://github.com/brazilian-utils/javascript/issues/82) + +## [1.0.0-rc.6](https://github.com/brazilian-utils/javascript/compare/1.0.0-rc.5...1.0.0-rc.6) (2020-04-29) + +### Features + +- **cpf, cnpj:** format accepts numbers, optional fillZeroes behavior ([5293721](https://github.com/brazilian-utils/javascript/commit/52937214563dbbe4d30447e3a16019b419e5324a)) +- **cpf, cnpj:** using an options object instead of a parameter ([db9ca46](https://github.com/brazilian-utils/javascript/commit/db9ca4628e5244e6c9cdf107ec08d9a8007efafd)) + +## [1.0.0-rc.5](https://github.com/brazilian-utils/javascript/compare/38207ffce9347774e86349ebeb59e97d6fdfb14a...1.0.0-rc.5) (2020-04-23) + +### ⚠ BREAKING CHANGES + +- n + +Closes: n + +- n + +Closes: n + +### Features + +- add cep formatter ([d8b0deb](https://github.com/brazilian-utils/javascript/commit/d8b0deb4aa9152db2bc32db200c11347da0c461d)) +- **boleto:** add boleto formatter ([3cfd0b4](https://github.com/brazilian-utils/javascript/commit/3cfd0b480788b928b3465e708c52c187a7127c81)) +- CheckSum generator ([7dd6978](https://github.com/brazilian-utils/javascript/commit/7dd6978c44a301d30cc14143e10c10d85ef8f6b6)) +- **CNPJ:** migrate CNPJ formatter ([f34317d](https://github.com/brazilian-utils/javascript/commit/f34317dbf4bf032d144e9c54d25611f4cf53e3e9)) +- cpf generator, requested on [#4](https://github.com/brazilian-utils/javascript/issues/4) ([5ffc2a1](https://github.com/brazilian-utils/javascript/commit/5ffc2a1b96c114e4acd88e02d3ee9100897652c3)) +- **CPF:** migrate CPF formatter ([ef1ed2a](https://github.com/brazilian-utils/javascript/commit/ef1ed2a6f3fdaec91e6947bb662564a24d39ae8e)) +- **CPF:** migrate CPF generation ([6463b40](https://github.com/brazilian-utils/javascript/commit/6463b40ed1411235cdbb79926967dc496f6dfbba)) +- **CPF:** migrate isValidCPF ([620f798](https://github.com/brazilian-utils/javascript/commit/620f7989b11bbeb3bf78f8c409250a3add3b0a8c)) +- inscricao-estadual validator ([c4cf9ae](https://github.com/brazilian-utils/javascript/commit/c4cf9ae453b18c979498af33250c2bbc858d4a67)) +- **refactoring:** add TSDX and rewrite helpers ([ebb2d18](https://github.com/brazilian-utils/javascript/commit/ebb2d188c52636c6c2c8f7772ce9885f666fb621)) +- **validators:** Add validators package ([38207ff](https://github.com/brazilian-utils/javascript/commit/38207ffce9347774e86349ebeb59e97d6fdfb14a)) + +### Bug Fixes + +- adjust circular dependency ([6f4f53b](https://github.com/brazilian-utils/javascript/commit/6f4f53b7dce0fe6f25c7c5b33c52df11138a8330)) +- adjust pt-BR documentation ([bb79fe6](https://github.com/brazilian-utils/javascript/commit/bb79fe6626cad39c2400b6dc6d5ec012f860faab)) +- **build:** update tsconfig rootDir for tsdx 0.13 ([17ea338](https://github.com/brazilian-utils/javascript/commit/17ea3382303f0e3dd990a25d7c738a6522c418ac)) +- function typo on usage section ([06ca0ca](https://github.com/brazilian-utils/javascript/commit/06ca0cafd22acc3d103883fe33bdd5246af6929e)) +- **jest:** update jest to fix security warning ([a3af687](https://github.com/brazilian-utils/javascript/commit/a3af6879303279ad15367bc86f15336c09794369)) +- length typo ([711ba8a](https://github.com/brazilian-utils/javascript/commit/711ba8a33e9816a2396cd59dda704aa89c43beec)) +- **release-it:** update config filename ([afc46a3](https://github.com/brazilian-utils/javascript/commit/afc46a3a7ef74167cfa9967939885594cae10681)) +- remove unnecessary escape ([74c53ed](https://github.com/brazilian-utils/javascript/commit/74c53edf8609efaa299ed4910958a27e04cff8f3)) +- **tooling:** remote git-add from lint-staged ([b000e8e](https://github.com/brazilian-utils/javascript/commit/b000e8e5e22ccd0f16edf047fb28c1d3b363a574)) + +### Continuous Integration + +- 🎡 adjust travis config ([0ec107f](https://github.com/brazilian-utils/javascript/commit/0ec107f1ebee536e15a6bb991750342457bf1a44)) +- 🎡 rename travis file ([25da01e](https://github.com/brazilian-utils/javascript/commit/25da01e5c700c17c3e88217c4bf272f5eef5639a)) + +[Unreleased]: https://github.com/brazilian-utils/javascript/compare/2.3.0...HEAD diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..d46f43c1 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,83 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience +- Focusing on what is best not just for us as individuals, but for the overall community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or advances of any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at support@brazilian-utils.com.br. All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of actions. + +**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..a6f9c1a9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,209 @@ +# Contributing to Brazilian Utils + +Thank you for your interest in contributing to Brazilian Utils! This project exists thanks to +[everyone who contributes](README.md#contributors), and we'd love your help solving the little +day-to-day problems of building software for Brazilian businesses. + +By participating in this project, you agree to abide by our +[Code of Conduct](CODE_OF_CONDUCT.md). + +## Getting started + +### Requirements + +- Node.js `24` for development (see `.nvmrc`): the toolchain (Vite+) needs it. The **library itself** supports Node.js `>=20.19.0`; the CI runs the test suite on Node 20, 22, 24 and 26. +- [npm](https://docs.npmjs.com) `10.x` (the project is pinned via `packageManager` in `package.json`) + +### Setup + +```bash +git clone https://github.com/brazilian-utils/javascript.git +cd javascript +npm install +``` + +This repository uses [Vite+](https://github.com/voidzero-dev/vite-plus) (`vp`) as its local +toolchain for linting, formatting, type-checking and testing. `vp` is installed as a dependency +and is invoked through the `npm` scripts below, so you don't need to install anything globally. + +### Useful scripts + +| Command | What it does | +| --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | +| `npm check` | Runs `vp check`: format check, lint and type-check together. Run this before opening a PR. | +| `npm check:fix` | Same as above, but auto-fixes what it can. | +| `npm format` / `npm format:check` | Formats the codebase / checks formatting with `vp fmt`. | +| `npm lint` / `npm lint:fix` | Lints the codebase with `vp lint`. | +| `npm test` | Runs the unit test suite with `vp test`. | +| `npm test:coverage` | Runs tests with coverage (`vp test run --coverage`). | +| `npm test:bun` | Runs the test suite on [Bun](https://bun.sh) (`bun test src`). | +| `npm test:deno` | Runs the test suite on [Deno](https://deno.com) (`deno test`). | +| `npm test:chrome-browser`, `npm test:firefox-browser`, `npm test:edge-browser`, `npm test:safari-browser` | Runs the test suite in real browsers via `vp test --browser.enabled`. | +| `npm build` | Builds the library with `vp build`. | + +Before opening a pull request, make sure `npm check` and `npm test` both pass locally. If your +change touches runtime behavior, also consider running the Bun/Deno scripts above. The library is +tested and must keep working on Node.js, Bun, Deno and in browsers. + +## Adding a new utility + +Brazilian Utils follows a consistent folder convention for every utility. To add a new one (for +example `formatSomething`): + +1. Create a folder under `src/` named after the utility in kebab-case, e.g. `src/format-something/`. + The folder name must match the function name (in kebab-case). This is not only a naming + convention: `vite.config.ts` scans `src/` at build time and turns every folder with a + same-named entry file (`src/format-something/format-something.ts`) into its own build entry, + published as the subpath `@brazilian-utils/brazilian-utils/format-something`, with no manual + wiring needed. That subpath lets consumers lazy-load a single heavy util (see `getCities` in + [Bundle size](docs/getting-started.md#bundle-size)) without touching the root bundle. +2. Add the implementation in `src/format-something/format-something.ts`. If the function takes an + options object, type it as `FormatSomethingOptions` (i.e. the function's `PascalCase` name plus + `Options`) and export it alongside the function. Write a JSDoc comment (description, `@param`, + `@returns`, `@example`, and an `@see` link to the authoritative source when the utility + implements an official Brazilian specification/algorithm (e.g. a Bacen manual, an IBGE table, + a government validation algorithm) following the style used in the existing utilities (see + `src/format-cpf/format-cpf.ts` for a reference). Keep the module tree-shakeable: no top-level + allocations or calls (`new Map()`, `new Set()`, etc.) that a bundler cannot prove side-effect + free, since those pin the module into every bundle that imports any util from the package. + Build such values lazily on first call instead (see `src/format-currency/format-currency.ts` + or `src/is-valid-service-phone/is-valid-service-phone.ts` for examples). +3. Add tests alongside it in `src/format-something/format-something.test.ts`. Cover valid input, + invalid/edge-case input, and options, if any. Tests must pass on Node, Bun and Deno (see + `npm test:bun` / `npm test:deno` under Useful scripts). +4. Export the new function (and any exported types) from `src/index.ts`, keeping the existing + alphabetical ordering. Then add the function name to the `PUBLIC` list and the type(s) to the + `publicTypes` map in `src/index.test.ts`, alphabetically. These two make up the package's + public surface contract, and the test suite fails the build if either is out of sync. +5. Document the utility in **both**: + - `docs/utilities.md` (English) + - `docs/pt-br/utilities.md` (Portuguese translation) + + Follow the existing format: a `##` heading with the function name, a short description, and a + `javascript` code block showing example input/output. Place the new section next to the other + utilities in the same domain, keeping both files in the same order. + + After editing `docs/getting-started.md` or `docs/utilities.md`, run `npm run build:llms` to + regenerate `docs/llms.txt` and `docs/llms-full.txt` (see [llms.txt](https://llmstxt.org/)) and + commit the result. CI fails the build if these files are stale. + +6. If the utility is based on an official Brazilian specification/document (e.g. a government + validation algorithm), link to the authoritative source in the code comment (`@see`) or PR + description so reviewers can verify the implementation. + +When an exported function has a source to credit, list the authoritative source first, labeled +`@see Official:` (a law, regulator, standard body or government dataset), followed by one +`@see Based on:` line for every third-party implementation, mirror dataset or reference test +vector the code actually relied on (a GitHub repo, a blog article, a community CSV/JSON mirror, +and so on), one `@see` per line. A utility with no located source of either kind (e.g. +`capitalize`, `formatCurrency`) can be left without an `@see` block. See +`src/is-valid-certidao/is-valid-certidao.ts` and `src/is-valid-cei/is-valid-cei.ts` for the style. + +Shared helpers used by multiple utilities live under `src/_internals/`. Check there before +duplicating logic (e.g. `src/_internals/format/format.ts`, +`src/_internals/sanitize-to-digits/sanitize-to-digits.ts`). + +`npm run check:tree-shaking` (`scripts/tree-shaking.ts`) checks the tree-shakeable-module rule +above for every function the package exports, by building a one-import consumer bundle per +export with esbuild and printing its size. There is no committed budgets file: instead, the +`tree-shaking` job in CI measures every export's single-import bundle size on the PR's base +branch and on the PR head, then comments a Markdown diff on the PR (sorted by absolute delta, +with new and removed exports called out and unchanged exports collapsed). The check fails the PR +when a pre-existing export grows by more than 20% and more than 256 bytes, or when a bundle +importing every export that already existed on the base grows by more than 5% (new exports +never count as a regression); those thresholds live as constants at the top of +`scripts/tree-shaking.ts`. When a size increase is intentional (a dataset refresh, a validator +that now covers more cases), a maintainer adds the `tree-shaking: accepted` label to the pull +request: the report is still posted, but the check no longer fails. Run `node scripts/tree-shaking.ts` locally to see the current sizes, +or `node scripts/tree-shaking.ts --json before.json` before a change and +`node scripts/tree-shaking.ts --compare before.json` after it to preview the same diff. + +## Zero runtime dependencies + +Brazilian Utils ships with **zero runtime dependencies**. This is a deliberate, load-bearing +design decision, since the library is meant to be small, safe and embeddable anywhere (Node.js, +Bun, Deno, bundlers, ` + ``` +### Runtime support + +Node `^20.19.0 || >=22.12.0`, Bun, Deno, and modern browsers. + ## Usage -To use one of our utilities you just need to import the required function as in the example below: +To use a utility, import the required function, as shown below: ```javascript import { isValidCpf } from "@brazilian-utils/brazilian-utils"; @@ -61,6 +77,8 @@ isValidCpf("1232454233345"); // false You can check a list of utilities [by clicking here](https://brazilian-utils.com.br/#/utilities). +- The package is tree-shakeable. Every util is also available as its own subpath (e.g. `@brazilian-utils/brazilian-utils/get-cities`) so you can lazy-load the few heavy ones. See [Bundle size](docs/getting-started.md#bundle-size). + ## Development This repository uses Vite+ as the local toolchain. @@ -127,8 +145,8 @@ Our "thank you" goes to these wonderful people ([emoji key](https://github.com/k
Vicente Vendramin

💻 - - +
Joao Assad

💻 ⚠️ +
Jander Silva

💻 diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..0aec3fbf --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,68 @@ +# Security Policy + +## Supported Versions + +Only the latest major version of `@brazilian-utils/brazilian-utils` receives security updates. + +| Version | Supported | +| ------- | ------------------ | +| 2.x | :white_check_mark: | +| < 2.0 | :x: | + +If you are on an unsupported version, please upgrade to the latest `2.x` release before reporting +an issue, as it may already be fixed. + +## Reporting a Vulnerability + +**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull +requests.** + +### Preferred: GitHub Private Vulnerability Reporting + +The preferred way to report a vulnerability is through GitHub's private reporting feature: + +1. Go to the [Security tab](https://github.com/brazilian-utils/javascript/security) of this + repository. +2. Click **"Report a vulnerability"**. +3. Fill in as much detail as you can (see below). + +This creates a private conversation with maintainers and lets us coordinate a fix and disclosure +without exposing the issue publicly before a patch is available. + +### Alternative: Email + +If you are unable to use GitHub's private reporting for any reason, you can email +**support@brazilian-utils.com.br** instead. + +### What to include + +To help us triage and fix the issue quickly, please include: + +- A description of the vulnerability and its potential impact. +- Steps to reproduce it (a minimal code sample using + `@brazilian-utils/brazilian-utils` is ideal). +- The affected version(s). +- Any suggested fix or mitigation, if you have one. + +## What's in scope + +This policy covers the `@brazilian-utils/brazilian-utils` npm package and the source code in this +repository, including: + +- Any code path that could lead to unexpected/unsafe behavior when using the library's exported + utilities (e.g. ReDoS in a validator/formatter, prototype pollution, unsafe use of dynamic code). +- Build/release tooling in this repository (`scripts/`, CI workflows) if it could compromise the + integrity of the published package. + +Out of scope: vulnerabilities in third-party dependencies of _your_ project, or issues that only +affect the documentation site (`docs/`) content itself rather than the published package (still +report doc-content issues, but via a regular issue). + +This library ships with **zero runtime dependencies**, which limits the supply-chain +attack surface, but does not eliminate the need for review. Still report anything you find. + +## Response Expectations + +This project is maintained by volunteers in their spare time. We will look at security reports +as soon as we can and treat them as a priority, but we cannot commit to response times. If a fix +is published we will credit you in the release notes unless you prefer to remain anonymous. diff --git a/SUPPORT.md b/SUPPORT.md new file mode 100644 index 00000000..2102ec03 --- /dev/null +++ b/SUPPORT.md @@ -0,0 +1,16 @@ +# Support + +Thanks for using Brazilian Utils! Here's where to get help, depending on what you need: + +- **Documentation**: the full list of utilities, usage examples and migration guides live at + [brazilian-utils.com.br](https://brazilian-utils.com.br/#/getting-started). +- **Questions & usage help**: open a [GitHub Discussion](https://github.com/brazilian-utils/javascript/discussions). + This is the best place for "how do I...?" questions or to propose an idea before it becomes an issue. +- **Bugs & feature requests**: use our [issue templates](https://github.com/brazilian-utils/javascript/issues/new/choose) + to report a bug or propose a new utility. +- **Security vulnerabilities**: do **not** open a public issue; see [SECURITY.md](SECURITY.md) for + how to report privately. +- **Contributing**: see [CONTRIBUTING.md](CONTRIBUTING.md) for how to set up the project and submit changes. + +This is a community-maintained, volunteer-run project, so response times may vary. Thanks for your +patience! diff --git a/docs/getting-started.md b/docs/getting-started.md index 0db8f4d4..77e13537 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -4,7 +4,7 @@ Brazilian Utils is a library focused on solving problems that we face daily in t ## Installation -Using **Brazilian Utils** is quite simple and you can use it in some ways: +You can install **Brazilian Utils** in a few ways: as npm package: @@ -18,15 +18,31 @@ with yarn package manager: yarn add @brazilian-utils/brazilian-utils ``` -or ` + ``` +### Runtime support + +Node `^20.19.0 || >=22.12.0`, Bun, Deno, and modern browsers. + ## Usage -To use one of our utilities you just need to import the required function as in the example below: +To use a utility, import the required function, as shown below: ```javascript import { isValidCpf } from '@brazilian-utils/brazilian-utils'; @@ -35,3 +51,41 @@ isValidCpf('1232454233345'); // false ``` You can check a list of utilities [by clicking here](utilities.md). + +## Bundle size + +The package is tree-shakeable: importing one util from the root pulls in only that util's code, not the rest of the library. `isValidCpf`, for example, adds roughly 0.7 KB minified to your bundle. A bundler that supports tree-shaking (webpack, Rollup, esbuild, Vite, etc.) drops every other util. + +A handful of utils are the exception: each embeds an official dataset, so it weighs far more than every other util combined. These are their single-import sizes, minified and gzipped: + +| Util | Dataset | Minified | Gzipped | +| --- | --- | --- | --- | +| `getMunicipalities` · `getMunicipalityByCode` · `getMunicipality` | 5571 IBGE municipalities, with names and codes | 156 KB | 50 KB | +| `getCities` | 5571 IBGE municipality names | 153 KB | 49 KB | +| `isValidNcm` | NCM (Nomenclatura Comum do Mercosul) codes | 113 KB | 24 KB | +| `isValidCbo` · `getCbo` | CBO 2002 occupation titles | 110 KB | 27 KB | +| `isValidCnae` · `getCnae` | CNAE 2.3 subclasses | 93 KB | 21 KB | +| `isValidCfop` · `getCfop` | CFOP operation descriptions | 55 KB | 5.4 KB | +| `getBanks` · `getBankByCode` | Banco Central STR participants (COMPE + ISPB) | 28 KB | 7.3 KB | + +Importing any of them from the root, even alongside a single small util, pulls that whole dataset into your main bundle, because this package ships as a single ESM module: a dynamic `import()` of the root (`await import('@brazilian-utils/brazilian-utils')`) still resolves to that same one file, so it can't be split out on its own. A bundler doing code-splitting needs a separate module to split *into*. + +Those separate modules are the per-util subpaths. Load a heavy util lazily, only where you actually need its data: + +```javascript +const { getCities } = await import('@brazilian-utils/brazilian-utils/get-cities'); + +getCities('SP'); +``` + +```javascript +const { getMunicipalityByCode } = await import( + '@brazilian-utils/brazilian-utils/get-municipality-by-code' +); + +getMunicipalityByCode('3550308'); +``` + +Every util is available this way, as `@brazilian-utils/brazilian-utils/` (kebab-case, matching the function name: `isValidCpf` → `is-valid-cpf`), for the same lazy-loading/code-splitting reason. + +Pick one style per util in a given app: a bundler treats the root import and the subpath import as two unrelated modules, so importing `getCities` from both the root *and* `/get-cities` in the same app bundles the 153 KB city table twice, once in each module's own output. diff --git a/docs/index.html b/docs/index.html index a480d002..a4daacde 100644 --- a/docs/index.html +++ b/docs/index.html @@ -8,7 +8,7 @@ - + @@ -22,10 +22,10 @@ '/': '/#/' }, alias: { - '/': 'https://raw.githubusercontent.com/brazilian-utils/brazilian-utils/main/README.md', - '/pt-br/': 'https://raw.githubusercontent.com/brazilian-utils/brazilian-utils/main/README.md' + '/': 'https://raw.githubusercontent.com/brazilian-utils/javascript/main/README.md', + '/pt-br/': '/pt-br/getting-started.md' }, - repo: 'brazilian-utils/brazilian-utils', + repo: 'brazilian-utils/javascript', loadNavbar: true, loadSidebar: true, auto2top: true, @@ -50,11 +50,11 @@ }, }; - - - - - + + + + + \ No newline at end of file diff --git a/docs/llms-full.txt b/docs/llms-full.txt new file mode 100644 index 00000000..11753d2a --- /dev/null +++ b/docs/llms-full.txt @@ -0,0 +1,2068 @@ +# Brazilian Utils + +> Brazilian Utils is a zero-dependency JavaScript/TypeScript library of small, focused utilities for the day-to-day problems of building software for Brazilian businesses. This file concatenates the full English documentation (getting started + utilities reference) in one Markdown document for LLM context loading. + +## Table of contents + +- [Getting Started](#getting-started) + - [Installation](#installation) + - [Runtime support](#runtime-support) + - [Usage](#usage) + - [Bundle size](#bundle-size) +- [Utilities](#utilities) + - [isValidCpf](#isvalidcpf) + - [formatCpf](#formatcpf) + - [parseCpf](#parsecpf) + - [generateCpf](#generatecpf) + - [isValidCnpj](#isvalidcnpj) + - [formatCnpj](#formatcnpj) + - [parseCnpj](#parsecnpj) + - [isValidCep](#isvalidcep) + - [generateCnpj](#generatecnpj) + - [isValidBoleto](#isvalidboleto) + - [formatBoleto](#formatboleto) + - [parseBoleto](#parseboleto) + - [generateBoleto](#generateboleto) + - [getBoletoInfo](#getboletoinfo) + - [isValidPixKey](#isvalidpixkey) + - [parsePixKey](#parsepixkey) + - [isValidPixPayload](#isvalidpixpayload) + - [parsePixPayload](#parsepixpayload) + - [generatePixPayload](#generatepixpayload) + - [isValidNfeKey](#isvalidnfekey) + - [formatNfeKey](#formatnfekey) + - [parseNfeKey](#parsenfekey) + - [isValidEmail](#isvalidemail) + - [isValidPhone](#isvalidphone) + - [formatPhone](#formatphone) + - [parsePhone](#parsephone) + - [isValidMobilePhone](#isvalidmobilephone) + - [isValidLandlinePhone](#isvalidlandlinephone) + - [isValidServicePhone](#isvalidservicephone) + - [getAreaCodeInfo](#getareacodeinfo) + - [getAreaCodesByState](#getareacodesbystate) + - [isValidLicensePlate](#isvalidlicenseplate) + - [isValidRenavam](#isvalidrenavam) + - [isValidPis](#isvalidpis) + - [formatPis](#formatpis) + - [parsePis](#parsepis) + - [formatCep](#formatcep) + - [parseCep](#parsecep) + - [getAddressInfoByCep](#getaddressinfobycep) + - [isValidProcessoJuridico](#isvalidprocessojuridico) + - [formatProcessoJuridico](#formatprocessojuridico) + - [parseProcessoJuridico](#parseprocessojuridico) + - [isValidIe](#isvalidie) + - [isValidBankAccount](#isvalidbankaccount) + - [getBanks](#getbanks) + - [getBankByCode](#getbankbycode) + - [getBankByIspb](#getbankbyispb) + - [isValidIban](#isvalidiban) + - [formatIban](#formatiban) + - [parseIban](#parseiban) + - [isValidCreditCard](#isvalidcreditcard) + - [capitalize](#capitalize) + - [formatCurrency](#formatcurrency) + - [parseCurrency](#parsecurrency) + - [convertNumberToWords](#convertnumbertowords) + - [convertCurrencyToWords](#convertcurrencytowords) + - [getStates](#getstates) + - [getStateByIbgeCode](#getstatebyibgecode) + - [getStateCodeByName](#getstatecodebyname) + - [getStateNameByCode](#getstatenamebycode) + - [getTimezoneByState](#gettimezonebystate) + - [getCities](#getcities) + - [getHolidays](#getholidays) + - [isValidPassport](#isvalidpassport) + - [formatPassport](#formatpassport) + - [generatePassport](#generatepassport) + - [parsePassport](#parsepassport) + - [generateCep](#generatecep) + - [formatCnh](#formatcnh) + - [isValidCnh](#isvalidcnh) + - [generateCnh](#generatecnh) + - [parseCnh](#parsecnh) + - [getCepInfoByAddress](#getcepinfobyaddress) + - [generateProcessoJuridico](#generateprocessojuridico) + - [formatLegalNature](#formatlegalnature) + - [isValidLegalNature](#isvalidlegalnature) + - [generateLegalNature](#generatelegalnature) + - [parseLegalNature](#parselegalnature) + - [getLegalNatures](#getlegalnatures) + - [getLegalNature](#getlegalnature) + - [generatePhone](#generatephone) + - [formatLicensePlate](#formatlicenseplate) + - [generateLicensePlate](#generatelicenseplate) + - [getFormatLicensePlate](#getformatlicenseplate) + - [parseLicensePlate](#parselicenseplate) + - [convertLicensePlateToMercosul](#convertlicenseplatetomercosul) + - [generatePis](#generatepis) + - [getMunicipality](#getmunicipality) + - [getMunicipalities](#getmunicipalities) + - [getMunicipalityByCode](#getmunicipalitybycode) + - [isHoliday](#isholiday) + - [isBusinessDay](#isbusinessday) + - [addBusinessDays](#addbusinessdays) + - [differenceInBusinessDays](#differenceinbusinessdays) + - [convertDateToWords](#convertdatetowords) + - [formatVoterId](#formatvoterid) + - [isValidVoterId](#isvalidvoterid) + - [generateVoterId](#generatevoterid) + - [parseVoterId](#parsevoterid) + - [isValidCns](#isvalidcns) + - [formatCns](#formatcns) + - [isValidCertidao](#isvalidcertidao) + - [parseCertidao](#parsecertidao) + - [formatCertidao](#formatcertidao) + - [isValidCei](#isvalidcei) + - [formatCei](#formatcei) + - [isValidCno](#isvalidcno) + - [formatCno](#formatcno) + - [isValidCaepf](#isvalidcaepf) + - [formatCaepf](#formatcaepf) + - [isValidRegistroProfissional](#isvalidregistroprofissional) + - [isValidVin](#isvalidvin) + - [isValidCbo](#isvalidcbo) + - [getCbo](#getcbo) + - [isValidCnae](#isvalidcnae) + - [formatCnae](#formatcnae) + - [getCnae](#getcnae) + - [isValidNcm](#isvalidncm) + - [formatNcm](#formatncm) + - [isValidCfop](#isvalidcfop) + - [getCfop](#getcfop) + - [isValidCst](#isvalidcst) + - [isValidCsosn](#isvalidcsosn) + - [removeAccents](#removeaccents) + +## Getting Started + +Brazilian Utils is a library focused on solving problems that we face daily in the development of applications for the Brazilian business. + +### Installation + +You can install **Brazilian Utils** in a few ways: + +as npm package: + +```bash +npm install --save @brazilian-utils/brazilian-utils +``` + +with yarn package manager: + +```bash +yarn add @brazilian-utils/brazilian-utils +``` + +with pnpm: + +```bash +pnpm add @brazilian-utils/brazilian-utils +``` + +with bun: + +```bash +bun add @brazilian-utils/brazilian-utils +``` + +or ` +``` + +#### Runtime support + +Node `^20.19.0 || >=22.12.0`, Bun, Deno, and modern browsers. + +### Usage + +To use a utility, import the required function, as shown below: + +```javascript +import { isValidCpf } from '@brazilian-utils/brazilian-utils'; + +isValidCpf('1232454233345'); // false +``` + +You can check a list of utilities [by clicking here](utilities.md). + +### Bundle size + +The package is tree-shakeable: importing one util from the root pulls in only that util's code, not the rest of the library. `isValidCpf`, for example, adds roughly 0.7 KB minified to your bundle. A bundler that supports tree-shaking (webpack, Rollup, esbuild, Vite, etc.) drops every other util. + +A handful of utils are the exception: each embeds an official dataset, so it weighs far more than every other util combined. These are their single-import sizes, minified and gzipped: + +| Util | Dataset | Minified | Gzipped | +| --- | --- | --- | --- | +| `getMunicipalities` · `getMunicipalityByCode` · `getMunicipality` | 5571 IBGE municipalities, with names and codes | 156 KB | 50 KB | +| `getCities` | 5571 IBGE municipality names | 153 KB | 49 KB | +| `isValidNcm` | NCM (Nomenclatura Comum do Mercosul) codes | 113 KB | 24 KB | +| `isValidCbo` · `getCbo` | CBO 2002 occupation titles | 110 KB | 27 KB | +| `isValidCnae` · `getCnae` | CNAE 2.3 subclasses | 93 KB | 21 KB | +| `isValidCfop` · `getCfop` | CFOP operation descriptions | 55 KB | 5.4 KB | +| `getBanks` · `getBankByCode` | Banco Central STR participants (COMPE + ISPB) | 28 KB | 7.3 KB | + +Importing any of them from the root, even alongside a single small util, pulls that whole dataset into your main bundle, because this package ships as a single ESM module: a dynamic `import()` of the root (`await import('@brazilian-utils/brazilian-utils')`) still resolves to that same one file, so it can't be split out on its own. A bundler doing code-splitting needs a separate module to split *into*. + +Those separate modules are the per-util subpaths. Load a heavy util lazily, only where you actually need its data: + +```javascript +const { getCities } = await import('@brazilian-utils/brazilian-utils/get-cities'); + +getCities('SP'); +``` + +```javascript +const { getMunicipalityByCode } = await import( + '@brazilian-utils/brazilian-utils/get-municipality-by-code' +); + +getMunicipalityByCode('3550308'); +``` + +Every util is available this way, as `@brazilian-utils/brazilian-utils/` (kebab-case, matching the function name: `isValidCpf` → `is-valid-cpf`), for the same lazy-loading/code-splitting reason. + +Pick one style per util in a given app: a bundler treats the root import and the subpath import as two unrelated modules, so importing `getCities` from both the root *and* `/get-cities` in the same app bundles the 153 KB city table twice, once in each module's own output. + +## Utilities + +Here you will find all the utilities available for use. + +> **Input handling:** no public function throws on `null`/`undefined` or a wrong-type value. `isValid*` predicates return `false`; `isHoliday` returns `false`; `getHolidays` returns `[]`; `generateProcessoJuridico` returns `null`; `getMunicipality` returns `null` for a malformed/unmatched lookup. Every other `format*`/`parse*` function (including `capitalize`) returns an empty value of its return type: `""` for strings, `0` for `parseCurrency`. `formatCurrency` returns `""` for a non-finite number. + +### isValidCpf + +Check if CPF is valid. Accepts the usual mask characters and whitespace between/around groups. + +```javascript +import { isValidCpf } from '@brazilian-utils/brazilian-utils'; + +isValidCpf('155151475'); // false +isValidCpf('111 444 777 35'); // true (whitespace mask) +``` + +### formatCpf + +Format CPF. `options.obfuscate` (part of `FormatCpfOptions`) hides the first 3 digits and the 2 check digits (`***.456.789-**`), the gov.br / Receita Federal display convention, applied after `pad`. + +```javascript +import { formatCpf } from '@brazilian-utils/brazilian-utils'; + +formatCpf('74650688000'); // 746.506.880-00 +formatCpf('746506880', { pad: true }); // 007.465.068-80 +formatCpf('12345678909', { obfuscate: true }); // ***.456.789-** +``` + +### parseCpf + +Remove CPF formatting, keep only digits, and cap the result to 11 digits. + +```javascript +import { parseCpf } from '@brazilian-utils/brazilian-utils'; + +parseCpf('746.506.880-00'); // 74650688000 +``` + +### generateCpf + +Generate a valid random CPF. + +```javascript +import { generateCpf } from '@brazilian-utils/brazilian-utils' + +generateCpf(); +``` + +### isValidCnpj + +Check if CNPJ is valid. Supports both the numeric format (`version: 1`, default) and the alphanumeric format (`version: 2`), and accepts the usual mask characters and whitespace. Options are typed as `IsValidCnpjOptions`. + +```javascript +import { isValidCnpj } from '@brazilian-utils/brazilian-utils'; + +isValidCnpj('15515147234255'); // false +isValidCnpj('q0slfmbd7vx439', { version: 2 }); // true (lowercase alphanumeric) +``` + +### formatCnpj + +Format CNPJ. `options.obfuscate` (part of `FormatCnpjOptions`) hides the first 2 digits and the 2 check digits (`**.345.678/0001-**`), the gov.br / Receita Federal display convention. It applies to both versions and comes after `pad`. + +```javascript +import { formatCnpj } from '@brazilian-utils/brazilian-utils'; + +formatCnpj('24522200000174'); // 24.522.200/0001-74 +formatCnpj('245222000174', { pad: true }); // 00.245.222/0001-74 +formatCnpj('12OUT345000199', { version: 2 }); // 12.OUT.345/0001-99 +formatCnpj('12345678000195', { obfuscate: true }); // **.345.678/0001-** +``` + +### parseCnpj + +Remove CNPJ formatting, return a normalized value, and cap the result to 14 characters. Options are typed as `ParseCnpjOptions`. + +```javascript +import { parseCnpj } from '@brazilian-utils/brazilian-utils'; + +parseCnpj('24.522.200/0001-74'); // 24522200000174 +parseCnpj('12.OUT.345/0001-99', { version: 2 }); // 12OUT345000199 +``` + +### isValidCep + +Check if CEP ([brazilian postal code](https://en.wikipedia.org/wiki/C%C3%B3digo_de_Endere%C3%A7amento_Postal)) is valid. Accepts both `string` and `number` input; any spaces, dots and hyphens around/between the 8 digits are ignored, but any other character, a letter in particular, makes the value invalid. + +```javascript +import { isValidCep } from '@brazilian-utils/brazilian-utils'; + +isValidCep('01310100'); // true +isValidCep('92500-000'); // true (hyphen between groups) +isValidCep('92.500-000'); // true (dot and hyphen) +isValidCep('013 10 100'); // true (spaces anywhere between the digits) +isValidCep(20040020); // true (number input) +isValidCep('9250000A'); // false (letters are rejected) +isValidCep('12345'); // false (invalid length) +``` + +### generateCnpj + +Generate a valid random CNPJ. + +```javascript +import { generateCnpj } from '@brazilian-utils/brazilian-utils' + +generateCnpj(); +``` + +### isValidBoleto + +Check if boleto ([brazilian payment method](https://en.wikipedia.org/wiki/Boleto)) is valid. Supports both the 47 digit "cobrança bancária" boleto and the "boleto de arrecadação" (convênio/tributos): either its 48 digit linha digitável or its 44 digit barcode, both starting with `8`. + +```javascript +import { isValidBoleto } from '@brazilian-utils/brazilian-utils'; + +isValidBoleto('00190000090114971860168524522114675860000102656'); // true +isValidBoleto('846100000005246100291102005460339004695895061080'); // true (boleto de arrecadação) +``` + +### formatBoleto + +Format a boleto number. The arrecadação (convênio/tributos) mask applies only to the 48 digit linha digitável starting with `8`; the 44 digit arrecadação barcode has no display grouping defined by FEBRABAN and keeps the "cobrança bancária" mask instead. + +```javascript +import { formatBoleto } from '@brazilian-utils/brazilian-utils'; + +formatBoleto('00190000090114971860168524522114675860000102656'); // 00190.00009 01149.718601 68524.522114 6 75860000102656 +formatBoleto('1900000901149', { pad: true }); // 00000.00000 00000.000000 00000.000000 0 01900000901149 +formatBoleto('846100000005246100291102005460339004695895061080'); // 84610000000-5 24610029110-2 00546033900-4 69589506108-0 (48 digit arrecadação linha digitável) +formatBoleto('84610000000246100291100054603390069589506108'); // 84610.00000 02461.002911 00054.603390 0 69589506108 (44 digit arrecadação barcode keeps the bancária mask) +``` + +### parseBoleto + +Remove boleto formatting, keep only digits, and cap the result to 47 digits (48 for boleto de arrecadação). + +```javascript +import { parseBoleto } from '@brazilian-utils/brazilian-utils'; + +parseBoleto('00190.00009 01149.718601 68524.522114 6 75860000102656'); // 00190000090114971860168524522114675860000102656 +``` + +### generateBoleto + +Generate a valid random boleto. Pass `{ type: "arrecadacao" }` (typed as `GenerateBoletoOptions`) to generate a boleto de arrecadação instead of the default "bancario" (cobrança bancária) type. + +```javascript +import { generateBoleto } from '@brazilian-utils/brazilian-utils'; + +generateBoleto(); // "00190000090114971860168524522114675860000102656" +generateBoleto({ type: 'arrecadacao' }); // "846100000005246100291102005460339004695895061080" +``` + +### getBoletoInfo + +Extract information from a boleto (amount, expiration date, bank code). Accepts an optional `{ referenceDate }` (typed as `GetBoletoInfoOptions`) to resolve the "fator de vencimento" cycle as of a specific date instead of now (the factor's date-base cycle reset on 22/02/2025 per FEBRABAN). For a boleto de arrecadação, the result, typed as `BoletoInfo`, has no `bankCode`/`expirationDate` and instead carries `type: "arrecadacao"`, `segment`, `value` and `hasEffectiveValue`. + +```javascript +import { getBoletoInfo } from '@brazilian-utils/brazilian-utils'; + +getBoletoInfo('00190000090114971860168524522114675860000102656'); +// { amount: 102656, expirationDate: Date, bankCode: '001' } + +getBoletoInfo('00190000090114971860168524522114675860000102656', { + referenceDate: new Date(2018, 6, 1) +}); +// Resolves the fator de vencimento cycle as of 2018-07-01 + +getBoletoInfo('846100000005246100291102005460339004695895061080'); +// { amount: 2461, expirationDate: null, bankCode: '', type: 'arrecadacao', segment: 4, value: 24.61, hasEffectiveValue: true } +``` + +### 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. + +```javascript +import { isValidPixKey } from '@brazilian-utils/brazilian-utils'; + +isValidPixKey('123.456.789-09'); // true +isValidPixKey('fulano@example.com'); // true +isValidPixKey('(11) 98765-4321'); // true +isValidPixKey('71c7d9be-4b85-4e43-9f1c-1f3b8b4e9a2d'); // true +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`. + +```javascript +import { parsePixKey } from '@brazilian-utils/brazilian-utils'; + +parsePixKey('123.456.789-09'); // { type: 'cpf', value: '12345678909' } +parsePixKey('Fulano@Example.COM '); // { type: 'email', value: 'fulano@example.com' } +parsePixKey('(11) 98765-4321'); // { type: 'phone', value: '+5511987654321' } +parsePixKey('71C7D9BE-4B85-4E43-9F1C-1F3B8B4E9A2D'); +// { type: 'evp', value: '71c7d9be-4b85-4e43-9f1c-1f3b8b4e9a2d' } +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. + +```javascript +import { isValidPixPayload } from '@brazilian-utils/brazilian-utils'; + +isValidPixPayload( + '00020126580014br.gov.bcb.pix0136123e4567-e12b-12d1-a456-426655440000' + + '5204000053039865802BR5913Fulano de Tal6008BRASILIA62070503***63041D3D' +); // true + +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"`). + +```javascript +import { parsePixPayload } from '@brazilian-utils/brazilian-utils'; + +parsePixPayload( + '00020126580014br.gov.bcb.pix0136123e4567-e12b-12d1-a456-426655440000' + + '5204000053039865802BR5913Fulano de Tal6008BRASILIA62070503***63041D3D' +); +// { +// key: '123e4567-e12b-12d1-a456-426655440000', +// merchantName: 'Fulano de Tal', +// merchantCity: 'BRASILIA' +// } +``` + +### generatePixPayload + +Generates the payload of a Pix BR Code. Exactly one of `params.key` or `params.url` must be given (part of `GeneratePixPayloadParams`); `null` is returned when both or neither are given. + +When `params.key` is given, it is normalized to its DICT canonical form by `parsePixKey` and the payload is static. When `params.url` is given instead (the PSP location, without a URL scheme, e.g. `"pix.example.com/qr/v2/1234"`), the payload is dynamic per the Manual de Padrões para Iniciação do Pix: the URL takes the key's place in the "Merchant Account Information" template and the "Point of Initiation Method" object is set to dynamic (`12`); `params.url` can be at most 77 characters. `merchantName`, `merchantCity` and `description` are folded to printable ASCII (accents dropped) and truncated to what the BR Code allows. `parsePixPayload` already parses both shapes, so `parsePixPayload(generatePixPayload({ url, ... }))` round-trips. + +```javascript +import { generatePixPayload } from '@brazilian-utils/brazilian-utils'; + +generatePixPayload({ + key: '123.456.789-09', + merchantName: 'Fulano de Tal', + merchantCity: 'Brasília', + amount: 123.45 +}); +// "00020126330014br.gov.bcb.pix0111123456789095204000053039865406123.455802BR5913Fulano de Tal6008Brasilia62070503***630479EE" + +generatePixPayload({ + url: 'pix.example.com/qr/v2/1234', + merchantName: 'Fulano de Tal', + merchantCity: 'Brasília' +}); +// "00020101021226480014br.gov.bcb.pix2526pix.example.com/qr/v2/12345204000053039865802BR5913Fulano de Tal6008Brasilia62070503***6304FC66" + +generatePixPayload({ merchantName: 'Fulano', merchantCity: 'Brasília' }); // null (neither key nor url) +``` + +### 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. + +```javascript +import { isValidNfeKey } from '@brazilian-utils/brazilian-utils'; + +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) +``` + +### formatNfeKey + +Format a DF-e (NF-e, NFC-e, CT-e or MDF-e) access key into groups of 4 digits separated by spaces, the common display form printed on the DANFE. + +```javascript +import { formatNfeKey } from '@brazilian-utils/brazilian-utils'; + +formatNfeKey('35170458716523000119550010000000121000123458'); +// '3517 0458 7165 2300 0119 5500 1000 0000 1210 0012 3458' +``` + +### parseNfeKey + +Parses a DF-e access key into its fields (state, year, month, taxId, model, series, number, emissionType, code, checkDigit). Accepts the same input forms as `isValidNfeKey` and returns `null` when the key is not valid. The result is typed as `NfeKey`. + +```javascript +import { parseNfeKey } from '@brazilian-utils/brazilian-utils'; + +parseNfeKey('35170458716523000119550010000000121000123458'); +// { state: 'SP', year: 2017, month: 4, taxId: '58716523000119', model: '55', +// series: 1, number: 12, emissionType: 1, code: '00012345', checkDigit: 8 } + +parseNfeKey('invalid'); // null +``` + +### isValidEmail + +Check if email is valid. + +```javascript +import { isValidEmail } from '@brazilian-utils/brazilian-utils'; + +isValidEmail('john.doe@hotmail.com'); // true +``` + +### isValidPhone + +Check if phone number (mobile or landline) is valid. A Brazilian country code (`+55`, `0055` or a bare `55`) is accepted and removed before validation, under the rule documented in `parsePhone`. `options.accept` (typed as `PhoneType[]`, part of `IsValidPhoneOptions`) picks which kinds of number count as valid and defaults to `['mobile', 'landline']`; add `'service'` to also accept the non-geographic numbers recognized by `isValidServicePhone`, or pass `[]` to accept none. + +```javascript +import { isValidPhone } from '@brazilian-utils/brazilian-utils'; + +isValidPhone('11900000000'); // true +isValidPhone('+55 11 98765-4321'); // true (country code accepted) +isValidPhone('08001234567'); // false (service numbers rejected by default) +isValidPhone('08001234567', { accept: ['service'] }); // true +isValidPhone('11900000000', { accept: [] }); // false +``` + +### formatPhone + +Format phone number according to Brazilian patterns. `options.mask` (typed as `PhoneMask`) accepts `"sn"` (default, subscriber number only, 9 digits, no DDD), `"nanp"` (DDD + subscriber number, 11 digits), `"e164"` (`"+5511987654321"`), `"international"` (`"+55 11 98765-4321"`, the way a Brazilian number is printed for foreign callers), `"service"` (`"0800 123 4567"` or `"4004-1234"`, the conventional groupings for service numbers) or `"auto"`. `"auto"` picks `"international"` when `value` carries a Brazilian country code (`+55`, `0055` or a bare `55` followed by 10 or 11 digits), `"service"` when `value` is a service number, and otherwise falls back to the digit count: `"nanp"` when `value` has more digits than a bare subscriber number, `"sn"` when it does not. `"e164"` and `"international"` drop the country code from `value` first, under the rule documented in `parsePhone`, and fall back to the `"service"` presentation for a service number, since those have no E.164 form. If `value` includes a DDD, pass `{ mask: 'auto' }` (or `'nanp'`) explicitly, since the default `"sn"` mask assumes no DDD and silently truncates one if present. + +```javascript +import { formatPhone } from '@brazilian-utils/brazilian-utils'; + +formatPhone('987654321'); // 98765-4321 (default "sn", no DDD) +formatPhone('11900000000', { mask: 'nanp' }); // (11) 90000-0000 +formatPhone('11900000000', { mask: 'auto' }); // (11) 90000-0000 +formatPhone('11987654321', { mask: 'e164' }); // +5511987654321 +formatPhone('+5511987654321', { mask: 'international' }); // +55 11 98765-4321 +formatPhone('08001234567', { mask: 'service' }); // 0800 123 4567 +formatPhone('40041234', { mask: 'service' }); // 4004-1234 +formatPhone('+5511987654321', { mask: 'auto' }); // (55) 11987-6543 (BEWARE: "auto" does not detect the +55 prefix) +formatPhone('11900000000'); // 11900-0000 (BEWARE: default "sn" truncates a DDD-prefixed number) +``` + +### parsePhone + +Remove phone formatting, keep only digits, and cap the result to 11 digits. A Brazilian country code is stripped first, but only when the digits left behind are exactly 10 or 11 long, i.e. a plausible national number. The rule is length-based, not sign-based, so a number from area code 55 is not mistaken for a country code. + +```javascript +import { parsePhone } from '@brazilian-utils/brazilian-utils'; + +parsePhone('(11) 90000-0000'); // 11900000000 +parsePhone('+55 (11) 98765-4321'); // 11987654321 +parsePhone('5511987654321'); // 11987654321 +parsePhone('55987654321'); // 55987654321 (area code 55, not mistaken for the +55 country code) +``` + +### 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. + +```javascript +import { isValidMobilePhone } from '@brazilian-utils/brazilian-utils'; + +isValidMobilePhone('11900000000'); // true +isValidMobilePhone('11712345678', { version: 1 }); // true (legacy format) +isValidMobilePhone('11712345678', { version: 2 }); // false (v2 requires 9 as the first digit) +``` + +### isValidLandlinePhone + +Check if landline phone number is valid. + +```javascript +import { isValidLandlinePhone } from '@brazilian-utils/brazilian-utils'; + +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. + +```javascript +import { isValidServicePhone } from '@brazilian-utils/brazilian-utils'; + +isValidServicePhone('0800 123 4567'); // true +isValidServicePhone('4004-1234'); // true +isValidServicePhone('190'); // true +isValidServicePhone('11987654321'); // false (geographic number) +``` + +### getAreaCodeInfo + +Get the state (and its region) a Brazilian DDD (area code) belongs to, out of the 67 DDDs in use under the Anatel Plano Geral de Numeração. Accepts a string or a number, stripping any non-digit characters before matching. Exports the `AreaCodeInfo` type. + +```javascript +import { getAreaCodeInfo } from '@brazilian-utils/brazilian-utils'; + +getAreaCodeInfo('11'); +// { areaCode: 11, stateCode: 'SP', stateName: 'São Paulo', region: 'Sudeste' } + +getAreaCodeInfo(21); +// { areaCode: 21, stateCode: 'RJ', stateName: 'Rio de Janeiro', region: 'Sudeste' } + +getAreaCodeInfo('68'); +// { areaCode: 68, stateCode: 'AC', stateName: 'Acre', region: 'Norte' } + +getAreaCodeInfo('00'); // null +``` + +### getAreaCodesByState + +Get every DDD (area code) that belongs to a given Brazilian state, under the Anatel Plano Geral de Numeração. The match is case-insensitive and the result is sorted in ascending order. + +```javascript +import { getAreaCodesByState } from '@brazilian-utils/brazilian-utils'; + +getAreaCodesByState('SP'); // [11, 12, 13, 14, 15, 16, 17, 18, 19] +getAreaCodesByState('ac'); // [68] +getAreaCodesByState('XX'); // [] +``` + +### isValidLicensePlate + +Check if license plate is valid. Supports the old Brazilian format (ABC-1234) and the Mercosul format (ABC1D23), the single sequence Resolução CONTRAN nº 969/2022 defines for every vehicle, motorcycles included. + +```javascript +import { isValidLicensePlate } from '@brazilian-utils/brazilian-utils'; + +isValidLicensePlate('ABC1234'); // true (Brazilian format) +isValidLicensePlate('ABC-1234'); // true (Brazilian format with hyphen) +isValidLicensePlate('ABC 1234'); // true (whitespace mask) +isValidLicensePlate('ABC1D23'); // true (Mercosul format) +isValidLicensePlate('ABC12D3'); // false (not a Mercosul sequence) +isValidLicensePlate('ABC1234EXTRA'); // false (too many characters) +``` + +### isValidRenavam + +Check if RENAVAM (Registro Nacional de Veículos Automotores) is valid. Supports both the old format (9 digits) and the new format (11 digits). + +```javascript +import { isValidRenavam } from '@brazilian-utils/brazilian-utils'; + +isValidRenavam('639884962'); // true (9 digits, old format) +isValidRenavam('00639884962'); // true (11 digits, new format) +isValidRenavam('12345678901'); // false (invalid checksum) +``` + +### isValidPis + +Check if PIS is valid. Accepts the usual mask characters and whitespace. + +```javascript +import { isValidPis } from '@brazilian-utils/brazilian-utils'; + +isValidPis('12056412547'); // false +``` + +### formatPis + +Format PIS number. + +```javascript +import { formatPis } from '@brazilian-utils/brazilian-utils'; + +formatPis('12345678901'); // 123.45678.90-1 +formatPis('123456789', { pad: true }); // 001.23456.78-9 +``` + +### parsePis + +Remove PIS formatting, keep only digits, and cap the result to 11 digits. + +```javascript +import { parsePis } from '@brazilian-utils/brazilian-utils'; + +parsePis('123.45678.90-1'); // 12345678901 +``` + +### formatCep + +Format CEP ([brazilian postal code](https://en.wikipedia.org/wiki/C%C3%B3digo_de_Endere%C3%A7amento_Postal)). + +```javascript +import { formatCep } from '@brazilian-utils/brazilian-utils'; + +formatCep('92500000'); // 92500-000 +``` + +### parseCep + +Remove CEP formatting, keep only digits, and cap the result to 8 digits. + +```javascript +import { parseCep } from '@brazilian-utils/brazilian-utils'; + +parseCep('92500-000'); // 92500000 +``` + +### getAddressInfoByCep + +Fetch address information for a given CEP using multiple providers. Defaults to `['viacep', 'brasilapi']`. The `'widenet'` provider is deprecated (its endpoint no longer responds) and excluded from the default list, but it can still be requested explicitly via `options.providers` (typed as `CepProvider[]`). The resolved address is typed as `AddressInfo`. + +```javascript +import { getAddressInfoByCep } from '@brazilian-utils/brazilian-utils'; + +// Using the default providers (['viacep', 'brasilapi']) +const address = await getAddressInfoByCep('01310100'); +// { cep: '01310100', state: 'SP', city: 'São Paulo', neighborhood: 'Bela Vista', street: 'Avenida Paulista' } + +// Using specific providers +const address = await getAddressInfoByCep('01310-100', { + providers: ['viacep', 'brasilapi'] +}); + +// Using number input (will be padded automatically) +const address = await getAddressInfoByCep(1310100); +``` + +### isValidProcessoJuridico + +Validate the processo jurídico number according to [CNJ's definition](https://www.conjur.com.br/dl/resolucao-65-cnj.pdf). + +```javascript +import { isValidProcessoJuridico } from '@brazilian-utils/brazilian-utils'; + +isValidProcessoJuridico('00020802520125150049'); // true +``` + +### formatProcessoJuridico + +Format the processo jurídico number according to [CNJ's definition](https://www.conjur.com.br/dl/resolucao-65-cnj.pdf) (mask `NNNNNNN-DD.AAAA.J.TR.OOOO`). + +```javascript +import { formatProcessoJuridico } from '@brazilian-utils/brazilian-utils'; + +formatProcessoJuridico('00020802520125150049'); // 0002080-25.2012.5.15.0049 +``` + +### parseProcessoJuridico + +Remove processo jurídico formatting, keep only digits, and cap the result to 20 digits. Both the current CNJ mask (`NNNNNNN-DD.AAAA.J.TR.OOOO`) and the older one are accepted, since only the digits are kept. + +```javascript +import { parseProcessoJuridico } from '@brazilian-utils/brazilian-utils'; + +parseProcessoJuridico('0002080-25.2012.5.15.0049'); // 00020802520125150049 +``` + +### isValidIe + +Check if inscrição estadual (state registration) is valid. The state code is case-insensitive. Notable per-state rules: GO accepts prefixes `10`, `11` and `15`; PA accepts `15` and `75`-`79`; MS accepts `28` and `50`; SP has a produtor rural pattern `P0MMMSSSSD000`; TO uses 11-digit type codes (`01`, `02`, `03`, `99`). + +```javascript +import { isValidIe } from '@brazilian-utils/brazilian-utils'; + +isValidIe('AC', '0187634580933'); // false +isValidIe('go', '109161793'); // true (case-insensitive) +``` + +### isValidBankAccount + +Check if a Brazilian bank account is valid. The `bankCode` must belong to the Banco Central do Brasil STR participants list (the same dataset used by `getBankByCode`), so an unassigned code such as `'999'` is always invalid. Banks are then validated in one of three ways: by their published check digit algorithm, by structure only (bank exists and the agency/account match the documented digit lengths, for banks that publish no check digit rule) or by a generic mod10/mod11 check, which stays the fallback for every other listed bank. + +Banks validated by their published check digit algorithm: + +| Bank | Code | Agency | Account | Notes | +| --- | --- | --- | --- | --- | +| Banco do Brasil | `001` | 4-5 digits | 8-10 digits | mod11 with weights 9..2; `digit` may be `"X"` | +| Santander | `033` | 4 digits | 8 digits | weights `9,7,3,1,0,0,9,7,1,3,1,9,7,3` over agency + `"00"` + account, tens discarded | +| Banrisul | `041` | 4 digits | 9 digits | weights `3,2,4,7,6,5,4,3,2`; remainder 0 gives `0` and remainder 1 gives `6`; `account` is tipo (2 digits) + conta (7 digits) | +| Caixa Econômica Federal | `104` | 4 digits | 11 digits | mod11 over agency + account; `account` is operação (3 digits) + conta (8 digits) | +| Bradesco | `237` | 4 digits | 7 digits | mod11 with weights 2..7; `digit` may be `"P"` (often rendered as `"0"`) | +| Nubank | `260` | 4 digits | 5-13 digits | Verhoeff check digit over the account, leading zeros dropped | +| Itaú Unibanco | `341` | 4 digits | 5 digits | mod10 over agency + account | +| HSBC / Kirton Bank | `399` | 4 digits | 6 digits | weights `8,9,2,3,4,5,6,7,8,9` over agency + account; remainder 10 gives `0` | +| Citibank | `745` | 4 digits | 10 digits | weights `11..2` over the account; remainder 0 or 1 gives `0` | + +Banks validated by structure only, because they publish no check digit rule. The agency (1-5 digits), the account (1-13 digits) and a single numeric `digit` are enough to make the account valid: + +| Bank | Code | | Bank | Code | +| --- | --- | --- | --- | --- | +| Inter | `077` | | PicPay | `380` | +| Ailos | `085` | | Cora | `403` | +| XP | `102` | | Pan | `623` | +| Unicred | `136` | | BV | `655` | +| Stone | `197` | | Daycoval | `707` | +| BTG Pactual | `208` | | Modal | `746` | +| Original | `212` | | Sicredi | `748` | +| PagBank | `290` | | Sicoob | `756` | +| BMG | `318` | | | | +| Mercado Pago | `323` | | | | +| C6 | `336` | | | | + +When `digit` has 2 characters, the generic fallback chains mod10 followed by mod11 over the account, the same way CPF/CNPJ check digits are chained. + +Sources: the "Regras de Validação de dígito verificador de agência e conta corrente" compendium, cross checked against `banktools-br` (Ruby), `luizalabs/heimdall` (Python) and `Xerpa/bran_checker` (Elixir). Each shipped algorithm agrees on at least two independent sources. + +```javascript +import { isValidBankAccount } from '@brazilian-utils/brazilian-utils'; + +isValidBankAccount({ + bankCode: '001', + agency: '1584', + account: '00210169', + digit: '6' +}); // true (Banco do Brasil) + +isValidBankAccount({ + bankCode: '341', + agency: '2545', + account: '02366', + digit: '1' +}); // true (Itaú) + +isValidBankAccount({ + bankCode: '104', + agency: '0647', + account: '00188888888', + digit: '7' +}); // true (Caixa: operação "001" + conta "88888888") + +isValidBankAccount({ + bankCode: '041', + agency: '2664', + account: '358507670', + digit: '6' +}); // true (Banrisul: tipo "35" + conta "8507670") + +isValidBankAccount({ + bankCode: '260', + agency: '0001', + account: '5216125', + digit: '0' +}); // true (Nubank, Verhoeff) + +isValidBankAccount({ + bankCode: '077', + agency: '0001', + account: '123456789', + digit: '0' +}); // true (Banco Inter, structure only) + +isValidBankAccount({ + bankCode: '077', + agency: '0001', + account: '123456789', + digit: 'X' +}); // false (a structure only bank still requires a numeric digit) + +isValidBankAccount({ + bankCode: '999', + agency: '1234', + account: '123456', + digit: '6' +}); // false (999 is not a Banco Central participant) + +isValidBankAccount({ + bankCode: '246', + agency: '1234', + account: '123456', + digit: '6' +}); // true (Banco ABC Brasil, generic mod10 fallback) +``` + +### getBanks + +Get every Brazilian bank with a compensation code (COMPE), published by Banco Central do Brasil in the [STR participants list](https://www.bcb.gov.br/pom/spb/estatistica/port/ParticipantesSTRport.csv). Each bank (typed as `Bank`) has a `code` (COMPE, 3 digits), an `ispb` (Identificador do Sistema de Pagamentos Brasileiro, 8 digits) and a `name`. Each call returns a fresh array of fresh objects, so mutating the result never affects subsequent calls. + +```javascript +import { getBanks } from '@brazilian-utils/brazilian-utils'; + +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 +// ] +``` + +### getBankByCode + +Look a Brazilian bank up by its compensation code (COMPE), published by Banco Central do Brasil in the [STR participants list](https://www.bcb.gov.br/pom/spb/estatistica/port/ParticipantesSTRport.csv). Accepts both `string` and `number` input, with or without leading zeros. Returns a fresh copy (typed as `Bank`) of the matching bank, or `null` when no bank has that code. + +```javascript +import { getBankByCode } from '@brazilian-utils/brazilian-utils'; + +getBankByCode('001'); // { code: '001', ispb: '00000000', name: 'Banco do Brasil S.A.' } +getBankByCode(1); // { code: '001', ispb: '00000000', name: 'Banco do Brasil S.A.' } +getBankByCode('999'); // null +``` + +### getBankByIspb + +Look a Brazilian bank up by its ISPB (Identificador do Sistema de Pagamentos Brasileiro), the 8 digit code published by Banco Central do Brasil in the [STR participants list](https://www.bcb.gov.br/pom/spb/estatistica/port/ParticipantesSTRport.csv). Unlike the COMPE code (`getBankByCode`), every SPB participant has an ISPB, including institutions with no COMPE code of their own. Accepts both `string` and `number` input, with or without leading zeros. Returns a fresh copy (typed as `Bank`) of the matching bank, or `null` when no bank has that ISPB. + +```javascript +import { getBankByIspb } from '@brazilian-utils/brazilian-utils'; + +getBankByIspb('00000000'); // { code: '001', ispb: '00000000', name: 'Banco do Brasil S.A.' } +getBankByIspb('60701190'); // { code: '341', ispb: '60701190', name: 'ITAÚ UNIBANCO S.A.' } +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. + +```javascript +import { isValidIban } from '@brazilian-utils/brazilian-utils'; + +isValidIban('BR1500000000000010932840814P2'); // true +isValidIban('BR15 0000 0000 0000 1093 2840 814P 2'); // true (grouping spaces) +isValidIban('BR1500000000000010932840814P3'); // false (bad check digits) +isValidIban('DE89370400440532013000'); // false (non Brazilian IBAN) +``` + +### formatIban + +Format a Brazilian IBAN by grouping it in blocks of 4 characters, the ISO 13616 "print" presentation used on statements and bank forms. Does not validate the check digits or the field layout; formats whatever is given, up to the 29 character length of a Brazilian IBAN, as far as it goes, so the function can also be used as an input mask. Use `isValidIban` to check validity. + +```javascript +import { formatIban } from '@brazilian-utils/brazilian-utils'; + +formatIban('BR1500000000000010932840814P2'); // 'BR15 0000 0000 0000 1093 2840 814P 2' +formatIban('br1500000000000010932840814p2'); // 'BR15 0000 0000 0000 1093 2840 814P 2' +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`. + +```javascript +import { parseIban } from '@brazilian-utils/brazilian-utils'; + +parseIban('BR1500000000000010932840814P2'); +// { +// countryCode: 'BR', +// checkDigits: '15', +// bankIspb: '00000000', +// branch: '00001', +// account: '0932840814', +// accountType: 'P', +// owner: '2' +// } + +parseIban('DE89370400440532013000'); // null (non Brazilian IBAN) +``` + +### isValidCreditCard + +Check if a payment card number is valid using the Luhn algorithm ([ISO/IEC 7812-1](https://www.iso.org/standard/70484.html)). Accepts the usual mask characters (spaces, hyphens) between digits. Performs no brand detection (Visa, Mastercard, Amex...), issuer range lookup or expiration/CVV checks, only the digit count (12 to 19) and the Luhn check digit. + +```javascript +import { isValidCreditCard } from '@brazilian-utils/brazilian-utils'; + +isValidCreditCard('4111111111111111'); // true (Visa test number) +isValidCreditCard('5555555555554444'); // true (Mastercard test number) +isValidCreditCard('378282246310005'); // true (American Express test number) +isValidCreditCard('4111 1111 1111 1111'); // true (spaced mask) +isValidCreditCard('4111111111111112'); // false (bad check digit) +``` + +### capitalize + +Transforms the first letter into a capital one of each word ignoring prepositions. Words are separated by whitespace, by `-` and by `/`, so `'MOGI-GUAÇU'` becomes `'Mogi-Guaçu'` and `'SANTANA/RS'` becomes `'Santana/Rs'`. Every run of whitespace (tabs, newlines, repeated spaces) collapses into a single space, and the leading and trailing whitespace is dropped. `options.upperCaseWords` defaults to `[]`, so no acronym is upper-cased unless you list it, and the comparison against both `upperCaseWords` and `lowerCaseWords` is case-insensitive (pt-BR locale). Options are typed as `CapitalizeOptions`. + +```javascript +import { capitalize } from '@brazilian-utils/brazilian-utils'; + +capitalize('josé e maria'); // José e Maria +capitalize('josé Ama MARIA', { lowerCaseWords: ['ama'] }); // José ama Maria +capitalize('doc inválido', { upperCaseWords: ['DOC'] }); // DOC Inválido +capitalize('MOGI-GUAÇU'); // Mogi-guaçu ("-" does not start a new word) +capitalize('SANTANA/RS', { upperCaseWords: ['RS'] }); // Santana/rs ("SANTANA/RS" is a single word, so it doesn't match "RS") +capitalize('empresa ltda'); // Empresa Ltda (no default acronyms) +capitalize('empresa ltda', { upperCaseWords: ['LTDA'] }); // Empresa LTDA (case-insensitive match) +capitalize(' josé maria '); // José Maria (repeated plain spaces collapse; tabs/newlines would not) +``` + +### formatCurrency + +Formats an integer or float to a string in the BRL pattern. A `number` is formatted as-is (sign and decimals preserved). A `string` input is read by the same rule as `parseCurrency`, except that a value written without any separator stays in whole units: the last `,` or `.` followed by 1 to 2 digits is the decimal separator, every other `,` or `.` is a thousands separator, and a `-` written before the first digit is preserved. So `'1.234,56'` formats as `1.234,56`, `'-10.5'` as `-10,50` and `'1234'` as `1.234,00`. `precision` is clamped to `0..20` (the range `Intl.NumberFormat` accepts) and defaults to 2. A value that is not a finite number (`NaN`, `Infinity`, `-Infinity`) formats as an empty string. Options are typed as `FormatCurrencyOptions`. + +```javascript +import { formatCurrency } from '@brazilian-utils/brazilian-utils'; + +formatCurrency(10); // 10,00 +formatCurrency(10756.11); // 10.756,11 +formatCurrency(10756.123, { precision: 3 }); // 10.756,123 +formatCurrency(1234.56, { symbol: true }); // R$ 1.234,56 +formatCurrency(-1050); // -1.050,00 (a number's sign is preserved) +formatCurrency('123456'); // 123.456,00 (a plain digit string is read as a whole number) +formatCurrency('1.234,56'); // 123.456,00 (a string's separators are stripped, not interpreted) +formatCurrency('-10.5'); // 105,00 (a string's sign is discarded) +formatCurrency(Number.NaN); // "NaN" +``` + +### parseCurrency + +Transforms a string to an integer or float format. The last `,` or `.` followed by 1 to 2 digits (or up to `precision` digits, when that is larger) is the decimal separator; every other `,` or `.` is a thousands separator. So `'R$ 1.234,56'` parses to `1234.56`, `'R$ 1.234'` to `1234`, `'1,5'` to `1.5` and `'12.34'` to `12.34`. A value written without any separator keeps the cents convention and is divided by `10 ** precision`, so `'1234'` parses to `12.34`. A `-` written before the first digit is preserved, so `'-R$ 1,00'` parses to `-1`. `precision` (default 2, clamped to `0..20`) controls how many digits are treated as minor units. Options are typed as `ParseCurrencyOptions`. + +```javascript +import { parseCurrency } from '@brazilian-utils/brazilian-utils'; + +parseCurrency('R$ 1.234,56'); // 1234.56 +parseCurrency('1234,56'); // 1234.56 +parseCurrency('R$ 0,50'); // 0.5 +parseCurrency('R$ 1.234'); // 12.34 (separators carry no meaning; only the digit count matters) +parseCurrency('1,5'); // 0.15 +parseCurrency('-R$ 1,00'); // -1 (a leading "-" is preserved) +parseCurrency('R$ 1,001', { precision: 3 }); // 1.001 +parseCurrency(''); // 0 +``` + +### convertNumberToWords + +Formats an integer as its Brazilian Portuguese cardinal number words ("por extenso"), e.g. `1235` becomes `"mil, duzentos e trinta e cinco"`. Only integers from `-999999999999999` to `999999999999999` (999 trillion in absolute value) are supported; anything outside that range, `NaN` or a non-finite value returns `""`. A non-integer `value` is truncated toward zero before conversion. `options.gender` (part of `ConvertNumberToWordsOptions`) agrees "um/dois" and the hundreds group ("duzentos/duzentas", etc.) with the noun the number qualifies, defaulting to `"masculine"`. `options.case` sets the letter case of the result: `"lower"` (default, unchanged), `"sentence"` (capitalizes only the first letter) or `"upper"` (uppercases everything with the "pt-BR" locale, keeping accents, e.g. "três" -> "TRÊS"). An invalid `gender`/`case` value is ignored and the default is used. + +```javascript +import { convertNumberToWords } from '@brazilian-utils/brazilian-utils'; + +convertNumberToWords(123); // "cento e vinte e três" +convertNumberToWords(1001); // "mil e um" +convertNumberToWords(2000000); // "dois milhões" +convertNumberToWords(-42); // "menos quarenta e dois" +convertNumberToWords(2, { gender: 'feminine' }); // "duas" +convertNumberToWords(3, { case: 'upper' }); // "TRÊS" +convertNumberToWords(NaN); // "" +``` + +### convertCurrencyToWords + +Formats a monetary amount in Brazilian Reais as its "por extenso" textual representation, the style used to write out the amount by hand on cheques and contracts, e.g. `1523.45` becomes `"mil, quinhentos e vinte e três reais e quarenta e cinco centavos"`. `value` is truncated (not rounded) to 2 decimal places. The singular noun is used for exactly 1 ("um real", "um centavo") and "de" is inserted before "reais" when the amount is a round million, billion or trillion of reais. An amount that truncates to nothing becomes `"zero reais"` with no "menos" prefix, any other negative amount is prefixed with "menos", and invalid input returns `""`. Above `Number.MAX_SAFE_INTEGER / 100` reais (about 90 trillion) a double cannot carry cents, so the amount is read as a whole number of reais. `options.case` (part of `ConvertCurrencyToWordsOptions`) sets the letter case of the result: `"lower"` (default), `"sentence"` (capitalizes only the first letter) or `"upper"` (uppercases everything, keeping accents). An invalid `case` value is ignored and `"lower"` is used. + +```javascript +import { convertCurrencyToWords } from '@brazilian-utils/brazilian-utils'; + +convertCurrencyToWords(1523.45); // "mil, quinhentos e vinte e três reais e quarenta e cinco centavos" +convertCurrencyToWords(1); // "um real" +convertCurrencyToWords(0.01); // "um centavo" +convertCurrencyToWords(1000000); // "um milhão de reais" +convertCurrencyToWords(0); // "zero reais" +convertCurrencyToWords(-5.5); // "menos cinco reais e cinquenta centavos" +convertCurrencyToWords(-0.001); // "zero reais" (truncates to nothing) +convertCurrencyToWords(1000, { case: 'upper' }); // "MIL REAIS" +``` + +### getStates + +Get all Brazilian states, each with its two-letter code, name, region code, region name and 2-digit IBGE code of the Federative Unit (`cUF`). The list is sorted by name with `localeCompare` in the "pt-BR" locale, so accented names land where a Brazilian reader expects them: Pará, Paraíba, Paraná and Rio de Janeiro, Rio Grande do Norte, Rio Grande do Sul. Each call returns a fresh array of fresh objects, so mutating the result never affects subsequent calls. Exports the `State`, `StateCode` and `StateName` types. + +```javascript +import { getStates } from '@brazilian-utils/brazilian-utils'; + +getStates(); +// [ +// { code: 'AC', name: 'Acre', regionCode: 'N', regionName: 'Norte', ibgeCode: 12 }, +// { code: 'AL', name: 'Alagoas', regionCode: 'NE', regionName: 'Nordeste', ibgeCode: 27 }, +// { code: 'AP', name: 'Amapá', regionCode: 'N', regionName: 'Norte', ibgeCode: 16 }, +// { code: 'AM', name: 'Amazonas', regionCode: 'N', regionName: 'Norte', ibgeCode: 13 }, +// { code: 'BA', name: 'Bahia', regionCode: 'NE', regionName: 'Nordeste', ibgeCode: 29 }, +// { code: 'CE', name: 'Ceará', regionCode: 'NE', regionName: 'Nordeste', ibgeCode: 23 }, +// { code: 'DF', name: 'Distrito Federal', regionCode: 'CO', regionName: 'Centro-Oeste', ibgeCode: 53 }, +// { code: 'ES', name: 'Espírito Santo', regionCode: 'SE', regionName: 'Sudeste', ibgeCode: 32 }, +// { code: 'GO', name: 'Goiás', regionCode: 'CO', regionName: 'Centro-Oeste', ibgeCode: 52 }, +// { code: 'MA', name: 'Maranhão', regionCode: 'NE', regionName: 'Nordeste', ibgeCode: 21 }, +// { code: 'MT', name: 'Mato Grosso', regionCode: 'CO', regionName: 'Centro-Oeste', ibgeCode: 51 }, +// { code: 'MS', name: 'Mato Grosso do Sul', regionCode: 'CO', regionName: 'Centro-Oeste', ibgeCode: 50 }, +// { code: 'MG', name: 'Minas Gerais', regionCode: 'SE', regionName: 'Sudeste', ibgeCode: 31 }, +// { code: 'PA', name: 'Pará', regionCode: 'N', regionName: 'Norte', ibgeCode: 15 }, +// { code: 'PB', name: 'Paraíba', regionCode: 'NE', regionName: 'Nordeste', ibgeCode: 25 }, +// { code: 'PR', name: 'Paraná', regionCode: 'S', regionName: 'Sul', ibgeCode: 41 }, +// { code: 'PE', name: 'Pernambuco', regionCode: 'NE', regionName: 'Nordeste', ibgeCode: 26 }, +// { code: 'PI', name: 'Piauí', regionCode: 'NE', regionName: 'Nordeste', ibgeCode: 22 }, +// { code: 'RJ', name: 'Rio de Janeiro', regionCode: 'SE', regionName: 'Sudeste', ibgeCode: 33 }, +// { code: 'RN', name: 'Rio Grande do Norte', regionCode: 'NE', regionName: 'Nordeste', ibgeCode: 24 }, +// { code: 'RS', name: 'Rio Grande do Sul', regionCode: 'S', regionName: 'Sul', ibgeCode: 43 }, +// { code: 'RO', name: 'Rondônia', regionCode: 'N', regionName: 'Norte', ibgeCode: 11 }, +// { code: 'RR', name: 'Roraima', regionCode: 'N', regionName: 'Norte', ibgeCode: 14 }, +// { code: 'SC', name: 'Santa Catarina', regionCode: 'S', regionName: 'Sul', ibgeCode: 42 }, +// { code: 'SP', name: 'São Paulo', regionCode: 'SE', regionName: 'Sudeste', ibgeCode: 35 }, +// { code: 'SE', name: 'Sergipe', regionCode: 'NE', regionName: 'Nordeste', ibgeCode: 28 }, +// { code: 'TO', name: 'Tocantins', regionCode: 'N', regionName: 'Norte', ibgeCode: 17 }, +// ] +``` + +### getStateByIbgeCode + +Get the Brazilian state whose 2-digit IBGE code ("cUF", the Código da Unidade da Federação) matches the given value. This is the same 2-digit UF code found in the first field of every DF-e access key (chave de acesso) issued for NF-e, NFC-e, CT-e and MDF-e documents. Accepts a string or a number, stripping any non-digit characters before matching. Exports the `State` type. + +```javascript +import { getStateByIbgeCode } from '@brazilian-utils/brazilian-utils'; + +getStateByIbgeCode('35'); +// { code: 'SP', name: 'São Paulo', regionCode: 'SE', regionName: 'Sudeste', ibgeCode: 35 } + +getStateByIbgeCode(11); +// { code: 'RO', name: 'Rondônia', regionCode: 'N', regionName: 'Norte', ibgeCode: 11 } + +getStateByIbgeCode('00'); // null +``` + +### getStateCodeByName + +Get the two-letter code (sigla) of a Brazilian state given its full name. The match is accent-insensitive, case-insensitive and ignores leading/trailing whitespace, so `'sao paulo'`, `'SÃO PAULO'` and `' São Paulo '` all resolve to `'SP'`. Exports the `StateCode` type. + +```javascript +import { getStateCodeByName } from '@brazilian-utils/brazilian-utils'; + +getStateCodeByName('São Paulo'); // 'SP' +getStateCodeByName('sao paulo'); // 'SP' +getStateCodeByName(' Rio de Janeiro '); // 'RJ' +getStateCodeByName('Neverland'); // null +``` + +### getStateNameByCode + +Get the full name of a Brazilian state given its two-letter code (sigla). The match is case-insensitive and ignores leading/trailing whitespace, so `'sp'`, `'SP'` and `' Sp '` all resolve to `'São Paulo'`. Exports the `StateName` type. + +```javascript +import { getStateNameByCode } from '@brazilian-utils/brazilian-utils'; + +getStateNameByCode('SP'); // 'São Paulo' +getStateNameByCode('sp'); // 'São Paulo' +getStateNameByCode(' Rj '); // 'Rio de Janeiro' +getStateNameByCode('ZZ'); // null +``` + +### getTimezoneByState + +Get the IANA time zone database name (tzdata zone) for a Brazilian state, chosen as the zone of the state capital. The match is case-insensitive and ignores leading/trailing whitespace. 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. + +```javascript +import { getTimezoneByState } from '@brazilian-utils/brazilian-utils'; + +getTimezoneByState('SP'); // 'America/Sao_Paulo' +getTimezoneByState('am'); // 'America/Manaus' +getTimezoneByState('AC'); // 'America/Rio_Branco' +getTimezoneByState('PE'); // 'America/Recife' +getTimezoneByState('ZZ'); // null +``` + +### getCities + +Get Brazilian cities. Returns all cities if no state is provided, or cities from a specific state. Each call returns a fresh array, so mutating the result never affects subsequent calls. An unknown state code (or a non-`StateCode` value) returns an empty array instead of throwing. + +```javascript +import { getCities } from '@brazilian-utils/brazilian-utils'; + +// Return all Brazilian cities (sorted alphabetically). +getCities(); +// [ +// 'Abadia de Goiás', +// 'Abadia dos Dourados', +// 'Abadiânia', +// 'Abaeté', +// 'Abaetetuba', +// 'Abaiara', +// 'Abaíra', +// 'Abaré', +// 'Abatiá', +// 'Abdon Batista', +// ... 5561 more items +// ] + +// Return all Brazilian cities of the São Paulo state (sorted alphabetically). +getCities('SP'); +// [ +// "Adamantina", +// "Adolfo", +// "Aguaí", +// "Águas da Prata", +// "Águas de Lindóia", +// "Águas de Santa Bárbara", +// "Águas de São Pedro", +// "Agudos", +// "Alambari", +// "Alfredo Marcondes", +// ... 635 more items +// ] +``` + +`getCities` embeds all 5571 IBGE municipality names (~153 KB minified, ~49 KB gzipped) and is one of the few heavy exceptions in this otherwise tree-shakeable package. See [Bundle size](getting-started.md#bundle-size) for how to lazy-load it via `@brazilian-utils/brazilian-utils/get-cities` instead of the root import. + +### getHolidays + +Get Brazilian holidays for a given year. Returns national holidays and optionally state-specific holidays. Each holiday (typed as `Holiday`) has a `type` field (`HolidayType`: `"national"`, `"state"`, `"optional"` or `"religious"`). "Dia da Consciência Negra" (Nov 20) is a national holiday from 2024 onward (Lei nº 14.759/2023). Before that, MT and RJ still carry their own state-level entry named `"Consciência Negra"` on the same date. Results are memoized per `year`/`stateCode`, but each call still returns a fresh copy. An unknown/invalid `stateCode` is ignored, returning national holidays only. + +```javascript +import { getHolidays } from '@brazilian-utils/brazilian-utils'; + +// Get all national holidays for 2024 +getHolidays(2024); +// [ +// { name: 'Ano novo', date: Date('2024-01-01'), type: 'national' }, +// { name: 'Carnaval (terça-feira)', date: Date('2024-02-13'), type: 'optional' }, +// { name: 'Sexta-feira Santa', date: Date('2024-03-29'), type: 'national' }, +// { name: 'Páscoa', date: Date('2024-03-31'), type: 'religious' }, +// { name: 'Dia da Consciência Negra', date: Date('2024-11-20'), type: 'national' }, +// // ... more holidays +// ] + +// Get holidays for a specific state +getHolidays({ year: 2024, stateCode: 'SP' }); +// Includes national holidays plus state-specific holidays (e.g., "Revolução Constitucionalista") +``` + +### 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. + +```javascript +import { isValidPassport } from '@brazilian-utils/brazilian-utils'; + +isValidPassport('AB123456'); // true +isValidPassport('ab123456'); // true (case-insensitive) +isValidPassport('AB-123.456'); // true (symbols are ignored) +isValidPassport('12345678'); // false +``` + +### formatPassport + +Format a Brazilian passport number (uppercase, without symbols, capped to 8 characters). + +```javascript +import { formatPassport } from '@brazilian-utils/brazilian-utils'; + +formatPassport('ab123456'); // 'AB123456' +formatPassport('AB-123.456'); // 'AB123456' +``` + +### generatePassport + +Generate a random valid Brazilian passport number. + +```javascript +import { generatePassport } from '@brazilian-utils/brazilian-utils'; + +generatePassport(); // 'RY393097' +``` + +### parsePassport + +Remove all non-alphanumeric characters from a passport number, uppercase the result, and cap it to 8 characters. + +```javascript +import { parsePassport } from '@brazilian-utils/brazilian-utils'; + +parsePassport('AB-123.456'); // 'AB123456' +parsePassport(' AB 123 456 '); // 'AB123456' +``` + +### generateCep + +Generate a random CEP. + +```javascript +import { generateCep } from '@brazilian-utils/brazilian-utils'; + +generateCep(); // '92500000' +``` + +### formatCnh + +Format CNH. + +```javascript +import { formatCnh } from '@brazilian-utils/brazilian-utils'; + +formatCnh('02650306461'); // 026503064-61 +formatCnh('2650306461', { pad: true }); // 026503064-61 +``` + +### isValidCnh + +Check if CNH is valid. + +```javascript +import { isValidCnh } from '@brazilian-utils/brazilian-utils'; + +isValidCnh('00000000119'); // true +``` + +### generateCnh + +Generate a valid random CNH. + +```javascript +import { generateCnh } from '@brazilian-utils/brazilian-utils'; + +generateCnh(); // '02650306461' +``` + +### parseCnh + +Remove CNH formatting, keep only digits, and cap the result to 11 digits. + +```javascript +import { parseCnh } from '@brazilian-utils/brazilian-utils'; + +parseCnh('026503064-61'); // '02650306461' +``` + +### getCepInfoByAddress + +Fetch CEPs from an address using ViaCEP. Throws `GetCepInfoByAddressValidationError` when the UF, city or street is missing/invalid, `GetCepInfoByAddressNotFoundError` when no address matches the query, and `GetCepInfoByAddressError` when ViaCEP itself answers with an HTTP error status. A request that cannot be performed at all (a transport failure) rejects with the underlying `fetch` error instead. + +```javascript +import { getCepInfoByAddress } from '@brazilian-utils/brazilian-utils'; + +const ceps = await getCepInfoByAddress({ + federalUnit: 'SP', + city: 'Sao Paulo', + street: 'Avenida Paulista' +}); + +// [ +// { +// cep: '01310100', +// logradouro: 'Avenida Paulista', +// complemento: 'lado par', +// bairro: 'Bela Vista', +// localidade: 'São Paulo', +// uf: 'SP' +// } +// ] +``` + +### generateProcessoJuridico + +Generate a valid random processo jurídico number according to [CNJ's definition](https://www.conjur.com.br/dl/resolucao-65-cnj.pdf). `year` must be between the current year and 9999, `court` between 1 and 9; out-of-range values return `null`. Uses `Math.random()` internally, so it is not cryptographically secure. + +```javascript +import { generateProcessoJuridico } from '@brazilian-utils/brazilian-utils'; + +generateProcessoJuridico(); // '00020802520125150049' +generateProcessoJuridico({ year: 2026, court: 5 }); // string | null +generateProcessoJuridico({ year: 10000 }); // null (year out of range) +``` + +### formatLegalNature + +Format a legal nature code. + +```javascript +import { formatLegalNature } from '@brazilian-utils/brazilian-utils'; + +formatLegalNature('2062'); // 206-2 +``` + +### isValidLegalNature + +Check if a legal nature code exists in the official list. The table follows IBGE/CONCLA's "Natureza Jurídica 2021": 92 official codes plus 8 legacy codes kept for backwards compatibility. Only the usual mask characters (hyphens, dots, whitespace) are tolerated around the 4 digits, so `'2062a'` is rejected instead of being read as `'2062'`. + +```javascript +import { isValidLegalNature } from '@brazilian-utils/brazilian-utils'; + +isValidLegalNature('2062'); // true +isValidLegalNature('9999'); // false +``` + +### generateLegalNature + +Generate a random valid legal nature code. + +```javascript +import { generateLegalNature } from '@brazilian-utils/brazilian-utils'; + +generateLegalNature(); // '2062' +``` + +### parseLegalNature + +Remove legal nature formatting, keep only digits, and cap the result to 4 digits. + +```javascript +import { parseLegalNature } from '@brazilian-utils/brazilian-utils'; + +parseLegalNature('206-2'); // '2062' +``` + +### getLegalNatures + +Get the legal nature map keyed by code. + +```javascript +import { getLegalNatures } from '@brazilian-utils/brazilian-utils'; + +const legalNatures = getLegalNatures(); + +legalNatures['2062']; // 'Sociedade Empresária Limitada' +``` + +### getLegalNature + +Look a legal nature code up in the official IBGE/CONCLA table. + +```javascript +import { getLegalNature } from '@brazilian-utils/brazilian-utils'; + +getLegalNature('2062'); // { code: '2062', description: 'Sociedade Empresária Limitada' } +getLegalNature('0000'); // null +``` + +### generatePhone + +Generate a random Brazilian phone number. Accepts `'mobile'`, `'landline'` or `'service'` (typed as `GeneratePhoneType`); a service number has no DDD. Omitted, it randomly generates a mobile or a landline, never a service number. + +```javascript +import { generatePhone } from '@brazilian-utils/brazilian-utils'; + +generatePhone(); // '11912345678' or '1131234567' +generatePhone('mobile'); // '11912345678' +generatePhone('landline'); // '1131234567' +generatePhone('service'); // '08001234567' or '40041234' +``` + +### formatLicensePlate + +Format a license plate. Old Brazilian plates (`LLLNNNN`) are returned with a hyphen and Mercosul plates (`LLLNLNN`) stay normalized. + +```javascript +import { formatLicensePlate } from '@brazilian-utils/brazilian-utils'; + +formatLicensePlate('abc1234'); // 'ABC-1234' +formatLicensePlate('abc1d23'); // 'ABC1D23' +``` + +### generateLicensePlate + +Generate a random license plate in the chosen format. + +```javascript +import { generateLicensePlate } from '@brazilian-utils/brazilian-utils'; + +generateLicensePlate(); // 'ABC1D23' (Mercosul, the default) +generateLicensePlate('LLLNNNN'); // 'ABC1234' +``` + +### getFormatLicensePlate + +Detect the normalized format of a license plate. + +```javascript +import { getFormatLicensePlate } from '@brazilian-utils/brazilian-utils'; + +getFormatLicensePlate('ABC-1234'); // 'LLLNNNN' +getFormatLicensePlate('ABC1D23'); // 'LLLNLNN' +getFormatLicensePlate('ABC12D3'); // null (not a Mercosul sequence) +getFormatLicensePlate('INVALID'); // null +getFormatLicensePlate('ABC1234EXTRA'); // null (too many characters) +``` + +`getFormatLicensePlate` exports the `LicensePlateFormat` type (`"LLLNNNN" | "LLLNLNN"`); `generateLicensePlate` re-exports it as `GenerateLicensePlateFormat`. + +### parseLicensePlate + +Remove separators from a license plate, normalize it to uppercase, and cap it to 7 characters. + +```javascript +import { parseLicensePlate } from '@brazilian-utils/brazilian-utils'; + +parseLicensePlate('abc-1234'); // 'ABC1234' +``` + +### convertLicensePlateToMercosul + +Convert an old format Brazilian license plate (`LLLNNNN`) to the Mercosul format (`LLLNLNN`), following the official conversion table: the digit in the 5th position becomes a letter (`0` through `9` mapping to `A` through `J`). Returns `""` when the value is not a valid old format license plate. + +```javascript +import { convertLicensePlateToMercosul } from '@brazilian-utils/brazilian-utils'; + +convertLicensePlateToMercosul('ABC1234'); // 'ABC1C34' +convertLicensePlateToMercosul('abc-1234'); // 'ABC1C34' +convertLicensePlateToMercosul('ABC1D23'); // '' (already Mercosul) +``` + +### generatePis + +Generate a valid random PIS. + +```javascript +import { generatePis } from '@brazilian-utils/brazilian-utils'; + +generatePis(); // '12345678901' +``` + +### 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`. + +```javascript +import { getMunicipality } from '@brazilian-utils/brazilian-utils'; + +await getMunicipality({ code: '3550308' }); +// ['São Paulo', 'SP'] + +await getMunicipality({ municipalityName: 'sao paulo', uf: 'sp' }); +// '3550308' + +await getMunicipality({ code: '0000000' }); +// null (unknown code) + +await getMunicipality({ code: '123' }); +// null (not 7 digits) +``` + +### getMunicipalities + +Get Brazilian municipalities published by the IBGE. Returns all municipalities if no state is provided, or municipalities from a specific state. Each municipality is returned as `{ code, name, stateCode }`, where `code` is the 7-digit IBGE municipality code. Results are sorted by name with `localeCompare` in the "pt-BR" locale. Each call returns a fresh array of fresh objects, so mutating the result never affects subsequent calls. An unknown state code returns an empty array instead of throwing. + +```javascript +import { getMunicipalities } from '@brazilian-utils/brazilian-utils'; + +// Return every Brazilian municipality (sorted by name). +getMunicipalities(); +// [ +// { code: '5200050', name: 'Abadia de Goiás', stateCode: 'GO' }, +// { code: '3100104', name: 'Abadia dos Dourados', stateCode: 'MG' }, +// { code: '5200100', name: 'Abadiânia', stateCode: 'GO' }, +// { code: '3100203', name: 'Abaeté', stateCode: 'MG' }, +// { code: '1500107', name: 'Abaetetuba', stateCode: 'PA' }, +// ... 5566 more items +// ] + +// Return every municipality of the São Paulo state. +getMunicipalities('SP'); +// [ +// { code: '3500105', name: 'Adamantina', stateCode: 'SP' }, +// { code: '3500204', name: 'Adolfo', stateCode: 'SP' }, +// { code: '3500303', name: 'Aguaí', stateCode: 'SP' }, +// { code: '3500402', name: 'Águas da Prata', stateCode: 'SP' }, +// { code: '3500501', name: 'Águas de Lindóia', stateCode: 'SP' }, +// ... 640 more items +// ] + +getMunicipalities('ZZ'); // [] +``` + +`getMunicipalities` embeds all 5571 IBGE municipalities and their codes, so it carries the same bundle-size cost as `getCities`. See [Bundle size](getting-started.md#bundle-size) for how to lazy-load it via `@brazilian-utils/brazilian-utils/get-municipalities` instead of the root import. + +### getMunicipalityByCode + +Look up a Brazilian municipality by its 7-digit IBGE code. Accepts the code as a string or a number, with any non-digit characters stripped before matching. Returns `{ code, name, stateCode }`, a fresh object, or `null` when the code is not 7 digits long or does not match any known municipality. + +```javascript +import { getMunicipalityByCode } from '@brazilian-utils/brazilian-utils'; + +getMunicipalityByCode('3550308'); +// { code: '3550308', name: 'São Paulo', stateCode: 'SP' } + +getMunicipalityByCode(3550308); +// { code: '3550308', name: 'São Paulo', stateCode: 'SP' } + +getMunicipalityByCode('0000000'); // null (unknown code) +getMunicipalityByCode('123'); // null (not 7 digits) +``` + +### isHoliday + +Check if a specific date is a Brazilian holiday. The check compares `targetDate`'s local calendar date (year/month/day as read locally), not its underlying UTC instant. Returns `false` when `targetDate` is missing or not a valid `Date`. + +```javascript +import { isHoliday } from '@brazilian-utils/brazilian-utils'; + +isHoliday({ targetDate: new Date(2024, 0, 1) }); // true +isHoliday({ targetDate: new Date(2024, 6, 9), stateCode: 'SP' }); // true +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`. + +```javascript +import { isBusinessDay } from '@brazilian-utils/brazilian-utils'; + +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, counts 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) +isBusinessDay(new Date('not a date')); // false +``` + +### addBusinessDays + +Add a number of Brazilian business days (dias úteis) to a date, skipping Saturdays, Sundays and Brazilian holidays exactly as `isBusinessDay` defines them (same `stateCode`/`includeOptional` options). Returns a new `Date`; the input `date` (part of `AddBusinessDaysParams`) is never mutated, and its time-of-day is preserved in the result. `days: 0` returns a new `Date` equal to `date`, unchanged, even when `date` itself falls on a weekend or holiday, this mirrors the verified behavior of [date-fns' `addBusinessDays(date, 0)`](https://date-fns.org/docs/addBusinessDays), which also does not roll the input to the next business day. A negative `days` walks backwards, one business day at a time, also like date-fns. Returns `null` on bad input: a `date` that is not a valid `Date`, a `days` that is not a finite integer, or a `stateCode` that is not a string. + +```javascript +import { addBusinessDays } from '@brazilian-utils/brazilian-utils'; + +addBusinessDays({ date: new Date(2024, 0, 2, 12), days: 1 }); // Date, 2024-01-03 12:00 (next day is already a business day) +addBusinessDays({ date: new Date(2024, 11, 31, 12), days: 1 }); // Date, 2025-01-02 12:00 (2025-01-01 is Ano novo, skipped) +addBusinessDays({ date: new Date(2024, 0, 5, 12), days: -1 }); // Date, 2024-01-04 12:00 (walks backwards) +addBusinessDays({ date: new Date(2024, 0, 6, 12), days: 0 }); // Date, 2024-01-06 12:00 (unchanged, even though Saturday is not a business day) +addBusinessDays({ date: new Date(2024, 6, 8, 12), days: 1, stateCode: 'SP' }); // Date, 2024-07-10 12:00 (2024-07-09 is Revolução Constitucionalista in SP, skipped) +addBusinessDays({ date: new Date('not a date'), days: 1 }); // null +addBusinessDays({ date: new Date(2024, 0, 2), days: 1.5 }); // null (not an integer) +``` + +### differenceInBusinessDays + +Count the number of Brazilian business days (dias úteis) between two dates, mirroring the semantics of [date-fns' `differenceInBusinessDays`](https://date-fns.org/docs/differenceInBusinessDays) (verified against its source): `params.from` is counted when it is itself a business day, `params.to` is never counted, and every business day strictly in between is counted once. Only the calendar day of each `Date` matters, the time of day is ignored. Business days are determined exactly like `isBusinessDay` (same `stateCode`/`includeOptional` options). `from`/`to` on the same calendar day return `0`; a `to` before `from` returns a negative number. Returns `null` on bad input: a `from`/`to` that is not a valid `Date`, or a `stateCode` that is not a string. Parameters are typed as `DifferenceInBusinessDaysParams`. + +```javascript +import { differenceInBusinessDays } from '@brazilian-utils/brazilian-utils'; + +differenceInBusinessDays({ from: new Date(2024, 0, 1), to: new Date(2024, 0, 2) }); // 0 (Jan 1 is Ano novo) +differenceInBusinessDays({ from: new Date(2024, 0, 2), to: new Date(2024, 0, 3) }); // 1 (Jan 2 counted, a Tuesday) +differenceInBusinessDays({ from: new Date(2024, 0, 3), to: new Date(2024, 0, 2) }); // -1 (to before from) +differenceInBusinessDays({ from: new Date(2024, 0, 2), to: new Date(2024, 0, 2) }); // 0 (same day) +differenceInBusinessDays({ from: new Date(2024, 6, 8), to: new Date(2024, 6, 10), stateCode: 'SP' }); // 1 (2024-07-09 is a state holiday in SP) +differenceInBusinessDays({ from: new Date('not a date'), to: new Date() }); // null +``` + +### convertDateToWords + +Formats a date as its Brazilian Portuguese "por extenso" textual representation, e.g. `"01/01/2024"` becomes `"primeiro de janeiro de dois mil e vinte e quatro"`. Accepts a `Date` (read by its local calendar date, the same convention used by `isHoliday`) or a string in `"dd/mm/yyyy"` or ISO `"yyyy-mm-dd"` format. With the default `options.style` of `"full"`, day 1 is written as "primeiro" and every other day uses the cardinal number; with `"month"`, only the month name is spelled out and the day/year are left as digits (day 1 as `"1º"`, e.g. `"2 de março de 2024"`, `"1º de janeiro de 2024"`). Month names are lowercase. In `"full"` style the year is written out as a cardinal number without the thousands comma that `convertNumberToWords`/`convertCurrencyToWords` use (`1999` reads as `"mil novecentos e noventa e nove"`, not `"mil, novecentos e noventa e nove"`), matching how a date is read aloud. `options.weekday` (default `false`) prefixes the pt-BR weekday name in lowercase followed by a comma (`"sábado, dois de março de dois mil e vinte e quatro"`), computed from the resolved calendar date. `options.case` sets the letter case of the whole result: `"lower"` (default), `"sentence"` (capitalizes only the first letter) or `"upper"` (uppercases everything, keeping accents). Invalid `case`/`style` values are ignored and the default is used; the previous `capitalize` boolean option has been removed in favor of `case: "sentence"`. February 29th is accepted on the leap years of the proleptic Gregorian calendar (divisible by 4, except centuries not divisible by 400). Returns `""` for an invalid `Date`, a malformed string, a day/month that does not exist, or a date before year 1. + +```javascript +import { convertDateToWords } from '@brazilian-utils/brazilian-utils'; + +convertDateToWords('01/01/2024'); // "primeiro de janeiro de dois mil e vinte e quatro" +convertDateToWords('2024-01-02'); // "dois de janeiro de dois mil e vinte e quatro" +convertDateToWords(new Date(2024, 0, 1)); // "primeiro de janeiro de dois mil e vinte e quatro" +convertDateToWords('01/01/2024', { case: 'sentence' }); // "Primeiro de janeiro de dois mil e vinte e quatro" +convertDateToWords('02/03/2024', { style: 'month' }); // "2 de março de 2024" +convertDateToWords('01/01/2024', { style: 'month' }); // "1º de janeiro de 2024" +convertDateToWords('02/03/2024', { weekday: true }); // "sábado, dois de março de dois mil e vinte e quatro" +convertDateToWords('10/05/1999'); // "dez de maio de mil novecentos e noventa e nove" +convertDateToWords('31/04/2024'); // "" (April has 30 days) +convertDateToWords('invalid'); // "" +convertDateToWords('29/02/1900'); // "" (1900 is not a leap year) +``` + +### formatVoterId + +Format a voter ID number. Uses the 12-digit grouping `0000 0000 00 00` by default; when the sanitized value has 13 digits (São Paulo/Minas Gerais voter ids may have a 9-digit sequential number) the `0000 0000 0 00 00` grouping is used instead. + +```javascript +import { formatVoterId } from '@brazilian-utils/brazilian-utils'; + +formatVoterId('123456780175'); // '1234 5678 01 75' +formatVoterId('1234567880191'); // '1234 5678 8 01 91' (13-digit SP/MG voter id) +``` + +### isValidVoterId + +Check if a voter ID number is valid. Accepts both the standard 12-digit id and the 13-digit id issued by São Paulo (UF `01`) and Minas Gerais (UF `02`). + +```javascript +import { generateVoterId, isValidVoterId } from '@brazilian-utils/brazilian-utils'; + +const voterId = generateVoterId('SP'); + +isValidVoterId(voterId); // true +``` + +### generateVoterId + +Generate a valid random voter ID number. You can optionally provide a state code; an unknown state code falls back to `"ZZ"` (issued abroad) instead of throwing. Uses `Math.random()` internally, so it is not cryptographically secure. + +```javascript +import { generateVoterId } from '@brazilian-utils/brazilian-utils'; + +generateVoterId(); // valid random voter ID (abroad, "ZZ") +generateVoterId('SP'); // valid random voter ID for Sao Paulo +generateVoterId('XX'); // falls back to "ZZ" instead of throwing +``` + +### 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). + +```javascript +import { parseVoterId } from '@brazilian-utils/brazilian-utils'; + +parseVoterId('1234 5678 01 75'); // '123456780175' +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. + +```javascript +import { isValidCns } from '@brazilian-utils/brazilian-utils'; + +isValidCns('123456789010001'); // true (definitive) +isValidCns('700000000000005'); // true (provisional) +isValidCns('12345678901'); // false (wrong length) +``` + +### 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. Options are typed as `FormatCnsOptions`. + +```javascript +import { formatCns } from '@brazilian-utils/brazilian-utils'; + +formatCns('123456789010001'); // '123 4567 8901 0001' +formatCns(123456789010001); // '123 4567 8901 0001' +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 46/2015, 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). + +`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. + +```javascript +import { isValidCertidao } from '@brazilian-utils/brazilian-utils'; + +isValidCertidao('104539 01 55 2013 1 00012 021 0000123 21'); // true +isValidCertidao('09430001552010100020112000012087'); // true +isValidCertidao('104539 01 55 2013 1 00012 021 0000123 22'); // false (invalid check digits) +isValidCertidao('123456'); // false (wrong length) +isValidCertidao('104539 01 55 2013 1 00012 021 0000123 21', { accept: ['birth'] }); // true +isValidCertidao('104539 01 55 2013 1 00012 021 0000123 21', { accept: ['death'] }); // false +``` + +### 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 46/2015, as listed by [ghiorzi.org](http://ghiorzi.org/DVnew.htm). + +```javascript +import { parseCertidao } from '@brazilian-utils/brazilian-utils'; + +parseCertidao('104539 01 55 2013 1 00012 021 0000123 21'); +// { +// registryCns: '104539', +// acervo: '01', +// service: '55', +// year: 2013, +// type: 'birth', +// typeCode: 1, +// book: '00012', +// page: '021', +// term: '0000123', +// checkDigits: '21' +// } + +parseCertidao('invalid'); // null +``` + +The `Certidao` result carries: + +| Key | Description | +| --- | --- | +| `registryCns` | The 6 digit CNS (Código Nacional de Serventia) of the serventia that issued the act. | +| `acervo` | Acervo the book belongs to: `"01"` the serventia's own, `"02"` a collection it absorbed. | +| `service` | Service rendered by the serventia, `"55"` for registro civil das pessoas naturais. | +| `year` | Four digit year the act was recorded. | +| `type` | The book the act belongs to: `"birth"`, `"marriage"`, `"religious-marriage"`, `"death"`, `"stillbirth"`, `"banns"`, `"other"`, `"emancipation"` or `"interdiction"`. | +| `typeCode` | Raw book code, 1 to 9, as printed in the fifteenth position of the matrícula. | +| `book` | The 5 digit book (livro) number, zero padded. | +| `page` | The 3 digit page (folha) number, zero padded. | +| `term` | The 7 digit term (termo) number, zero padded. | +| `checkDigits` | The 2 modulus 11 check digits of the matrícula. | + +### 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 46/2015. + +```javascript +import { formatCertidao } from '@brazilian-utils/brazilian-utils'; + +formatCertidao('10453901552013100012021000012321'); // 104539 01 55 2013 1 00012 021 0000123 21 +formatCertidao('104539.01.55.2013.1.00012.021.0000123-21'); // 104539 01 55 2013 1 00012 021 0000123 21 +formatCertidao('1552010100020112000012087', { pad: true }); // 000000 01 55 2010 1 00020 112 0000120 87 +``` + +### 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. + +```javascript +import { isValidCei } from '@brazilian-utils/brazilian-utils'; + +isValidCei('11.583.00249/85'); // true +isValidCei('277297118187'); // true +isValidCei(249859674386); // true +isValidCei('24.985.96743/68'); // false (invalid check digit) +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. + +```javascript +import { formatCei } from '@brazilian-utils/brazilian-utils'; + +formatCei('277297118187'); // 27.729.71181/87 +formatCei(249859674386); // 24.985.96743/86 +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. + +```javascript +import { isValidCno } from '@brazilian-utils/brazilian-utils'; + +isValidCno('11.084.01680/62'); // true +isValidCno('111130137368'); // true +isValidCno(401800097960); // true +isValidCno('110840168063'); // false (invalid check digit) +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. + +```javascript +import { formatCno } from '@brazilian-utils/brazilian-utils'; + +formatCno('111130137368'); // 11.113.01373/68 +formatCno(401800097960); // 40.180.00979/60 +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). + +```javascript +import { isValidCaepf } from '@brazilian-utils/brazilian-utils'; + +isValidCaepf('293.118.610/001-84'); // true +isValidCaepf('41142260000101'); // true +isValidCaepf(29311861000184); // true +isValidCaepf('29311861000185'); // false (invalid check digits) +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. + +```javascript +import { formatCaepf } from '@brazilian-utils/brazilian-utils'; + +formatCaepf('29311861000184'); // 293.118.610/001-84 +formatCaepf(41142260000101); // 411.422.600/001-01 +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). + +```javascript +import { isValidRegistroProfissional } from '@brazilian-utils/brazilian-utils'; + +isValidRegistroProfissional('123456/SP', { council: 'OAB' }); // true +isValidRegistroProfissional('123456-RJ', { council: 'OAB', stateCode: 'SP' }); // false (UF mismatch) +isValidRegistroProfissional('06/12345', { council: 'CRP' }); // true +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. + +```javascript +import { isValidVin } from '@brazilian-utils/brazilian-utils'; + +isValidVin('1HGCM82633A004352'); // true +isValidVin('1m8gdm9axkp042788'); // true (check digit X, lowercase) +isValidVin('1HGCM82633A004353'); // false (bad check digit) +isValidVin('1HGCM8263IA004352'); // false (contains the excluded letter I) +``` + +### isValidCbo + +Check if a CBO (Classificação Brasileira de Ocupações) code exists in the MTE occupation table. Accepts the code with or without the hyphen mask, or as a number. + +```javascript +import { isValidCbo } from '@brazilian-utils/brazilian-utils'; + +isValidCbo('2124-05'); // true +isValidCbo('212405'); // true +isValidCbo(212405); // true +isValidCbo('000000'); // false +``` + +The occupation titles come from the [official CBO 2002 tables published by the MTE](http://www.mtecbo.gov.br/cbosite/pages/downloads.jsf). + +### getCbo + +Look a CBO (Classificação Brasileira de Ocupações) code up and get its official occupation title. + +```javascript +import { getCbo } from '@brazilian-utils/brazilian-utils'; + +getCbo('2124-05'); // { code: '212405', title: 'Analista de desenvolvimento de sistemas' } +getCbo('000000'); // null +``` + +The occupation titles come from the [official CBO 2002 tables published by the MTE](http://www.mtecbo.gov.br/cbosite/pages/downloads.jsf). + +### isValidCnae + +Check if a CNAE (Classificação Nacional de Atividades Econômicas) subclass code exists in the CNAE 2.3 table published by IBGE. Accepts the code with or without the `NNNN-N/NN` mask, or as a number. + +```javascript +import { isValidCnae } from '@brazilian-utils/brazilian-utils'; + +isValidCnae('6201-5/01'); // true +isValidCnae('6201501'); // true +isValidCnae('0000000'); // false +``` + +### formatCnae + +Format a CNAE (Classificação Nacional de Atividades Econômicas) subclass code. + +```javascript +import { formatCnae } from '@brazilian-utils/brazilian-utils'; + +formatCnae('6201501'); // 6201-5/01 +``` + +### getCnae + +Look a CNAE (Classificação Nacional de Atividades Econômicas) subclass code up and get its formatted code and official description. + +```javascript +import { getCnae } from '@brazilian-utils/brazilian-utils'; + +getCnae('6201501'); // { code: '6201-5/01', description: 'DESENVOLVIMENTO DE PROGRAMAS DE COMPUTADOR SOB ENCOMENDA' } +getCnae('0000000'); // null +``` + +### isValidNcm + +Check if an NCM (Nomenclatura Comum do Mercosul) code exists in the current table published by Siscomex/MDIC. Accepts the code with or without the dotted mask, or as a number. + +```javascript +import { isValidNcm } from '@brazilian-utils/brazilian-utils'; + +isValidNcm('8471.30.12'); // true +isValidNcm('84713012'); // true +isValidNcm('00000000'); // false +``` + +### formatNcm + +Format an NCM (Nomenclatura Comum do Mercosul) code. + +```javascript +import { formatNcm } from '@brazilian-utils/brazilian-utils'; + +formatNcm('84713012'); // 8471.30.12 +``` + +### isValidCfop + +Check if a CFOP (Código Fiscal de Operações e Prestações) code exists in the official table (Ajuste SINIEF 07/2001 and updates). + +```javascript +import { isValidCfop } from '@brazilian-utils/brazilian-utils'; + +isValidCfop('5102'); // true +isValidCfop('0000'); // false +``` + +### getCfop + +Look a CFOP (Código Fiscal de Operações e Prestações) code up and get its code and official description. + +```javascript +import { getCfop } from '@brazilian-utils/brazilian-utils'; + +getCfop('5102'); // { code: '5102', description: 'Venda de mercadoria adquirida ou recebida de terceiros' } +getCfop('0000'); // null +``` + +### isValidCst + +Check if a CST (Código de Situação Tributária) code is valid for a given tax. Pass the tax through `options.tax`: + +| Tax | Format | Accepted codes | +| --- | --- | --- | +| `icms` | 3 digits (origem + CST) | origem `0`-`8` + one of `00`, `10`, `20`, `30`, `40`, `41`, `50`, `51`, `60`, `70`, `90` | +| `ipi` | 2 digits | `00`, `01`, `02`, `03`, `04`, `05`, `49`, `50`, `51`, `52`, `53`, `54`, `55`, `99` | +| `pis` | 2 digits | `01`-`09`, `49`, `50`-`56`, `60`-`67`, `70`-`75`, `98`, `99` | +| `cofins` | 2 digits | same table as `pis` | + +`options.tax` (part of `IsValidCstOptions`) is optional: omit it to accept a code that exists in any one of the four tables above. + +```javascript +import { isValidCst } from '@brazilian-utils/brazilian-utils'; + +isValidCst('000', { tax: 'icms' }); // true +isValidCst('110', { tax: 'icms' }); // true +isValidCst('06', { tax: 'pis' }); // true +isValidCst('99', { tax: 'ipi' }); // true +isValidCst('110'); // true (found in the icms table, tax omitted) +isValidCst('999'); // false (not in any table) +``` + +### isValidCsosn + +Check if a CSOSN (Código de Situação da Operação no Simples Nacional) code is one of the 10 codes defined by Convênio ICMS 92/2015: `101`, `102`, `103`, `201`, `202`, `203`, `300`, `400`, `500` or `900`. + +```javascript +import { isValidCsosn } from '@brazilian-utils/brazilian-utils'; + +isValidCsosn('101'); // true +isValidCsosn('999'); // false +``` + +### removeAccents + +Remove diacritical marks (accents, tildes, cedillas) from a string, decomposing every accented character into its base letter plus combining marks (Unicode NFD) and dropping the combining marks. + +```javascript +import { removeAccents } from '@brazilian-utils/brazilian-utils'; + +removeAccents('São Paulo'); // 'Sao Paulo' +removeAccents('Piauí'); // 'Piaui' +removeAccents('Ceará'); // 'Ceara' +removeAccents('Açaí'); // 'Acai' +removeAccents(''); // '' +``` diff --git a/docs/llms.txt b/docs/llms.txt new file mode 100644 index 00000000..30be2601 --- /dev/null +++ b/docs/llms.txt @@ -0,0 +1,173 @@ +# Brazilian Utils + +> Brazilian Utils is a zero-dependency JavaScript/TypeScript library of small, focused utilities for the day-to-day problems of building software for Brazilian businesses: validating, formatting, parsing and generating documents (CPF, CNPJ, CEP, Pix, boleto, NF-e, phone numbers, license plates and more). + +The package has **zero runtime dependencies**, is fully tree-shakeable and runs on Node.js `^20.19.0 || >=22.12.0`, Bun, Deno and modern browsers (including a UMD ` + ``` +### Suporte a runtimes + +Node `^20.19.0 || >=22.12.0`, Bun, Deno e navegadores modernos. + ## Como usar Para usar um de nossos utilitários, basta importar a função necessária, como no exemplo abaixo: @@ -34,4 +50,42 @@ import { isValidCpf } from '@brazilian-utils/brazilian-utils'; isValidCpf('1232454233345'); // false ``` -Você pode conferir a lista de utilitários [clicando aqui](pt-br/utilities.md). +Você pode conferir a lista de utilitários [clicando aqui](utilities.md). + +## Tamanho do bundle + +O pacote é tree-shakeable: importar um utilitário da raiz traz apenas o código daquele utilitário, não o resto da biblioteca. `isValidCpf`, por exemplo, adiciona cerca de 0,7 KB minificado ao seu bundle. Um bundler com suporte a tree-shaking (webpack, Rollup, esbuild, Vite etc.) descarta todos os outros utilitários. + +Alguns utilitários são a exceção: cada um embute um dataset oficial e pesa muito mais que todos os outros utilitários somados. Estes são os tamanhos de um import isolado, minificado e com gzip: + +| Utilitário | Dataset | Minificado | Gzip | +| --- | --- | --- | --- | +| `getMunicipalities` · `getMunicipalityByCode` · `getMunicipality` | 5571 municípios do IBGE, com nomes e códigos | 156 KB | 50 KB | +| `getCities` | nomes dos 5571 municípios do IBGE | 153 KB | 49 KB | +| `isValidNcm` | códigos NCM (Nomenclatura Comum do Mercosul) | 113 KB | 24 KB | +| `isValidCbo` · `getCbo` | títulos das ocupações da CBO 2002 | 110 KB | 27 KB | +| `isValidCnae` · `getCnae` | subclasses da CNAE 2.3 | 93 KB | 21 KB | +| `isValidCfop` · `getCfop` | descrições das operações do CFOP | 55 KB | 5,4 KB | +| `getBanks` · `getBankByCode` | participantes do STR do Banco Central (COMPE + ISPB) | 28 KB | 7,3 KB | + +Importar qualquer um deles da raiz, mesmo ao lado de um único utilitário pequeno, traz todo esse dataset para o seu bundle principal, porque este pacote é publicado como um único módulo ESM: um `import()` dinâmico da raiz (`await import('@brazilian-utils/brazilian-utils')`) ainda resolve para esse mesmo arquivo único, então não há como separá-lo sozinho. Um bundler que faz code-splitting precisa de um módulo separado para separar. + +Esses módulos separados são os subpaths por utilitário. Carregue um utilitário pesado sob demanda, apenas onde você realmente precisar dos dados dele: + +```javascript +const { getCities } = await import('@brazilian-utils/brazilian-utils/get-cities'); + +getCities('SP'); +``` + +```javascript +const { getMunicipalityByCode } = await import( + '@brazilian-utils/brazilian-utils/get-municipality-by-code' +); + +getMunicipalityByCode('3550308'); +``` + +Todos os utilitários estão disponíveis dessa forma, como `@brazilian-utils/brazilian-utils/` (kebab-case, seguindo o nome da função: `isValidCpf` → `is-valid-cpf`), pelo mesmo motivo de lazy-loading/code-splitting. + +Escolha um estilo por utilitário em cada aplicação: um bundler trata o import da raiz e o import do subpath como dois módulos independentes, então importar `getCities` tanto da raiz quanto de `/get-cities` na mesma aplicação inclui a tabela de 153 KB de cidades duas vezes, uma em cada módulo. diff --git a/docs/pt-br/migration-v1-to-v2.md b/docs/pt-br/migration-v1-to-v2.md index 2c6e74a3..9b2f67c7 100644 --- a/docs/pt-br/migration-v1-to-v2.md +++ b/docs/pt-br/migration-v1-to-v2.md @@ -6,9 +6,9 @@ Este guia irá ajudá-lo a migrar do Brazilian Utils v1.x para v2.0.0. **Boas notícias!** A v2.x mantém compatibilidade para a maioria das mudanças quebradoras: -✅ **Você pode atualizar para v2.x sem alterar seu código** - nomes antigos de funções como `formatCPF`, `isValidCNPJ`, etc. ainda funcionam -⚠️ **Você receberá avisos de deprecação** - encorajando você a migrar para os novos nomes -🗑️ **Nomes antigos serão removidos na v3.0.0** - então migre gradualmente +**Você pode atualizar para v2.x sem alterar seu código** - nomes antigos de funções como `formatCPF`, `isValidCNPJ`, etc. ainda funcionam +**Você receberá avisos de deprecação** - encorajando você a migrar para os novos nomes +**Nomes antigos serão removidos na v3.0.0** - então migre gradualmente **Porém**, você deve remover o uso dessas funções helper antes de atualizar: - `onlyNumbers` → use `string.replace(/\D/g, '')` @@ -20,7 +20,7 @@ Este guia irá ajudá-lo a migrar do Brazilian Utils v1.x para v2.0.0. A versão 2.0.0 traz melhorias significativas em arquitetura, ferramentas e experiência do desenvolvedor: -### 🎯 Melhor Tree Shaking +### Melhor Tree Shaking A biblioteca agora usa exports de módulos ES modernos com o campo `exports` adequado no `package.json`, permitindo melhor tree shaking em bundlers modernos. Você pode importar apenas o que precisa: @@ -29,7 +29,7 @@ A biblioteca agora usa exports de módulos ES modernos com o campo `exports` ade import { isValidCpf, formatCpf } from '@brazilian-utils/brazilian-utils'; ``` -### 📁 Estrutura Mais Simples +### Estrutura Mais Simples O código foi reorganizado para melhor manutenibilidade: - **v1**: Estrutura complexa com diretórios separados `utilities/` e `helpers/` @@ -37,7 +37,7 @@ O código foi reorganizado para melhor manutenibilidade: - Cada utilitário é autocontido em seu próprio diretório - Caminhos de importação mais limpos e melhor organização do código -### 🔧 Ferramentas Modernas +### Ferramentas Modernas Atualizado para ferramentas modernas e mais rápidas: - **Build**: Migrado de `tsdx` para uma stack com **Vite+** para builds e scripts mais rápidos @@ -45,7 +45,7 @@ Atualizado para ferramentas modernas e mais rápidas: - **Linting/Formatação**: Migrado de `prettier` + `eslint` para **Biome** (mais rápido, tudo-em-um) - **TypeScript**: Configuração moderna otimizada para bundlers -### 🌐 Testes em Browsers +### Testes em Browsers Agora inclui suporte para testes cross-browser: - Testes rodam em browsers reais (Chrome, Firefox, Safari, Edge) @@ -60,7 +60,7 @@ npm run test:safari-browser npm run test:edge-browser ``` -### 📦 Menos Dependências +### Menos Dependências Redução de dependências de desenvolvimento mantendo zero dependências de runtime: - **v1**: Múltiplas ferramentas (tsdx, jest, prettier, eslint, husky, lint-staged, commitlint, etc.) @@ -68,7 +68,7 @@ Redução de dependências de desenvolvimento mantendo zero dependências de run - Manutenção mais simples e pipelines CI/CD mais rápidos - Zero dependências de runtime (mantido) -### ✨ Novas Funções & Recursos +### Novas Funções e Recursos Adicionadas novas utilitários úteis: - `getHolidays` - Obtém feriados brasileiros (nacionais e estaduais) @@ -101,7 +101,7 @@ isValidCnpj("12.345.678/0001-95", { version: 1 }); // true (explícito) **Importante**: Por padrão, `isValidCnpj()` valida apenas CNPJs numéricos (versão 1). Para validar CNPJs alfanuméricos, você deve passar explicitamente `{ version: 2 }`. -### 📈 Melhor Suporte TypeScript +### Melhor Suporte TypeScript - Configuração TypeScript moderna otimizada para bundlers - Melhor inferência de tipos e exports @@ -113,13 +113,13 @@ isValidCnpj("12.345.678/0001-95", { version: 1 }); // true (explícito) Todos os nomes de funções foram alterados de PascalCase para camelCase para seguir as convenções de nomenclatura JavaScript. -**⚠️ Importante: Compatibilidade com Versões Anteriores** +**Importante: Compatibilidade com Versões Anteriores** Para facilitar a migração, **a v2.x ainda exporta os nomes antigos em PascalCase como aliases deprecated**. Isso significa: -- ✅ Seu código existente usando `formatCPF`, `isValidCNPJ`, etc. continuará funcionando na v2.x -- ⚠️ Você receberá avisos de deprecação no seu IDE/TypeScript -- 🗑️ Os nomes antigos serão **removidos na v3.0.0** +- Seu código existente usando `formatCPF`, `isValidCNPJ`, etc. continuará funcionando na v2.x +- Você receberá avisos de deprecação no seu IDE/TypeScript +- Os nomes antigos serão **removidos na v3.0.0** **Recomendação:** Embora você possa atualizar para v2.x sem alterar seu código imediatamente, recomendamos migrar para os novos nomes em camelCase o quanto antes para se preparar para a v3.0.0. @@ -162,7 +162,7 @@ Para facilitar a migração, **a v2.x ainda exporta os nomes antigos em PascalCa | `generateCNPJ` | `generateCnpj` | | `generateBoleto` | `generateBoleto` (inalterado) | -**⚠️ Nota sobre o comportamento do `generateCnpj`:** +**Nota sobre o comportamento do `generateCnpj`:** Na v2.x, `generateCnpj()` sem argumentos retorna por padrão a versão 1 (CNPJ numérico). Na v3.0.0, este comportamento mudará para selecionar aleatoriamente entre versão 1 (numérico) e versão 2 (alfanumérico) para melhor aleatoriedade. Se você precisa de uma versão específica, sempre passe o parâmetro de versão explicitamente: @@ -209,7 +209,7 @@ const cnpj = generateCnpj(); As seguintes funções helper não são mais exportadas na API pública. Estas eram utilitários internos que não deveriam ter sido expostos. -**⚠️ Nota:** Diferentemente das funções renomeadas acima, esses helpers **NÃO** possuem aliases de compatibilidade. Você deve migrar para longe deles antes de atualizar para a v2.x. +**Nota:** Diferentemente das funções renomeadas acima, esses helpers **NÃO** possuem aliases de compatibilidade. Você deve migrar para longe deles antes de atualizar para a v2.x. #### `onlyNumbers` Esta função foi removida da API pública. Agora é um utilitário interno chamado `sanitizeToDigits`. diff --git a/docs/pt-br/utilities.md b/docs/pt-br/utilities.md index 5571c808..a22ec9aa 100644 --- a/docs/pt-br/utilities.md +++ b/docs/pt-br/utilities.md @@ -2,25 +2,29 @@ Aqui você encontrará todos os utilitários disponíveis para uso. +> **Tratamento de entrada:** nenhuma função pública lança exceção com `null`/`undefined` ou um valor de tipo incorreto. Os validadores (`isValid*`) retornam `false`; `isHoliday` retorna `false`; `getHolidays` retorna `[]`; `generateProcessoJuridico` retorna `null`; `getMunicipality` retorna `null` para uma busca malformada/sem correspondência. Todas as demais funções `format*`/`parse*` (incluindo `capitalize`) retornam um valor vazio do seu tipo de retorno: `""` para strings, `0` para `parseCurrency`. `formatCurrency` retorna `""` para um número não finito. + ## isValidCpf -Valida se o CPF é válido. +Valida se o CPF é válido. Aceita os caracteres de máscara usuais e espaços em branco entre/ao redor dos grupos. ```javascript import { isValidCpf } from '@brazilian-utils/brazilian-utils'; isValidCpf('155151475'); // false +isValidCpf('111 444 777 35'); // true (máscara com espaços) ``` ## formatCpf -Formata o CPF. +Formata o CPF. `options.obfuscate` (parte de `FormatCpfOptions`) esconde os 3 primeiros dígitos e os 2 dígitos verificadores (`***.456.789-**`), a convenção de exibição do gov.br / Receita Federal, aplicada após o `pad`. ```javascript import { formatCpf } from '@brazilian-utils/brazilian-utils'; formatCpf('74650688000'); // 746.506.880-00 formatCpf('746506880', { pad: true }); // 007.465.068-80 +formatCpf('12345678909', { obfuscate: true }); // ***.456.789-** ``` ## parseCpf @@ -45,17 +49,18 @@ generateCpf(); ## isValidCnpj -Valida se o CNPJ é válido. +Valida se o CNPJ é válido. Suporta tanto o formato numérico (`version: 1`, padrão) quanto o formato alfanumérico (`version: 2`), e aceita os caracteres de máscara usuais e espaços em branco. As opções são tipadas como `IsValidCnpjOptions`. ```javascript import { isValidCnpj } from '@brazilian-utils/brazilian-utils'; isValidCnpj('15515147234255'); // false +isValidCnpj('q0slfmbd7vx439', { version: 2 }); // true (alfanumérico minúsculo) ``` ## formatCnpj -Formata o CNPJ. +Formata o CNPJ. `options.obfuscate` (parte de `FormatCnpjOptions`) esconde os 2 primeiros dígitos e os 2 dígitos verificadores (`**.345.678/0001-**`), a convenção de exibição do gov.br / Receita Federal. Vale para as duas versões e é aplicada após o `pad`. ```javascript import { formatCnpj } from '@brazilian-utils/brazilian-utils'; @@ -63,11 +68,12 @@ import { formatCnpj } from '@brazilian-utils/brazilian-utils'; formatCnpj('24522200000174'); // 24.522.200/0001-74 formatCnpj('245222000174', { pad: true }); // 00.245.222/0001-74 formatCnpj('12OUT345000199', { version: 2 }); // 12.OUT.345/0001-99 +formatCnpj('12345678000195', { obfuscate: true }); // **.345.678/0001-** ``` ## parseCnpj -Remove a formatação do CNPJ, retorna um valor normalizado e limita o resultado a 14 caracteres. +Remove a formatação do CNPJ, retorna um valor normalizado e limita o resultado a 14 caracteres. As opções são tipadas como `ParseCnpjOptions`. ```javascript import { parseCnpj } from '@brazilian-utils/brazilian-utils'; @@ -76,50 +82,59 @@ parseCnpj('24.522.200/0001-74'); // 24522200000174 parseCnpj('12.OUT.345/0001-99', { version: 2 }); // 12OUT345000199 ``` -## generateCnpj +## isValidCep -Gera um CNPJ válido aleatório. +Valida se o CEP é válido. Aceita entrada como `string` ou `number`; espaços, pontos e hífens ao redor/entre os 8 dígitos são ignorados, mas qualquer outro caractere, uma letra em especial, invalida o valor. ```javascript -import { generateCnpj } from '@brazilian-utils/brazilian-utils' +import { isValidCep } from '@brazilian-utils/brazilian-utils'; -generateCnpj(); +isValidCep('01310100'); // true +isValidCep('92500-000'); // true (hífen entre os grupos) +isValidCep('92.500-000'); // true (ponto e hífen) +isValidCep('013 10 100'); // true (espaços entre os dígitos) +isValidCep(20040020); // true (entrada numérica) +isValidCep('9250000A'); // false (letras são rejeitadas) +isValidCep('12345'); // false (tamanho inválido) ``` -## isValidCep +## generateCnpj -Valida se o CEP é válido. +Gera um CNPJ válido aleatório. ```javascript -import { isValidCep } from '@brazilian-utils/brazilian-utils'; +import { generateCnpj } from '@brazilian-utils/brazilian-utils' -isValidCep('92500000'); // true +generateCnpj(); ``` ## isValidBoleto -Valida se o boleto é válido. +Valida se o boleto é válido. Suporta tanto o boleto de "cobrança bancária" de 47 dígitos quanto o "boleto de arrecadação" (convênio/tributos): seja a linha digitável de 48 dígitos, seja o código de barras de 44 dígitos, ambos iniciados com `8`. ```javascript import { isValidBoleto } from '@brazilian-utils/brazilian-utils'; isValidBoleto('00190000090114971860168524522114675860000102656'); // true +isValidBoleto('846100000005246100291102005460339004695895061080'); // true (boleto de arrecadação) ``` ## formatBoleto -Formata um número de boleto. +Formata um número de boleto. A máscara de arrecadação (convênio/tributos) só se aplica à linha digitável de 48 dígitos que começa com `8`; o código de barras de arrecadação de 44 dígitos não tem agrupamento de exibição definido pela FEBRABAN e mantém a máscara de "cobrança bancária". ```javascript import { formatBoleto } from '@brazilian-utils/brazilian-utils'; formatBoleto('00190000090114971860168524522114675860000102656'); // 00190.00009 01149.718601 68524.522114 6 75860000102656 -formatBoleto('1900000901149', { pad: true }); // 00000.00000 00000.000019 00000.901149 0 00000000000000 +formatBoleto('1900000901149', { pad: true }); // 00000.00000 00000.000000 00000.000000 0 01900000901149 +formatBoleto('846100000005246100291102005460339004695895061080'); // 84610000000-5 24610029110-2 00546033900-4 69589506108-0 (linha digitável de arrecadação, 48 dígitos) +formatBoleto('84610000000246100291100054603390069589506108'); // 84610.00000 02461.002911 00054.603390 0 69589506108 (código de barras de arrecadação de 44 dígitos mantém a máscara bancária) ``` ## parseBoleto -Remove a formatação do boleto, mantém apenas os dígitos e limita o resultado a 47 dígitos. +Remove a formatação do boleto, mantém apenas os dígitos e limita o resultado a 47 dígitos (48 para boleto de arrecadação). ```javascript import { parseBoleto } from '@brazilian-utils/brazilian-utils'; @@ -129,23 +144,161 @@ parseBoleto('00190.00009 01149.718601 68524.522114 6 75860000102656'); // 001900 ## generateBoleto -Gera um boleto válido aleatório. +Gera um boleto válido aleatório. Informe `{ type: "arrecadacao" }` (tipado como `GenerateBoletoOptions`) para gerar um boleto de arrecadação em vez do tipo padrão "bancario" (cobrança bancária). ```javascript import { generateBoleto } from '@brazilian-utils/brazilian-utils'; generateBoleto(); // "00190000090114971860168524522114675860000102656" +generateBoleto({ type: 'arrecadacao' }); // "846100000005246100291102005460339004695895061080" ``` ## getBoletoInfo -Extrai informações de um boleto (valor, data de vencimento, código do banco). +Extrai informações de um boleto (valor, data de vencimento, código do banco). Aceita opcionalmente `{ referenceDate }` (tipado como `GetBoletoInfoOptions`) para resolver o ciclo do "fator de vencimento" a partir de uma data específica em vez de agora (o ciclo do fator reiniciou em 22/02/2025, segundo a FEBRABAN). Para um boleto de arrecadação, o resultado, tipado como `BoletoInfo`, não tem `bankCode`/`expirationDate` e traz em vez disso `type: "arrecadacao"`, `segment`, `value` e `hasEffectiveValue`. ```javascript import { getBoletoInfo } from '@brazilian-utils/brazilian-utils'; getBoletoInfo('00190000090114971860168524522114675860000102656'); // { amount: 102656, expirationDate: Date, bankCode: '001' } + +getBoletoInfo('00190000090114971860168524522114675860000102656', { + referenceDate: new Date(2018, 6, 1) +}); +// Resolve o ciclo do fator de vencimento a partir de 2018-07-01 + +getBoletoInfo('846100000005246100291102005460339004695895061080'); +// { amount: 2461, expirationDate: null, bankCode: '', type: 'arrecadacao', segment: 4, value: 24.61, hasEffectiveValue: true } +``` + +## 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`. + +```javascript +import { isValidPixKey } from '@brazilian-utils/brazilian-utils'; + +isValidPixKey('123.456.789-09'); // true +isValidPixKey('fulano@example.com'); // true +isValidPixKey('(11) 98765-4321'); // true +isValidPixKey('71c7d9be-4b85-4e43-9f1c-1f3b8b4e9a2d'); // true +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`. + +```javascript +import { parsePixKey } from '@brazilian-utils/brazilian-utils'; + +parsePixKey('123.456.789-09'); // { type: 'cpf', value: '12345678909' } +parsePixKey('Fulano@Example.COM '); // { type: 'email', value: 'fulano@example.com' } +parsePixKey('(11) 98765-4321'); // { type: 'phone', value: '+5511987654321' } +parsePixKey('71C7D9BE-4B85-4E43-9F1C-1F3B8B4E9A2D'); +// { type: 'evp', value: '71c7d9be-4b85-4e43-9f1c-1f3b8b4e9a2d' } +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. + +```javascript +import { isValidPixPayload } from '@brazilian-utils/brazilian-utils'; + +isValidPixPayload( + '00020126580014br.gov.bcb.pix0136123e4567-e12b-12d1-a456-426655440000' + + '5204000053039865802BR5913Fulano de Tal6008BRASILIA62070503***63041D3D' +); // true + +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"`). + +```javascript +import { parsePixPayload } from '@brazilian-utils/brazilian-utils'; + +parsePixPayload( + '00020126580014br.gov.bcb.pix0136123e4567-e12b-12d1-a456-426655440000' + + '5204000053039865802BR5913Fulano de Tal6008BRASILIA62070503***63041D3D' +); +// { +// key: '123e4567-e12b-12d1-a456-426655440000', +// merchantName: 'Fulano de Tal', +// merchantCity: 'BRASILIA' +// } +``` + +## generatePixPayload + +Gera o payload de um BR Code Pix. Exatamente um entre `params.key` e `params.url` deve ser informado (parte de `GeneratePixPayloadParams`); `null` é retornado quando ambos ou nenhum são informados. + +Quando `params.key` é informado, ela é normalizada para a forma canônica do DICT pelo `parsePixKey` e o payload é estático. Quando `params.url` é informado no lugar (a localização do PSP, sem o esquema da URL, ex.: `"pix.example.com/qr/v2/1234"`), o payload é dinâmico conforme o Manual de Padrões para Iniciação do Pix: a URL ocupa o lugar da chave no template "Merchant Account Information" e o objeto "Point of Initiation Method" é definido como dinâmico (`12`); `params.url` pode ter no máximo 77 caracteres. `merchantName`, `merchantCity` e `description` são convertidos para ASCII imprimível (acentos removidos) e truncados ao que o BR Code permite. O `parsePixPayload` já interpreta os dois formatos, então `parsePixPayload(generatePixPayload({ url, ... }))` forma um round-trip. + +```javascript +import { generatePixPayload } from '@brazilian-utils/brazilian-utils'; + +generatePixPayload({ + key: '123.456.789-09', + merchantName: 'Fulano de Tal', + merchantCity: 'Brasília', + amount: 123.45 +}); +// "00020126330014br.gov.bcb.pix0111123456789095204000053039865406123.455802BR5913Fulano de Tal6008Brasilia62070503***630479EE" + +generatePixPayload({ + url: 'pix.example.com/qr/v2/1234', + merchantName: 'Fulano de Tal', + merchantCity: 'Brasília' +}); +// "00020101021226480014br.gov.bcb.pix2526pix.example.com/qr/v2/12345204000053039865802BR5913Fulano de Tal6008Brasilia62070503***6304FC66" + +generatePixPayload({ merchantName: 'Fulano', merchantCity: 'Brasília' }); // null (nem key nem url) +``` + +## 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. + +```javascript +import { isValidNfeKey } from '@brazilian-utils/brazilian-utils'; + +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) +``` + +## formatNfeKey + +Formata uma chave de acesso de DF-e (NF-e, NFC-e, CT-e ou MDF-e) em grupos de 4 dígitos separados por espaço, a forma de exibição usual impressa na DANFE. + +```javascript +import { formatNfeKey } from '@brazilian-utils/brazilian-utils'; + +formatNfeKey('35170458716523000119550010000000121000123458'); +// '3517 0458 7165 2300 0119 5500 1000 0000 1210 0012 3458' +``` + +## parseNfeKey + +Interpreta uma chave de acesso de DF-e e retorna seus campos (state, year, month, taxId, model, series, number, emissionType, code, checkDigit). Aceita as mesmas formas de entrada do `isValidNfeKey` e retorna `null` quando a chave não é válida. O resultado é tipado como `NfeKey`. + +```javascript +import { parseNfeKey } from '@brazilian-utils/brazilian-utils'; + +parseNfeKey('35170458716523000119550010000000121000123458'); +// { state: 'SP', year: 2017, month: 4, taxId: '58716523000119', model: '55', +// series: 1, number: 12, emissionType: 1, code: '00012345', checkDigit: 8 } + +parseNfeKey('invalid'); // null ``` ## isValidEmail @@ -160,45 +313,59 @@ isValidEmail('john.doe@hotmail.com'); // true ## isValidPhone -Valida se o número de telefone (celular ou residencial) é válido. +Valida se o número de telefone (celular ou residencial) é válido. Um código de país brasileiro (`+55`, `0055` ou um `55` isolado) é aceito e removido antes da validação, seguindo a regra documentada em `parsePhone`. `options.accept` (tipado como `PhoneType[]`, parte de `IsValidPhoneOptions`) define quais tipos de número são aceitos e tem como padrão `['mobile', 'landline']`; adicione `'service'` para também aceitar os números não geográficos reconhecidos por `isValidServicePhone`, ou informe `[]` para não aceitar nenhum. ```javascript import { isValidPhone } from '@brazilian-utils/brazilian-utils'; isValidPhone('11900000000'); // true +isValidPhone('+55 11 98765-4321'); // true (código de país aceito) +isValidPhone('08001234567'); // false (números de serviço não são aceitos por padrão) +isValidPhone('08001234567', { accept: ['service'] }); // true +isValidPhone('11900000000', { accept: [] }); // false ``` ## formatPhone -Formata número de telefone de acordo com padrões brasileiros. +Formata número de telefone de acordo com padrões brasileiros. `options.mask` (tipado como `PhoneMask`) aceita `"sn"` (padrão, apenas o número assinante, 9 dígitos, sem DDD), `"nanp"` (DDD + número assinante, 11 dígitos), `"e164"` (`"+5511987654321"`), `"international"` (`"+55 11 98765-4321"`, a forma como um número brasileiro é exibido para quem liga do exterior), `"service"` (`"0800 123 4567"` ou `"4004-1234"`, os agrupamentos convencionais para números de serviço) ou `"auto"`. O `"auto"` usa `"international"` quando `value` traz um código de país brasileiro (`+55`, `0055` ou um `55` seguido de 10 ou 11 dígitos), `"service"` quando `value` é um número de serviço e, nos demais casos, decide pela quantidade de dígitos: `"nanp"` quando `value` tem mais dígitos que um número assinante isolado, `"sn"` quando não tem. `"e164"` e `"international"` removem antes o código de país (regra documentada em `parsePhone`) e recaem para a apresentação `"service"` no caso de um número de serviço, já que esses não têm forma E.164. Se `value` incluir o DDD, informe `{ mask: 'auto' }` (ou `'nanp'`) explicitamente, já que a máscara padrão `"sn"` assume que não há DDD e trunca silenciosamente um DDD presente. ```javascript import { formatPhone } from '@brazilian-utils/brazilian-utils'; -formatPhone('11900000000'); // 90000-0000 +formatPhone('987654321'); // 98765-4321 (padrão "sn", sem DDD) formatPhone('11900000000', { mask: 'nanp' }); // (11) 90000-0000 -formatPhone('11900000000', { mask: 'auto' }); // Detecta automaticamente a máscara baseado no comprimento +formatPhone('11900000000', { mask: 'auto' }); // (11) 90000-0000 +formatPhone('11987654321', { mask: 'e164' }); // +5511987654321 +formatPhone('+5511987654321', { mask: 'international' }); // +55 11 98765-4321 +formatPhone('08001234567', { mask: 'service' }); // 0800 123 4567 +formatPhone('40041234', { mask: 'service' }); // 4004-1234 +formatPhone('+5511987654321', { mask: 'auto' }); // (55) 11987-6543 (CUIDADO: "auto" não detecta o prefixo +55) +formatPhone('11900000000'); // 11900-0000 (CUIDADO: a máscara padrão "sn" trunca um número com DDD) ``` ## parsePhone -Remove a formatação do telefone, mantém apenas os dígitos e limita o resultado a 11 dígitos. +Remove a formatação do telefone, mantém apenas os dígitos e limita o resultado a 11 dígitos. Um código de país brasileiro é removido antes, mas somente quando os dígitos restantes tiverem exatamente 10 ou 11 dígitos, ou seja, um número nacional plausível. A regra é baseada no tamanho, não no sinal, então um número da área 55 não é confundido com o código de país. ```javascript import { parsePhone } from '@brazilian-utils/brazilian-utils'; parsePhone('(11) 90000-0000'); // 11900000000 -parsePhone('+55 (11) 90000-0000'); // 55119000000 +parsePhone('+55 (11) 98765-4321'); // 11987654321 +parsePhone('5511987654321'); // 11987654321 +parsePhone('55987654321'); // 55987654321 (DDD 55, não confundido com o código de país +55) ``` ## isValidMobilePhone -Valida se o número de telefone celular é válido. +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. ```javascript import { isValidMobilePhone } from '@brazilian-utils/brazilian-utils'; isValidMobilePhone('11900000000'); // true +isValidMobilePhone('11712345678', { version: 1 }); // true (formato antigo) +isValidMobilePhone('11712345678', { version: 2 }); // false (v2 exige 9 como primeiro dígito) ``` ## isValidLandlinePhone @@ -211,17 +378,63 @@ import { isValidLandlinePhone } from '@brazilian-utils/brazilian-utils'; 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. + +```javascript +import { isValidServicePhone } from '@brazilian-utils/brazilian-utils'; + +isValidServicePhone('0800 123 4567'); // true +isValidServicePhone('4004-1234'); // true +isValidServicePhone('190'); // true +isValidServicePhone('11987654321'); // false (número geográfico) +``` + +## getAreaCodeInfo + +Retorna o estado (e a região) a que um DDD brasileiro pertence, dentre os 67 DDDs em uso no Plano Geral de Numeração da Anatel. Aceita string ou número, removendo caracteres não numéricos antes de comparar. Exporta o tipo `AreaCodeInfo`. + +```javascript +import { getAreaCodeInfo } from '@brazilian-utils/brazilian-utils'; + +getAreaCodeInfo('11'); +// { areaCode: 11, stateCode: 'SP', stateName: 'São Paulo', region: 'Sudeste' } + +getAreaCodeInfo(21); +// { areaCode: 21, stateCode: 'RJ', stateName: 'Rio de Janeiro', region: 'Sudeste' } + +getAreaCodeInfo('68'); +// { areaCode: 68, stateCode: 'AC', stateName: 'Acre', region: 'Norte' } + +getAreaCodeInfo('00'); // null +``` + +## getAreaCodesByState + +Retorna todos os DDDs (códigos de área) que pertencem a um determinado estado brasileiro, dentro do Plano Geral de Numeração da Anatel. A comparação não diferencia maiúsculas de minúsculas e o resultado vem ordenado de forma crescente. + +```javascript +import { getAreaCodesByState } from '@brazilian-utils/brazilian-utils'; + +getAreaCodesByState('SP'); // [11, 12, 13, 14, 15, 16, 17, 18, 19] +getAreaCodesByState('ac'); // [68] +getAreaCodesByState('XX'); // [] +``` + ## isValidLicensePlate -Valida se a placa de carro ou moto é válida. Suporta o formato antigo brasileiro (ABC-1234) e os formatos Mercosul para carros (ABC1D23) e motos (ABC12D3). +Valida se a placa de carro ou moto é válida. Suporta o formato antigo brasileiro (ABC-1234) e o formato Mercosul (ABC1D23), a sequência única que a Resolução CONTRAN nº 969/2022 define para todo veículo, motos incluídas. ```javascript import { isValidLicensePlate } from '@brazilian-utils/brazilian-utils'; isValidLicensePlate('ABC1234'); // true (formato brasileiro) isValidLicensePlate('ABC-1234'); // true (formato brasileiro com hífen) -isValidLicensePlate('ABC1D23'); // true (formato Mercosul de carro) -isValidLicensePlate('ABC12D3'); // true (formato Mercosul de moto) +isValidLicensePlate('ABC 1234'); // true (máscara com espaço) +isValidLicensePlate('ABC1D23'); // true (formato Mercosul) +isValidLicensePlate('ABC12D3'); // false (não é uma sequência Mercosul) +isValidLicensePlate('ABC1234EXTRA'); // false (caracteres em excesso) ``` ## isValidRenavam @@ -238,7 +451,7 @@ isValidRenavam('12345678901'); // false (checksum inválido) ## isValidPis -Valida se o PIS é válido. +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'; @@ -289,12 +502,12 @@ parseCep('92500-000'); // 92500000 ## getAddressInfoByCep -Busca informações de endereço para um CEP usando múltiplos provedores. +Busca informações de endereço para um CEP usando múltiplos provedores. O padrão é `['viacep', 'brasilapi']`. O provedor `'widenet'` está descontinuado (seu endpoint não responde mais) e foi excluído da lista padrão, mas ainda pode ser solicitado explicitamente via `options.providers` (tipado como `CepProvider[]`). O endereço retornado é tipado como `AddressInfo`. ```javascript import { getAddressInfoByCep } from '@brazilian-utils/brazilian-utils'; -// Usando todos os provedores (padrão) +// Usando os provedores padrão (['viacep', 'brasilapi']) const address = await getAddressInfoByCep('01310100'); // { cep: '01310100', state: 'SP', city: 'São Paulo', neighborhood: 'Bela Vista', street: 'Avenida Paulista' } @@ -319,59 +532,243 @@ isValidProcessoJuridico('00020802520125150049'); // true ## formatProcessoJuridico -Formata um número no formato definido pelo [CNJ](https://www.conjur.com.br/dl/resolucao-65-cnj.pdf). +Formata um número no formato definido pelo [CNJ](https://www.conjur.com.br/dl/resolucao-65-cnj.pdf) (máscara `NNNNNNN-DD.AAAA.J.TR.OOOO`). ```javascript import { formatProcessoJuridico } from '@brazilian-utils/brazilian-utils'; -formatProcessoJuridico('00020802520125150049'); // 0002080-25.2012.515.0049 +formatProcessoJuridico('00020802520125150049'); // 0002080-25.2012.5.15.0049 ``` ## parseProcessoJuridico -Remove a formatação do processo jurídico, mantém apenas os dígitos e limita o resultado a 20 dígitos. +Remove a formatação do processo jurídico, mantém apenas os dígitos e limita o resultado a 20 dígitos. Tanto a máscara atual do CNJ (`NNNNNNN-DD.AAAA.J.TR.OOOO`) quanto a máscara antiga são aceitas, já que apenas os dígitos são mantidos. ```javascript import { parseProcessoJuridico } from '@brazilian-utils/brazilian-utils'; -parseProcessoJuridico('0002080-25.2012.515.0049'); // 00020802520125150049 +parseProcessoJuridico('0002080-25.2012.5.15.0049'); // 00020802520125150049 ``` ## isValidIe -Valida se a inscrição estadual de um estado é válida. +Valida se a inscrição estadual de um estado é válida. A UF é case-insensitive. Regras notáveis por estado: GO aceita os prefixos `10`, `11` e `15`; PA aceita `15` e `75`-`79`; MS aceita `28` e `50`; SP tem o padrão de produtor rural `P0MMMSSSSD000`; TO usa códigos de tipo de 11 dígitos (`01`, `02`, `03`, `99`). ```javascript import { isValidIe } from '@brazilian-utils/brazilian-utils'; isValidIe('AC', '0187634580933'); // false +isValidIe('go', '109161793'); // true (case-insensitive) ``` ## isValidBankAccount -Valida se uma conta bancária brasileira é válida. Suporta algoritmos de validação específicos para os principais bancos (Banco do Brasil, Itaú, Bradesco, Santander, Caixa Econômica Federal) e validação genérica mod10/mod11 para outros bancos. +Verifica se uma conta bancária brasileira é válida. O `bankCode` precisa estar na lista de participantes do STR publicada pelo Banco Central do Brasil (o mesmo dataset usado por `getBankByCode`), então um código não atribuído como `'999'` é sempre inválido. A partir daí o banco é validado de três formas: pelo algoritmo de dígito verificador publicado, apenas pela estrutura (o banco existe e a agência/conta respeitam a quantidade de dígitos documentada, para bancos que não publicam regra de dígito) ou pela verificação genérica mod10/mod11, que continua sendo o fallback para os demais bancos da lista. + +Bancos validados pelo algoritmo de dígito verificador publicado: + +| Banco | Código | Agência | Conta | Observações | +| --- | --- | --- | --- | --- | +| Banco do Brasil | `001` | 4-5 dígitos | 8-10 dígitos | mod11 com pesos 9..2; `digit` pode ser `"X"` | +| Santander | `033` | 4 dígitos | 8 dígitos | pesos `9,7,3,1,0,0,9,7,1,3,1,9,7,3` sobre agência + `"00"` + conta, desprezando as dezenas | +| Banrisul | `041` | 4 dígitos | 9 dígitos | pesos `3,2,4,7,6,5,4,3,2`; resto 0 gera `0` e resto 1 gera `6`; `account` é tipo (2 dígitos) + conta (7 dígitos) | +| Caixa Econômica Federal | `104` | 4 dígitos | 11 dígitos | mod11 sobre agência + conta; `account` é operação (3 dígitos) + conta (8 dígitos) | +| Bradesco | `237` | 4 dígitos | 7 dígitos | mod11 com pesos 2..7; `digit` pode ser `"P"` (geralmente exibido como `"0"`) | +| Nubank | `260` | 4 dígitos | 5-13 dígitos | dígito de Verhoeff sobre a conta, ignorando zeros à esquerda | +| Itaú Unibanco | `341` | 4 dígitos | 5 dígitos | mod10 sobre agência + conta | +| HSBC / Kirton Bank | `399` | 4 dígitos | 6 dígitos | pesos `8,9,2,3,4,5,6,7,8,9` sobre agência + conta; resto 10 gera `0` | +| Citibank | `745` | 4 dígitos | 10 dígitos | pesos `11..2` sobre a conta; resto 0 ou 1 gera `0` | + +Bancos validados apenas pela estrutura, por não publicarem regra de dígito verificador. A agência (1-5 dígitos), a conta (1-13 dígitos) e um único `digit` numérico já tornam a conta válida: + +| Banco | Código | | Banco | Código | +| --- | --- | --- | --- | --- | +| Inter | `077` | | PicPay | `380` | +| Ailos | `085` | | Cora | `403` | +| XP | `102` | | Pan | `623` | +| Unicred | `136` | | BV | `655` | +| Stone | `197` | | Daycoval | `707` | +| BTG Pactual | `208` | | Modal | `746` | +| Original | `212` | | Sicredi | `748` | +| PagBank | `290` | | Sicoob | `756` | +| BMG | `318` | | | | +| Mercado Pago | `323` | | | | +| C6 | `336` | | | | + +Quando `digit` tem 2 caracteres, o fallback genérico encadeia mod10 seguido de mod11 sobre a conta, do mesmo jeito que os dígitos de CPF/CNPJ são encadeados. + +Fontes: o compêndio "Regras de Validação de dígito verificador de agência e conta corrente", conferido contra `banktools-br` (Ruby), `luizalabs/heimdall` (Python) e `Xerpa/bran_checker` (Elixir). Cada algoritmo publicado aqui tem pelo menos duas fontes independentes concordantes. ```javascript import { isValidBankAccount } from '@brazilian-utils/brazilian-utils'; isValidBankAccount({ bankCode: '001', - agency: '1234', - account: '12345678', - digit: '5' -}); // true (se conta válida do Banco do Brasil) + agency: '1584', + account: '00210169', + digit: '6' +}); // true (Banco do Brasil) isValidBankAccount({ bankCode: '341', + agency: '2545', + account: '02366', + digit: '1' +}); // true (Itaú) + +isValidBankAccount({ + bankCode: '104', + agency: '0647', + account: '00188888888', + digit: '7' +}); // true (Caixa: operação "001" + conta "88888888") + +isValidBankAccount({ + bankCode: '041', + agency: '2664', + account: '358507670', + digit: '6' +}); // true (Banrisul: tipo "35" + conta "8507670") + +isValidBankAccount({ + bankCode: '260', + agency: '0001', + account: '5216125', + digit: '0' +}); // true (Nubank, Verhoeff) + +isValidBankAccount({ + bankCode: '077', + agency: '0001', + account: '123456789', + digit: '0' +}); // true (Banco Inter, apenas estrutura) + +isValidBankAccount({ + bankCode: '077', + agency: '0001', + account: '123456789', + digit: 'X' +}); // false (banco validado por estrutura ainda exige dígito numérico) + +isValidBankAccount({ + bankCode: '999', agency: '1234', - account: '12345', + account: '123456', digit: '6' -}); // true (se conta válida do Itaú) +}); // false (999 não é participante do Banco Central) + +isValidBankAccount({ + bankCode: '246', + agency: '1234', + account: '123456', + digit: '6' +}); // true (Banco ABC Brasil, fallback genérico mod10) +``` + +## getBanks + +Obtém todos os bancos brasileiros com código de compensação (COMPE), publicados pelo Banco Central do Brasil na [lista de participantes do STR](https://www.bcb.gov.br/pom/spb/estatistica/port/ParticipantesSTRport.csv). Cada banco (tipado como `Bank`) tem um `code` (COMPE, 3 dígitos), um `ispb` (Identificador do Sistema de Pagamentos Brasileiro, 8 dígitos) e um `name`. Cada chamada retorna um novo array com novos objetos, então alterar o resultado nunca afeta chamadas seguintes. + +```javascript +import { getBanks } from '@brazilian-utils/brazilian-utils'; + +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 +// ] +``` + +## getBankByCode + +Busca um banco brasileiro pelo seu código de compensação (COMPE), publicado pelo Banco Central do Brasil na [lista de participantes do STR](https://www.bcb.gov.br/pom/spb/estatistica/port/ParticipantesSTRport.csv). Aceita tanto `string` quanto `number`, com ou sem zeros à esquerda. Retorna uma nova cópia (tipada como `Bank`) do banco correspondente, ou `null` quando nenhum banco tem esse código. + +```javascript +import { getBankByCode } from '@brazilian-utils/brazilian-utils'; + +getBankByCode('001'); // { code: '001', ispb: '00000000', name: 'Banco do Brasil S.A.' } +getBankByCode(1); // { code: '001', ispb: '00000000', name: 'Banco do Brasil S.A.' } +getBankByCode('999'); // null +``` + +## getBankByIspb + +Busca um banco brasileiro pelo seu ISPB (Identificador do Sistema de Pagamentos Brasileiro), o código de 8 dígitos publicado pelo Banco Central do Brasil na [lista de participantes do STR](https://www.bcb.gov.br/pom/spb/estatistica/port/ParticipantesSTRport.csv). Diferente do código COMPE (`getBankByCode`), todo participante do SPB tem um ISPB, incluindo instituições sem código COMPE próprio. Aceita tanto `string` quanto `number`, com ou sem zeros à esquerda. Retorna uma nova cópia (tipada como `Bank`) do banco correspondente, ou `null` quando nenhum banco tem esse ISPB. + +```javascript +import { getBankByIspb } from '@brazilian-utils/brazilian-utils'; + +getBankByIspb('00000000'); // { code: '001', ispb: '00000000', name: 'Banco do Brasil S.A.' } +getBankByIspb('60701190'); // { code: '341', ispb: '60701190', name: 'ITAÚ UNIBANCO S.A.' } +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. + +```javascript +import { isValidIban } from '@brazilian-utils/brazilian-utils'; + +isValidIban('BR1500000000000010932840814P2'); // true +isValidIban('BR15 0000 0000 0000 1093 2840 814P 2'); // true (espaços de agrupamento) +isValidIban('BR1500000000000010932840814P3'); // false (dígitos verificadores inválidos) +isValidIban('DE89370400440532013000'); // false (IBAN não brasileiro) +``` + +## formatIban + +Formata um IBAN brasileiro agrupando-o em blocos de 4 caracteres, a apresentação "impressa" da ISO 13616 usada em extratos e formulários bancários. Não valida os dígitos verificadores nem o layout dos campos; formata o que for passado, até o limite de 29 caracteres de um IBAN brasileiro, até onde for possível, então a função também pode ser usada como máscara de digitação. Use `isValidIban` para verificar a validade. + +```javascript +import { formatIban } from '@brazilian-utils/brazilian-utils'; + +formatIban('BR1500000000000010932840814P2'); // 'BR15 0000 0000 0000 1093 2840 814P 2' +formatIban('br1500000000000010932840814p2'); // 'BR15 0000 0000 0000 1093 2840 814P 2' +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`. + +```javascript +import { parseIban } from '@brazilian-utils/brazilian-utils'; + +parseIban('BR1500000000000010932840814P2'); +// { +// countryCode: 'BR', +// checkDigits: '15', +// bankIspb: '00000000', +// branch: '00001', +// account: '0932840814', +// accountType: 'P', +// owner: '2' +// } + +parseIban('DE89370400440532013000'); // null (IBAN não brasileiro) +``` + +## isValidCreditCard + +Valida se um número de cartão de pagamento é válido usando o algoritmo de Luhn ([ISO/IEC 7812-1](https://www.iso.org/standard/70484.html)). Aceita os caracteres de máscara usuais (espaços, hifens) entre os dígitos. Não faz detecção de bandeira (Visa, Mastercard, Amex...), consulta de faixa de emissor nem validação de validade/CVV, verifica apenas a quantidade de dígitos (12 a 19) e o dígito verificador de Luhn. + +```javascript +import { isValidCreditCard } from '@brazilian-utils/brazilian-utils'; + +isValidCreditCard('4111111111111111'); // true (número de teste Visa) +isValidCreditCard('5555555555554444'); // true (número de teste Mastercard) +isValidCreditCard('378282246310005'); // true (número de teste American Express) +isValidCreditCard('4111 1111 1111 1111'); // true (máscara com espaços) +isValidCreditCard('4111111111111112'); // false (dígito verificador inválido) ``` ## capitalize -Transforma primeira letra de cada palavra em maiúscula ignorando preposições. +Transforma primeira letra de cada palavra em maiúscula ignorando preposições. As palavras são separadas por espaço em branco, por `-` e por `/`, então `'MOGI-GUAÇU'` vira `'Mogi-Guaçu'` e `'SANTANA/RS'` vira `'Santana/Rs'`. Toda sequência de espaços em branco (tabs, quebras de linha, espaços repetidos) vira um único espaço, e o espaço no início e no fim é descartado. `options.upperCaseWords` tem como padrão `[]`, ou seja, nenhuma sigla é colocada em maiúsculas a menos que você a liste, e a comparação com `upperCaseWords` e `lowerCaseWords` é case-insensitive (locale pt-BR). As opções são tipadas como `CapitalizeOptions`. ```javascript import { capitalize } from '@brazilian-utils/brazilian-utils'; @@ -379,11 +776,16 @@ import { capitalize } from '@brazilian-utils/brazilian-utils'; capitalize('josé e maria'); // José e Maria capitalize('josé Ama MARIA', { lowerCaseWords: ['ama'] }); // José ama Maria capitalize('doc inválido', { upperCaseWords: ['DOC'] }); // DOC Inválido +capitalize('MOGI-GUAÇU'); // Mogi-guaçu ("-" não inicia uma nova palavra) +capitalize('SANTANA/RS', { upperCaseWords: ['RS'] }); // Santana/rs ("SANTANA/RS" é uma única palavra, então não corresponde a "RS") +capitalize('empresa ltda'); // Empresa Ltda (sem siglas padrão) +capitalize('empresa ltda', { upperCaseWords: ['LTDA'] }); // Empresa LTDA (comparação case-insensitive) +capitalize(' josé maria '); // José Maria (espaços simples repetidos são reduzidos; tabs/quebras de linha não seriam) ``` ## formatCurrency -Formata um número inteiro ou float para uma string no padrão BRL +Formata um número inteiro ou float para uma string no padrão BRL. Um `number` é formatado como está (sinal e decimais preservados). Uma entrada em `string` é lida pela mesma regra do `parseCurrency`, com a diferença de que um valor escrito sem nenhum separador permanece em unidades inteiras: o último `,` ou `.` seguido de 1 ou 2 dígitos é o separador decimal, todo outro `,` ou `.` é separador de milhar, e um `-` escrito antes do primeiro dígito é preservado. Assim `'1.234,56'` vira `1.234,56`, `'-10.5'` vira `-10,50` e `'1234'` vira `1.234,00`. `precision` é limitado ao intervalo `0..20` (o aceito pelo `Intl.NumberFormat`) e o padrão é 2. Um valor que não seja um número finito (`NaN`, `Infinity`, `-Infinity`) vira string vazia. As opções são tipadas como `FormatCurrencyOptions`. ```javascript import { formatCurrency } from '@brazilian-utils/brazilian-utils'; @@ -391,61 +793,162 @@ import { formatCurrency } from '@brazilian-utils/brazilian-utils'; formatCurrency(10); // 10,00 formatCurrency(10756.11); // 10.756,11 formatCurrency(10756.123, { precision: 3 }); // 10.756,123 +formatCurrency(1234.56, { symbol: true }); // R$ 1.234,56 +formatCurrency(-1050); // -1.050,00 (o sinal de um number é preservado) +formatCurrency('123456'); // 123.456,00 (dígitos simples são lidos como número inteiro) +formatCurrency('1.234,56'); // 123.456,00 (separadores de uma string são removidos, não interpretados) +formatCurrency('-10.5'); // 105,00 (o sinal de uma string é descartado) +formatCurrency(Number.NaN); // "NaN" ``` ## parseCurrency -Transforma uma string para o formato de inteiro ou float +Transforma uma string para o formato de inteiro ou float. O último `,` ou `.` seguido de 1 ou 2 dígitos (ou de até `precision` dígitos, quando esse valor for maior) é o separador decimal; todo outro `,` ou `.` é separador de milhar. Assim `'R$ 1.234,56'` vira `1234.56`, `'R$ 1.234'` vira `1234`, `'1,5'` vira `1.5` e `'12.34'` vira `12.34`. Um valor escrito sem nenhum separador mantém a convenção de centavos e é dividido por `10 ** precision`, então `'1234'` vira `12.34`. Um `-` escrito antes do primeiro dígito é preservado, então `'-R$ 1,00'` vira `-1`. `precision` (padrão 2, limitado a `0..20`) controla quantos dígitos são tratados como centavos. As opções são tipadas como `ParseCurrencyOptions`. ```javascript import { parseCurrency } from '@brazilian-utils/brazilian-utils'; -parseCurrency('10.756,11'); // 10756.11 -parseCurrency('R$ 10.59'); // 10.59 +parseCurrency('R$ 1.234,56'); // 1234.56 +parseCurrency('1234,56'); // 1234.56 +parseCurrency('R$ 0,50'); // 0.5 +parseCurrency('R$ 1.234'); // 12.34 (separadores não têm significado; só a quantidade de dígitos importa) +parseCurrency('1,5'); // 0.15 +parseCurrency('-R$ 1,00'); // -1 (o "-" inicial é preservado) +parseCurrency('R$ 1,001', { precision: 3 }); // 1.001 +parseCurrency(''); // 0 +``` + +## convertNumberToWords + +Formata um número inteiro por extenso em português do Brasil, ex.: `1235` vira `"mil, duzentos e trinta e cinco"`. Só são suportados inteiros de `-999999999999999` a `999999999999999` (999 trilhões em valor absoluto); fora desse intervalo, `NaN` ou um valor não finito retornam `""`. Um `value` não inteiro é truncado em direção a zero antes da conversão. `options.gender` (parte de `ConvertNumberToWordsOptions`) concorda "um/dois" e a centena ("duzentos/duzentas" etc.) com o substantivo que o número qualifica, com padrão `"masculine"`. `options.case` define a caixa do resultado: `"lower"` (padrão, sem alteração), `"sentence"` (só a primeira letra em maiúscula) ou `"upper"` (tudo em maiúscula pelo locale "pt-BR", preservando os acentos, ex.: "três" -> "TRÊS"). Um valor inválido de `gender`/`case` é ignorado e o padrão é usado. + +```javascript +import { convertNumberToWords } from '@brazilian-utils/brazilian-utils'; + +convertNumberToWords(123); // "cento e vinte e três" +convertNumberToWords(1001); // "mil e um" +convertNumberToWords(2000000); // "dois milhões" +convertNumberToWords(-42); // "menos quarenta e dois" +convertNumberToWords(2, { gender: 'feminine' }); // "duas" +convertNumberToWords(3, { case: 'upper' }); // "TRÊS" +convertNumberToWords(NaN); // "" +``` + +## convertCurrencyToWords + +Formata um valor monetário em Reais por extenso, no estilo usado para escrever o valor à mão em cheques e contratos, ex.: `1523.45` vira `"mil, quinhentos e vinte e três reais e quarenta e cinco centavos"`. O `value` é truncado (não arredondado) para 2 casas decimais. O substantivo no singular é usado para exatamente 1 ("um real", "um centavo") e "de" é inserido antes de "reais" quando o valor é um milhão, bilhão ou trilhão de reais redondo. Um valor que trunca para nada vira `"zero reais"`, sem o prefixo "menos"; qualquer outro valor negativo recebe o prefixo "menos", e uma entrada inválida retorna `""`. Acima de `Number.MAX_SAFE_INTEGER / 100` reais (cerca de 90 trilhões) um double não consegue carregar centavos, então o valor é lido como um número inteiro de reais. `options.case` (parte de `ConvertCurrencyToWordsOptions`) define a caixa do resultado: `"lower"` (padrão), `"sentence"` (só a primeira letra em maiúscula) ou `"upper"` (tudo em maiúscula, preservando os acentos). Um valor inválido de `case` é ignorado e `"lower"` é usado. + +```javascript +import { convertCurrencyToWords } from '@brazilian-utils/brazilian-utils'; + +convertCurrencyToWords(1523.45); // "mil, quinhentos e vinte e três reais e quarenta e cinco centavos" +convertCurrencyToWords(1); // "um real" +convertCurrencyToWords(0.01); // "um centavo" +convertCurrencyToWords(1000000); // "um milhão de reais" +convertCurrencyToWords(0); // "zero reais" +convertCurrencyToWords(-5.5); // "menos cinco reais e cinquenta centavos" +convertCurrencyToWords(-0.001); // "zero reais" (trunca para nada) +convertCurrencyToWords(1000, { case: 'upper' }); // "MIL REAIS" ``` ## getStates -Retorna todos os estados brasileiros. +Retorna todos os estados brasileiros, cada um com sigla, nome, código da região, nome da região e código IBGE de 2 dígitos da Unidade da Federação (`cUF`). A lista é ordenada por nome com `localeCompare` no locale "pt-BR", então nomes acentuados caem onde um leitor brasileiro espera: Pará, Paraíba, Paraná e Rio de Janeiro, Rio Grande do Norte, Rio Grande do Sul. Cada chamada retorna um array novo com objetos novos, então alterar o resultado nunca afeta chamadas seguintes. Exporta os tipos `State`, `StateCode` e `StateName`. ```javascript import { getStates } from '@brazilian-utils/brazilian-utils'; getStates(); // [ -// { code: 'AC', name: 'Acre' }, -// { code: 'AL', name: 'Alagoas' }, -// { code: 'AP', name: 'Amapá' }, -// { code: 'AM', name: 'Amazonas' }, -// { code: 'BA', name: 'Bahia' }, -// { code: 'CE', name: 'Ceará' }, -// { code: 'DF', name: 'Distrito Federal' }, -// { code: 'ES', name: 'Espírito Santo' }, -// { code: 'GO', name: 'Goiás' }, -// { code: 'MA', name: 'Maranhão' }, -// { code: 'MT', name: 'Mato Grosso' }, -// { code: 'MS', name: 'Mato Grosso do Sul' }, -// { code: 'MG', name: 'Minas Gerais' }, -// { code: 'PA', name: 'Pará' }, -// { code: 'PB', name: 'Paraíba' }, -// { code: 'PR', name: 'Paraná' }, -// { code: 'PE', name: 'Pernambuco' }, -// { code: 'PI', name: 'Piauí' }, -// { code: 'RJ', name: 'Rio de Janeiro' }, -// { code: 'RN', name: 'Rio Grande do Norte' }, -// { code: 'RS', name: 'Rio Grande do Sul' }, -// { code: 'RO', name: 'Rondônia' }, -// { code: 'RR', name: 'Roraima' }, -// { code: 'SC', name: 'Santa Catarina' }, -// { code: 'SP', name: 'São Paulo' }, -// { code: 'SE', name: 'Sergipe' }, -// { code: 'TO', name: 'Tocantins' }, +// { code: 'AC', name: 'Acre', regionCode: 'N', regionName: 'Norte', ibgeCode: 12 }, +// { code: 'AL', name: 'Alagoas', regionCode: 'NE', regionName: 'Nordeste', ibgeCode: 27 }, +// { code: 'AP', name: 'Amapá', regionCode: 'N', regionName: 'Norte', ibgeCode: 16 }, +// { code: 'AM', name: 'Amazonas', regionCode: 'N', regionName: 'Norte', ibgeCode: 13 }, +// { code: 'BA', name: 'Bahia', regionCode: 'NE', regionName: 'Nordeste', ibgeCode: 29 }, +// { code: 'CE', name: 'Ceará', regionCode: 'NE', regionName: 'Nordeste', ibgeCode: 23 }, +// { code: 'DF', name: 'Distrito Federal', regionCode: 'CO', regionName: 'Centro-Oeste', ibgeCode: 53 }, +// { code: 'ES', name: 'Espírito Santo', regionCode: 'SE', regionName: 'Sudeste', ibgeCode: 32 }, +// { code: 'GO', name: 'Goiás', regionCode: 'CO', regionName: 'Centro-Oeste', ibgeCode: 52 }, +// { code: 'MA', name: 'Maranhão', regionCode: 'NE', regionName: 'Nordeste', ibgeCode: 21 }, +// { code: 'MT', name: 'Mato Grosso', regionCode: 'CO', regionName: 'Centro-Oeste', ibgeCode: 51 }, +// { code: 'MS', name: 'Mato Grosso do Sul', regionCode: 'CO', regionName: 'Centro-Oeste', ibgeCode: 50 }, +// { code: 'MG', name: 'Minas Gerais', regionCode: 'SE', regionName: 'Sudeste', ibgeCode: 31 }, +// { code: 'PA', name: 'Pará', regionCode: 'N', regionName: 'Norte', ibgeCode: 15 }, +// { code: 'PB', name: 'Paraíba', regionCode: 'NE', regionName: 'Nordeste', ibgeCode: 25 }, +// { code: 'PR', name: 'Paraná', regionCode: 'S', regionName: 'Sul', ibgeCode: 41 }, +// { code: 'PE', name: 'Pernambuco', regionCode: 'NE', regionName: 'Nordeste', ibgeCode: 26 }, +// { code: 'PI', name: 'Piauí', regionCode: 'NE', regionName: 'Nordeste', ibgeCode: 22 }, +// { code: 'RJ', name: 'Rio de Janeiro', regionCode: 'SE', regionName: 'Sudeste', ibgeCode: 33 }, +// { code: 'RN', name: 'Rio Grande do Norte', regionCode: 'NE', regionName: 'Nordeste', ibgeCode: 24 }, +// { code: 'RS', name: 'Rio Grande do Sul', regionCode: 'S', regionName: 'Sul', ibgeCode: 43 }, +// { code: 'RO', name: 'Rondônia', regionCode: 'N', regionName: 'Norte', ibgeCode: 11 }, +// { code: 'RR', name: 'Roraima', regionCode: 'N', regionName: 'Norte', ibgeCode: 14 }, +// { code: 'SC', name: 'Santa Catarina', regionCode: 'S', regionName: 'Sul', ibgeCode: 42 }, +// { code: 'SP', name: 'São Paulo', regionCode: 'SE', regionName: 'Sudeste', ibgeCode: 35 }, +// { code: 'SE', name: 'Sergipe', regionCode: 'NE', regionName: 'Nordeste', ibgeCode: 28 }, +// { code: 'TO', name: 'Tocantins', regionCode: 'N', regionName: 'Norte', ibgeCode: 17 }, // ] ``` +## getStateByIbgeCode + +Retorna o estado brasileiro cujo código IBGE de 2 dígitos ("cUF", Código da Unidade da Federação) corresponde ao valor informado. É o mesmo código de UF de 2 dígitos presente no primeiro campo de toda chave de acesso de DF-e (NF-e, NFC-e, CT-e e MDF-e). Aceita string ou número, removendo caracteres não numéricos antes de comparar. Exporta o tipo `State`. + +```javascript +import { getStateByIbgeCode } from '@brazilian-utils/brazilian-utils'; + +getStateByIbgeCode('35'); +// { code: 'SP', name: 'São Paulo', regionCode: 'SE', regionName: 'Sudeste', ibgeCode: 35 } + +getStateByIbgeCode(11); +// { code: 'RO', name: 'Rondônia', regionCode: 'N', regionName: 'Norte', ibgeCode: 11 } + +getStateByIbgeCode('00'); // null +``` + +## getStateCodeByName + +Retorna a sigla de um estado brasileiro a partir do nome completo. A comparação ignora acentos, maiúsculas/minúsculas e espaços nas pontas, então `'sao paulo'`, `'SÃO PAULO'` e `' São Paulo '` resolvem para `'SP'`. Exporta o tipo `StateCode`. + +```javascript +import { getStateCodeByName } from '@brazilian-utils/brazilian-utils'; + +getStateCodeByName('São Paulo'); // 'SP' +getStateCodeByName('sao paulo'); // 'SP' +getStateCodeByName(' Rio de Janeiro '); // 'RJ' +getStateCodeByName('Neverland'); // null +``` + +## getStateNameByCode + +Retorna o nome completo de um estado brasileiro a partir da sigla. A comparação ignora maiúsculas/minúsculas e espaços nas pontas, então `'sp'`, `'SP'` e `' Sp '` resolvem para `'São Paulo'`. Exporta o tipo `StateName`. + +```javascript +import { getStateNameByCode } from '@brazilian-utils/brazilian-utils'; + +getStateNameByCode('SP'); // 'São Paulo' +getStateNameByCode('sp'); // 'São Paulo' +getStateNameByCode(' Rj '); // 'Rio de Janeiro' +getStateNameByCode('ZZ'); // null +``` + +## getTimezoneByState + +Retorna o nome do fuso horário do banco de dados IANA (tzdata) para um estado brasileiro, escolhido como o fuso da capital do estado. A comparação ignora maiúsculas/minúsculas e espaços nas pontas. Alguns fusos do tzdata cobrem mais de um estado: `America/Sao_Paulo` também cobre DF, GO, MG, ES, RJ, PR, SC e RS além de SP, e `America/Fortaleza` também cobre MA, PI, RN e PB além do CE. Pernambuco resolve para `America/Recife`, não `America/Noronha`: Fernando de Noronha é um distrito arquipélago de PE, não um estado próprio. + +```javascript +import { getTimezoneByState } from '@brazilian-utils/brazilian-utils'; + +getTimezoneByState('SP'); // 'America/Sao_Paulo' +getTimezoneByState('am'); // 'America/Manaus' +getTimezoneByState('AC'); // 'America/Rio_Branco' +getTimezoneByState('PE'); // 'America/Recife' +getTimezoneByState('ZZ'); // null +``` + ## getCities -Retorna as cidades brasileiras. Retorna todas as cidades se nenhum estado for fornecido, ou cidades de um estado específico. +Retorna as cidades brasileiras. Retorna todas as cidades se nenhum estado for fornecido, ou cidades de um estado específico. Cada chamada retorna um array novo, então alterar o resultado nunca afeta chamadas seguintes. Um código de estado desconhecido (ou um valor que não seja `StateCode`) retorna um array vazio em vez de lançar erro. ```javascript import { getCities } from '@brazilian-utils/brazilian-utils'; @@ -463,7 +966,7 @@ getCities(); // 'Abaré', // 'Abatiá', // 'Abdon Batista', -// ... 5460 more items +// ... 5561 more items // ] // Retorna todas as cidades brasileiras do estado de São Paulo (ordenadas alfabeticamente). @@ -483,9 +986,11 @@ getCities('SP'); // ] ``` +`getCities` embute os nomes dos 5571 municípios do IBGE (~153 KB minificado, ~49 KB com gzip) e é uma das poucas exceções pesadas neste pacote, que é tree-shakeable no restante. Veja [Tamanho do bundle](getting-started.md#tamanho-do-bundle) para saber como carregá-lo sob demanda via `@brazilian-utils/brazilian-utils/get-cities` em vez do import da raiz. + ## getHolidays -Retorna feriados brasileiros para um determinado ano. Retorna feriados nacionais e opcionalmente feriados estaduais. +Retorna feriados brasileiros para um determinado ano. Retorna feriados nacionais e opcionalmente feriados estaduais. Cada feriado (tipado como `Holiday`) tem um campo `type` (`HolidayType`: `"national"`, `"state"`, `"optional"` ou `"religious"`). O "Dia da Consciência Negra" (20 de novembro) é feriado nacional a partir de 2024 (Lei nº 14.759/2023). Antes disso, MT e RJ ainda trazem seu próprio feriado estadual chamado `"Consciência Negra"` na mesma data. Os resultados são memoizados por `year`/`stateCode`, mas cada chamada ainda retorna uma cópia nova. Um `stateCode` desconhecido/inválido é ignorado, retornando apenas os feriados nacionais. ```javascript import { getHolidays } from '@brazilian-utils/brazilian-utils'; @@ -493,10 +998,11 @@ import { getHolidays } from '@brazilian-utils/brazilian-utils'; // Obtém todos os feriados nacionais de 2024 getHolidays(2024); // [ -// { name: 'Ano novo', date: Date('2024-01-01') }, -// { name: 'Carnaval (terça-feira)', date: Date('2024-02-13') }, -// { name: 'Sexta-feira Santa', date: Date('2024-03-29') }, -// { name: 'Páscoa', date: Date('2024-03-31') }, +// { name: 'Ano novo', date: Date('2024-01-01'), type: 'national' }, +// { name: 'Carnaval (terça-feira)', date: Date('2024-02-13'), type: 'optional' }, +// { name: 'Sexta-feira Santa', date: Date('2024-03-29'), type: 'national' }, +// { name: 'Páscoa', date: Date('2024-03-31'), type: 'religious' }, +// { name: 'Dia da Consciência Negra', date: Date('2024-11-20'), type: 'national' }, // // ... mais feriados // ] @@ -507,13 +1013,14 @@ getHolidays({ year: 2024, stateCode: 'SP' }); ## isValidPassport -Verifica se um número de passaporte brasileiro é válido (2 letras maiúsculas seguidas de 6 dígitos). +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. ```javascript import { isValidPassport } from '@brazilian-utils/brazilian-utils'; isValidPassport('AB123456'); // true -isValidPassport('Ab123456'); // false +isValidPassport('ab123456'); // true (case-insensitive) +isValidPassport('AB-123.456'); // true (símbolos são ignorados) isValidPassport('12345678'); // false ``` @@ -602,7 +1109,7 @@ parseCnh('026503064-61'); // '02650306461' ## getCepInfoByAddress -Busca CEPs a partir de um endereço usando a ViaCEP. +Busca CEPs a partir de um endereço usando a ViaCEP. Lança `GetCepInfoByAddressValidationError` quando a UF, a cidade ou a rua estão ausentes/inválidas, `GetCepInfoByAddressNotFoundError` quando nenhum endereço corresponde à busca, e `GetCepInfoByAddressError` quando a própria ViaCEP responde com um status de erro HTTP. Uma requisição que não pode ser realizada (falha de transporte) rejeita com o erro original do `fetch`. ```javascript import { getCepInfoByAddress } from '@brazilian-utils/brazilian-utils'; @@ -627,13 +1134,14 @@ const ceps = await getCepInfoByAddress({ ## generateProcessoJuridico -Gera um número de processo jurídico válido de acordo com a definição do [CNJ](https://www.conjur.com.br/dl/resolucao-65-cnj.pdf). +Gera um número de processo jurídico válido de acordo com a definição do [CNJ](https://www.conjur.com.br/dl/resolucao-65-cnj.pdf). `year` deve estar entre o ano atual e 9999, `court` entre 1 e 9; valores fora do intervalo retornam `null`. Usa `Math.random()` internamente, então não é criptograficamente seguro. ```javascript import { generateProcessoJuridico } from '@brazilian-utils/brazilian-utils'; generateProcessoJuridico(); // '00020802520125150049' generateProcessoJuridico({ year: 2026, court: 5 }); // string | null +generateProcessoJuridico({ year: 10000 }); // null (ano fora do intervalo) ``` ## formatLegalNature @@ -648,7 +1156,7 @@ formatLegalNature('2062'); // 206-2 ## isValidLegalNature -Valida se um código de natureza jurídica existe na lista oficial. +Valida se um código de natureza jurídica existe na lista oficial. A tabela segue a "Natureza Jurídica 2021" do IBGE/CONCLA: 92 códigos oficiais mais 8 códigos legados mantidos por compatibilidade. Somente os caracteres de máscara usuais (hífens, pontos, espaços) são tolerados ao redor dos 4 dígitos, então `'2062a'` é rejeitado em vez de ser lido como `'2062'`. ```javascript import { isValidLegalNature } from '@brazilian-utils/brazilian-utils'; @@ -689,9 +1197,20 @@ const legalNatures = getLegalNatures(); legalNatures['2062']; // 'Sociedade Empresária Limitada' ``` +## getLegalNature + +Busca um código de natureza jurídica na tabela oficial do IBGE/CONCLA. + +```javascript +import { getLegalNature } from '@brazilian-utils/brazilian-utils'; + +getLegalNature('2062'); // { code: '2062', description: 'Sociedade Empresária Limitada' } +getLegalNature('0000'); // null +``` + ## generatePhone -Gera um telefone brasileiro aleatório. +Gera um telefone brasileiro aleatório. Aceita `'mobile'`, `'landline'` ou `'service'` (tipado como `GeneratePhoneType`); um número de serviço não tem DDD. Se omitido, gera aleatoriamente um celular ou um fixo, nunca um número de serviço. ```javascript import { generatePhone } from '@brazilian-utils/brazilian-utils'; @@ -699,11 +1218,12 @@ import { generatePhone } from '@brazilian-utils/brazilian-utils'; generatePhone(); // '11912345678' ou '1131234567' generatePhone('mobile'); // '11912345678' generatePhone('landline'); // '1131234567' +generatePhone('service'); // '08001234567' ou '40041234' ``` ## formatLicensePlate -Formata uma placa. Placas antigas brasileiras são retornadas com hífen e placas Mercosul permanecem normalizadas. +Formata uma placa. Placas antigas brasileiras (`LLLNNNN`) são retornadas com hífen e placas Mercosul (`LLLNLNN`) permanecem normalizadas. ```javascript import { formatLicensePlate } from '@brazilian-utils/brazilian-utils'; @@ -719,9 +1239,8 @@ Gera uma placa aleatória no formato escolhido. ```javascript import { generateLicensePlate } from '@brazilian-utils/brazilian-utils'; -generateLicensePlate(); // 'ABC1D23' +generateLicensePlate(); // 'ABC1D23' (Mercosul, o padrão) generateLicensePlate('LLLNNNN'); // 'ABC1234' -generateLicensePlate('LLLNNLN'); // 'ABC12D3' ``` ## getFormatLicensePlate @@ -733,10 +1252,13 @@ import { getFormatLicensePlate } from '@brazilian-utils/brazilian-utils'; getFormatLicensePlate('ABC-1234'); // 'LLLNNNN' getFormatLicensePlate('ABC1D23'); // 'LLLNLNN' -getFormatLicensePlate('ABC12D3'); // 'LLLNNLN' +getFormatLicensePlate('ABC12D3'); // null (não é uma sequência Mercosul) getFormatLicensePlate('INVALID'); // null +getFormatLicensePlate('ABC1234EXTRA'); // null (caracteres em excesso) ``` +`getFormatLicensePlate` exporta o tipo `LicensePlateFormat` (`"LLLNNNN" | "LLLNLNN"`); `generateLicensePlate` reexporta como `GenerateLicensePlateFormat`. + ## parseLicensePlate Remove separadores de uma placa, normaliza para letras maiúsculas e limita o resultado a 7 caracteres. @@ -747,6 +1269,18 @@ import { parseLicensePlate } from '@brazilian-utils/brazilian-utils'; parseLicensePlate('abc-1234'); // 'ABC1234' ``` +## convertLicensePlateToMercosul + +Converte uma placa brasileira no formato antigo (`LLLNNNN`) para o formato Mercosul (`LLLNLNN`), seguindo a tabela oficial de conversão: o dígito na 5ª posição vira uma letra (`0` a `9` mapeados para `A` a `J`). Retorna `""` quando o valor não é uma placa válida no formato antigo. + +```javascript +import { convertLicensePlateToMercosul } from '@brazilian-utils/brazilian-utils'; + +convertLicensePlateToMercosul('ABC1234'); // 'ABC1C34' +convertLicensePlateToMercosul('abc-1234'); // 'ABC1C34' +convertLicensePlateToMercosul('ABC1D23'); // '' (já está no formato Mercosul) +``` + ## generatePis Gera um PIS válido aleatório. @@ -759,7 +1293,7 @@ generatePis(); // '12345678901' ## 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. +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`. ```javascript import { getMunicipality } from '@brazilian-utils/brazilian-utils'; @@ -767,34 +1301,161 @@ import { getMunicipality } from '@brazilian-utils/brazilian-utils'; await getMunicipality({ code: '3550308' }); // ['São Paulo', 'SP'] -await getMunicipality({ municipalityName: 'São Paulo', uf: 'SP' }); +await getMunicipality({ municipalityName: 'sao paulo', uf: 'sp' }); // '3550308' + +await getMunicipality({ code: '0000000' }); +// null (código desconhecido) + +await getMunicipality({ code: '123' }); +// null (não tem 7 dígitos) +``` + +## getMunicipalities + +Retorna os municípios brasileiros publicados pelo IBGE. Retorna todos os municípios se nenhum estado for fornecido, ou os municípios de um estado específico. Cada município é retornado como `{ code, name, stateCode }`, onde `code` é o código IBGE de 7 dígitos do município. Os resultados são ordenados por nome com `localeCompare` no locale "pt-BR". Cada chamada retorna um array novo com objetos novos, então alterar o resultado nunca afeta chamadas seguintes. Um código de estado desconhecido retorna um array vazio em vez de lançar erro. + +```javascript +import { getMunicipalities } from '@brazilian-utils/brazilian-utils'; + +// Retorna todos os municípios brasileiros (ordenados por nome). +getMunicipalities(); +// [ +// { code: '5200050', name: 'Abadia de Goiás', stateCode: 'GO' }, +// { code: '3100104', name: 'Abadia dos Dourados', stateCode: 'MG' }, +// { code: '5200100', name: 'Abadiânia', stateCode: 'GO' }, +// { code: '3100203', name: 'Abaeté', stateCode: 'MG' }, +// { code: '1500107', name: 'Abaetetuba', stateCode: 'PA' }, +// ... mais 5566 itens +// ] + +// Retorna todos os municípios do estado de São Paulo. +getMunicipalities('SP'); +// [ +// { code: '3500105', name: 'Adamantina', stateCode: 'SP' }, +// { code: '3500204', name: 'Adolfo', stateCode: 'SP' }, +// { code: '3500303', name: 'Aguaí', stateCode: 'SP' }, +// { code: '3500402', name: 'Águas da Prata', stateCode: 'SP' }, +// { code: '3500501', name: 'Águas de Lindóia', stateCode: 'SP' }, +// ... mais 640 itens +// ] + +getMunicipalities('ZZ'); // [] +``` + +`getMunicipalities` embute todos os 5571 municípios do IBGE e seus códigos, então carrega o mesmo custo de tamanho de pacote que `getCities`. Veja [Tamanho do bundle](getting-started.md#tamanho-do-bundle) para saber como carregá-lo sob demanda via `@brazilian-utils/brazilian-utils/get-municipalities` em vez do import da raiz. + +## getMunicipalityByCode + +Busca um município brasileiro pelo código IBGE de 7 dígitos. Aceita o código como string ou número, removendo qualquer caractere não numérico antes de comparar. Retorna `{ code, name, stateCode }`, um objeto novo, ou `null` quando o código não tem 7 dígitos ou não corresponde a nenhum município conhecido. + +```javascript +import { getMunicipalityByCode } from '@brazilian-utils/brazilian-utils'; + +getMunicipalityByCode('3550308'); +// { code: '3550308', name: 'São Paulo', stateCode: 'SP' } + +getMunicipalityByCode(3550308); +// { code: '3550308', name: 'São Paulo', stateCode: 'SP' } + +getMunicipalityByCode('0000000'); // null (código desconhecido) +getMunicipalityByCode('123'); // null (não tem 7 dígitos) ``` ## isHoliday -Verifica se uma data específica é feriado brasileiro. +Verifica se uma data específica é feriado brasileiro. A verificação compara a data local do `targetDate` (ano/mês/dia lidos localmente), não seu instante UTC subjacente. Retorna `false` quando `targetDate` está ausente ou não é um `Date` válido. ```javascript import { isHoliday } from '@brazilian-utils/brazilian-utils'; -isHoliday({ targetDate: new Date('2024-01-01') }); // true -isHoliday({ targetDate: new Date('2024-07-09'), stateCode: 'SP' }); // true +isHoliday({ targetDate: new Date(2024, 0, 1) }); // true +isHoliday({ targetDate: new Date(2024, 6, 9), stateCode: 'SP' }); // true +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`. + +```javascript +import { isBusinessDay } from '@brazilian-utils/brazilian-utils'; + +isBusinessDay(new Date(2024, 0, 2)); // true (terça-feira, não é feriado) +isBusinessDay(new Date(2024, 0, 1)); // false (Ano novo) +isBusinessDay(new Date(2024, 0, 6)); // false (sábado) +isBusinessDay(new Date(2024, 1, 13)); // false (Carnaval, feriado opcional, conta por padrão) +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 (feriado estadual ignorado sem stateCode) +isBusinessDay(new Date('not a date')); // false +``` + +## addBusinessDays + +Adiciona um número de dias úteis brasileiros a uma data, pulando sábados, domingos e feriados brasileiros exatamente como `isBusinessDay` os define (mesmas opções `stateCode`/`includeOptional`). Retorna um novo `Date`; a `date` de entrada (parte de `AddBusinessDaysParams`) nunca é alterada, e seu horário é preservado no resultado. `days: 0` retorna um novo `Date` igual a `date`, sem alterações, mesmo quando `date` cai em um fim de semana ou feriado, isso reflete o comportamento verificado de [`addBusinessDays(date, 0)` do date-fns](https://date-fns.org/docs/addBusinessDays), que também não avança a entrada para o próximo dia útil. Um `days` negativo anda para trás, um dia útil por vez, também como no date-fns. Retorna `null` em caso de entrada inválida: uma `date` que não é um `Date` válido, um `days` que não é um número inteiro finito, ou um `stateCode` que não é uma string. + +```javascript +import { addBusinessDays } from '@brazilian-utils/brazilian-utils'; + +addBusinessDays({ date: new Date(2024, 0, 2, 12), days: 1 }); // Date, 2024-01-03 12:00 (o dia seguinte já é útil) +addBusinessDays({ date: new Date(2024, 11, 31, 12), days: 1 }); // Date, 2025-01-02 12:00 (2025-01-01 é Ano novo, pulado) +addBusinessDays({ date: new Date(2024, 0, 5, 12), days: -1 }); // Date, 2024-01-04 12:00 (anda para trás) +addBusinessDays({ date: new Date(2024, 0, 6, 12), days: 0 }); // Date, 2024-01-06 12:00 (sem alteração, mesmo sendo sábado) +addBusinessDays({ date: new Date(2024, 6, 8, 12), days: 1, stateCode: 'SP' }); // Date, 2024-07-10 12:00 (2024-07-09 é a Revolução Constitucionalista em SP, pulado) +addBusinessDays({ date: new Date('not a date'), days: 1 }); // null +addBusinessDays({ date: new Date(2024, 0, 2), days: 1.5 }); // null (não é um número inteiro) +``` + +## differenceInBusinessDays + +Conta o número de dias úteis brasileiros entre duas datas, refletindo a semântica de [`differenceInBusinessDays` do date-fns](https://date-fns.org/docs/differenceInBusinessDays) (verificada em seu código-fonte): `params.from` é contado quando ele próprio é um dia útil, `params.to` nunca é contado, e cada dia útil estritamente entre os dois é contado uma vez. Só a data de calendário de cada `Date` importa, o horário é ignorado. Os dias úteis são determinados exatamente como em `isBusinessDay` (mesmas opções `stateCode`/`includeOptional`). `from`/`to` no mesmo dia de calendário retornam `0`; um `to` anterior a `from` retorna um número negativo. Retorna `null` em caso de entrada inválida: um `from`/`to` que não é um `Date` válido, ou um `stateCode` que não é uma string. Os parâmetros são tipados como `DifferenceInBusinessDaysParams`. + +```javascript +import { differenceInBusinessDays } from '@brazilian-utils/brazilian-utils'; + +differenceInBusinessDays({ from: new Date(2024, 0, 1), to: new Date(2024, 0, 2) }); // 0 (01/01 é Ano novo) +differenceInBusinessDays({ from: new Date(2024, 0, 2), to: new Date(2024, 0, 3) }); // 1 (02/01 contado, uma terça-feira) +differenceInBusinessDays({ from: new Date(2024, 0, 3), to: new Date(2024, 0, 2) }); // -1 (to anterior a from) +differenceInBusinessDays({ from: new Date(2024, 0, 2), to: new Date(2024, 0, 2) }); // 0 (mesmo dia) +differenceInBusinessDays({ from: new Date(2024, 6, 8), to: new Date(2024, 6, 10), stateCode: 'SP' }); // 1 (09/07/2024 é feriado estadual em SP) +differenceInBusinessDays({ from: new Date('not a date'), to: new Date() }); // null +``` + +## convertDateToWords + +Formata uma data por extenso em português do Brasil, ex.: `"01/01/2024"` vira `"primeiro de janeiro de dois mil e vinte e quatro"`. Aceita um `Date` (lido pela sua data de calendário local, a mesma convenção usada por `isHoliday`) ou uma string no formato `"dd/mm/yyyy"` ou ISO `"yyyy-mm-dd"`. Com o `options.style` padrão `"full"`, o dia 1 é escrito como "primeiro" e os demais dias usam o número cardinal; com `"month"`, só o nome do mês é escrito por extenso e o dia/ano ficam em dígitos (o dia 1 como `"1º"`, ex.: `"2 de março de 2024"`, `"1º de janeiro de 2024"`). Os nomes dos meses ficam em minúsculo. No estilo `"full"` o ano é escrito por extenso sem a vírgula de milhar que `convertNumberToWords`/`convertCurrencyToWords` usam (`1999` vira `"mil novecentos e noventa e nove"`, não `"mil, novecentos e noventa e nove"`), do jeito que uma data é lida em voz alta. `options.weekday` (padrão `false`) prefixa o nome do dia da semana em pt-BR minúsculo seguido de vírgula (`"sábado, dois de março de dois mil e vinte e quatro"`), calculado a partir da data de calendário resolvida. `options.case` define a caixa de todo o resultado: `"lower"` (padrão), `"sentence"` (só a primeira letra em maiúscula) ou `"upper"` (tudo em maiúscula, preservando os acentos). Valores inválidos de `case`/`style` são ignorados e o padrão é usado; a antiga opção booleana `capitalize` foi removida em favor de `case: "sentence"`. O dia 29 de fevereiro é aceito nos anos bissextos do calendário gregoriano proléptico (divisíveis por 4, exceto séculos não divisíveis por 400). Retorna `""` para um `Date` inválido, uma string malformada, um dia/mês que não existe ou uma data anterior ao ano 1. + +```javascript +import { convertDateToWords } from '@brazilian-utils/brazilian-utils'; + +convertDateToWords('01/01/2024'); // "primeiro de janeiro de dois mil e vinte e quatro" +convertDateToWords('2024-01-02'); // "dois de janeiro de dois mil e vinte e quatro" +convertDateToWords(new Date(2024, 0, 1)); // "primeiro de janeiro de dois mil e vinte e quatro" +convertDateToWords('01/01/2024', { case: 'sentence' }); // "Primeiro de janeiro de dois mil e vinte e quatro" +convertDateToWords('02/03/2024', { style: 'month' }); // "2 de março de 2024" +convertDateToWords('01/01/2024', { style: 'month' }); // "1º de janeiro de 2024" +convertDateToWords('02/03/2024', { weekday: true }); // "sábado, dois de março de dois mil e vinte e quatro" +convertDateToWords('10/05/1999'); // "dez de maio de mil novecentos e noventa e nove" +convertDateToWords('31/04/2024'); // "" (abril tem 30 dias) +convertDateToWords('invalid'); // "" +convertDateToWords('29/02/1900'); // "" (1900 não é bissexto) ``` ## formatVoterId -Formata um título de eleitor. +Formata um título de eleitor. Usa por padrão o agrupamento de 12 dígitos `0000 0000 00 00`; quando o valor sanitizado tem 13 dígitos (títulos de São Paulo/Minas Gerais podem ter um número sequencial de 9 dígitos) é usado o agrupamento `0000 0000 0 00 00`. ```javascript import { formatVoterId } from '@brazilian-utils/brazilian-utils'; formatVoterId('123456780175'); // '1234 5678 01 75' +formatVoterId('1234567880191'); // '1234 5678 8 01 91' (título de 13 dígitos SP/MG) ``` ## isValidVoterId -Valida se um título de eleitor é válido. +Valida se um título de eleitor é válido. Aceita tanto o título padrão de 12 dígitos quanto o título de 13 dígitos emitido por São Paulo (UF `01`) e Minas Gerais (UF `02`). ```javascript import { generateVoterId, isValidVoterId } from '@brazilian-utils/brazilian-utils'; @@ -806,21 +1467,373 @@ isValidVoterId(voterId); // true ## generateVoterId -Gera um título de eleitor válido aleatório. Você pode opcionalmente informar a UF. +Gera um título de eleitor válido aleatório. Você pode opcionalmente informar a UF; uma UF desconhecida usa `"ZZ"` (título emitido no exterior) em vez de lançar erro. Usa `Math.random()` internamente, então não é criptograficamente seguro. ```javascript import { generateVoterId } from '@brazilian-utils/brazilian-utils'; -generateVoterId(); // título de eleitor aleatório válido +generateVoterId(); // título de eleitor aleatório válido (exterior, "ZZ") generateVoterId('SP'); // título de eleitor aleatório válido de São Paulo +generateVoterId('XX'); // usa "ZZ" em vez de lançar erro ``` ## parseVoterId -Remove a formatação do título de eleitor, mantém apenas os dígitos e limita o resultado a 12 dígitos. +Remove a formatação do título de eleitor, mantém apenas os dígitos e limita o resultado a 12 dígitos (13 quando os dígitos da UF identificam São Paulo ou Minas Gerais). ```javascript import { parseVoterId } from '@brazilian-utils/brazilian-utils'; parseVoterId('1234 5678 01 75'); // '123456780175' +parseVoterId('1234 5678 8 01 91'); // '1234567880191' (título de 13 dígitos SP/MG) +``` + +## 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. + +```javascript +import { isValidCns } from '@brazilian-utils/brazilian-utils'; + +isValidCns('123456789010001'); // true (definitivo) +isValidCns('700000000000005'); // true (provisório) +isValidCns('12345678901'); // false (tamanho inválido) +``` + +## formatCns + +Formata um número de CNS (Cartão Nacional de Saúde) nos grupos de exibição usuais de 3-4-4-4 dígitos separados por espaço. As opções são tipadas como `FormatCnsOptions`. + +```javascript +import { formatCns } from '@brazilian-utils/brazilian-utils'; + +formatCns('123456789010001'); // '123 4567 8901 0001' +formatCns(123456789010001); // '123 4567 8901 0001' +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 46/2015, 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). + +`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. + +```javascript +import { isValidCertidao } from '@brazilian-utils/brazilian-utils'; + +isValidCertidao('104539 01 55 2013 1 00012 021 0000123 21'); // true +isValidCertidao('09430001552010100020112000012087'); // true +isValidCertidao('104539 01 55 2013 1 00012 021 0000123 22'); // false (dígitos verificadores inválidos) +isValidCertidao('123456'); // false (tamanho inválido) +isValidCertidao('104539 01 55 2013 1 00012 021 0000123 21', { accept: ['birth'] }); // true +isValidCertidao('104539 01 55 2013 1 00012 021 0000123 21', { accept: ['death'] }); // false +``` + +## 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 46/2015, conforme listados em [ghiorzi.org](http://ghiorzi.org/DVnew.htm). + +```javascript +import { parseCertidao } from '@brazilian-utils/brazilian-utils'; + +parseCertidao('104539 01 55 2013 1 00012 021 0000123 21'); +// { +// registryCns: '104539', +// acervo: '01', +// service: '55', +// year: 2013, +// type: 'birth', +// typeCode: 1, +// book: '00012', +// page: '021', +// term: '0000123', +// checkDigits: '21' +// } + +parseCertidao('invalid'); // null +``` + +O resultado `Certidao` traz: + +| Chave | Descrição | +| --- | --- | +| `registryCns` | O CNS (Código Nacional de Serventia) de 6 dígitos da serventia que lavrou o ato. | +| `acervo` | Acervo a que o livro pertence: `"01"` acervo próprio, `"02"` acervo incorporado. | +| `service` | Serviço prestado pela serventia, `"55"` para registro civil das pessoas naturais. | +| `year` | Ano do registro, com 4 dígitos. | +| `type` | Livro a que o ato pertence: `"birth"`, `"marriage"`, `"religious-marriage"`, `"death"`, `"stillbirth"`, `"banns"`, `"other"`, `"emancipation"` ou `"interdiction"`. | +| `typeCode` | Código bruto do livro, de 1 a 9, como impresso na décima quinta posição da matrícula. | +| `book` | Número do livro, com 5 dígitos e zeros à esquerda. | +| `page` | Número da folha, com 3 dígitos e zeros à esquerda. | +| `term` | Número do termo, com 7 dígitos e zeros à esquerda. | +| `checkDigits` | Os 2 dígitos verificadores módulo 11 da matrícula. | + +## 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 46/2015. + +```javascript +import { formatCertidao } from '@brazilian-utils/brazilian-utils'; + +formatCertidao('10453901552013100012021000012321'); // 104539 01 55 2013 1 00012 021 0000123 21 +formatCertidao('104539.01.55.2013.1.00012.021.0000123-21'); // 104539 01 55 2013 1 00012 021 0000123 21 +formatCertidao('1552010100020112000012087', { pad: true }); // 000000 01 55 2010 1 00020 112 0000120 87 +``` + +## 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. + +```javascript +import { isValidCei } from '@brazilian-utils/brazilian-utils'; + +isValidCei('11.583.00249/85'); // true +isValidCei('277297118187'); // true +isValidCei(249859674386); // true +isValidCei('24.985.96743/68'); // false (dígito verificador inválido) +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. + +```javascript +import { formatCei } from '@brazilian-utils/brazilian-utils'; + +formatCei('277297118187'); // 27.729.71181/87 +formatCei(249859674386); // 24.985.96743/86 +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. + +```javascript +import { isValidCno } from '@brazilian-utils/brazilian-utils'; + +isValidCno('11.084.01680/62'); // true +isValidCno('111130137368'); // true +isValidCno(401800097960); // true +isValidCno('110840168063'); // false (dígito verificador inválido) +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. + +```javascript +import { formatCno } from '@brazilian-utils/brazilian-utils'; + +formatCno('111130137368'); // 11.113.01373/68 +formatCno(401800097960); // 40.180.00979/60 +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). + +```javascript +import { isValidCaepf } from '@brazilian-utils/brazilian-utils'; + +isValidCaepf('293.118.610/001-84'); // true +isValidCaepf('41142260000101'); // true +isValidCaepf(29311861000184); // true +isValidCaepf('29311861000185'); // false (dígitos verificadores inválidos) +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. + +```javascript +import { formatCaepf } from '@brazilian-utils/brazilian-utils'; + +formatCaepf('29311861000184'); // 293.118.610/001-84 +formatCaepf(41142260000101); // 411.422.600/001-01 +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). + +```javascript +import { isValidRegistroProfissional } from '@brazilian-utils/brazilian-utils'; + +isValidRegistroProfissional('123456/SP', { council: 'OAB' }); // true +isValidRegistroProfissional('123456-RJ', { council: 'OAB', stateCode: 'SP' }); // false (UF divergente) +isValidRegistroProfissional('06/12345', { council: 'CRP' }); // true +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. + +```javascript +import { isValidVin } from '@brazilian-utils/brazilian-utils'; + +isValidVin('1HGCM82633A004352'); // true +isValidVin('1m8gdm9axkp042788'); // true (dígito verificador X, minúsculo) +isValidVin('1HGCM82633A004353'); // false (dígito verificador inválido) +isValidVin('1HGCM8263IA004352'); // false (contém a letra excluída I) +``` + +## isValidCbo + +Valida se um código CBO (Classificação Brasileira de Ocupações) existe na tabela de ocupações do MTE. Aceita o código com ou sem a máscara de hífen, ou como número. + +```javascript +import { isValidCbo } from '@brazilian-utils/brazilian-utils'; + +isValidCbo('2124-05'); // true +isValidCbo('212405'); // true +isValidCbo(212405); // true +isValidCbo('000000'); // false +``` + +Os títulos das ocupações vêm das [tabelas oficiais da CBO 2002 publicadas pelo MTE](http://www.mtecbo.gov.br/cbosite/pages/downloads.jsf). + +## getCbo + +Consulta um código CBO (Classificação Brasileira de Ocupações) e retorna o título oficial da ocupação. + +```javascript +import { getCbo } from '@brazilian-utils/brazilian-utils'; + +getCbo('2124-05'); // { code: '212405', title: 'Analista de desenvolvimento de sistemas' } +getCbo('000000'); // null +``` + +Os títulos das ocupações vêm das [tabelas oficiais da CBO 2002 publicadas pelo MTE](http://www.mtecbo.gov.br/cbosite/pages/downloads.jsf). + +## isValidCnae + +Valida se um código de subclasse CNAE (Classificação Nacional de Atividades Econômicas) existe na tabela CNAE 2.3 publicada pelo IBGE. Aceita o código com ou sem a máscara `NNNN-N/NN`, ou como número. + +```javascript +import { isValidCnae } from '@brazilian-utils/brazilian-utils'; + +isValidCnae('6201-5/01'); // true +isValidCnae('6201501'); // true +isValidCnae('0000000'); // false +``` + +## formatCnae + +Formata um código de subclasse CNAE (Classificação Nacional de Atividades Econômicas). + +```javascript +import { formatCnae } from '@brazilian-utils/brazilian-utils'; + +formatCnae('6201501'); // 6201-5/01 +``` + +## getCnae + +Busca um código de subclasse CNAE (Classificação Nacional de Atividades Econômicas) e retorna seu código formatado e a descrição oficial. + +```javascript +import { getCnae } from '@brazilian-utils/brazilian-utils'; + +getCnae('6201501'); // { code: '6201-5/01', description: 'DESENVOLVIMENTO DE PROGRAMAS DE COMPUTADOR SOB ENCOMENDA' } +getCnae('0000000'); // null +``` + +## isValidNcm + +Valida se um código NCM (Nomenclatura Comum do Mercosul) existe na tabela vigente publicada pelo Siscomex/MDIC. Aceita o código com ou sem a máscara de pontos, ou como número. + +```javascript +import { isValidNcm } from '@brazilian-utils/brazilian-utils'; + +isValidNcm('8471.30.12'); // true +isValidNcm('84713012'); // true +isValidNcm('00000000'); // false +``` + +## formatNcm + +Formata um código NCM (Nomenclatura Comum do Mercosul). + +```javascript +import { formatNcm } from '@brazilian-utils/brazilian-utils'; + +formatNcm('84713012'); // 8471.30.12 +``` + +## isValidCfop + +Valida se um código CFOP (Código Fiscal de Operações e Prestações) existe na tabela oficial (Ajuste SINIEF 07/2001 e atualizações). + +```javascript +import { isValidCfop } from '@brazilian-utils/brazilian-utils'; + +isValidCfop('5102'); // true +isValidCfop('0000'); // false +``` + +## getCfop + +Busca um código CFOP (Código Fiscal de Operações e Prestações) e retorna seu código e a descrição oficial. + +```javascript +import { getCfop } from '@brazilian-utils/brazilian-utils'; + +getCfop('5102'); // { code: '5102', description: 'Venda de mercadoria adquirida ou recebida de terceiros' } +getCfop('0000'); // null +``` + +## isValidCst + +Valida um código de CST (Código de Situação Tributária) para um tributo. Informe o tributo em `options.tax`: + +| Tributo | Formato | Códigos aceitos | +| --- | --- | --- | +| `icms` | 3 dígitos (origem + CST) | origem `0`-`8` + um de `00`, `10`, `20`, `30`, `40`, `41`, `50`, `51`, `60`, `70`, `90` | +| `ipi` | 2 dígitos | `00`, `01`, `02`, `03`, `04`, `05`, `49`, `50`, `51`, `52`, `53`, `54`, `55`, `99` | +| `pis` | 2 dígitos | `01`-`09`, `49`, `50`-`56`, `60`-`67`, `70`-`75`, `98`, `99` | +| `cofins` | 2 dígitos | mesma tabela do `pis` | + +`options.tax` (parte de `IsValidCstOptions`) é opcional: omita-o para aceitar um código que exista em qualquer uma das quatro tabelas acima. + +```javascript +import { isValidCst } from '@brazilian-utils/brazilian-utils'; + +isValidCst('000', { tax: 'icms' }); // true +isValidCst('110', { tax: 'icms' }); // true +isValidCst('06', { tax: 'pis' }); // true +isValidCst('99', { tax: 'ipi' }); // true +isValidCst('110'); // true (encontrado na tabela icms, tax omitido) +isValidCst('999'); // false (não existe em nenhuma tabela) +``` + +## isValidCsosn + +Valida se um código de CSOSN (Código de Situação da Operação no Simples Nacional) é um dos 10 códigos definidos pelo Convênio ICMS 92/2015: `101`, `102`, `103`, `201`, `202`, `203`, `300`, `400`, `500` ou `900`. + +```javascript +import { isValidCsosn } from '@brazilian-utils/brazilian-utils'; + +isValidCsosn('101'); // true +isValidCsosn('999'); // false +``` + +## removeAccents + +Remove marcas diacríticas (acentos, tils, cedilhas) de uma string, decompondo cada caractere acentuado em sua letra base mais as marcas de combinação (Unicode NFD) e descartando essas marcas. + +```javascript +import { removeAccents } from '@brazilian-utils/brazilian-utils'; + +removeAccents('São Paulo'); // 'Sao Paulo' +removeAccents('Piauí'); // 'Piaui' +removeAccents('Ceará'); // 'Ceara' +removeAccents('Açaí'); // 'Acai' +removeAccents(''); // '' ``` diff --git a/docs/utilities.md b/docs/utilities.md index 7769f437..5c24dd6c 100644 --- a/docs/utilities.md +++ b/docs/utilities.md @@ -2,25 +2,29 @@ Here you will find all the utilities available for use. +> **Input handling:** no public function throws on `null`/`undefined` or a wrong-type value. `isValid*` predicates return `false`; `isHoliday` returns `false`; `getHolidays` returns `[]`; `generateProcessoJuridico` returns `null`; `getMunicipality` returns `null` for a malformed/unmatched lookup. Every other `format*`/`parse*` function (including `capitalize`) returns an empty value of its return type: `""` for strings, `0` for `parseCurrency`. `formatCurrency` returns `""` for a non-finite number. + ## isValidCpf -Check if CPF is valid. +Check if CPF is valid. Accepts the usual mask characters and whitespace between/around groups. ```javascript import { isValidCpf } from '@brazilian-utils/brazilian-utils'; isValidCpf('155151475'); // false +isValidCpf('111 444 777 35'); // true (whitespace mask) ``` ## formatCpf -Format CPF. +Format CPF. `options.obfuscate` (part of `FormatCpfOptions`) hides the first 3 digits and the 2 check digits (`***.456.789-**`), the gov.br / Receita Federal display convention, applied after `pad`. ```javascript import { formatCpf } from '@brazilian-utils/brazilian-utils'; formatCpf('74650688000'); // 746.506.880-00 formatCpf('746506880', { pad: true }); // 007.465.068-80 +formatCpf('12345678909', { obfuscate: true }); // ***.456.789-** ``` ## parseCpf @@ -45,17 +49,18 @@ generateCpf(); ## isValidCnpj -Check if CNPJ is valid. +Check if CNPJ is valid. Supports both the numeric format (`version: 1`, default) and the alphanumeric format (`version: 2`), and accepts the usual mask characters and whitespace. Options are typed as `IsValidCnpjOptions`. ```javascript import { isValidCnpj } from '@brazilian-utils/brazilian-utils'; isValidCnpj('15515147234255'); // false +isValidCnpj('q0slfmbd7vx439', { version: 2 }); // true (lowercase alphanumeric) ``` ## formatCnpj -Format CNPJ. +Format CNPJ. `options.obfuscate` (part of `FormatCnpjOptions`) hides the first 2 digits and the 2 check digits (`**.345.678/0001-**`), the gov.br / Receita Federal display convention. It applies to both versions and comes after `pad`. ```javascript import { formatCnpj } from '@brazilian-utils/brazilian-utils'; @@ -63,11 +68,12 @@ import { formatCnpj } from '@brazilian-utils/brazilian-utils'; formatCnpj('24522200000174'); // 24.522.200/0001-74 formatCnpj('245222000174', { pad: true }); // 00.245.222/0001-74 formatCnpj('12OUT345000199', { version: 2 }); // 12.OUT.345/0001-99 +formatCnpj('12345678000195', { obfuscate: true }); // **.345.678/0001-** ``` ## parseCnpj -Remove CNPJ formatting, return a normalized value, and cap the result to 14 characters. +Remove CNPJ formatting, return a normalized value, and cap the result to 14 characters. Options are typed as `ParseCnpjOptions`. ```javascript import { parseCnpj } from '@brazilian-utils/brazilian-utils'; @@ -78,12 +84,18 @@ parseCnpj('12.OUT.345/0001-99', { version: 2 }); // 12OUT345000199 ## isValidCep -Check if CEP ([brazilian postal code](https://en.wikipedia.org/wiki/C%C3%B3digo_de_Endere%C3%A7amento_Postal)) is valid. +Check if CEP ([brazilian postal code](https://en.wikipedia.org/wiki/C%C3%B3digo_de_Endere%C3%A7amento_Postal)) is valid. Accepts both `string` and `number` input; any spaces, dots and hyphens around/between the 8 digits are ignored, but any other character, a letter in particular, makes the value invalid. ```javascript import { isValidCep } from '@brazilian-utils/brazilian-utils'; -isValidCep('92500000'); // true +isValidCep('01310100'); // true +isValidCep('92500-000'); // true (hyphen between groups) +isValidCep('92.500-000'); // true (dot and hyphen) +isValidCep('013 10 100'); // true (spaces anywhere between the digits) +isValidCep(20040020); // true (number input) +isValidCep('9250000A'); // false (letters are rejected) +isValidCep('12345'); // false (invalid length) ``` ## generateCnpj @@ -98,28 +110,31 @@ generateCnpj(); ## isValidBoleto -Check if boleto ([brazilian payment method](https://en.wikipedia.org/wiki/Boleto)) is valid. +Check if boleto ([brazilian payment method](https://en.wikipedia.org/wiki/Boleto)) is valid. Supports both the 47 digit "cobrança bancária" boleto and the "boleto de arrecadação" (convênio/tributos): either its 48 digit linha digitável or its 44 digit barcode, both starting with `8`. ```javascript import { isValidBoleto } from '@brazilian-utils/brazilian-utils'; isValidBoleto('00190000090114971860168524522114675860000102656'); // true +isValidBoleto('846100000005246100291102005460339004695895061080'); // true (boleto de arrecadação) ``` ## formatBoleto -Format a boleto number. +Format a boleto number. The arrecadação (convênio/tributos) mask applies only to the 48 digit linha digitável starting with `8`; the 44 digit arrecadação barcode has no display grouping defined by FEBRABAN and keeps the "cobrança bancária" mask instead. ```javascript import { formatBoleto } from '@brazilian-utils/brazilian-utils'; formatBoleto('00190000090114971860168524522114675860000102656'); // 00190.00009 01149.718601 68524.522114 6 75860000102656 -formatBoleto('1900000901149', { pad: true }); // 00000.00000 00000.000019 00000.901149 0 00000000000000 +formatBoleto('1900000901149', { pad: true }); // 00000.00000 00000.000000 00000.000000 0 01900000901149 +formatBoleto('846100000005246100291102005460339004695895061080'); // 84610000000-5 24610029110-2 00546033900-4 69589506108-0 (48 digit arrecadação linha digitável) +formatBoleto('84610000000246100291100054603390069589506108'); // 84610.00000 02461.002911 00054.603390 0 69589506108 (44 digit arrecadação barcode keeps the bancária mask) ``` ## parseBoleto -Remove boleto formatting, keep only digits, and cap the result to 47 digits. +Remove boleto formatting, keep only digits, and cap the result to 47 digits (48 for boleto de arrecadação). ```javascript import { parseBoleto } from '@brazilian-utils/brazilian-utils'; @@ -129,23 +144,161 @@ parseBoleto('00190.00009 01149.718601 68524.522114 6 75860000102656'); // 001900 ## generateBoleto -Generate a valid random boleto. +Generate a valid random boleto. Pass `{ type: "arrecadacao" }` (typed as `GenerateBoletoOptions`) to generate a boleto de arrecadação instead of the default "bancario" (cobrança bancária) type. ```javascript import { generateBoleto } from '@brazilian-utils/brazilian-utils'; generateBoleto(); // "00190000090114971860168524522114675860000102656" +generateBoleto({ type: 'arrecadacao' }); // "846100000005246100291102005460339004695895061080" ``` ## getBoletoInfo -Extract information from a boleto (amount, expiration date, bank code). +Extract information from a boleto (amount, expiration date, bank code). Accepts an optional `{ referenceDate }` (typed as `GetBoletoInfoOptions`) to resolve the "fator de vencimento" cycle as of a specific date instead of now (the factor's date-base cycle reset on 22/02/2025 per FEBRABAN). For a boleto de arrecadação, the result, typed as `BoletoInfo`, has no `bankCode`/`expirationDate` and instead carries `type: "arrecadacao"`, `segment`, `value` and `hasEffectiveValue`. ```javascript import { getBoletoInfo } from '@brazilian-utils/brazilian-utils'; getBoletoInfo('00190000090114971860168524522114675860000102656'); // { amount: 102656, expirationDate: Date, bankCode: '001' } + +getBoletoInfo('00190000090114971860168524522114675860000102656', { + referenceDate: new Date(2018, 6, 1) +}); +// Resolves the fator de vencimento cycle as of 2018-07-01 + +getBoletoInfo('846100000005246100291102005460339004695895061080'); +// { amount: 2461, expirationDate: null, bankCode: '', type: 'arrecadacao', segment: 4, value: 24.61, hasEffectiveValue: true } +``` + +## 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. + +```javascript +import { isValidPixKey } from '@brazilian-utils/brazilian-utils'; + +isValidPixKey('123.456.789-09'); // true +isValidPixKey('fulano@example.com'); // true +isValidPixKey('(11) 98765-4321'); // true +isValidPixKey('71c7d9be-4b85-4e43-9f1c-1f3b8b4e9a2d'); // true +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`. + +```javascript +import { parsePixKey } from '@brazilian-utils/brazilian-utils'; + +parsePixKey('123.456.789-09'); // { type: 'cpf', value: '12345678909' } +parsePixKey('Fulano@Example.COM '); // { type: 'email', value: 'fulano@example.com' } +parsePixKey('(11) 98765-4321'); // { type: 'phone', value: '+5511987654321' } +parsePixKey('71C7D9BE-4B85-4E43-9F1C-1F3B8B4E9A2D'); +// { type: 'evp', value: '71c7d9be-4b85-4e43-9f1c-1f3b8b4e9a2d' } +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. + +```javascript +import { isValidPixPayload } from '@brazilian-utils/brazilian-utils'; + +isValidPixPayload( + '00020126580014br.gov.bcb.pix0136123e4567-e12b-12d1-a456-426655440000' + + '5204000053039865802BR5913Fulano de Tal6008BRASILIA62070503***63041D3D' +); // true + +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"`). + +```javascript +import { parsePixPayload } from '@brazilian-utils/brazilian-utils'; + +parsePixPayload( + '00020126580014br.gov.bcb.pix0136123e4567-e12b-12d1-a456-426655440000' + + '5204000053039865802BR5913Fulano de Tal6008BRASILIA62070503***63041D3D' +); +// { +// key: '123e4567-e12b-12d1-a456-426655440000', +// merchantName: 'Fulano de Tal', +// merchantCity: 'BRASILIA' +// } +``` + +## generatePixPayload + +Generates the payload of a Pix BR Code. Exactly one of `params.key` or `params.url` must be given (part of `GeneratePixPayloadParams`); `null` is returned when both or neither are given. + +When `params.key` is given, it is normalized to its DICT canonical form by `parsePixKey` and the payload is static. When `params.url` is given instead (the PSP location, without a URL scheme, e.g. `"pix.example.com/qr/v2/1234"`), the payload is dynamic per the Manual de Padrões para Iniciação do Pix: the URL takes the key's place in the "Merchant Account Information" template and the "Point of Initiation Method" object is set to dynamic (`12`); `params.url` can be at most 77 characters. `merchantName`, `merchantCity` and `description` are folded to printable ASCII (accents dropped) and truncated to what the BR Code allows. `parsePixPayload` already parses both shapes, so `parsePixPayload(generatePixPayload({ url, ... }))` round-trips. + +```javascript +import { generatePixPayload } from '@brazilian-utils/brazilian-utils'; + +generatePixPayload({ + key: '123.456.789-09', + merchantName: 'Fulano de Tal', + merchantCity: 'Brasília', + amount: 123.45 +}); +// "00020126330014br.gov.bcb.pix0111123456789095204000053039865406123.455802BR5913Fulano de Tal6008Brasilia62070503***630479EE" + +generatePixPayload({ + url: 'pix.example.com/qr/v2/1234', + merchantName: 'Fulano de Tal', + merchantCity: 'Brasília' +}); +// "00020101021226480014br.gov.bcb.pix2526pix.example.com/qr/v2/12345204000053039865802BR5913Fulano de Tal6008Brasilia62070503***6304FC66" + +generatePixPayload({ merchantName: 'Fulano', merchantCity: 'Brasília' }); // null (neither key nor url) +``` + +## 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. + +```javascript +import { isValidNfeKey } from '@brazilian-utils/brazilian-utils'; + +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) +``` + +## formatNfeKey + +Format a DF-e (NF-e, NFC-e, CT-e or MDF-e) access key into groups of 4 digits separated by spaces, the common display form printed on the DANFE. + +```javascript +import { formatNfeKey } from '@brazilian-utils/brazilian-utils'; + +formatNfeKey('35170458716523000119550010000000121000123458'); +// '3517 0458 7165 2300 0119 5500 1000 0000 1210 0012 3458' +``` + +## parseNfeKey + +Parses a DF-e access key into its fields (state, year, month, taxId, model, series, number, emissionType, code, checkDigit). Accepts the same input forms as `isValidNfeKey` and returns `null` when the key is not valid. The result is typed as `NfeKey`. + +```javascript +import { parseNfeKey } from '@brazilian-utils/brazilian-utils'; + +parseNfeKey('35170458716523000119550010000000121000123458'); +// { state: 'SP', year: 2017, month: 4, taxId: '58716523000119', model: '55', +// series: 1, number: 12, emissionType: 1, code: '00012345', checkDigit: 8 } + +parseNfeKey('invalid'); // null ``` ## isValidEmail @@ -160,45 +313,59 @@ isValidEmail('john.doe@hotmail.com'); // true ## isValidPhone -Check if phone number (mobile or landline) is valid. +Check if phone number (mobile or landline) is valid. A Brazilian country code (`+55`, `0055` or a bare `55`) is accepted and removed before validation, under the rule documented in `parsePhone`. `options.accept` (typed as `PhoneType[]`, part of `IsValidPhoneOptions`) picks which kinds of number count as valid and defaults to `['mobile', 'landline']`; add `'service'` to also accept the non-geographic numbers recognized by `isValidServicePhone`, or pass `[]` to accept none. ```javascript import { isValidPhone } from '@brazilian-utils/brazilian-utils'; isValidPhone('11900000000'); // true +isValidPhone('+55 11 98765-4321'); // true (country code accepted) +isValidPhone('08001234567'); // false (service numbers rejected by default) +isValidPhone('08001234567', { accept: ['service'] }); // true +isValidPhone('11900000000', { accept: [] }); // false ``` ## formatPhone -Format phone number according to Brazilian patterns. +Format phone number according to Brazilian patterns. `options.mask` (typed as `PhoneMask`) accepts `"sn"` (default, subscriber number only, 9 digits, no DDD), `"nanp"` (DDD + subscriber number, 11 digits), `"e164"` (`"+5511987654321"`), `"international"` (`"+55 11 98765-4321"`, the way a Brazilian number is printed for foreign callers), `"service"` (`"0800 123 4567"` or `"4004-1234"`, the conventional groupings for service numbers) or `"auto"`. `"auto"` picks `"international"` when `value` carries a Brazilian country code (`+55`, `0055` or a bare `55` followed by 10 or 11 digits), `"service"` when `value` is a service number, and otherwise falls back to the digit count: `"nanp"` when `value` has more digits than a bare subscriber number, `"sn"` when it does not. `"e164"` and `"international"` drop the country code from `value` first, under the rule documented in `parsePhone`, and fall back to the `"service"` presentation for a service number, since those have no E.164 form. If `value` includes a DDD, pass `{ mask: 'auto' }` (or `'nanp'`) explicitly, since the default `"sn"` mask assumes no DDD and silently truncates one if present. ```javascript import { formatPhone } from '@brazilian-utils/brazilian-utils'; -formatPhone('11900000000'); // 90000-0000 +formatPhone('987654321'); // 98765-4321 (default "sn", no DDD) formatPhone('11900000000', { mask: 'nanp' }); // (11) 90000-0000 -formatPhone('11900000000', { mask: 'auto' }); // Automatically detects mask based on length +formatPhone('11900000000', { mask: 'auto' }); // (11) 90000-0000 +formatPhone('11987654321', { mask: 'e164' }); // +5511987654321 +formatPhone('+5511987654321', { mask: 'international' }); // +55 11 98765-4321 +formatPhone('08001234567', { mask: 'service' }); // 0800 123 4567 +formatPhone('40041234', { mask: 'service' }); // 4004-1234 +formatPhone('+5511987654321', { mask: 'auto' }); // (55) 11987-6543 (BEWARE: "auto" does not detect the +55 prefix) +formatPhone('11900000000'); // 11900-0000 (BEWARE: default "sn" truncates a DDD-prefixed number) ``` ## parsePhone -Remove phone formatting, keep only digits, and cap the result to 11 digits. +Remove phone formatting, keep only digits, and cap the result to 11 digits. A Brazilian country code is stripped first, but only when the digits left behind are exactly 10 or 11 long, i.e. a plausible national number. The rule is length-based, not sign-based, so a number from area code 55 is not mistaken for a country code. ```javascript import { parsePhone } from '@brazilian-utils/brazilian-utils'; parsePhone('(11) 90000-0000'); // 11900000000 -parsePhone('+55 (11) 90000-0000'); // 55119000000 +parsePhone('+55 (11) 98765-4321'); // 11987654321 +parsePhone('5511987654321'); // 11987654321 +parsePhone('55987654321'); // 55987654321 (area code 55, not mistaken for the +55 country code) ``` ## isValidMobilePhone -Check if mobile phone number is valid. +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. ```javascript import { isValidMobilePhone } from '@brazilian-utils/brazilian-utils'; isValidMobilePhone('11900000000'); // true +isValidMobilePhone('11712345678', { version: 1 }); // true (legacy format) +isValidMobilePhone('11712345678', { version: 2 }); // false (v2 requires 9 as the first digit) ``` ## isValidLandlinePhone @@ -211,17 +378,63 @@ import { isValidLandlinePhone } from '@brazilian-utils/brazilian-utils'; 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. + +```javascript +import { isValidServicePhone } from '@brazilian-utils/brazilian-utils'; + +isValidServicePhone('0800 123 4567'); // true +isValidServicePhone('4004-1234'); // true +isValidServicePhone('190'); // true +isValidServicePhone('11987654321'); // false (geographic number) +``` + +## getAreaCodeInfo + +Get the state (and its region) a Brazilian DDD (area code) belongs to, out of the 67 DDDs in use under the Anatel Plano Geral de Numeração. Accepts a string or a number, stripping any non-digit characters before matching. Exports the `AreaCodeInfo` type. + +```javascript +import { getAreaCodeInfo } from '@brazilian-utils/brazilian-utils'; + +getAreaCodeInfo('11'); +// { areaCode: 11, stateCode: 'SP', stateName: 'São Paulo', region: 'Sudeste' } + +getAreaCodeInfo(21); +// { areaCode: 21, stateCode: 'RJ', stateName: 'Rio de Janeiro', region: 'Sudeste' } + +getAreaCodeInfo('68'); +// { areaCode: 68, stateCode: 'AC', stateName: 'Acre', region: 'Norte' } + +getAreaCodeInfo('00'); // null +``` + +## getAreaCodesByState + +Get every DDD (area code) that belongs to a given Brazilian state, under the Anatel Plano Geral de Numeração. The match is case-insensitive and the result is sorted in ascending order. + +```javascript +import { getAreaCodesByState } from '@brazilian-utils/brazilian-utils'; + +getAreaCodesByState('SP'); // [11, 12, 13, 14, 15, 16, 17, 18, 19] +getAreaCodesByState('ac'); // [68] +getAreaCodesByState('XX'); // [] +``` + ## isValidLicensePlate -Check if license plate is valid. Supports the old Brazilian format (ABC-1234) and the Mercosul formats for cars (ABC1D23) and motorcycles (ABC12D3). +Check if license plate is valid. Supports the old Brazilian format (ABC-1234) and the Mercosul format (ABC1D23), the single sequence Resolução CONTRAN nº 969/2022 defines for every vehicle, motorcycles included. ```javascript import { isValidLicensePlate } from '@brazilian-utils/brazilian-utils'; isValidLicensePlate('ABC1234'); // true (Brazilian format) isValidLicensePlate('ABC-1234'); // true (Brazilian format with hyphen) -isValidLicensePlate('ABC1D23'); // true (Mercosul car format) -isValidLicensePlate('ABC12D3'); // true (Mercosul motorcycle format) +isValidLicensePlate('ABC 1234'); // true (whitespace mask) +isValidLicensePlate('ABC1D23'); // true (Mercosul format) +isValidLicensePlate('ABC12D3'); // false (not a Mercosul sequence) +isValidLicensePlate('ABC1234EXTRA'); // false (too many characters) ``` ## isValidRenavam @@ -238,7 +451,7 @@ isValidRenavam('12345678901'); // false (invalid checksum) ## isValidPis -Check if PIS is valid. +Check if PIS is valid. Accepts the usual mask characters and whitespace. ```javascript import { isValidPis } from '@brazilian-utils/brazilian-utils'; @@ -289,12 +502,12 @@ parseCep('92500-000'); // 92500000 ## getAddressInfoByCep -Fetch address information for a given CEP using multiple providers. +Fetch address information for a given CEP using multiple providers. Defaults to `['viacep', 'brasilapi']`. The `'widenet'` provider is deprecated (its endpoint no longer responds) and excluded from the default list, but it can still be requested explicitly via `options.providers` (typed as `CepProvider[]`). The resolved address is typed as `AddressInfo`. ```javascript import { getAddressInfoByCep } from '@brazilian-utils/brazilian-utils'; -// Using all providers (default) +// Using the default providers (['viacep', 'brasilapi']) const address = await getAddressInfoByCep('01310100'); // { cep: '01310100', state: 'SP', city: 'São Paulo', neighborhood: 'Bela Vista', street: 'Avenida Paulista' } @@ -319,59 +532,243 @@ isValidProcessoJuridico('00020802520125150049'); // true ## formatProcessoJuridico -Format the processo jurídico number according to [CNJ's definition](https://www.conjur.com.br/dl/resolucao-65-cnj.pdf). +Format the processo jurídico number according to [CNJ's definition](https://www.conjur.com.br/dl/resolucao-65-cnj.pdf) (mask `NNNNNNN-DD.AAAA.J.TR.OOOO`). ```javascript import { formatProcessoJuridico } from '@brazilian-utils/brazilian-utils'; -formatProcessoJuridico('00020802520125150049'); // 0002080-25.2012.515.0049 +formatProcessoJuridico('00020802520125150049'); // 0002080-25.2012.5.15.0049 ``` ## parseProcessoJuridico -Remove processo jurídico formatting, keep only digits, and cap the result to 20 digits. +Remove processo jurídico formatting, keep only digits, and cap the result to 20 digits. Both the current CNJ mask (`NNNNNNN-DD.AAAA.J.TR.OOOO`) and the older one are accepted, since only the digits are kept. ```javascript import { parseProcessoJuridico } from '@brazilian-utils/brazilian-utils'; -parseProcessoJuridico('0002080-25.2012.515.0049'); // 00020802520125150049 +parseProcessoJuridico('0002080-25.2012.5.15.0049'); // 00020802520125150049 ``` ## isValidIe -Check if inscrição estadual (state registration) is valid. +Check if inscrição estadual (state registration) is valid. The state code is case-insensitive. Notable per-state rules: GO accepts prefixes `10`, `11` and `15`; PA accepts `15` and `75`-`79`; MS accepts `28` and `50`; SP has a produtor rural pattern `P0MMMSSSSD000`; TO uses 11-digit type codes (`01`, `02`, `03`, `99`). ```javascript import { isValidIe } from '@brazilian-utils/brazilian-utils'; isValidIe('AC', '0187634580933'); // false +isValidIe('go', '109161793'); // true (case-insensitive) ``` ## isValidBankAccount -Check if a Brazilian bank account is valid. Supports specific validation algorithms for major banks (Banco do Brasil, Itaú, Bradesco, Santander, Caixa Econômica Federal) and generic mod10/mod11 validation for other banks. +Check if a Brazilian bank account is valid. The `bankCode` must belong to the Banco Central do Brasil STR participants list (the same dataset used by `getBankByCode`), so an unassigned code such as `'999'` is always invalid. Banks are then validated in one of three ways: by their published check digit algorithm, by structure only (bank exists and the agency/account match the documented digit lengths, for banks that publish no check digit rule) or by a generic mod10/mod11 check, which stays the fallback for every other listed bank. + +Banks validated by their published check digit algorithm: + +| Bank | Code | Agency | Account | Notes | +| --- | --- | --- | --- | --- | +| Banco do Brasil | `001` | 4-5 digits | 8-10 digits | mod11 with weights 9..2; `digit` may be `"X"` | +| Santander | `033` | 4 digits | 8 digits | weights `9,7,3,1,0,0,9,7,1,3,1,9,7,3` over agency + `"00"` + account, tens discarded | +| Banrisul | `041` | 4 digits | 9 digits | weights `3,2,4,7,6,5,4,3,2`; remainder 0 gives `0` and remainder 1 gives `6`; `account` is tipo (2 digits) + conta (7 digits) | +| Caixa Econômica Federal | `104` | 4 digits | 11 digits | mod11 over agency + account; `account` is operação (3 digits) + conta (8 digits) | +| Bradesco | `237` | 4 digits | 7 digits | mod11 with weights 2..7; `digit` may be `"P"` (often rendered as `"0"`) | +| Nubank | `260` | 4 digits | 5-13 digits | Verhoeff check digit over the account, leading zeros dropped | +| Itaú Unibanco | `341` | 4 digits | 5 digits | mod10 over agency + account | +| HSBC / Kirton Bank | `399` | 4 digits | 6 digits | weights `8,9,2,3,4,5,6,7,8,9` over agency + account; remainder 10 gives `0` | +| Citibank | `745` | 4 digits | 10 digits | weights `11..2` over the account; remainder 0 or 1 gives `0` | + +Banks validated by structure only, because they publish no check digit rule. The agency (1-5 digits), the account (1-13 digits) and a single numeric `digit` are enough to make the account valid: + +| Bank | Code | | Bank | Code | +| --- | --- | --- | --- | --- | +| Inter | `077` | | PicPay | `380` | +| Ailos | `085` | | Cora | `403` | +| XP | `102` | | Pan | `623` | +| Unicred | `136` | | BV | `655` | +| Stone | `197` | | Daycoval | `707` | +| BTG Pactual | `208` | | Modal | `746` | +| Original | `212` | | Sicredi | `748` | +| PagBank | `290` | | Sicoob | `756` | +| BMG | `318` | | | | +| Mercado Pago | `323` | | | | +| C6 | `336` | | | | + +When `digit` has 2 characters, the generic fallback chains mod10 followed by mod11 over the account, the same way CPF/CNPJ check digits are chained. + +Sources: the "Regras de Validação de dígito verificador de agência e conta corrente" compendium, cross checked against `banktools-br` (Ruby), `luizalabs/heimdall` (Python) and `Xerpa/bran_checker` (Elixir). Each shipped algorithm agrees on at least two independent sources. ```javascript import { isValidBankAccount } from '@brazilian-utils/brazilian-utils'; isValidBankAccount({ bankCode: '001', - agency: '1234', - account: '12345678', - digit: '5' -}); // true (if valid Banco do Brasil account) + agency: '1584', + account: '00210169', + digit: '6' +}); // true (Banco do Brasil) isValidBankAccount({ bankCode: '341', + agency: '2545', + account: '02366', + digit: '1' +}); // true (Itaú) + +isValidBankAccount({ + bankCode: '104', + agency: '0647', + account: '00188888888', + digit: '7' +}); // true (Caixa: operação "001" + conta "88888888") + +isValidBankAccount({ + bankCode: '041', + agency: '2664', + account: '358507670', + digit: '6' +}); // true (Banrisul: tipo "35" + conta "8507670") + +isValidBankAccount({ + bankCode: '260', + agency: '0001', + account: '5216125', + digit: '0' +}); // true (Nubank, Verhoeff) + +isValidBankAccount({ + bankCode: '077', + agency: '0001', + account: '123456789', + digit: '0' +}); // true (Banco Inter, structure only) + +isValidBankAccount({ + bankCode: '077', + agency: '0001', + account: '123456789', + digit: 'X' +}); // false (a structure only bank still requires a numeric digit) + +isValidBankAccount({ + bankCode: '999', + agency: '1234', + account: '123456', + digit: '6' +}); // false (999 is not a Banco Central participant) + +isValidBankAccount({ + bankCode: '246', agency: '1234', - account: '12345', + account: '123456', digit: '6' -}); // true (if valid Itaú account) +}); // true (Banco ABC Brasil, generic mod10 fallback) +``` + +## getBanks + +Get every Brazilian bank with a compensation code (COMPE), published by Banco Central do Brasil in the [STR participants list](https://www.bcb.gov.br/pom/spb/estatistica/port/ParticipantesSTRport.csv). Each bank (typed as `Bank`) has a `code` (COMPE, 3 digits), an `ispb` (Identificador do Sistema de Pagamentos Brasileiro, 8 digits) and a `name`. Each call returns a fresh array of fresh objects, so mutating the result never affects subsequent calls. + +```javascript +import { getBanks } from '@brazilian-utils/brazilian-utils'; + +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 +// ] +``` + +## getBankByCode + +Look a Brazilian bank up by its compensation code (COMPE), published by Banco Central do Brasil in the [STR participants list](https://www.bcb.gov.br/pom/spb/estatistica/port/ParticipantesSTRport.csv). Accepts both `string` and `number` input, with or without leading zeros. Returns a fresh copy (typed as `Bank`) of the matching bank, or `null` when no bank has that code. + +```javascript +import { getBankByCode } from '@brazilian-utils/brazilian-utils'; + +getBankByCode('001'); // { code: '001', ispb: '00000000', name: 'Banco do Brasil S.A.' } +getBankByCode(1); // { code: '001', ispb: '00000000', name: 'Banco do Brasil S.A.' } +getBankByCode('999'); // null +``` + +## getBankByIspb + +Look a Brazilian bank up by its ISPB (Identificador do Sistema de Pagamentos Brasileiro), the 8 digit code published by Banco Central do Brasil in the [STR participants list](https://www.bcb.gov.br/pom/spb/estatistica/port/ParticipantesSTRport.csv). Unlike the COMPE code (`getBankByCode`), every SPB participant has an ISPB, including institutions with no COMPE code of their own. Accepts both `string` and `number` input, with or without leading zeros. Returns a fresh copy (typed as `Bank`) of the matching bank, or `null` when no bank has that ISPB. + +```javascript +import { getBankByIspb } from '@brazilian-utils/brazilian-utils'; + +getBankByIspb('00000000'); // { code: '001', ispb: '00000000', name: 'Banco do Brasil S.A.' } +getBankByIspb('60701190'); // { code: '341', ispb: '60701190', name: 'ITAÚ UNIBANCO S.A.' } +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. + +```javascript +import { isValidIban } from '@brazilian-utils/brazilian-utils'; + +isValidIban('BR1500000000000010932840814P2'); // true +isValidIban('BR15 0000 0000 0000 1093 2840 814P 2'); // true (grouping spaces) +isValidIban('BR1500000000000010932840814P3'); // false (bad check digits) +isValidIban('DE89370400440532013000'); // false (non Brazilian IBAN) +``` + +## formatIban + +Format a Brazilian IBAN by grouping it in blocks of 4 characters, the ISO 13616 "print" presentation used on statements and bank forms. Does not validate the check digits or the field layout; formats whatever is given, up to the 29 character length of a Brazilian IBAN, as far as it goes, so the function can also be used as an input mask. Use `isValidIban` to check validity. + +```javascript +import { formatIban } from '@brazilian-utils/brazilian-utils'; + +formatIban('BR1500000000000010932840814P2'); // 'BR15 0000 0000 0000 1093 2840 814P 2' +formatIban('br1500000000000010932840814p2'); // 'BR15 0000 0000 0000 1093 2840 814P 2' +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`. + +```javascript +import { parseIban } from '@brazilian-utils/brazilian-utils'; + +parseIban('BR1500000000000010932840814P2'); +// { +// countryCode: 'BR', +// checkDigits: '15', +// bankIspb: '00000000', +// branch: '00001', +// account: '0932840814', +// accountType: 'P', +// owner: '2' +// } + +parseIban('DE89370400440532013000'); // null (non Brazilian IBAN) +``` + +## isValidCreditCard + +Check if a payment card number is valid using the Luhn algorithm ([ISO/IEC 7812-1](https://www.iso.org/standard/70484.html)). Accepts the usual mask characters (spaces, hyphens) between digits. Performs no brand detection (Visa, Mastercard, Amex...), issuer range lookup or expiration/CVV checks, only the digit count (12 to 19) and the Luhn check digit. + +```javascript +import { isValidCreditCard } from '@brazilian-utils/brazilian-utils'; + +isValidCreditCard('4111111111111111'); // true (Visa test number) +isValidCreditCard('5555555555554444'); // true (Mastercard test number) +isValidCreditCard('378282246310005'); // true (American Express test number) +isValidCreditCard('4111 1111 1111 1111'); // true (spaced mask) +isValidCreditCard('4111111111111112'); // false (bad check digit) ``` ## capitalize -Transforms the first letter into a capital one of each word ignoring prepositions. +Transforms the first letter into a capital one of each word ignoring prepositions. Words are separated by whitespace, by `-` and by `/`, so `'MOGI-GUAÇU'` becomes `'Mogi-Guaçu'` and `'SANTANA/RS'` becomes `'Santana/Rs'`. Every run of whitespace (tabs, newlines, repeated spaces) collapses into a single space, and the leading and trailing whitespace is dropped. `options.upperCaseWords` defaults to `[]`, so no acronym is upper-cased unless you list it, and the comparison against both `upperCaseWords` and `lowerCaseWords` is case-insensitive (pt-BR locale). Options are typed as `CapitalizeOptions`. ```javascript import { capitalize } from '@brazilian-utils/brazilian-utils'; @@ -379,11 +776,16 @@ import { capitalize } from '@brazilian-utils/brazilian-utils'; capitalize('josé e maria'); // José e Maria capitalize('josé Ama MARIA', { lowerCaseWords: ['ama'] }); // José ama Maria capitalize('doc inválido', { upperCaseWords: ['DOC'] }); // DOC Inválido +capitalize('MOGI-GUAÇU'); // Mogi-guaçu ("-" does not start a new word) +capitalize('SANTANA/RS', { upperCaseWords: ['RS'] }); // Santana/rs ("SANTANA/RS" is a single word, so it doesn't match "RS") +capitalize('empresa ltda'); // Empresa Ltda (no default acronyms) +capitalize('empresa ltda', { upperCaseWords: ['LTDA'] }); // Empresa LTDA (case-insensitive match) +capitalize(' josé maria '); // José Maria (repeated plain spaces collapse; tabs/newlines would not) ``` ## formatCurrency -Formats an integer or float to a string in the BRL pattern +Formats an integer or float to a string in the BRL pattern. A `number` is formatted as-is (sign and decimals preserved). A `string` input is read by the same rule as `parseCurrency`, except that a value written without any separator stays in whole units: the last `,` or `.` followed by 1 to 2 digits is the decimal separator, every other `,` or `.` is a thousands separator, and a `-` written before the first digit is preserved. So `'1.234,56'` formats as `1.234,56`, `'-10.5'` as `-10,50` and `'1234'` as `1.234,00`. `precision` is clamped to `0..20` (the range `Intl.NumberFormat` accepts) and defaults to 2. A value that is not a finite number (`NaN`, `Infinity`, `-Infinity`) formats as an empty string. Options are typed as `FormatCurrencyOptions`. ```javascript import { formatCurrency } from '@brazilian-utils/brazilian-utils'; @@ -391,61 +793,162 @@ import { formatCurrency } from '@brazilian-utils/brazilian-utils'; formatCurrency(10); // 10,00 formatCurrency(10756.11); // 10.756,11 formatCurrency(10756.123, { precision: 3 }); // 10.756,123 +formatCurrency(1234.56, { symbol: true }); // R$ 1.234,56 +formatCurrency(-1050); // -1.050,00 (a number's sign is preserved) +formatCurrency('123456'); // 123.456,00 (a plain digit string is read as a whole number) +formatCurrency('1.234,56'); // 123.456,00 (a string's separators are stripped, not interpreted) +formatCurrency('-10.5'); // 105,00 (a string's sign is discarded) +formatCurrency(Number.NaN); // "NaN" ``` ## parseCurrency -Transforms a string to an integer or float format +Transforms a string to an integer or float format. The last `,` or `.` followed by 1 to 2 digits (or up to `precision` digits, when that is larger) is the decimal separator; every other `,` or `.` is a thousands separator. So `'R$ 1.234,56'` parses to `1234.56`, `'R$ 1.234'` to `1234`, `'1,5'` to `1.5` and `'12.34'` to `12.34`. A value written without any separator keeps the cents convention and is divided by `10 ** precision`, so `'1234'` parses to `12.34`. A `-` written before the first digit is preserved, so `'-R$ 1,00'` parses to `-1`. `precision` (default 2, clamped to `0..20`) controls how many digits are treated as minor units. Options are typed as `ParseCurrencyOptions`. ```javascript import { parseCurrency } from '@brazilian-utils/brazilian-utils'; -parseCurrency('10.756,11'); // 10756.11 -parseCurrency('R$ 10.59'); // 10.59 +parseCurrency('R$ 1.234,56'); // 1234.56 +parseCurrency('1234,56'); // 1234.56 +parseCurrency('R$ 0,50'); // 0.5 +parseCurrency('R$ 1.234'); // 12.34 (separators carry no meaning; only the digit count matters) +parseCurrency('1,5'); // 0.15 +parseCurrency('-R$ 1,00'); // -1 (a leading "-" is preserved) +parseCurrency('R$ 1,001', { precision: 3 }); // 1.001 +parseCurrency(''); // 0 +``` + +## convertNumberToWords + +Formats an integer as its Brazilian Portuguese cardinal number words ("por extenso"), e.g. `1235` becomes `"mil, duzentos e trinta e cinco"`. Only integers from `-999999999999999` to `999999999999999` (999 trillion in absolute value) are supported; anything outside that range, `NaN` or a non-finite value returns `""`. A non-integer `value` is truncated toward zero before conversion. `options.gender` (part of `ConvertNumberToWordsOptions`) agrees "um/dois" and the hundreds group ("duzentos/duzentas", etc.) with the noun the number qualifies, defaulting to `"masculine"`. `options.case` sets the letter case of the result: `"lower"` (default, unchanged), `"sentence"` (capitalizes only the first letter) or `"upper"` (uppercases everything with the "pt-BR" locale, keeping accents, e.g. "três" -> "TRÊS"). An invalid `gender`/`case` value is ignored and the default is used. + +```javascript +import { convertNumberToWords } from '@brazilian-utils/brazilian-utils'; + +convertNumberToWords(123); // "cento e vinte e três" +convertNumberToWords(1001); // "mil e um" +convertNumberToWords(2000000); // "dois milhões" +convertNumberToWords(-42); // "menos quarenta e dois" +convertNumberToWords(2, { gender: 'feminine' }); // "duas" +convertNumberToWords(3, { case: 'upper' }); // "TRÊS" +convertNumberToWords(NaN); // "" +``` + +## convertCurrencyToWords + +Formats a monetary amount in Brazilian Reais as its "por extenso" textual representation, the style used to write out the amount by hand on cheques and contracts, e.g. `1523.45` becomes `"mil, quinhentos e vinte e três reais e quarenta e cinco centavos"`. `value` is truncated (not rounded) to 2 decimal places. The singular noun is used for exactly 1 ("um real", "um centavo") and "de" is inserted before "reais" when the amount is a round million, billion or trillion of reais. An amount that truncates to nothing becomes `"zero reais"` with no "menos" prefix, any other negative amount is prefixed with "menos", and invalid input returns `""`. Above `Number.MAX_SAFE_INTEGER / 100` reais (about 90 trillion) a double cannot carry cents, so the amount is read as a whole number of reais. `options.case` (part of `ConvertCurrencyToWordsOptions`) sets the letter case of the result: `"lower"` (default), `"sentence"` (capitalizes only the first letter) or `"upper"` (uppercases everything, keeping accents). An invalid `case` value is ignored and `"lower"` is used. + +```javascript +import { convertCurrencyToWords } from '@brazilian-utils/brazilian-utils'; + +convertCurrencyToWords(1523.45); // "mil, quinhentos e vinte e três reais e quarenta e cinco centavos" +convertCurrencyToWords(1); // "um real" +convertCurrencyToWords(0.01); // "um centavo" +convertCurrencyToWords(1000000); // "um milhão de reais" +convertCurrencyToWords(0); // "zero reais" +convertCurrencyToWords(-5.5); // "menos cinco reais e cinquenta centavos" +convertCurrencyToWords(-0.001); // "zero reais" (truncates to nothing) +convertCurrencyToWords(1000, { case: 'upper' }); // "MIL REAIS" ``` ## getStates -Get all Brazilian states. +Get all Brazilian states, each with its two-letter code, name, region code, region name and 2-digit IBGE code of the Federative Unit (`cUF`). The list is sorted by name with `localeCompare` in the "pt-BR" locale, so accented names land where a Brazilian reader expects them: Pará, Paraíba, Paraná and Rio de Janeiro, Rio Grande do Norte, Rio Grande do Sul. Each call returns a fresh array of fresh objects, so mutating the result never affects subsequent calls. Exports the `State`, `StateCode` and `StateName` types. ```javascript import { getStates } from '@brazilian-utils/brazilian-utils'; getStates(); // [ -// { code: 'AC', name: 'Acre' }, -// { code: 'AL', name: 'Alagoas' }, -// { code: 'AP', name: 'Amapá' }, -// { code: 'AM', name: 'Amazonas' }, -// { code: 'BA', name: 'Bahia' }, -// { code: 'CE', name: 'Ceará' }, -// { code: 'DF', name: 'Distrito Federal' }, -// { code: 'ES', name: 'Espírito Santo' }, -// { code: 'GO', name: 'Goiás' }, -// { code: 'MA', name: 'Maranhão' }, -// { code: 'MT', name: 'Mato Grosso' }, -// { code: 'MS', name: 'Mato Grosso do Sul' }, -// { code: 'MG', name: 'Minas Gerais' }, -// { code: 'PA', name: 'Pará' }, -// { code: 'PB', name: 'Paraíba' }, -// { code: 'PR', name: 'Paraná' }, -// { code: 'PE', name: 'Pernambuco' }, -// { code: 'PI', name: 'Piauí' }, -// { code: 'RJ', name: 'Rio de Janeiro' }, -// { code: 'RN', name: 'Rio Grande do Norte' }, -// { code: 'RS', name: 'Rio Grande do Sul' }, -// { code: 'RO', name: 'Rondônia' }, -// { code: 'RR', name: 'Roraima' }, -// { code: 'SC', name: 'Santa Catarina' }, -// { code: 'SP', name: 'São Paulo' }, -// { code: 'SE', name: 'Sergipe' }, -// { code: 'TO', name: 'Tocantins' }, +// { code: 'AC', name: 'Acre', regionCode: 'N', regionName: 'Norte', ibgeCode: 12 }, +// { code: 'AL', name: 'Alagoas', regionCode: 'NE', regionName: 'Nordeste', ibgeCode: 27 }, +// { code: 'AP', name: 'Amapá', regionCode: 'N', regionName: 'Norte', ibgeCode: 16 }, +// { code: 'AM', name: 'Amazonas', regionCode: 'N', regionName: 'Norte', ibgeCode: 13 }, +// { code: 'BA', name: 'Bahia', regionCode: 'NE', regionName: 'Nordeste', ibgeCode: 29 }, +// { code: 'CE', name: 'Ceará', regionCode: 'NE', regionName: 'Nordeste', ibgeCode: 23 }, +// { code: 'DF', name: 'Distrito Federal', regionCode: 'CO', regionName: 'Centro-Oeste', ibgeCode: 53 }, +// { code: 'ES', name: 'Espírito Santo', regionCode: 'SE', regionName: 'Sudeste', ibgeCode: 32 }, +// { code: 'GO', name: 'Goiás', regionCode: 'CO', regionName: 'Centro-Oeste', ibgeCode: 52 }, +// { code: 'MA', name: 'Maranhão', regionCode: 'NE', regionName: 'Nordeste', ibgeCode: 21 }, +// { code: 'MT', name: 'Mato Grosso', regionCode: 'CO', regionName: 'Centro-Oeste', ibgeCode: 51 }, +// { code: 'MS', name: 'Mato Grosso do Sul', regionCode: 'CO', regionName: 'Centro-Oeste', ibgeCode: 50 }, +// { code: 'MG', name: 'Minas Gerais', regionCode: 'SE', regionName: 'Sudeste', ibgeCode: 31 }, +// { code: 'PA', name: 'Pará', regionCode: 'N', regionName: 'Norte', ibgeCode: 15 }, +// { code: 'PB', name: 'Paraíba', regionCode: 'NE', regionName: 'Nordeste', ibgeCode: 25 }, +// { code: 'PR', name: 'Paraná', regionCode: 'S', regionName: 'Sul', ibgeCode: 41 }, +// { code: 'PE', name: 'Pernambuco', regionCode: 'NE', regionName: 'Nordeste', ibgeCode: 26 }, +// { code: 'PI', name: 'Piauí', regionCode: 'NE', regionName: 'Nordeste', ibgeCode: 22 }, +// { code: 'RJ', name: 'Rio de Janeiro', regionCode: 'SE', regionName: 'Sudeste', ibgeCode: 33 }, +// { code: 'RN', name: 'Rio Grande do Norte', regionCode: 'NE', regionName: 'Nordeste', ibgeCode: 24 }, +// { code: 'RS', name: 'Rio Grande do Sul', regionCode: 'S', regionName: 'Sul', ibgeCode: 43 }, +// { code: 'RO', name: 'Rondônia', regionCode: 'N', regionName: 'Norte', ibgeCode: 11 }, +// { code: 'RR', name: 'Roraima', regionCode: 'N', regionName: 'Norte', ibgeCode: 14 }, +// { code: 'SC', name: 'Santa Catarina', regionCode: 'S', regionName: 'Sul', ibgeCode: 42 }, +// { code: 'SP', name: 'São Paulo', regionCode: 'SE', regionName: 'Sudeste', ibgeCode: 35 }, +// { code: 'SE', name: 'Sergipe', regionCode: 'NE', regionName: 'Nordeste', ibgeCode: 28 }, +// { code: 'TO', name: 'Tocantins', regionCode: 'N', regionName: 'Norte', ibgeCode: 17 }, // ] ``` +## getStateByIbgeCode + +Get the Brazilian state whose 2-digit IBGE code ("cUF", the Código da Unidade da Federação) matches the given value. This is the same 2-digit UF code found in the first field of every DF-e access key (chave de acesso) issued for NF-e, NFC-e, CT-e and MDF-e documents. Accepts a string or a number, stripping any non-digit characters before matching. Exports the `State` type. + +```javascript +import { getStateByIbgeCode } from '@brazilian-utils/brazilian-utils'; + +getStateByIbgeCode('35'); +// { code: 'SP', name: 'São Paulo', regionCode: 'SE', regionName: 'Sudeste', ibgeCode: 35 } + +getStateByIbgeCode(11); +// { code: 'RO', name: 'Rondônia', regionCode: 'N', regionName: 'Norte', ibgeCode: 11 } + +getStateByIbgeCode('00'); // null +``` + +## getStateCodeByName + +Get the two-letter code (sigla) of a Brazilian state given its full name. The match is accent-insensitive, case-insensitive and ignores leading/trailing whitespace, so `'sao paulo'`, `'SÃO PAULO'` and `' São Paulo '` all resolve to `'SP'`. Exports the `StateCode` type. + +```javascript +import { getStateCodeByName } from '@brazilian-utils/brazilian-utils'; + +getStateCodeByName('São Paulo'); // 'SP' +getStateCodeByName('sao paulo'); // 'SP' +getStateCodeByName(' Rio de Janeiro '); // 'RJ' +getStateCodeByName('Neverland'); // null +``` + +## getStateNameByCode + +Get the full name of a Brazilian state given its two-letter code (sigla). The match is case-insensitive and ignores leading/trailing whitespace, so `'sp'`, `'SP'` and `' Sp '` all resolve to `'São Paulo'`. Exports the `StateName` type. + +```javascript +import { getStateNameByCode } from '@brazilian-utils/brazilian-utils'; + +getStateNameByCode('SP'); // 'São Paulo' +getStateNameByCode('sp'); // 'São Paulo' +getStateNameByCode(' Rj '); // 'Rio de Janeiro' +getStateNameByCode('ZZ'); // null +``` + +## getTimezoneByState + +Get the IANA time zone database name (tzdata zone) for a Brazilian state, chosen as the zone of the state capital. The match is case-insensitive and ignores leading/trailing whitespace. 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. + +```javascript +import { getTimezoneByState } from '@brazilian-utils/brazilian-utils'; + +getTimezoneByState('SP'); // 'America/Sao_Paulo' +getTimezoneByState('am'); // 'America/Manaus' +getTimezoneByState('AC'); // 'America/Rio_Branco' +getTimezoneByState('PE'); // 'America/Recife' +getTimezoneByState('ZZ'); // null +``` + ## getCities -Get Brazilian cities. Returns all cities if no state is provided, or cities from a specific state. +Get Brazilian cities. Returns all cities if no state is provided, or cities from a specific state. Each call returns a fresh array, so mutating the result never affects subsequent calls. An unknown state code (or a non-`StateCode` value) returns an empty array instead of throwing. ```javascript import { getCities } from '@brazilian-utils/brazilian-utils'; @@ -463,7 +966,7 @@ getCities(); // 'Abaré', // 'Abatiá', // 'Abdon Batista', -// ... 5460 more items +// ... 5561 more items // ] // Return all Brazilian cities of the São Paulo state (sorted alphabetically). @@ -483,9 +986,11 @@ getCities('SP'); // ] ``` +`getCities` embeds all 5571 IBGE municipality names (~153 KB minified, ~49 KB gzipped) and is one of the few heavy exceptions in this otherwise tree-shakeable package. See [Bundle size](getting-started.md#bundle-size) for how to lazy-load it via `@brazilian-utils/brazilian-utils/get-cities` instead of the root import. + ## getHolidays -Get Brazilian holidays for a given year. Returns national holidays and optionally state-specific holidays. +Get Brazilian holidays for a given year. Returns national holidays and optionally state-specific holidays. Each holiday (typed as `Holiday`) has a `type` field (`HolidayType`: `"national"`, `"state"`, `"optional"` or `"religious"`). "Dia da Consciência Negra" (Nov 20) is a national holiday from 2024 onward (Lei nº 14.759/2023). Before that, MT and RJ still carry their own state-level entry named `"Consciência Negra"` on the same date. Results are memoized per `year`/`stateCode`, but each call still returns a fresh copy. An unknown/invalid `stateCode` is ignored, returning national holidays only. ```javascript import { getHolidays } from '@brazilian-utils/brazilian-utils'; @@ -493,10 +998,11 @@ import { getHolidays } from '@brazilian-utils/brazilian-utils'; // Get all national holidays for 2024 getHolidays(2024); // [ -// { name: 'Ano novo', date: Date('2024-01-01') }, -// { name: 'Carnaval (terça-feira)', date: Date('2024-02-13') }, -// { name: 'Sexta-feira Santa', date: Date('2024-03-29') }, -// { name: 'Páscoa', date: Date('2024-03-31') }, +// { name: 'Ano novo', date: Date('2024-01-01'), type: 'national' }, +// { name: 'Carnaval (terça-feira)', date: Date('2024-02-13'), type: 'optional' }, +// { name: 'Sexta-feira Santa', date: Date('2024-03-29'), type: 'national' }, +// { name: 'Páscoa', date: Date('2024-03-31'), type: 'religious' }, +// { name: 'Dia da Consciência Negra', date: Date('2024-11-20'), type: 'national' }, // // ... more holidays // ] @@ -507,13 +1013,14 @@ getHolidays({ year: 2024, stateCode: 'SP' }); ## isValidPassport -Check if a Brazilian passport number is valid (2 uppercase letters followed by 6 digits). +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. ```javascript import { isValidPassport } from '@brazilian-utils/brazilian-utils'; isValidPassport('AB123456'); // true -isValidPassport('Ab123456'); // false +isValidPassport('ab123456'); // true (case-insensitive) +isValidPassport('AB-123.456'); // true (symbols are ignored) isValidPassport('12345678'); // false ``` @@ -602,7 +1109,7 @@ parseCnh('026503064-61'); // '02650306461' ## getCepInfoByAddress -Fetch CEPs from an address using ViaCEP. +Fetch CEPs from an address using ViaCEP. Throws `GetCepInfoByAddressValidationError` when the UF, city or street is missing/invalid, `GetCepInfoByAddressNotFoundError` when no address matches the query, and `GetCepInfoByAddressError` when ViaCEP itself answers with an HTTP error status. A request that cannot be performed at all (a transport failure) rejects with the underlying `fetch` error instead. ```javascript import { getCepInfoByAddress } from '@brazilian-utils/brazilian-utils'; @@ -627,13 +1134,14 @@ const ceps = await getCepInfoByAddress({ ## generateProcessoJuridico -Generate a valid random processo jurídico number according to [CNJ's definition](https://www.conjur.com.br/dl/resolucao-65-cnj.pdf). +Generate a valid random processo jurídico number according to [CNJ's definition](https://www.conjur.com.br/dl/resolucao-65-cnj.pdf). `year` must be between the current year and 9999, `court` between 1 and 9; out-of-range values return `null`. Uses `Math.random()` internally, so it is not cryptographically secure. ```javascript import { generateProcessoJuridico } from '@brazilian-utils/brazilian-utils'; generateProcessoJuridico(); // '00020802520125150049' generateProcessoJuridico({ year: 2026, court: 5 }); // string | null +generateProcessoJuridico({ year: 10000 }); // null (year out of range) ``` ## formatLegalNature @@ -648,7 +1156,7 @@ formatLegalNature('2062'); // 206-2 ## isValidLegalNature -Check if a legal nature code exists in the official list. +Check if a legal nature code exists in the official list. The table follows IBGE/CONCLA's "Natureza Jurídica 2021": 92 official codes plus 8 legacy codes kept for backwards compatibility. Only the usual mask characters (hyphens, dots, whitespace) are tolerated around the 4 digits, so `'2062a'` is rejected instead of being read as `'2062'`. ```javascript import { isValidLegalNature } from '@brazilian-utils/brazilian-utils'; @@ -689,9 +1197,20 @@ const legalNatures = getLegalNatures(); legalNatures['2062']; // 'Sociedade Empresária Limitada' ``` +## getLegalNature + +Look a legal nature code up in the official IBGE/CONCLA table. + +```javascript +import { getLegalNature } from '@brazilian-utils/brazilian-utils'; + +getLegalNature('2062'); // { code: '2062', description: 'Sociedade Empresária Limitada' } +getLegalNature('0000'); // null +``` + ## generatePhone -Generate a random Brazilian phone number. +Generate a random Brazilian phone number. Accepts `'mobile'`, `'landline'` or `'service'` (typed as `GeneratePhoneType`); a service number has no DDD. Omitted, it randomly generates a mobile or a landline, never a service number. ```javascript import { generatePhone } from '@brazilian-utils/brazilian-utils'; @@ -699,11 +1218,12 @@ import { generatePhone } from '@brazilian-utils/brazilian-utils'; generatePhone(); // '11912345678' or '1131234567' generatePhone('mobile'); // '11912345678' generatePhone('landline'); // '1131234567' +generatePhone('service'); // '08001234567' or '40041234' ``` ## formatLicensePlate -Format a license plate. Old Brazilian plates are returned with a hyphen and Mercosul plates stay normalized. +Format a license plate. Old Brazilian plates (`LLLNNNN`) are returned with a hyphen and Mercosul plates (`LLLNLNN`) stay normalized. ```javascript import { formatLicensePlate } from '@brazilian-utils/brazilian-utils'; @@ -719,9 +1239,8 @@ Generate a random license plate in the chosen format. ```javascript import { generateLicensePlate } from '@brazilian-utils/brazilian-utils'; -generateLicensePlate(); // 'ABC1D23' +generateLicensePlate(); // 'ABC1D23' (Mercosul, the default) generateLicensePlate('LLLNNNN'); // 'ABC1234' -generateLicensePlate('LLLNNLN'); // 'ABC12D3' ``` ## getFormatLicensePlate @@ -733,10 +1252,13 @@ import { getFormatLicensePlate } from '@brazilian-utils/brazilian-utils'; getFormatLicensePlate('ABC-1234'); // 'LLLNNNN' getFormatLicensePlate('ABC1D23'); // 'LLLNLNN' -getFormatLicensePlate('ABC12D3'); // 'LLLNNLN' +getFormatLicensePlate('ABC12D3'); // null (not a Mercosul sequence) getFormatLicensePlate('INVALID'); // null +getFormatLicensePlate('ABC1234EXTRA'); // null (too many characters) ``` +`getFormatLicensePlate` exports the `LicensePlateFormat` type (`"LLLNNNN" | "LLLNLNN"`); `generateLicensePlate` re-exports it as `GenerateLicensePlateFormat`. + ## parseLicensePlate Remove separators from a license plate, normalize it to uppercase, and cap it to 7 characters. @@ -747,6 +1269,18 @@ import { parseLicensePlate } from '@brazilian-utils/brazilian-utils'; parseLicensePlate('abc-1234'); // 'ABC1234' ``` +## convertLicensePlateToMercosul + +Convert an old format Brazilian license plate (`LLLNNNN`) to the Mercosul format (`LLLNLNN`), following the official conversion table: the digit in the 5th position becomes a letter (`0` through `9` mapping to `A` through `J`). Returns `""` when the value is not a valid old format license plate. + +```javascript +import { convertLicensePlateToMercosul } from '@brazilian-utils/brazilian-utils'; + +convertLicensePlateToMercosul('ABC1234'); // 'ABC1C34' +convertLicensePlateToMercosul('abc-1234'); // 'ABC1C34' +convertLicensePlateToMercosul('ABC1D23'); // '' (already Mercosul) +``` + ## generatePis Generate a valid random PIS. @@ -759,7 +1293,7 @@ generatePis(); // '12345678901' ## getMunicipality -Get municipality information by IBGE code, or get an IBGE code from municipality name and UF. +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`. ```javascript import { getMunicipality } from '@brazilian-utils/brazilian-utils'; @@ -767,34 +1301,161 @@ import { getMunicipality } from '@brazilian-utils/brazilian-utils'; await getMunicipality({ code: '3550308' }); // ['São Paulo', 'SP'] -await getMunicipality({ municipalityName: 'São Paulo', uf: 'SP' }); +await getMunicipality({ municipalityName: 'sao paulo', uf: 'sp' }); // '3550308' + +await getMunicipality({ code: '0000000' }); +// null (unknown code) + +await getMunicipality({ code: '123' }); +// null (not 7 digits) +``` + +## getMunicipalities + +Get Brazilian municipalities published by the IBGE. Returns all municipalities if no state is provided, or municipalities from a specific state. Each municipality is returned as `{ code, name, stateCode }`, where `code` is the 7-digit IBGE municipality code. Results are sorted by name with `localeCompare` in the "pt-BR" locale. Each call returns a fresh array of fresh objects, so mutating the result never affects subsequent calls. An unknown state code returns an empty array instead of throwing. + +```javascript +import { getMunicipalities } from '@brazilian-utils/brazilian-utils'; + +// Return every Brazilian municipality (sorted by name). +getMunicipalities(); +// [ +// { code: '5200050', name: 'Abadia de Goiás', stateCode: 'GO' }, +// { code: '3100104', name: 'Abadia dos Dourados', stateCode: 'MG' }, +// { code: '5200100', name: 'Abadiânia', stateCode: 'GO' }, +// { code: '3100203', name: 'Abaeté', stateCode: 'MG' }, +// { code: '1500107', name: 'Abaetetuba', stateCode: 'PA' }, +// ... 5566 more items +// ] + +// Return every municipality of the São Paulo state. +getMunicipalities('SP'); +// [ +// { code: '3500105', name: 'Adamantina', stateCode: 'SP' }, +// { code: '3500204', name: 'Adolfo', stateCode: 'SP' }, +// { code: '3500303', name: 'Aguaí', stateCode: 'SP' }, +// { code: '3500402', name: 'Águas da Prata', stateCode: 'SP' }, +// { code: '3500501', name: 'Águas de Lindóia', stateCode: 'SP' }, +// ... 640 more items +// ] + +getMunicipalities('ZZ'); // [] +``` + +`getMunicipalities` embeds all 5571 IBGE municipalities and their codes, so it carries the same bundle-size cost as `getCities`. See [Bundle size](getting-started.md#bundle-size) for how to lazy-load it via `@brazilian-utils/brazilian-utils/get-municipalities` instead of the root import. + +## getMunicipalityByCode + +Look up a Brazilian municipality by its 7-digit IBGE code. Accepts the code as a string or a number, with any non-digit characters stripped before matching. Returns `{ code, name, stateCode }`, a fresh object, or `null` when the code is not 7 digits long or does not match any known municipality. + +```javascript +import { getMunicipalityByCode } from '@brazilian-utils/brazilian-utils'; + +getMunicipalityByCode('3550308'); +// { code: '3550308', name: 'São Paulo', stateCode: 'SP' } + +getMunicipalityByCode(3550308); +// { code: '3550308', name: 'São Paulo', stateCode: 'SP' } + +getMunicipalityByCode('0000000'); // null (unknown code) +getMunicipalityByCode('123'); // null (not 7 digits) ``` ## isHoliday -Check if a specific date is a Brazilian holiday. +Check if a specific date is a Brazilian holiday. The check compares `targetDate`'s local calendar date (year/month/day as read locally), not its underlying UTC instant. Returns `false` when `targetDate` is missing or not a valid `Date`. ```javascript import { isHoliday } from '@brazilian-utils/brazilian-utils'; -isHoliday({ targetDate: new Date('2024-01-01') }); // true -isHoliday({ targetDate: new Date('2024-07-09'), stateCode: 'SP' }); // true +isHoliday({ targetDate: new Date(2024, 0, 1) }); // true +isHoliday({ targetDate: new Date(2024, 6, 9), stateCode: 'SP' }); // true +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`. + +```javascript +import { isBusinessDay } from '@brazilian-utils/brazilian-utils'; + +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, counts 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) +isBusinessDay(new Date('not a date')); // false +``` + +## addBusinessDays + +Add a number of Brazilian business days (dias úteis) to a date, skipping Saturdays, Sundays and Brazilian holidays exactly as `isBusinessDay` defines them (same `stateCode`/`includeOptional` options). Returns a new `Date`; the input `date` (part of `AddBusinessDaysParams`) is never mutated, and its time-of-day is preserved in the result. `days: 0` returns a new `Date` equal to `date`, unchanged, even when `date` itself falls on a weekend or holiday, this mirrors the verified behavior of [date-fns' `addBusinessDays(date, 0)`](https://date-fns.org/docs/addBusinessDays), which also does not roll the input to the next business day. A negative `days` walks backwards, one business day at a time, also like date-fns. Returns `null` on bad input: a `date` that is not a valid `Date`, a `days` that is not a finite integer, or a `stateCode` that is not a string. + +```javascript +import { addBusinessDays } from '@brazilian-utils/brazilian-utils'; + +addBusinessDays({ date: new Date(2024, 0, 2, 12), days: 1 }); // Date, 2024-01-03 12:00 (next day is already a business day) +addBusinessDays({ date: new Date(2024, 11, 31, 12), days: 1 }); // Date, 2025-01-02 12:00 (2025-01-01 is Ano novo, skipped) +addBusinessDays({ date: new Date(2024, 0, 5, 12), days: -1 }); // Date, 2024-01-04 12:00 (walks backwards) +addBusinessDays({ date: new Date(2024, 0, 6, 12), days: 0 }); // Date, 2024-01-06 12:00 (unchanged, even though Saturday is not a business day) +addBusinessDays({ date: new Date(2024, 6, 8, 12), days: 1, stateCode: 'SP' }); // Date, 2024-07-10 12:00 (2024-07-09 is Revolução Constitucionalista in SP, skipped) +addBusinessDays({ date: new Date('not a date'), days: 1 }); // null +addBusinessDays({ date: new Date(2024, 0, 2), days: 1.5 }); // null (not an integer) +``` + +## differenceInBusinessDays + +Count the number of Brazilian business days (dias úteis) between two dates, mirroring the semantics of [date-fns' `differenceInBusinessDays`](https://date-fns.org/docs/differenceInBusinessDays) (verified against its source): `params.from` is counted when it is itself a business day, `params.to` is never counted, and every business day strictly in between is counted once. Only the calendar day of each `Date` matters, the time of day is ignored. Business days are determined exactly like `isBusinessDay` (same `stateCode`/`includeOptional` options). `from`/`to` on the same calendar day return `0`; a `to` before `from` returns a negative number. Returns `null` on bad input: a `from`/`to` that is not a valid `Date`, or a `stateCode` that is not a string. Parameters are typed as `DifferenceInBusinessDaysParams`. + +```javascript +import { differenceInBusinessDays } from '@brazilian-utils/brazilian-utils'; + +differenceInBusinessDays({ from: new Date(2024, 0, 1), to: new Date(2024, 0, 2) }); // 0 (Jan 1 is Ano novo) +differenceInBusinessDays({ from: new Date(2024, 0, 2), to: new Date(2024, 0, 3) }); // 1 (Jan 2 counted, a Tuesday) +differenceInBusinessDays({ from: new Date(2024, 0, 3), to: new Date(2024, 0, 2) }); // -1 (to before from) +differenceInBusinessDays({ from: new Date(2024, 0, 2), to: new Date(2024, 0, 2) }); // 0 (same day) +differenceInBusinessDays({ from: new Date(2024, 6, 8), to: new Date(2024, 6, 10), stateCode: 'SP' }); // 1 (2024-07-09 is a state holiday in SP) +differenceInBusinessDays({ from: new Date('not a date'), to: new Date() }); // null +``` + +## convertDateToWords + +Formats a date as its Brazilian Portuguese "por extenso" textual representation, e.g. `"01/01/2024"` becomes `"primeiro de janeiro de dois mil e vinte e quatro"`. Accepts a `Date` (read by its local calendar date, the same convention used by `isHoliday`) or a string in `"dd/mm/yyyy"` or ISO `"yyyy-mm-dd"` format. With the default `options.style` of `"full"`, day 1 is written as "primeiro" and every other day uses the cardinal number; with `"month"`, only the month name is spelled out and the day/year are left as digits (day 1 as `"1º"`, e.g. `"2 de março de 2024"`, `"1º de janeiro de 2024"`). Month names are lowercase. In `"full"` style the year is written out as a cardinal number without the thousands comma that `convertNumberToWords`/`convertCurrencyToWords` use (`1999` reads as `"mil novecentos e noventa e nove"`, not `"mil, novecentos e noventa e nove"`), matching how a date is read aloud. `options.weekday` (default `false`) prefixes the pt-BR weekday name in lowercase followed by a comma (`"sábado, dois de março de dois mil e vinte e quatro"`), computed from the resolved calendar date. `options.case` sets the letter case of the whole result: `"lower"` (default), `"sentence"` (capitalizes only the first letter) or `"upper"` (uppercases everything, keeping accents). Invalid `case`/`style` values are ignored and the default is used; the previous `capitalize` boolean option has been removed in favor of `case: "sentence"`. February 29th is accepted on the leap years of the proleptic Gregorian calendar (divisible by 4, except centuries not divisible by 400). Returns `""` for an invalid `Date`, a malformed string, a day/month that does not exist, or a date before year 1. + +```javascript +import { convertDateToWords } from '@brazilian-utils/brazilian-utils'; + +convertDateToWords('01/01/2024'); // "primeiro de janeiro de dois mil e vinte e quatro" +convertDateToWords('2024-01-02'); // "dois de janeiro de dois mil e vinte e quatro" +convertDateToWords(new Date(2024, 0, 1)); // "primeiro de janeiro de dois mil e vinte e quatro" +convertDateToWords('01/01/2024', { case: 'sentence' }); // "Primeiro de janeiro de dois mil e vinte e quatro" +convertDateToWords('02/03/2024', { style: 'month' }); // "2 de março de 2024" +convertDateToWords('01/01/2024', { style: 'month' }); // "1º de janeiro de 2024" +convertDateToWords('02/03/2024', { weekday: true }); // "sábado, dois de março de dois mil e vinte e quatro" +convertDateToWords('10/05/1999'); // "dez de maio de mil novecentos e noventa e nove" +convertDateToWords('31/04/2024'); // "" (April has 30 days) +convertDateToWords('invalid'); // "" +convertDateToWords('29/02/1900'); // "" (1900 is not a leap year) ``` ## formatVoterId -Format a voter ID number. +Format a voter ID number. Uses the 12-digit grouping `0000 0000 00 00` by default; when the sanitized value has 13 digits (São Paulo/Minas Gerais voter ids may have a 9-digit sequential number) the `0000 0000 0 00 00` grouping is used instead. ```javascript import { formatVoterId } from '@brazilian-utils/brazilian-utils'; formatVoterId('123456780175'); // '1234 5678 01 75' +formatVoterId('1234567880191'); // '1234 5678 8 01 91' (13-digit SP/MG voter id) ``` ## isValidVoterId -Check if a voter ID number is valid. +Check if a voter ID number is valid. Accepts both the standard 12-digit id and the 13-digit id issued by São Paulo (UF `01`) and Minas Gerais (UF `02`). ```javascript import { generateVoterId, isValidVoterId } from '@brazilian-utils/brazilian-utils'; @@ -806,21 +1467,373 @@ isValidVoterId(voterId); // true ## generateVoterId -Generate a valid random voter ID number. You can optionally provide a state code. +Generate a valid random voter ID number. You can optionally provide a state code; an unknown state code falls back to `"ZZ"` (issued abroad) instead of throwing. Uses `Math.random()` internally, so it is not cryptographically secure. ```javascript import { generateVoterId } from '@brazilian-utils/brazilian-utils'; -generateVoterId(); // valid random voter ID +generateVoterId(); // valid random voter ID (abroad, "ZZ") generateVoterId('SP'); // valid random voter ID for Sao Paulo +generateVoterId('XX'); // falls back to "ZZ" instead of throwing ``` ## parseVoterId -Remove voter ID formatting, keep only digits, and cap the result to 12 digits. +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). ```javascript import { parseVoterId } from '@brazilian-utils/brazilian-utils'; parseVoterId('1234 5678 01 75'); // '123456780175' +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. + +```javascript +import { isValidCns } from '@brazilian-utils/brazilian-utils'; + +isValidCns('123456789010001'); // true (definitive) +isValidCns('700000000000005'); // true (provisional) +isValidCns('12345678901'); // false (wrong length) +``` + +## 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. Options are typed as `FormatCnsOptions`. + +```javascript +import { formatCns } from '@brazilian-utils/brazilian-utils'; + +formatCns('123456789010001'); // '123 4567 8901 0001' +formatCns(123456789010001); // '123 4567 8901 0001' +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 46/2015, 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). + +`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. + +```javascript +import { isValidCertidao } from '@brazilian-utils/brazilian-utils'; + +isValidCertidao('104539 01 55 2013 1 00012 021 0000123 21'); // true +isValidCertidao('09430001552010100020112000012087'); // true +isValidCertidao('104539 01 55 2013 1 00012 021 0000123 22'); // false (invalid check digits) +isValidCertidao('123456'); // false (wrong length) +isValidCertidao('104539 01 55 2013 1 00012 021 0000123 21', { accept: ['birth'] }); // true +isValidCertidao('104539 01 55 2013 1 00012 021 0000123 21', { accept: ['death'] }); // false +``` + +## 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 46/2015, as listed by [ghiorzi.org](http://ghiorzi.org/DVnew.htm). + +```javascript +import { parseCertidao } from '@brazilian-utils/brazilian-utils'; + +parseCertidao('104539 01 55 2013 1 00012 021 0000123 21'); +// { +// registryCns: '104539', +// acervo: '01', +// service: '55', +// year: 2013, +// type: 'birth', +// typeCode: 1, +// book: '00012', +// page: '021', +// term: '0000123', +// checkDigits: '21' +// } + +parseCertidao('invalid'); // null +``` + +The `Certidao` result carries: + +| Key | Description | +| --- | --- | +| `registryCns` | The 6 digit CNS (Código Nacional de Serventia) of the serventia that issued the act. | +| `acervo` | Acervo the book belongs to: `"01"` the serventia's own, `"02"` a collection it absorbed. | +| `service` | Service rendered by the serventia, `"55"` for registro civil das pessoas naturais. | +| `year` | Four digit year the act was recorded. | +| `type` | The book the act belongs to: `"birth"`, `"marriage"`, `"religious-marriage"`, `"death"`, `"stillbirth"`, `"banns"`, `"other"`, `"emancipation"` or `"interdiction"`. | +| `typeCode` | Raw book code, 1 to 9, as printed in the fifteenth position of the matrícula. | +| `book` | The 5 digit book (livro) number, zero padded. | +| `page` | The 3 digit page (folha) number, zero padded. | +| `term` | The 7 digit term (termo) number, zero padded. | +| `checkDigits` | The 2 modulus 11 check digits of the matrícula. | + +## 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 46/2015. + +```javascript +import { formatCertidao } from '@brazilian-utils/brazilian-utils'; + +formatCertidao('10453901552013100012021000012321'); // 104539 01 55 2013 1 00012 021 0000123 21 +formatCertidao('104539.01.55.2013.1.00012.021.0000123-21'); // 104539 01 55 2013 1 00012 021 0000123 21 +formatCertidao('1552010100020112000012087', { pad: true }); // 000000 01 55 2010 1 00020 112 0000120 87 +``` + +## 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. + +```javascript +import { isValidCei } from '@brazilian-utils/brazilian-utils'; + +isValidCei('11.583.00249/85'); // true +isValidCei('277297118187'); // true +isValidCei(249859674386); // true +isValidCei('24.985.96743/68'); // false (invalid check digit) +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. + +```javascript +import { formatCei } from '@brazilian-utils/brazilian-utils'; + +formatCei('277297118187'); // 27.729.71181/87 +formatCei(249859674386); // 24.985.96743/86 +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. + +```javascript +import { isValidCno } from '@brazilian-utils/brazilian-utils'; + +isValidCno('11.084.01680/62'); // true +isValidCno('111130137368'); // true +isValidCno(401800097960); // true +isValidCno('110840168063'); // false (invalid check digit) +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. + +```javascript +import { formatCno } from '@brazilian-utils/brazilian-utils'; + +formatCno('111130137368'); // 11.113.01373/68 +formatCno(401800097960); // 40.180.00979/60 +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). + +```javascript +import { isValidCaepf } from '@brazilian-utils/brazilian-utils'; + +isValidCaepf('293.118.610/001-84'); // true +isValidCaepf('41142260000101'); // true +isValidCaepf(29311861000184); // true +isValidCaepf('29311861000185'); // false (invalid check digits) +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. + +```javascript +import { formatCaepf } from '@brazilian-utils/brazilian-utils'; + +formatCaepf('29311861000184'); // 293.118.610/001-84 +formatCaepf(41142260000101); // 411.422.600/001-01 +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). + +```javascript +import { isValidRegistroProfissional } from '@brazilian-utils/brazilian-utils'; + +isValidRegistroProfissional('123456/SP', { council: 'OAB' }); // true +isValidRegistroProfissional('123456-RJ', { council: 'OAB', stateCode: 'SP' }); // false (UF mismatch) +isValidRegistroProfissional('06/12345', { council: 'CRP' }); // true +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. + +```javascript +import { isValidVin } from '@brazilian-utils/brazilian-utils'; + +isValidVin('1HGCM82633A004352'); // true +isValidVin('1m8gdm9axkp042788'); // true (check digit X, lowercase) +isValidVin('1HGCM82633A004353'); // false (bad check digit) +isValidVin('1HGCM8263IA004352'); // false (contains the excluded letter I) +``` + +## isValidCbo + +Check if a CBO (Classificação Brasileira de Ocupações) code exists in the MTE occupation table. Accepts the code with or without the hyphen mask, or as a number. + +```javascript +import { isValidCbo } from '@brazilian-utils/brazilian-utils'; + +isValidCbo('2124-05'); // true +isValidCbo('212405'); // true +isValidCbo(212405); // true +isValidCbo('000000'); // false +``` + +The occupation titles come from the [official CBO 2002 tables published by the MTE](http://www.mtecbo.gov.br/cbosite/pages/downloads.jsf). + +## getCbo + +Look a CBO (Classificação Brasileira de Ocupações) code up and get its official occupation title. + +```javascript +import { getCbo } from '@brazilian-utils/brazilian-utils'; + +getCbo('2124-05'); // { code: '212405', title: 'Analista de desenvolvimento de sistemas' } +getCbo('000000'); // null +``` + +The occupation titles come from the [official CBO 2002 tables published by the MTE](http://www.mtecbo.gov.br/cbosite/pages/downloads.jsf). + +## isValidCnae + +Check if a CNAE (Classificação Nacional de Atividades Econômicas) subclass code exists in the CNAE 2.3 table published by IBGE. Accepts the code with or without the `NNNN-N/NN` mask, or as a number. + +```javascript +import { isValidCnae } from '@brazilian-utils/brazilian-utils'; + +isValidCnae('6201-5/01'); // true +isValidCnae('6201501'); // true +isValidCnae('0000000'); // false +``` + +## formatCnae + +Format a CNAE (Classificação Nacional de Atividades Econômicas) subclass code. + +```javascript +import { formatCnae } from '@brazilian-utils/brazilian-utils'; + +formatCnae('6201501'); // 6201-5/01 +``` + +## getCnae + +Look a CNAE (Classificação Nacional de Atividades Econômicas) subclass code up and get its formatted code and official description. + +```javascript +import { getCnae } from '@brazilian-utils/brazilian-utils'; + +getCnae('6201501'); // { code: '6201-5/01', description: 'DESENVOLVIMENTO DE PROGRAMAS DE COMPUTADOR SOB ENCOMENDA' } +getCnae('0000000'); // null +``` + +## isValidNcm + +Check if an NCM (Nomenclatura Comum do Mercosul) code exists in the current table published by Siscomex/MDIC. Accepts the code with or without the dotted mask, or as a number. + +```javascript +import { isValidNcm } from '@brazilian-utils/brazilian-utils'; + +isValidNcm('8471.30.12'); // true +isValidNcm('84713012'); // true +isValidNcm('00000000'); // false +``` + +## formatNcm + +Format an NCM (Nomenclatura Comum do Mercosul) code. + +```javascript +import { formatNcm } from '@brazilian-utils/brazilian-utils'; + +formatNcm('84713012'); // 8471.30.12 +``` + +## isValidCfop + +Check if a CFOP (Código Fiscal de Operações e Prestações) code exists in the official table (Ajuste SINIEF 07/2001 and updates). + +```javascript +import { isValidCfop } from '@brazilian-utils/brazilian-utils'; + +isValidCfop('5102'); // true +isValidCfop('0000'); // false +``` + +## getCfop + +Look a CFOP (Código Fiscal de Operações e Prestações) code up and get its code and official description. + +```javascript +import { getCfop } from '@brazilian-utils/brazilian-utils'; + +getCfop('5102'); // { code: '5102', description: 'Venda de mercadoria adquirida ou recebida de terceiros' } +getCfop('0000'); // null +``` + +## isValidCst + +Check if a CST (Código de Situação Tributária) code is valid for a given tax. Pass the tax through `options.tax`: + +| Tax | Format | Accepted codes | +| --- | --- | --- | +| `icms` | 3 digits (origem + CST) | origem `0`-`8` + one of `00`, `10`, `20`, `30`, `40`, `41`, `50`, `51`, `60`, `70`, `90` | +| `ipi` | 2 digits | `00`, `01`, `02`, `03`, `04`, `05`, `49`, `50`, `51`, `52`, `53`, `54`, `55`, `99` | +| `pis` | 2 digits | `01`-`09`, `49`, `50`-`56`, `60`-`67`, `70`-`75`, `98`, `99` | +| `cofins` | 2 digits | same table as `pis` | + +`options.tax` (part of `IsValidCstOptions`) is optional: omit it to accept a code that exists in any one of the four tables above. + +```javascript +import { isValidCst } from '@brazilian-utils/brazilian-utils'; + +isValidCst('000', { tax: 'icms' }); // true +isValidCst('110', { tax: 'icms' }); // true +isValidCst('06', { tax: 'pis' }); // true +isValidCst('99', { tax: 'ipi' }); // true +isValidCst('110'); // true (found in the icms table, tax omitted) +isValidCst('999'); // false (not in any table) +``` + +## isValidCsosn + +Check if a CSOSN (Código de Situação da Operação no Simples Nacional) code is one of the 10 codes defined by Convênio ICMS 92/2015: `101`, `102`, `103`, `201`, `202`, `203`, `300`, `400`, `500` or `900`. + +```javascript +import { isValidCsosn } from '@brazilian-utils/brazilian-utils'; + +isValidCsosn('101'); // true +isValidCsosn('999'); // false +``` + +## removeAccents + +Remove diacritical marks (accents, tildes, cedillas) from a string, decomposing every accented character into its base letter plus combining marks (Unicode NFD) and dropping the combining marks. + +```javascript +import { removeAccents } from '@brazilian-utils/brazilian-utils'; + +removeAccents('São Paulo'); // 'Sao Paulo' +removeAccents('Piauí'); // 'Piaui' +removeAccents('Ceará'); // 'Ceara' +removeAccents('Açaí'); // 'Acai' +removeAccents(''); // '' ``` diff --git a/scripts/llms.ts b/scripts/llms.ts new file mode 100644 index 00000000..4216275c --- /dev/null +++ b/scripts/llms.ts @@ -0,0 +1,222 @@ +import { readFileSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; + +const ROOT = join(import.meta.dirname, ".."); +const DOCS_DIR = join(ROOT, "docs"); +const SITE = "https://brazilian-utils.com.br"; +const REPO = "https://github.com/brazilian-utils/javascript"; + +interface UtilSection { + name: string; + slug: string; + description: string; +} + +const SLUG_STRIP_PATTERN = new RegExp( + "[\\u2000-\\u206F\\u2E00-\\u2E7F\\\\'!\"#$%&()*+,./:;<=>?@\\[\\]^`{|}~]", + "g", +); +const VARIATION_SELECTOR_PATTERN = new RegExp("\\uFE0F", "g"); +const EMOJI_PATTERN = /[\p{Emoji_Presentation}\p{Extended_Pictographic}]/gu; + +/** + * Reproduces docsify's heading-to-anchor slug algorithm (see + * `src/core/render/slugify.js` in the docsify source) so links into + * `utilities.md`/`getting-started.md` resolve to the same anchors docsify + * renders at runtime. + */ +function slugify(heading: string): string { + return removeUntilStable(heading.trim().normalize("NFC"), /<[^>]+>/g) + .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1") + .replace(VARIATION_SELECTOR_PATTERN, "") + .replace(EMOJI_PATTERN, "") + .replace(/[A-Z]+/g, (match) => match.toLowerCase()) + .replace(SLUG_STRIP_PATTERN, "") + .replace(/\s/g, "-") + .replace(/^(\d)/, "_$1"); +} + +/** Removes every match of `pattern` repeatedly until nothing changes, so nested or overlapping matches cannot survive a single pass. */ +function removeUntilStable(value: string, pattern: RegExp): string { + let current = value; + let previous = ""; + while (current !== previous) { + previous = current; + current = current.replace(pattern, ""); + } + return current; +} + +const ABBREVIATION_PLACEHOLDER = String.fromCharCode(1); + +/** + * Extracts the first sentence of a paragraph, treating `e.g.`/`i.e.` as + * abbreviations rather than sentence boundaries. + */ +function firstSentence(paragraph: string): string { + const withoutLinks = paragraph.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1"); + const protectedText = withoutLinks.replace( + /\b(e\.g|i\.e)\./gi, + (_match, abbr: string) => `${abbr}${ABBREVIATION_PLACEHOLDER}`, + ); + const match = protectedText.match(/[\s\S]*?[.!?](?=\s|$)/); + const sentence = match ? match[0] : protectedText; + + return sentence.split(ABBREVIATION_PLACEHOLDER).join(".").trim(); +} + +/** Parses every `## ` section of `utilities.md` into name/slug/description. */ +function parseUtilities(utilitiesMd: string): UtilSection[] { + const sections = utilitiesMd.split(/^## /m).slice(1); + + return sections.map((section) => { + const newlineIndex = section.indexOf("\n"); + const name = section.slice(0, newlineIndex).trim(); + const body = section.slice(newlineIndex + 1); + const firstParagraph = body.split(/\n\s*\n/)[0].trim(); + + return { + name, + slug: slugify(name), + description: firstSentence(firstParagraph), + }; + }); +} + +const PREFIX_GROUPS: { title: string; test: (name: string) => boolean }[] = [ + { title: "Validators (isValid*)", test: (name) => name.startsWith("isValid") }, + { title: "Formatters (format*)", test: (name) => name.startsWith("format") }, + { title: "Parsers (parse*)", test: (name) => name.startsWith("parse") }, + { title: "Generators (generate*)", test: (name) => name.startsWith("generate") }, + { title: "Getters (get*)", test: (name) => name.startsWith("get") }, +]; + +function groupUtilities(utils: UtilSection[]) { + const groups: { title: string; utils: UtilSection[] }[] = PREFIX_GROUPS.map((group) => ({ + title: group.title, + utils: [], + })); + const other: UtilSection[] = []; + + for (const util of utils) { + const groupIndex = PREFIX_GROUPS.findIndex((group) => group.test(util.name)); + + if (groupIndex === -1) { + other.push(util); + } else { + groups[groupIndex].utils.push(util); + } + } + + if (other.length > 0) { + groups.push({ title: "Other utilities", utils: other }); + } + + return groups.filter((group) => group.utils.length > 0); +} + +function utilLink(util: UtilSection): string { + return `- [${util.name}](${SITE}/utilities.md#${util.slug}): ${util.description}`; +} + +function buildLlmsTxt(utils: UtilSection[]): string { + const groups = groupUtilities(utils); + const groupSections = groups + .map((group) => `## ${group.title}\n\n${group.utils.map(utilLink).join("\n")}`) + .join("\n\n"); + + return `# Brazilian Utils + +> Brazilian Utils is a zero-dependency JavaScript/TypeScript library of small, focused utilities for the day-to-day problems of building software for Brazilian businesses: validating, formatting, parsing and generating documents (CPF, CNPJ, CEP, Pix, boleto, NF-e, phone numbers, license plates and more). + +The package has **zero runtime dependencies**, is fully tree-shakeable and runs on Node.js \`^20.19.0 || >=22.12.0\`, Bun, Deno and modern browsers (including a UMD \`