diff --git a/README.md b/README.md
index 868d39c..abc1e1d 100644
--- a/README.md
+++ b/README.md
@@ -1,76 +1,707 @@
-
+
-Brazilian data utilities (CPF, CNPJ, etc.) as a **multi-gem monorepo**, publishable to RubyGems with independent versioning and GitHub Actions (Trusted Publishing / OIDC).
+[](https://rubygems.org/gems/br-utilities)
+[](https://rubygems.org/gems/br-utilities)
+[](https://www.ruby-lang.org/)
+[](https://github.com/LacusSolutions/br-utils-ruby/actions)
+[](https://github.com/LacusSolutions/br-utils-ruby)
+[](https://github.com/LacusSolutions/br-utils-ruby/blob/main/LICENSE)
-## Structure
+> 🚀 **Full support for the [new alphanumeric CNPJ format](https://github.com/user-attachments/files/23937961/calculodvcnpjalfanaumerico.pdf).**
-- **Root**: Tooling (Rake, RuboCop), shared config in `config/gems.yml`, no app code.
-- **Packages**: Under `packages/` — each is a gem (e.g. `cpf-dv`, `cpf-utilities`, `br-utilities`). Internal dependencies use path in development and version constraints when published.
+> 🌎 [Acessar documentação em português](./README.pt.md)
-See [CONTRIBUTING.md](CONTRIBUTING.md) for folder layout, tagging, dependency resolution, and development workflow.
+A Ruby toolkit to handle the main operations with Brazilian-related data: CPF (Individual's Taxpayer ID) and CNPJ (Business Tax ID). It wraps [`cpf-utilities`](https://rubygems.org/gems/cpf-utilities) and [`cnpj-utilities`](https://rubygems.org/gems/cnpj-utilities) in a single façade class (`BrUtils`).
-## Local setup
+## Ruby Support
+
+|  |  |  |  |  |
+| --- | --- | --- | --- | --- |
+| Passing ✔ | Passing ✔ | Passing ✔ | Passing ✔ | Passing ✔ |
+
+Requires Ruby **≥ 3.1** (see `required_ruby_version` in the gemspec).
+
+## Features
+
+- ✅ **Unified top-level API**: Class helpers `BrUtils.cpf` / `.cnpj` alias `BrUtils::DEFAULT`; each domain offers `format`, `generate`, and `is_valid`
+- ✅ **Bundled domains**: [`cpf-utilities`](https://rubygems.org/gems/cpf-utilities) and [`cnpj-utilities`](https://rubygems.org/gems/cnpj-utilities) installed together
+- ✅ **Alphanumeric CNPJ**: Full support for the new alphanumeric CNPJ format (introduced in 2026)
+- ✅ **Reusable instance**: `BrUtils` class with optional default CPF and CNPJ settings (nested mappings, flat component kwargs, or pre-built utils instances)
+- ✅ **Two-tier access**: Prefer main-class shortcuts at the façade root (`BrUtils::CpfFormatter`, `BrUtils::CnpjValidator`, …); Options, helpers, and errors live under nested package modules (`BrUtils::CpfFmt`, `BrUtils::CnpjUtils`, …). Root siblings (`CpfUtils`, `CnpjUtils`, `CpfFmt`, …) still work
+- ✅ **Per-call overrides**: Configure defaults on the façade / domain utils; override options on a single `format` / `generate` / `is_valid` call
+- ✅ **Error handling**: Domain errors propagate unchanged from the bundled packages; this gem defines `BrUtils::TypeMismatchError` and `BrUtils::InvalidArgumentCombinationError` for API misuse
+
+## Installation
+
+Install the gem directly:
```bash
-bundle install
-rake hooks:install # enable the git hooks (pre-commit, pre-push, commit-msg)
-rake monorepo:check_cycles
-cd packages/cpf-dv && bundle install && rake test
+gem install br-utilities
+```
+
+Or add it to your `Gemfile` and run `bundle install`:
+
+```ruby
+gem 'br-utilities'
+```
+
+This installs **`br-utilities`** together with [`cpf-utilities`](https://rubygems.org/gems/cpf-utilities) and [`cnpj-utilities`](https://rubygems.org/gems/cnpj-utilities) (which in turn pull in the CPF and CNPJ component packages). You do **not** need separate `gem install` / `gem` lines for the domain packages when using **`br-utilities`**.
+
+## Require
+
+```ruby
+require 'br-utilities'
```
-## Git hooks
+## Quick Start
-`rake hooks:install` points `core.hooksPath` at `.githooks/`, enabling:
+Prefer the aggregator class helpers (`BrUtils.cpf` / `BrUtils.cnpj`) for one-off calls — they forward to `BrUtils::DEFAULT`:
-- **pre-commit**: RuboCop auto-corrects the staged Ruby files and re-stages only the
- linting changes (unstaged edits in the same files are preserved, never committed).
- Aborts if offenses remain that safe auto-correction can't fix.
-- **pre-push**: runs the repository specs and every package's tests in build order,
- aborting the push if any test fails.
-- **commit-msg**: rejects commit messages that don't follow Conventional Commits.
+```ruby
+require 'br-utilities'
-Undo with `rake hooks:uninstall`. See [CONTRIBUTING.md](CONTRIBUTING.md#git-hooks) for details.
+cpf = '12345678909'
+cnpj = '03603568000195'
+
+# CPF (personal ID)
+BrUtils.cpf.format(cpf) # => "123.456.789-09"
+BrUtils.cpf.generate(format: true) # => e.g. "478.442.410-55"
+BrUtils.cpf.is_valid('123.456.789-09') # => true
+
+# CNPJ (business ID)
+BrUtils.cnpj.format(cnpj) # => "03.603.568/0001-95"
+BrUtils.cnpj.generate(format: true) # => e.g. "AB.123.CDE/0001-55"
+BrUtils.cnpj.is_valid('98765432000198') # => true
+```
+
+**With domain aggregators:**
+
+```ruby
+require 'br-utilities'
+
+cpf = '12345678909'
+cnpj = '03603568000195'
+
+CpfUtils.format(cpf) # => "123.456.789-09"
+CnpjUtils.format(cnpj) # => "03.603.568/0001-95"
+CpfUtils.is_valid(cpf) # => true
+CnpjUtils.is_valid(cnpj) # => true
+```
+
+**With functional helpers** (root sibling modules, loaded by this gem):
+
+```ruby
+require 'br-utilities'
+
+cpf = '12345678909'
+cnpj = '03603568000195'
+
+CpfFmt.cpf_fmt(cpf) # => "123.456.789-09"
+CpfVal.cpf_val(cpf) # => true
+CnpjFmt.cnpj_fmt(cnpj) # => "03.603.568/0001-95"
+CnpjVal.cnpj_val(cnpj) # => true
+```
-## Commit messages
+## Usage
-Commits follow [Conventional Commits](https://www.conventionalcommits.org). A pure-Ruby
-linter (`bin/commit-lint`) enforces this in two places:
+You can work in these equivalent ways:
-- **Locally**: `rake hooks:install` sets `core.hooksPath` to `.githooks`, so the
- `commit-msg` hook rejects non-conforming messages. Undo with `rake hooks:uninstall`.
-- **CI**: the `Commit Lint` workflow validates every commit in a push/PR.
+1. **`BrUtils.cpf` / `.cnpj`** — class helpers for quick one-off calls (forward to `DEFAULT`).
+2. **`BrUtils::DEFAULT`** — mutable shared singleton (same object the class helpers use; process-wide / not thread-isolated).
+3. **`BrUtils.new`** — configurable instance with shared defaults across both CPF and CNPJ domains.
+4. **Domain aggregators** — `CpfUtils` / `CnpjUtils` (or `BrUtils::CpfUtils` / `BrUtils::CnpjUtils`) directly.
+5. **Main classes under `BrUtils`** — `BrUtils::CpfFormatter`, `BrUtils::CnpjGenerator`, and related shortcuts.
+6. **Nested package modules** — Options, helpers, errors, and types via `BrUtils::CpfFmt` / `CpfGen` / `CpfVal` / `CnpjFmt` / `CnpjGen` / `CnpjVal` / `CpfUtils` / `CnpjUtils`.
+7. **Root sibling modules** (still supported) — `CpfFmt`, `CnpjUtils`, and the rest unchanged.
-Allowed `type`s: `build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test`.
-The `(scope)` is optional; when present it must be one of the per-package scopes below
-(a few differ from the gem name). Example: `feat(cpf-gen): add batch generator`.
+All approaches expose the same options and behavior within each domain. For exhaustive option tables and component-specific details, see the README of each [bundled package](#bundled-packages).
+
+### Class helpers (`BrUtils.cpf` / `.cnpj`)
+
+These class methods return the same domain utils instances as `BrUtils::DEFAULT`. Prefer them for one-off calls:
+
+```ruby
+BrUtils.cpf.format('12345678909')
+BrUtils.cpf.generate(format: true)
+BrUtils.cpf.is_valid('12345678909')
+
+BrUtils.cnpj.format('03603568000195')
+BrUtils.cnpj.generate(type: 'numeric')
+BrUtils.cnpj.is_valid('98765432000198')
+```
+
+### `BrUtils::DEFAULT` (default instance)
+
+`BrUtils::DEFAULT` is the pre-built, **mutable** singleton behind the class helpers (parity with the JS default export / Python `br_utils`). Its configuration is **process-wide and shared across threads**: mutating it (e.g. `DEFAULT.cpf = …`) affects subsequent `BrUtils.cpf` / `.cnpj` calls for every caller in the process. Prefer `BrUtils.new` or per-call options for concurrent or isolated work; custom instances stay independent of `DEFAULT`:
+
+```ruby
+BrUtils::DEFAULT.cpf = CpfUtils.new(formatter: { dash_key: '|' })
+BrUtils.cpf.format('12345678909') # => "123.456.789|09"
+
+custom = BrUtils.new
+custom.cpf.format('12345678909') # => "123.456.789-09" (unaffected)
+```
+
+### `BrUtils` (class)
+
+For custom default CPF or CNPJ utils, create your own instance:
+
+```ruby
+require 'br-utilities'
+
+utils = BrUtils.new(
+ cpf: {
+ formatter: { hidden: true, hidden_key: '#' },
+ generator: { format: true }
+ },
+ cnpj: {
+ formatter: { hidden: true },
+ generator: { type: 'numeric', format: true },
+ validator: { type: 'numeric' }
+ }
+)
+
+utils.cpf.format('12345678909') # => "123.###.###-##"
+utils.cpf.generate # => e.g. "005.265.352-88"
+utils.cnpj.format('03603568000195') # => "03.603.***/****-**"
+utils.cnpj.generate # => e.g. "73.008.535/0005-06"
+
+# Access or replace internal domain instances
+utils.cpf # => CpfUtils
+utils.cnpj # => CnpjUtils
+```
+
+- **`BrUtils.new(settings = nil, **keywords)`**: Optional settings. Pass either a settings `Hash` with `:cpf` and/or `:cnpj` keys, **or** the same keys (plus flat component kwargs) as keyword arguments — not both (passing both raises `BrUtils::InvalidArgumentCombinationError`).
+ - **`:cpf` / `:cnpj`**: A pre-built `CpfUtils` / `CnpjUtils` instance **or** a configuration `Hash` spread into the corresponding utils constructor. Within that `Hash`, each resource key (`:formatter`, `:generator`, and `:validator` for CNPJ) accepts either an options object or a mapping of option values.
+ - **`:cpf_formatter`**, **`:cpf_generator`**, **`:cnpj_formatter`**, **`:cnpj_generator`**, **`:cnpj_validator`**: Flat convenience arguments when only individual components need customization. They are ignored when the corresponding `:cpf` or `:cnpj` argument is provided.
+- **`#cpf`**, **`#cnpj`**: Accessors (getters and setters) for the domain utils instances. Setters accept a utils instance, a configuration `Hash`, or `nil` to reset to defaults (replaces the entire instance; does not merge).
+
+Flat constructor options (alternative to nested `:cpf` / `:cnpj` mappings):
+
+```ruby
+require 'br-utilities'
+
+utils = BrUtils.new(
+ cpf_formatter: CpfFmt::CpfFormatterOptions.new(hidden: true, hidden_key: '#'),
+ cpf_generator: CpfGen::CpfGeneratorOptions.new(format: true),
+ cnpj_formatter: CnpjFmt::CnpjFormatterOptions.new(hidden: true, hidden_key: '#'),
+ cnpj_generator: CnpjGen::CnpjGeneratorOptions.new(format: true, type: 'numeric'),
+ cnpj_validator: CnpjVal::CnpjValidatorOptions.new(type: 'numeric')
+)
+```
+
+Passing a settings `Hash` positional argument together with any keyword raises:
+
+```ruby
+BrUtils.new({ cpf: {} }, cnpj: CnpjUtils.new)
+# raises BrUtils::InvalidArgumentCombinationError
+```
-| Scope | Package |
-|-------|---------|
-| `utils` | `lacus-utils` |
-| `cnpj-dv` | `cnpj-dv` |
-| `cnpj-fmt` | `cnpj-fmt` |
-| `cnpj-gen` | `cnpj-gen` |
-| `cnpj-val` | `cnpj-val` |
-| `cnpj-utils` | `cnpj-utilities` |
-| `cpf-dv` | `cpf-dv` |
-| `cpf-fmt` | `cpf-fmt` |
-| `cpf-gen` | `cpf-gen` |
-| `cpf-val` | `cpf-val` |
-| `cpf-utils` | `cpf-utilities` |
-| `br-utils` | `br-utilities` |
+### Instance defaults and per-call overrides
-Lint a range manually with `rake lint:commits` (defaults to `origin/main..HEAD`, override
-with `COMMIT_RANGE`).
+```ruby
+require 'br-utilities'
+
+utils = BrUtils.new(
+ cpf: {
+ formatter: { hidden: true, hidden_key: '#' },
+ generator: { format: true }
+ },
+ cnpj: {
+ formatter: { hidden: true, hidden_key: '#' },
+ generator: { format: true },
+ validator: { type: 'numeric' }
+ }
+)
+
+cpf = '12345678909'
+cnpj = '03603568000195'
+
+utils.cpf.format(cpf) # => "123.###.###-##"
+utils.cpf.format(cpf, hidden: false) # this call only: unmasked
+utils.cpf.generate(format: false) # this call only: compact output
+
+utils.cnpj.format(cnpj) # => "03.603.###/####-##"
+utils.cnpj.format(cnpj, hidden: false) # this call only: unmasked
+utils.cnpj.is_valid('1QB5UKALPYFP59') # => false (instance validator is numeric-only)
+utils.cnpj.is_valid( # => true for this call
+ '1QB5UKALPYFP59',
+ type: 'alphanumeric'
+)
+```
+
+Passing a `CnpjFmt::CnpjFormatterOptions`, `CnpjGen::CnpjGeneratorOptions`, or `CnpjVal::CnpjValidatorOptions` instance into the `BrUtils` constructor stores that object by reference — mutating it later affects subsequent calls with no per-call override.
+
+To change a single nested option without replacing the whole domain utils, mutate via the domain accessors (e.g. `utils.cpf.formatter.options.hidden = true`).
+
+### CPF operations
+
+CPF methods are accessed via `BrUtils.cpf`, `utils.cpf`, `CpfUtils`, or the `CpfFmt` / `CpfGen` / `CpfVal` helpers. CPF uses the API from [`cpf-utilities`](../cpf-utilities/README.md).
+
+#### Formatting (`#format` / `CpfFmt.cpf_fmt`)
+
+| Option | Type | Default | Description |
+|--------|------|---------|-------------|
+| `hidden` | `Boolean` | `false` | When `true`, mask digits in `hidden_start`–`hidden_end` with `hidden_key` |
+| `hidden_key` | `String` | `'*'` | Character(s) used to replace masked digits |
+| `hidden_start` | `Integer` | `3` | Start index (0–10, inclusive) of the range to hide |
+| `hidden_end` | `Integer` | `10` | End index (0–10, inclusive) of the range to hide |
+| `dot_key` | `String` | `'.'` | Dot delimiter (e.g. in `123.456.789`) |
+| `dash_key` | `String` | `'-'` | Dash delimiter (e.g. before check digits `…-09`) |
+| `escape` | `Boolean` | `false` | When `true`, escape HTML special characters in the result |
+| `encode` | `Boolean` | `false` | When `true`, URL-encode the result (similar to JavaScript `encodeURIComponent`) |
+| `on_fail` | `Proc` / callable | returns `''` | Callback when sanitized input length ≠ 11; return value is used as result |
+
+Default **`on_fail`** returns an empty string. Invalid length does **not** raise from `#format`.
+
+```ruby
+require 'br-utilities'
+
+cpf = '12345678909'
+
+BrUtils.cpf.format(cpf) # => "123.456.789-09"
+BrUtils.cpf.format(cpf, hidden: true, hidden_key: '#') # => "123.###.###-##"
+BrUtils.cpf.format(cpf, dot_key: '', dash_key: '_') # => "123456789_09"
+
+CpfFmt.cpf_fmt(cpf, hidden: true) # => "123.***.***-**"
+```
+
+#### Generation (`#generate` / `CpfGen.cpf_gen`)
+
+| Option | Type | Default | Description |
+|--------|------|---------|-------------|
+| `format` | `Boolean` | `false` | When `true`, return the generated CPF in standard format (`000.000.000-00`) |
+| `prefix` | `String` | `''` | Partial start string (0–9 digits). Non-digits are stripped; missing characters are generated and check digits computed. Prefixes longer than 9 digits are truncated silently. |
+
+Prefix rules: the base (first 9 digits) cannot be all zeros; 9 repeated digits (e.g. `999999999`) are not allowed.
+
+```ruby
+require 'br-utilities'
+
+BrUtils.cpf.generate # => e.g. "11508890048"
+BrUtils.cpf.generate(format: true) # => e.g. "661.134.831-00"
+BrUtils.cpf.generate(prefix: '123456789') # => "12345678909"
+CpfGen.cpf_gen(prefix: '123456789', format: true) # => "123.456.789-09"
+```
+
+#### Validation (`#is_valid` / `CpfVal.cpf_val`)
+
+Accepts formatted or unformatted CPF strings (or an `Array` of strings). Returns **`true`** or **`false`** without raising for invalid CPF. No validator options exist.
+
+```ruby
+require 'br-utilities'
+
+BrUtils.cpf.is_valid('12345678909') # => true
+BrUtils.cpf.is_valid('123.456.789-09') # => true
+BrUtils.cpf.is_valid('12345678900') # => false
+CpfVal.cpf_val('12345678909') # => true
+```
+
+### CNPJ operations
+
+CNPJ methods are accessed via `BrUtils.cnpj`, `utils.cnpj`, `CnpjUtils`, or the `CnpjFmt` / `CnpjGen` / `CnpjVal` helpers. CNPJ uses the API from [`cnpj-utilities`](../cnpj-utilities/README.md).
+
+#### Formatting (`#format` / `CnpjFmt.cnpj_fmt`)
+
+| Option | Type | Default | Description |
+|--------|------|---------|-------------|
+| `hidden` | `Boolean` | `false` | When `true`, mask characters in `hidden_start`–`hidden_end` with `hidden_key` |
+| `hidden_key` | `String` | `'*'` | Character(s) used to replace masked characters |
+| `hidden_start` | `Integer` | `5` | Start index (0–13, inclusive) of the range to hide |
+| `hidden_end` | `Integer` | `13` | End index (0–13, inclusive) of the range to hide |
+| `dot_key` | `String` | `'.'` | Dot delimiter (e.g. in `12.345.678`) |
+| `slash_key` | `String` | `'/'` | Slash delimiter (e.g. before branch `…/0001-90`) |
+| `dash_key` | `String` | `'-'` | Dash delimiter (e.g. before check digits `…-90`) |
+| `escape` | `Boolean` | `false` | When `true`, escape HTML special characters in the result |
+| `encode` | `Boolean` | `false` | When `true`, URL-encode the result (similar to JavaScript `encodeURIComponent`) |
+| `on_fail` | `Proc` / callable | returns `''` | Callback when sanitized input length ≠ 14; return value is used as result |
+
+Default **`on_fail`** returns an empty string. Wrong input types raise **`CnpjFmt::TypeMismatchError`**.
+
+```ruby
+require 'br-utilities'
+
+cnpj = '03603568000195'
+
+BrUtils.cnpj.format(cnpj) # => "03.603.568/0001-95"
+BrUtils.cnpj.format('12ABC34500DE99') # => "12.ABC.345/00DE-99"
+BrUtils.cnpj.format( # => "03.603.###/####-##"
+ cnpj,
+ hidden: true,
+ hidden_key: '#'
+)
+BrUtils.cnpj.format( # => "03603568|0001_95"
+ cnpj,
+ dot_key: '',
+ slash_key: '|',
+ dash_key: '_'
+)
+
+CnpjFmt.cnpj_fmt(cnpj) # => "03.603.568/0001-95"
+```
+
+#### Generation (`#generate` / `CnpjGen.cnpj_gen`)
+
+| Option | Type | Default | Description |
+|--------|------|---------|-------------|
+| `format` | `Boolean` | `false` | When `true`, return the generated CNPJ in standard format (`00.000.000/0000-00`) |
+| `prefix` | `String` | `''` | Partial start string (0–12 alphanumeric chars). Missing characters are generated and check digits computed. |
+| `type` | `String` | `'alphanumeric'` | Character set for the randomly generated part: `'numeric'`, `'alphabetic'`, or `'alphanumeric'`. **Check digits are always numeric.** |
+
+Prefix rules: base ID (first 8 chars) and branch ID (chars 9–12) cannot be all zeros; 12 repeated digits (e.g. `111111111111`) are also not allowed.
+
+```ruby
+require 'br-utilities'
+
+BrUtils.cnpj.generate # => e.g. "1GJTR3J3XSSA96"
+BrUtils.cnpj.generate(format: true) # => e.g. "V1.J0V.8WE/DVZ7-50"
+BrUtils.cnpj.generate( # => e.g. "12345678855883"
+ prefix: '12345678',
+ type: 'numeric'
+)
+CnpjGen.cnpj_gen(type: 'numeric') # => e.g. "65453043000178"
+```
+
+#### Validation (`#is_valid` / `CnpjVal.cnpj_val`)
+
+| Option | Type | Default | Description |
+|--------|------|---------|-------------|
+| `case_sensitive` | `Boolean` | `true` | When `false`, lowercase letters are accepted for alphanumeric CNPJ (input is uppercased before validation). |
+| `type` | `String` | `'alphanumeric'` | `'numeric'`: only digits (0–9); `'alphanumeric'`: digits and letters (0–9, A–Z). |
+
+```ruby
+require 'br-utilities'
+
+BrUtils.cnpj.is_valid('98765432000198') # => true
+BrUtils.cnpj.is_valid('98765432000199') # => false
+BrUtils.cnpj.is_valid('1QB5UKALPYFP59') # => true
+BrUtils.cnpj.is_valid('1QB5UKALpyfp59') # => false
+BrUtils.cnpj.is_valid( # => true
+ '1QB5UKALpyfp59',
+ case_sensitive: false
+)
+BrUtils.cnpj.is_valid( # => false
+ '1QB5UKALPYFP59',
+ type: 'numeric'
+)
+
+CnpjVal.cnpj_val('98765432000198') # => true
+CnpjVal.cnpj_val('1QB5UKALpyfp59', case_sensitive: false) # => true
+CnpjVal.cnpj_val('1QB5UKALPYFP59', type: 'numeric') # => false
+```
-## Releasing (one gem at a time)
+Invalid CNPJ returns **`false`** without raising. Wrong input types raise **`CnpjVal::TypeMismatchError`**.
-1. Bump version in `packages/
/src//version.rb`, commit.
-2. Push tag: `git tag @` (e.g. `cpf-dv@1.0.1`), then `git push origin `.
-3. Configure [RubyGems Trusted Publishing](https://guides.rubygems.org/trusted-publishing/) for this repo and the `release` environment.
-4. The Release workflow runs: tests the package, builds the gem, publishes to RubyGems via OIDC. No secrets required.
+### Domain aggregators (standalone)
-Release leaves first (e.g. `cpf-dv`, `cpf-fmt`), then dependents (`cpf-utilities`, `cnpj-utilities`), then `br-utilities`.
+Use `CpfUtils` or `CnpjUtils` directly when you only need one domain:
+
+```ruby
+require 'br-utilities'
+
+cpf_utils = CpfUtils.new(
+ formatter: { hidden: true },
+ generator: { format: true }
+)
+
+cnpj_utils = CnpjUtils.new(
+ formatter: { hidden: true },
+ generator: { format: true },
+ validator: { type: 'numeric' }
+)
+
+cpf_utils.format('12345678909') # => "123.***.***-**"
+cnpj_utils.format('03603568000195') # => "03.603.***/****-**"
+```
+
+### Accessing components
+
+Each domain aggregator exposes its internal formatter, generator, and validator:
+
+```ruby
+require 'br-utilities'
+
+utils = BrUtils.new
+
+utils.cpf.formatter.format('12345678909', hidden: true) # => "123.***.***-**"
+utils.cpf.generator.generate(format: true) # => e.g. "545.507.690-68"
+utils.cpf.validator.is_valid('12345678909') # => true
+
+utils.cnpj.formatter.format('12ABC34500DE99') # => "12.ABC.345/00DE-99"
+utils.cnpj.generator.generate(format: true) # => e.g. "8O.BE5.2KL/UI0Y-06"
+utils.cnpj.validator.is_valid('03603568000195') # => true
+```
+
+### Using component classes and nested modules
+
+Preferred paths after `require 'br-utilities'`:
+
+```ruby
+require 'br-utilities'
+
+# Main classes at the façade root
+formatter = BrUtils::CpfFormatter.new(hidden: true)
+generator = BrUtils::CnpjGenerator.new(type: 'numeric')
+validator = BrUtils::CnpjValidator.new
+
+formatter.format('12345678909') # => "123.***.***-**"
+
+# Options, helpers, and errors under nested package modules
+options = BrUtils::CpfFmt::CpfFormatterOptions.new(dash_key: '|')
+BrUtils::CpfFmt.cpf_fmt('12345678909') # => "123.456.789-09"
+
+begin
+ BrUtils::CnpjFmt.cnpj_fmt(12_345)
+rescue BrUtils::CnpjFmt::TypeMismatchError
+ # wrong input type
+end
+```
+
+Root siblings remain supported (same objects as the nests):
+
+```ruby
+CpfFmt.cpf_fmt('12345678909', dash_key: '|') # => "123.456.789|09"
+CpfGen.cpf_gen(format: true) # => e.g. "478.442.410-55"
+CpfVal.cpf_val('12345678909') # => true
+CnpjFmt.cnpj_fmt('01ABC234000X56', slash_key: '|') # => "01.ABC.234|000X-56"
+CnpjGen.cnpj_gen(type: 'numeric') # => e.g. "65453043000178"
+CnpjVal.cnpj_val('9JN7MGLJZXIO50') # => true
+```
+
+See [`cpf-utilities`](../cpf-utilities/README.md) and [`cnpj-utilities`](../cnpj-utilities/README.md) for full option and error details.
+
+### Mixing styles
+
+Use `BrUtils` where a shared configuration helps, and standalone components or helpers elsewhere — they are the same underlying classes:
+
+```ruby
+require 'br-utilities'
+
+utils = BrUtils.new(cnpj: { validator: { type: 'numeric' } })
+
+# Via façade
+utils.cpf.format('12345678909') # => "123.456.789-09"
+
+# Via component returned by the façade
+utils.cnpj.formatter.format('12ABC34500DE99') # => "12.ABC.345/00DE-99"
+
+# Via a separate component instance
+BrUtils::CnpjFormatter.new.format('03603568000195') # => "03.603.568/0001-95"
+
+# Via functional helpers
+CpfFmt.cpf_fmt('12345678909') # => "123.456.789-09"
+CnpjVal.cnpj_val('98.765.432/0001-98') # => true
+```
+
+## API
+
+### Exports
+
+After `require 'br-utilities'`:
+
+- **`BrUtils`**: Façade class to create an instance with optional default CPF and CNPJ utils settings.
+- **`BrUtils.cpf` / `.cnpj`**: Class helpers that forward to `BrUtils::DEFAULT` domain accessors.
+- **`BrUtils::DEFAULT`**: Mutable pre-built `BrUtils` instance (same object the class helpers use). Process-wide / shared across threads — prefer `BrUtils.new` or per-call options under concurrency.
+- **`BrUtils::VERSION`**: Gem version string.
+- **Main-class shortcuts**: `BrUtils::CpfFormatter`, `BrUtils::CpfFormatterOptions`, `BrUtils::CpfGenerator`, `BrUtils::CpfGeneratorOptions`, `BrUtils::CpfValidator`, `BrUtils::CnpjFormatter`, `BrUtils::CnpjFormatterOptions`, `BrUtils::CnpjGenerator`, `BrUtils::CnpjGeneratorOptions`, `BrUtils::CnpjValidator`, `BrUtils::CnpjValidatorOptions` (same objects as the sibling classes). Error-marker shortcuts: `BrUtils::CpfFormatterError`, `BrUtils::CpfGeneratorError`, `BrUtils::CpfValidatorError`, `BrUtils::CnpjFormatterError`, `BrUtils::CnpjGeneratorError`, `BrUtils::CnpjValidatorError`.
+- **Nested package modules**: `BrUtils::CpfUtils`, `BrUtils::CnpjUtils`, `BrUtils::CpfFmt`, `BrUtils::CpfGen`, `BrUtils::CpfVal`, `BrUtils::CnpjFmt`, `BrUtils::CnpjGen`, `BrUtils::CnpjVal` — full sibling surface (Options, helpers, errors, types).
+- **Root sibling modules** (still supported): `CpfUtils`, `CnpjUtils`, `CpfFmt`, `CpfGen`, `CpfVal`, `CnpjFmt`, `CnpjGen`, `CnpjVal` — same objects as the nests.
+
+### Errors & Exceptions
+
+`BrUtils` defines only API-misuse errors for this gem’s argument rules. Domain errors are raised by the bundled packages and propagate unchanged.
+
+#### Defined by `br-utilities`
+
+Errors defined by this gem are **API misuse** only (wrong type or invalid argument combination). Every custom error includes the `BrUtils::Error` marker module. This gem defines **no** `BrUtils::DomainError` and no domain leaves — domain failures come only from the [bundled packages](#propagated-from-bundled-packages) and keep those packages’ namespaces (`CpfFmt::…`, `CnpjGen::…`, …).
+
+`rescue BrUtils::Error` catches **only** errors this gem raises. It does **not** catch component errors that propagate unchanged.
+
+##### Summary
+
+| Class | Inherits from | Category | Trigger condition |
+|-------|---------------|----------|-------------------|
+| `BrUtils::InvalidArgumentCombinationError` | `BrUtils::InvalidArgumentCombinationError < ArgumentError < StandardError` (+ `include BrUtils::Error`) | API misuse | Non-`nil` settings `Hash` passed together with any non-`nil` keyword argument |
+| `BrUtils::TypeMismatchError` | `BrUtils::TypeMismatchError < TypeError < StandardError` (+ `include BrUtils::Error`) | API misuse | Non-`nil` `settings` argument to `BrUtils.new` is not a `Hash` |
+
+##### `BrUtils::Error` (marker module)
+
+- **Inheritance:** module marker mixed into every custom error this gem raises via `include` (not a class).
+- **Category:** N/A (rescue target only) — not a failure mode by itself.
+- **When it is raised:** Never raised directly; included by every custom error this gem raises.
+- **Example:** N/A
+- **How to rescue it:**
+
+```ruby
+rescue BrUtils::Error
+ # TypeMismatchError, InvalidArgumentCombinationError from this gem only
+ # (not CpfFmt::*, CnpjGen::*, or other bundled-package errors)
+```
+
+##### `BrUtils::TypeMismatchError`
+
+- **Inheritance:** `BrUtils::TypeMismatchError < TypeError < StandardError` (includes `BrUtils::Error`)
+- **Category:** API misuse — the caller passed a value of the wrong type.
+- **When it is raised:** Raised when `BrUtils.new` receives a non-`nil` `settings` argument that is not a `Hash`.
+- **Example:**
+
+```ruby
+BrUtils.new('not-a-hash') # raises BrUtils::TypeMismatchError
+BrUtils.new(false) # raises BrUtils::TypeMismatchError (false is non-nil)
+```
+
+- **How to rescue it:**
+
+```ruby
+rescue BrUtils::TypeMismatchError
+ # this gem's type-contract violation
+
+rescue TypeError
+ # native type errors, including this gem's TypeMismatchError
+```
+
+##### `BrUtils::InvalidArgumentCombinationError`
+
+- **Inheritance:** `BrUtils::InvalidArgumentCombinationError < ArgumentError < StandardError` (includes `BrUtils::Error`)
+- **Category:** API misuse — the caller mixed mutually exclusive argument patterns.
+- **When it is raised:** Raised when `BrUtils.new` receives both a non-`nil` settings `Hash` and any non-`nil` keyword argument (`cpf:`, `cnpj:`, `cpf_formatter:`, …) at the same time.
+- **Example:**
+
+```ruby
+BrUtils.new({ cpf: { formatter: { hidden: true } } }, cnpj: { formatter: { hidden: true } })
+# raises BrUtils::InvalidArgumentCombinationError
+```
+
+- **How to rescue it:**
+
+```ruby
+rescue BrUtils::InvalidArgumentCombinationError
+ # this gem's invalid signature combination
+
+rescue ArgumentError
+ # native argument errors, including this gem's InvalidArgumentCombinationError
+```
+
+##### Rescue granularity
+
+Each level is shown as its own standalone example (do not merge them into one `rescue` ladder — a broad native handler would make narrower clauses unreachable).
+
+```ruby
+require 'br-utilities'
+
+# 1) Single native class — catches misuse errors of that kind,
+# including non-library ones already handled elsewhere in the consumer's code.
+begin
+ BrUtils.new('not-a-hash')
+rescue TypeError
+ # BrUtils::TypeMismatchError and any other TypeError (library or not)
+end
+
+begin
+ BrUtils.new({ cpf: {} }, cnpj: CnpjUtils.new)
+rescue ArgumentError
+ # BrUtils::InvalidArgumentCombinationError and any other ArgumentError (library or not)
+end
+```
+
+```ruby
+require 'br-utilities'
+
+# 2) BrUtils::DomainError — not applicable: this gem defines no DomainError
+# (and no domain leaves). Domain failures come from bundled packages only.
+# begin
+# BrUtils.new(cpf: { formatter: { hidden_start: -1 } })
+# rescue BrUtils::DomainError # NameError — constant is not defined
+# end
+```
+
+```ruby
+require 'br-utilities'
+
+# 3) BrUtils::Error — catches everything this gem raises, regardless of native ancestry.
+# Does not catch CpfFmt::*, CnpjGen::*, or other bundled-package errors.
+begin
+ BrUtils.new('not-a-hash')
+rescue BrUtils::Error
+ # every custom error that includes BrUtils::Error
+end
+```
+
+```ruby
+require 'br-utilities'
+
+# 4) Specific leaf class — catches only that exact failure mode.
+begin
+ BrUtils.new('not-a-hash')
+rescue BrUtils::TypeMismatchError
+ # only BrUtils::TypeMismatchError
+end
+```
+
+#### Propagated from bundled packages
+
+`BrUtils` does not redefine domain exception types. Construction, setters, and domain method calls raise the same errors as [`cpf-utilities`](../cpf-utilities/README.md) and [`cnpj-utilities`](../cnpj-utilities/README.md):
+
+- **CPF formatting**: `CpfFmt::TypeMismatchError`, `CpfFmt::OutOfRangeError`, `CpfFmt::ValidationError`, `CpfFmt::InvalidLengthError` (passed to `on_fail`, not raised by `#format`), and related classes.
+- **CPF generation**: `CpfGen::TypeMismatchError`, `CpfGen::ValidationError`, and related classes.
+- **CPF validation**: `CpfVal::TypeMismatchError` and related classes.
+- **CNPJ formatting**: `CnpjFmt::TypeMismatchError`, `CnpjFmt::OutOfRangeError`, `CnpjFmt::ValidationError`, `CnpjFmt::InvalidLengthError` (passed to `on_fail`), and related classes.
+- **CNPJ generation**: `CnpjGen::TypeMismatchError`, `CnpjGen::ValidationError`, and related classes.
+- **CNPJ validation**: `CnpjVal::TypeMismatchError`, `CnpjVal::ValidationError`, and related classes.
+
+Invalid option types are typically **`TypeError`** subclasses (`*::TypeMismatchError`); invalid option values are domain errors under each package’s `DomainError` hierarchy. CPF and CNPJ validation failures return `false`. Formatting length failures are handled by **`on_fail`** (default returns an empty string).
+
+```ruby
+require 'br-utilities'
+
+begin
+ BrUtils.new.cnpj.format(12_345)
+rescue CnpjFmt::TypeMismatchError => e
+ puts e.message
+end
+
+begin
+ BrUtils.new.cnpj.is_valid(12_345_678_000_198)
+rescue CnpjVal::TypeMismatchError => e
+ puts e.message
+end
+
+# Custom on_fail for invalid length
+custom_fail = ->(value, _exception) { "Invalid: #{value}" }
+
+BrUtils.cpf.format('short', on_fail: custom_fail) # => "Invalid: short"
+BrUtils.cnpj.format('short', on_fail: custom_fail) # => "Invalid: short"
+BrUtils.cpf.format('short') # => "" (default on_fail)
+```
+
+For exhaustive exception lists and edge-case behavior, see each [bundled package](#bundled-packages) README.
+
+### Bundled packages
+
+| Package | Main resources | README |
+|---------|----------------|--------|
+| [`cpf-utilities`](https://rubygems.org/gems/cpf-utilities) | `CpfUtils`, `CpfFormatter`, `CpfGenerator`, `CpfValidator`, `CpfFmt.cpf_fmt`, `CpfGen.cpf_gen`, `CpfVal.cpf_val` | [docs](../cpf-utilities/README.md) |
+| [`cnpj-utilities`](https://rubygems.org/gems/cnpj-utilities) | `CnpjUtils`, `CnpjFormatter`, `CnpjGenerator`, `CnpjValidator`, `CnpjFmt.cnpj_fmt`, `CnpjGen.cnpj_gen`, `CnpjVal.cnpj_val` | [docs](../cnpj-utilities/README.md) |
+
+All of the above are pulled in as dependencies of **`br-utilities`**. Interactive demos: [CPF](https://cpf-utils.vercel.app/) and [CNPJ](https://cnpj-utils.vercel.app/).
+
+## Contribution & Support
+
+We welcome contributions! Please see our [Contributing Guidelines](https://github.com/LacusSolutions/br-utils-ruby/blob/main/CONTRIBUTING.md) for details. If you find this project helpful, please consider:
+
+- ⭐ Starring the repository
+- 🤝 Contributing to the codebase
+- 💡 [Suggesting new features](https://github.com/LacusSolutions/br-utils-ruby/issues)
+- 🐛 [Reporting bugs](https://github.com/LacusSolutions/br-utils-ruby/issues)
## License
-MIT. See [LICENSE](LICENSE).
+This project is licensed under the MIT License — see the [LICENSE](https://github.com/LacusSolutions/br-utils-ruby/blob/main/LICENSE) file for details.
+
+## Changelog
+
+See [CHANGELOG](./CHANGELOG.md) for a list of changes and version history.
+
+---
+
+Made with ❤️ by [Lacus Solutions](https://github.com/LacusSolutions)
diff --git a/README.pt.md b/README.pt.md
new file mode 100644
index 0000000..40d50ac
--- /dev/null
+++ b/README.pt.md
@@ -0,0 +1,700 @@
+
+
+> 🚀 **Suporte total ao [novo formato alfanumérico de CNPJ](https://github.com/user-attachments/files/23937961/calculodvcnpjalfanaumerico.pdf).**
+
+> 🌎 [Access documentation in English](./README.md)
+
+Kit em Ruby para as principais operações com dados brasileiros: CPF (Cadastro de Pessoa Física) e CNPJ (Cadastro Nacional da Pessoa Jurídica). Envolve [`cpf-utilities`](https://rubygems.org/gems/cpf-utilities) e [`cnpj-utilities`](https://rubygems.org/gems/cnpj-utilities) em uma única classe fachada (`BrUtils`).
+
+## Suporte a Ruby
+
+|  |  |  |  |  |
+| --- | --- | --- | --- | --- |
+| Passing ✔ | Passing ✔ | Passing ✔ | Passing ✔ | Passing ✔ |
+
+Requer Ruby **≥ 3.1** (veja `required_ruby_version` no gemspec).
+
+## Recursos
+
+- ✅ **API unificada de alto nível**: Helpers de classe `BrUtils.cpf` / `.cnpj` alias de `BrUtils::DEFAULT`; cada domínio oferece `format`, `generate` e `is_valid`
+- ✅ **Domínios empacotados**: [`cpf-utilities`](https://rubygems.org/gems/cpf-utilities) e [`cnpj-utilities`](https://rubygems.org/gems/cnpj-utilities) instalados juntos
+- ✅ **CNPJ alfanumérico**: Suporte completo ao novo formato alfanumérico de CNPJ (a partir de 2026)
+- ✅ **Instância reutilizável**: Classe `BrUtils` com configurações padrão opcionais de CPF e CNPJ (mapeamentos aninhados, kwargs planos de componentes ou instâncias prontas de utils)
+- ✅ **Acesso em dois níveis**: Prefira atalhos de classes principais na raiz da fachada (`BrUtils::CpfFormatter`, `BrUtils::CnpjValidator`, …); Options, helpers e erros ficam nos módulos aninhados (`BrUtils::CpfFmt`, `BrUtils::CnpjUtils`, …). Os irmãos na raiz (`CpfUtils`, `CnpjUtils`, `CpfFmt`, …) continuam funcionando
+- ✅ **Sobrescritas por chamada**: Configure padrões na fachada / utils de domínio; sobrescreva opções em uma única chamada de `format` / `generate` / `is_valid`
+- ✅ **Tratamento de erros**: Erros de domínio propagam inalterados dos pacotes incluídos; esta gem define `BrUtils::TypeMismatchError` e `BrUtils::InvalidArgumentCombinationError` para uso indevido da API
+
+## Instalação
+
+Instale a gem diretamente:
+
+```bash
+gem install br-utilities
+```
+
+Ou adicione ao seu `Gemfile` e execute `bundle install`:
+
+```ruby
+gem 'br-utilities'
+```
+
+Isso instala **`br-utilities`** junto com [`cpf-utilities`](https://rubygems.org/gems/cpf-utilities) e [`cnpj-utilities`](https://rubygems.org/gems/cnpj-utilities) (que por sua vez trazem os pacotes componentes de CPF e CNPJ). Você **não** precisa de `gem install` / linhas `gem` separados para os pacotes de domínio ao usar **`br-utilities`**.
+
+## Require
+
+```ruby
+require 'br-utilities'
+```
+
+## Início rápido
+
+Prefira os helpers de classe do agregador (`BrUtils.cpf` / `BrUtils.cnpj`) para chamadas pontuais — eles encaminham para `BrUtils::DEFAULT`:
+
+```ruby
+require 'br-utilities'
+
+cpf = '12345678909'
+cnpj = '03603568000195'
+
+# CPF (pessoa física)
+BrUtils.cpf.format(cpf) # => "123.456.789-09"
+BrUtils.cpf.generate(format: true) # => ex.: "478.442.410-55"
+BrUtils.cpf.is_valid('123.456.789-09') # => true
+
+# CNPJ (pessoa jurídica)
+BrUtils.cnpj.format(cnpj) # => "03.603.568/0001-95"
+BrUtils.cnpj.generate(format: true) # => ex.: "AB.123.CDE/0001-55"
+BrUtils.cnpj.is_valid('98765432000198') # => true
+```
+
+**Com agregadores de domínio:**
+
+```ruby
+require 'br-utilities'
+
+cpf = '12345678909'
+cnpj = '03603568000195'
+
+CpfUtils.format(cpf) # => "123.456.789-09"
+CnpjUtils.format(cnpj) # => "03.603.568/0001-95"
+CpfUtils.is_valid(cpf) # => true
+CnpjUtils.is_valid(cnpj) # => true
+```
+
+**Com helpers funcionais** (módulos irmãos na raiz, carregados por esta gem):
+
+```ruby
+require 'br-utilities'
+
+cpf = '12345678909'
+cnpj = '03603568000195'
+
+CpfFmt.cpf_fmt(cpf) # => "123.456.789-09"
+CpfVal.cpf_val(cpf) # => true
+CnpjFmt.cnpj_fmt(cnpj) # => "03.603.568/0001-95"
+CnpjVal.cnpj_val(cnpj) # => true
+```
+
+## Utilização
+
+Você pode trabalhar destas formas equivalentes:
+
+1. **`BrUtils.cpf` / `.cnpj`** — helpers de classe para chamadas rápidas (encaminham para `DEFAULT`).
+2. **`BrUtils::DEFAULT`** — singleton compartilhado mutável (o mesmo objeto usado pelos helpers de classe; em todo o processo / não isolado por thread).
+3. **`BrUtils.new`** — instância configurável com padrões compartilhados entre os domínios CPF e CNPJ.
+4. **Agregadores de domínio** — `CpfUtils` / `CnpjUtils` (ou `BrUtils::CpfUtils` / `BrUtils::CnpjUtils`) diretamente.
+5. **Classes principais sob `BrUtils`** — `BrUtils::CpfFormatter`, `BrUtils::CnpjGenerator` e atalhos relacionados.
+6. **Módulos aninhados do pacote** — Options, helpers, erros e tipos via `BrUtils::CpfFmt` / `CpfGen` / `CpfVal` / `CnpjFmt` / `CnpjGen` / `CnpjVal` / `CpfUtils` / `CnpjUtils`.
+7. **Módulos irmãos na raiz** (ainda suportados) — `CpfFmt`, `CnpjUtils` e demais inalterados.
+
+Todas as abordagens expõem as mesmas opções e comportamento dentro de cada domínio. Para tabelas de opções exaustivas e detalhes específicos de cada componente, consulte o README de cada [pacote incluído](#pacotes-incluídos).
+
+### Helpers de classe (`BrUtils.cpf` / `.cnpj`)
+
+Esses métodos de classe retornam as mesmas instâncias de utils de domínio que `BrUtils::DEFAULT`. Prefira-os para chamadas pontuais:
+
+```ruby
+BrUtils.cpf.format('12345678909')
+BrUtils.cpf.generate(format: true)
+BrUtils.cpf.is_valid('12345678909')
+
+BrUtils.cnpj.format('03603568000195')
+BrUtils.cnpj.generate(type: 'numeric')
+BrUtils.cnpj.is_valid('98765432000198')
+```
+
+### `BrUtils::DEFAULT` (instância padrão)
+
+`BrUtils::DEFAULT` é o singleton pré-construído e **mutável** por trás dos helpers de classe (paridade com a exportação padrão do JS / `br_utils` do Python). Sua configuração é **em todo o processo e compartilhada entre threads**: mutá-lo (ex.: `DEFAULT.cpf = …`) afeta chamadas subsequentes de `BrUtils.cpf` / `.cnpj` para todos os callers no processo. Prefira `BrUtils.new` ou opções por chamada para trabalho concorrente ou isolado; instâncias customizadas permanecem independentes de `DEFAULT`:
+
+```ruby
+BrUtils::DEFAULT.cpf = CpfUtils.new(formatter: { dash_key: '|' })
+BrUtils.cpf.format('12345678909') # => "123.456.789|09"
+
+custom = BrUtils.new
+custom.cpf.format('12345678909') # => "123.456.789-09" (não afetado)
+```
+
+### `BrUtils` (classe)
+
+Para utils de CPF ou CNPJ padrão customizados, crie sua própria instância:
+
+```ruby
+require 'br-utilities'
+
+utils = BrUtils.new(
+ cpf: {
+ formatter: { hidden: true, hidden_key: '#' },
+ generator: { format: true }
+ },
+ cnpj: {
+ formatter: { hidden: true },
+ generator: { type: 'numeric', format: true },
+ validator: { type: 'numeric' }
+ }
+)
+
+utils.cpf.format('12345678909') # => "123.###.###-##"
+utils.cpf.generate # => ex.: "005.265.352-88"
+utils.cnpj.format('03603568000195') # => "03.603.***/****-**"
+utils.cnpj.generate # => ex.: "73.008.535/0005-06"
+
+# Acessar ou substituir instâncias internas de domínio
+utils.cpf # => CpfUtils
+utils.cnpj # => CnpjUtils
+```
+
+- **`BrUtils.new(settings = nil, **keywords)`**: Configurações opcionais. Passe um `Hash` de settings com chaves `:cpf` e/ou `:cnpj`, **ou** as mesmas chaves (mais kwargs planos de componentes) como argumentos nomeados — não ambos (passar ambos lança `BrUtils::InvalidArgumentCombinationError`).
+ - **`:cpf` / `:cnpj`**: Uma instância pronta de `CpfUtils` / `CnpjUtils` **ou** um `Hash` de configuração repassado ao construtor do utils correspondente. Dentro desse `Hash`, cada chave de recurso (`:formatter`, `:generator` e `:validator` para CNPJ) aceita um objeto de opções ou um mapeamento de valores de opção.
+ - **`:cpf_formatter`**, **`:cpf_generator`**, **`:cnpj_formatter`**, **`:cnpj_generator`**, **`:cnpj_validator`**: Argumentos planos de conveniência quando apenas componentes individuais precisam de customização. São ignorados quando o argumento `:cpf` ou `:cnpj` correspondente é fornecido.
+- **`#cpf`**, **`#cnpj`**: Acessores (getters e setters) das instâncias de utils de domínio. Os setters aceitam uma instância de utils, um `Hash` de configuração ou `nil` para voltar aos padrões (substitui a instância inteira; não faz merge).
+
+Opções planas no construtor (alternativa aos mapeamentos aninhados `:cpf` / `:cnpj`):
+
+```ruby
+require 'br-utilities'
+
+utils = BrUtils.new(
+ cpf_formatter: CpfFmt::CpfFormatterOptions.new(hidden: true, hidden_key: '#'),
+ cpf_generator: CpfGen::CpfGeneratorOptions.new(format: true),
+ cnpj_formatter: CnpjFmt::CnpjFormatterOptions.new(hidden: true, hidden_key: '#'),
+ cnpj_generator: CnpjGen::CnpjGeneratorOptions.new(format: true, type: 'numeric'),
+ cnpj_validator: CnpjVal::CnpjValidatorOptions.new(type: 'numeric')
+)
+```
+
+Passar um `Hash` de settings posicional junto com qualquer palavra-chave lança:
+
+```ruby
+BrUtils.new({ cpf: {} }, cnpj: CnpjUtils.new)
+# lança BrUtils::InvalidArgumentCombinationError
+```
+
+### Padrões da instância e sobrescritas por chamada
+
+```ruby
+require 'br-utilities'
+
+utils = BrUtils.new(
+ cpf: {
+ formatter: { hidden: true, hidden_key: '#' },
+ generator: { format: true }
+ },
+ cnpj: {
+ formatter: { hidden: true, hidden_key: '#' },
+ generator: { format: true },
+ validator: { type: 'numeric' }
+ }
+)
+
+cpf = '12345678909'
+cnpj = '03603568000195'
+
+utils.cpf.format(cpf) # => "123.###.###-##"
+utils.cpf.format(cpf, hidden: false) # só nesta chamada: sem máscara
+utils.cpf.generate(format: false) # só nesta chamada: saída compacta
+
+utils.cnpj.format(cnpj) # => "03.603.###/####-##"
+utils.cnpj.format(cnpj, hidden: false) # só nesta chamada: sem máscara
+utils.cnpj.is_valid('1QB5UKALPYFP59') # => false (validador da instância é só numérico)
+utils.cnpj.is_valid( # => true nesta chamada
+ '1QB5UKALPYFP59',
+ type: 'alphanumeric'
+)
+```
+
+Passar uma instância de `CnpjFmt::CnpjFormatterOptions`, `CnpjGen::CnpjGeneratorOptions` ou `CnpjVal::CnpjValidatorOptions` ao construtor de `BrUtils` armazena esse objeto por referência — mutá-lo depois afeta chamadas subsequentes sem sobrescrita por chamada.
+
+Para alterar uma única opção aninhada sem substituir o utils de domínio inteiro, mute via os acessores de domínio (ex.: `utils.cpf.formatter.options.hidden = true`).
+
+### Operações de CPF
+
+Os métodos de CPF são acessados via `BrUtils.cpf`, `utils.cpf`, `CpfUtils` ou os helpers `CpfFmt` / `CpfGen` / `CpfVal`. O CPF usa a API de [`cpf-utilities`](../cpf-utilities/README.pt.md).
+
+#### Formatação (`#format` / `CpfFmt.cpf_fmt`)
+
+| Opção | Tipo | Padrão | Descrição |
+|--------|------|---------|-------------|
+| `hidden` | `Boolean` | `false` | Se `true`, mascara dígitos entre `hidden_start` e `hidden_end` com `hidden_key` |
+| `hidden_key` | `String` | `'*'` | Caractere(s) usados para substituir dígitos mascarados |
+| `hidden_start` | `Integer` | `3` | Índice inicial (0–10, inclusivo) do intervalo a ocultar |
+| `hidden_end` | `Integer` | `10` | Índice final (0–10, inclusivo) do intervalo a ocultar |
+| `dot_key` | `String` | `'.'` | Delimitador de ponto (ex.: em `123.456.789`) |
+| `dash_key` | `String` | `'-'` | Delimitador de hífen (ex.: antes dos dígitos verificadores `…-09`) |
+| `escape` | `Boolean` | `false` | Se `true`, escapa caracteres especiais HTML no resultado |
+| `encode` | `Boolean` | `false` | Se `true`, codifica o resultado para URL (similar ao `encodeURIComponent` do JavaScript) |
+| `on_fail` | `Proc` / invocável | retorna `''` | Callback quando o tamanho da entrada sanitizada ≠ 11; o retorno é usado como resultado |
+
+O **`on_fail`** padrão retorna uma string vazia. Comprimento inválido **não** lança exceção em `#format`.
+
+```ruby
+require 'br-utilities'
+
+cpf = '12345678909'
+
+BrUtils.cpf.format(cpf) # => "123.456.789-09"
+BrUtils.cpf.format(cpf, hidden: true, hidden_key: '#') # => "123.###.###-##"
+BrUtils.cpf.format(cpf, dot_key: '', dash_key: '_') # => "123456789_09"
+
+CpfFmt.cpf_fmt(cpf, hidden: true) # => "123.***.***-**"
+```
+
+#### Geração (`#generate` / `CpfGen.cpf_gen`)
+
+| Opção | Tipo | Padrão | Descrição |
+|--------|------|---------|-------------|
+| `format` | `Boolean` | `false` | Se `true`, retorna o CPF gerado no formato padrão (`000.000.000-00`) |
+| `prefix` | `String` | `''` | String parcial inicial (0–9 dígitos). Não dígitos são removidos; caracteres faltantes são gerados e os dígitos verificadores calculados. Prefixos com mais de 9 dígitos são truncados silenciosamente. |
+
+Regras de prefixo: a base (primeiros 9 dígitos) não pode ser toda zeros; 9 dígitos repetidos (ex.: `999999999`) não são permitidos.
+
+```ruby
+require 'br-utilities'
+
+BrUtils.cpf.generate # => ex.: "11508890048"
+BrUtils.cpf.generate(format: true) # => ex.: "661.134.831-00"
+BrUtils.cpf.generate(prefix: '123456789') # => "12345678909"
+CpfGen.cpf_gen(prefix: '123456789', format: true) # => "123.456.789-09"
+```
+
+#### Validação (`#is_valid` / `CpfVal.cpf_val`)
+
+Aceita CPF formatado ou não (ou um `Array` de strings). Retorna **`true`** ou **`false`** sem lançar exceção para CPF inválido. Não há opções de validador.
+
+```ruby
+require 'br-utilities'
+
+BrUtils.cpf.is_valid('12345678909') # => true
+BrUtils.cpf.is_valid('123.456.789-09') # => true
+BrUtils.cpf.is_valid('12345678900') # => false
+CpfVal.cpf_val('12345678909') # => true
+```
+
+### Operações de CNPJ
+
+Os métodos de CNPJ são acessados via `BrUtils.cnpj`, `utils.cnpj`, `CnpjUtils` ou os helpers `CnpjFmt` / `CnpjGen` / `CnpjVal`. O CNPJ usa a API de [`cnpj-utilities`](../cnpj-utilities/README.pt.md).
+
+#### Formatação (`#format` / `CnpjFmt.cnpj_fmt`)
+
+| Opção | Tipo | Padrão | Descrição |
+|--------|------|---------|-------------|
+| `hidden` | `Boolean` | `false` | Se `true`, mascara caracteres entre `hidden_start` e `hidden_end` com `hidden_key` |
+| `hidden_key` | `String` | `'*'` | Caractere(s) usados para substituir caracteres mascarados |
+| `hidden_start` | `Integer` | `5` | Índice inicial (0–13, inclusivo) do intervalo a ocultar |
+| `hidden_end` | `Integer` | `13` | Índice final (0–13, inclusivo) do intervalo a ocultar |
+| `dot_key` | `String` | `'.'` | Delimitador de ponto (ex.: em `12.345.678`) |
+| `slash_key` | `String` | `'/'` | Delimitador de barra (ex.: antes da filial `…/0001-90`) |
+| `dash_key` | `String` | `'-'` | Delimitador de hífen (ex.: antes dos dígitos verificadores `…-90`) |
+| `escape` | `Boolean` | `false` | Se `true`, escapa caracteres especiais HTML no resultado |
+| `encode` | `Boolean` | `false` | Se `true`, codifica o resultado para URL (similar ao `encodeURIComponent` do JavaScript) |
+| `on_fail` | `Proc` / invocável | retorna `''` | Callback quando o tamanho da entrada sanitizada ≠ 14; o retorno é usado como resultado |
+
+O **`on_fail`** padrão retorna uma string vazia. Tipos de entrada incorretos lançam **`CnpjFmt::TypeMismatchError`**.
+
+```ruby
+require 'br-utilities'
+
+cnpj = '03603568000195'
+
+BrUtils.cnpj.format(cnpj) # => "03.603.568/0001-95"
+BrUtils.cnpj.format('12ABC34500DE99') # => "12.ABC.345/00DE-99"
+BrUtils.cnpj.format( # => "03.603.###/####-##"
+ cnpj,
+ hidden: true,
+ hidden_key: '#'
+)
+BrUtils.cnpj.format( # => "03603568|0001_95"
+ cnpj,
+ dot_key: '',
+ slash_key: '|',
+ dash_key: '_'
+)
+
+CnpjFmt.cnpj_fmt(cnpj) # => "03.603.568/0001-95"
+```
+
+#### Geração (`#generate` / `CnpjGen.cnpj_gen`)
+
+| Opção | Tipo | Padrão | Descrição |
+|--------|------|---------|-------------|
+| `format` | `Boolean` | `false` | Se `true`, retorna o CNPJ gerado no formato padrão (`00.000.000/0000-00`) |
+| `prefix` | `String` | `''` | String parcial inicial (0–12 caracteres alfanuméricos). Caracteres faltantes são gerados e os dígitos verificadores calculados. |
+| `type` | `String` | `'alphanumeric'` | Conjunto de caracteres para a parte gerada aleatoriamente: `'numeric'`, `'alphabetic'` ou `'alphanumeric'`. **Os dígitos verificadores são sempre numéricos.** |
+
+Regras de prefixo: o ID base (primeiros 8 caracteres) e o ID da filial (caracteres 9–12) não podem ser todos zeros; 12 dígitos repetidos (ex.: `111111111111`) também não são permitidos.
+
+```ruby
+require 'br-utilities'
+
+BrUtils.cnpj.generate # => ex.: "1GJTR3J3XSSA96"
+BrUtils.cnpj.generate(format: true) # => ex.: "V1.J0V.8WE/DVZ7-50"
+BrUtils.cnpj.generate( # => ex.: "12345678855883"
+ prefix: '12345678',
+ type: 'numeric'
+)
+CnpjGen.cnpj_gen(type: 'numeric') # => ex.: "65453043000178"
+```
+
+#### Validação (`#is_valid` / `CnpjVal.cnpj_val`)
+
+| Opção | Tipo | Padrão | Descrição |
+|--------|------|---------|-------------|
+| `case_sensitive` | `Boolean` | `true` | Se `false`, letras minúsculas são aceitas para CNPJ alfanumérico (a entrada é convertida para maiúsculas antes da validação). |
+| `type` | `String` | `'alphanumeric'` | `'numeric'`: apenas dígitos (0–9); `'alphanumeric'`: dígitos e letras (0–9, A–Z). |
+
+```ruby
+require 'br-utilities'
+
+BrUtils.cnpj.is_valid('98765432000198') # => true
+BrUtils.cnpj.is_valid('98765432000199') # => false
+BrUtils.cnpj.is_valid('1QB5UKALPYFP59') # => true
+BrUtils.cnpj.is_valid('1QB5UKALpyfp59') # => false
+BrUtils.cnpj.is_valid( # => true
+ '1QB5UKALpyfp59',
+ case_sensitive: false
+)
+BrUtils.cnpj.is_valid( # => false
+ '1QB5UKALPYFP59',
+ type: 'numeric'
+)
+
+CnpjVal.cnpj_val('98765432000198') # => true
+CnpjVal.cnpj_val('1QB5UKALpyfp59', case_sensitive: false) # => true
+CnpjVal.cnpj_val('1QB5UKALPYFP59', type: 'numeric') # => false
+```
+
+CNPJ inválido retorna **`false`** sem lançar exceção. Tipos de entrada incorretos lançam **`CnpjVal::TypeMismatchError`**.
+
+### Agregadores de domínio (isolados)
+
+Use `CpfUtils` ou `CnpjUtils` diretamente quando precisar de apenas um domínio:
+
+```ruby
+require 'br-utilities'
+
+cpf_utils = CpfUtils.new(
+ formatter: { hidden: true },
+ generator: { format: true }
+)
+
+cnpj_utils = CnpjUtils.new(
+ formatter: { hidden: true },
+ generator: { format: true },
+ validator: { type: 'numeric' }
+)
+
+cpf_utils.format('12345678909') # => "123.***.***-**"
+cnpj_utils.format('03603568000195') # => "03.603.***/****-**"
+```
+
+### Acessando componentes
+
+Cada agregador de domínio expõe seu formatador, gerador e validador internos:
+
+```ruby
+require 'br-utilities'
+
+utils = BrUtils.new
+
+utils.cpf.formatter.format('12345678909', hidden: true) # => "123.***.***-**"
+utils.cpf.generator.generate(format: true) # => ex.: "545.507.690-68"
+utils.cpf.validator.is_valid('12345678909') # => true
+
+utils.cnpj.formatter.format('12ABC34500DE99') # => "12.ABC.345/00DE-99"
+utils.cnpj.generator.generate(format: true) # => ex.: "8O.BE5.2KL/UI0Y-06"
+utils.cnpj.validator.is_valid('03603568000195') # => true
+```
+
+### Usando classes componentes e módulos aninhados
+
+Caminhos preferidos após `require 'br-utilities'`:
+
+```ruby
+require 'br-utilities'
+
+# Classes principais na raiz da fachada
+formatter = BrUtils::CpfFormatter.new(hidden: true)
+generator = BrUtils::CnpjGenerator.new(type: 'numeric')
+validator = BrUtils::CnpjValidator.new
+
+formatter.format('12345678909') # => "123.***.***-**"
+
+# Options, helpers e erros nos módulos aninhados do pacote
+options = BrUtils::CpfFmt::CpfFormatterOptions.new(dash_key: '|')
+BrUtils::CpfFmt.cpf_fmt('12345678909') # => "123.456.789-09"
+
+begin
+ BrUtils::CnpjFmt.cnpj_fmt(12_345)
+rescue BrUtils::CnpjFmt::TypeMismatchError
+ # tipo de entrada incorreto
+end
+```
+
+Os irmãos na raiz continuam suportados (os mesmos objetos dos nests):
+
+```ruby
+CpfFmt.cpf_fmt('12345678909', dash_key: '|') # => "123.456.789|09"
+CpfGen.cpf_gen(format: true) # => ex.: "478.442.410-55"
+CpfVal.cpf_val('12345678909') # => true
+CnpjFmt.cnpj_fmt('01ABC234000X56', slash_key: '|') # => "01.ABC.234|000X-56"
+CnpjGen.cnpj_gen(type: 'numeric') # => ex.: "65453043000178"
+CnpjVal.cnpj_val('9JN7MGLJZXIO50') # => true
+```
+
+Consulte [`cpf-utilities`](../cpf-utilities/README.pt.md) e [`cnpj-utilities`](../cnpj-utilities/README.pt.md) para detalhes completos de opções e erros.
+
+### Misturando estilos
+
+Use `BrUtils` onde uma configuração compartilhada ajuda, e componentes ou helpers isolados em outros pontos — são as mesmas classes subjacentes:
+
+```ruby
+require 'br-utilities'
+
+utils = BrUtils.new(cnpj: { validator: { type: 'numeric' } })
+
+# Via fachada
+utils.cpf.format('12345678909') # => "123.456.789-09"
+
+# Via componente retornado pela fachada
+utils.cnpj.formatter.format('12ABC34500DE99') # => "12.ABC.345/00DE-99"
+
+# Via instância de componente separada
+BrUtils::CnpjFormatter.new.format('03603568000195') # => "03.603.568/0001-95"
+
+# Via helpers funcionais
+CpfFmt.cpf_fmt('12345678909') # => "123.456.789-09"
+CnpjVal.cnpj_val('98.765.432/0001-98') # => true
+```
+
+## API
+
+### Exportações
+
+Após `require 'br-utilities'`:
+
+- **`BrUtils`**: Classe fachada para criar uma instância com configurações opcionais dos utils de CPF e CNPJ.
+- **`BrUtils.cpf` / `.cnpj`**: Helpers de classe que encaminham para os acessores de domínio de `BrUtils::DEFAULT`.
+- **`BrUtils::DEFAULT`**: Instância pré-construída e mutável de `BrUtils` (o mesmo objeto usado pelos helpers de classe). Em todo o processo / compartilhada entre threads — prefira `BrUtils.new` ou opções por chamada sob concorrência.
+- **`BrUtils::VERSION`**: String da versão da gem.
+- **Atalhos de classes principais**: `BrUtils::CpfFormatter`, `BrUtils::CpfFormatterOptions`, `BrUtils::CpfGenerator`, `BrUtils::CpfGeneratorOptions`, `BrUtils::CpfValidator`, `BrUtils::CnpjFormatter`, `BrUtils::CnpjFormatterOptions`, `BrUtils::CnpjGenerator`, `BrUtils::CnpjGeneratorOptions`, `BrUtils::CnpjValidator`, `BrUtils::CnpjValidatorOptions` (os mesmos objetos das classes irmãs). Atalhos de marcadores de erro: `BrUtils::CpfFormatterError`, `BrUtils::CpfGeneratorError`, `BrUtils::CpfValidatorError`, `BrUtils::CnpjFormatterError`, `BrUtils::CnpjGeneratorError`, `BrUtils::CnpjValidatorError`.
+- **Módulos aninhados do pacote**: `BrUtils::CpfUtils`, `BrUtils::CnpjUtils`, `BrUtils::CpfFmt`, `BrUtils::CpfGen`, `BrUtils::CpfVal`, `BrUtils::CnpjFmt`, `BrUtils::CnpjGen`, `BrUtils::CnpjVal` — superfície completa dos irmãos (Options, helpers, erros, tipos).
+- **Módulos irmãos na raiz** (ainda suportados): `CpfUtils`, `CnpjUtils`, `CpfFmt`, `CpfGen`, `CpfVal`, `CnpjFmt`, `CnpjGen`, `CnpjVal` — os mesmos objetos dos nests.
+
+### Erros e exceções
+
+`BrUtils` define apenas erros de uso indevido da API para as regras de argumentos desta gem. Erros de domínio são lançados pelos pacotes incluídos e propagam inalterados.
+
+#### Definidos por `br-utilities`
+
+Os erros definidos por esta gem são **apenas uso indevido da API** (tipo errado ou combinação inválida de argumentos). Todo erro customizado inclui o módulo marcador `BrUtils::Error`. Esta gem **não** define `BrUtils::DomainError` nem folhas de domínio — falhas de domínio vêm apenas dos [pacotes incluídos](#propagados-dos-pacotes-incluídos) e mantêm os namespaces desses pacotes (`CpfFmt::…`, `CnpjGen::…`, …).
+
+`rescue BrUtils::Error` captura **apenas** erros que esta gem lança. **Não** captura erros de componentes que propagam inalterados.
+
+##### Resumo
+
+| Classe | Herda de | Categoria | Condição de disparo |
+|-------|---------------|----------|-------------------|
+| `BrUtils::InvalidArgumentCombinationError` | `BrUtils::InvalidArgumentCombinationError < ArgumentError < StandardError` (+ `include BrUtils::Error`) | Uso indevido da API | `Hash` de settings não-`nil` passado junto com qualquer argumento nomeado não-`nil` |
+| `BrUtils::TypeMismatchError` | `BrUtils::TypeMismatchError < TypeError < StandardError` (+ `include BrUtils::Error`) | Uso indevido da API | Argumento `settings` não-`nil` em `BrUtils.new` não é um `Hash` |
+
+##### `BrUtils::Error` (módulo marcador)
+
+- **Herança:** módulo marcador misturado em todo erro customizado que esta gem lança via `include` (não é uma classe).
+- **Categoria:** N/A (apenas alvo de rescue) — não é um modo de falha por si só.
+- **Quando é lançado:** Nunca lançado diretamente; incluído por todo erro customizado que esta gem lança.
+- **Exemplo:** N/A
+- **Como resgatar:**
+
+```ruby
+rescue BrUtils::Error
+ # TypeMismatchError, InvalidArgumentCombinationError apenas desta gem
+ # (não CpfFmt::*, CnpjGen::* ou outros erros dos pacotes incluídos)
+```
+
+##### `BrUtils::TypeMismatchError`
+
+- **Herança:** `BrUtils::TypeMismatchError < TypeError < StandardError` (inclui `BrUtils::Error`)
+- **Categoria:** Uso indevido da API — o caller passou um valor do tipo errado.
+- **Quando é lançado:** Quando `BrUtils.new` recebe um argumento `settings` não-`nil` que não é um `Hash`.
+- **Exemplo:**
+
+```ruby
+BrUtils.new('not-a-hash') # lança BrUtils::TypeMismatchError
+BrUtils.new(false) # lança BrUtils::TypeMismatchError (false é não-nil)
+```
+
+- **Como resgatar:**
+
+```ruby
+rescue BrUtils::TypeMismatchError
+ # violação de contrato de tipo desta gem
+
+rescue TypeError
+ # erros nativos de tipo, incluindo TypeMismatchError desta gem
+```
+
+##### `BrUtils::InvalidArgumentCombinationError`
+
+- **Herança:** `BrUtils::InvalidArgumentCombinationError < ArgumentError < StandardError` (inclui `BrUtils::Error`)
+- **Categoria:** Uso indevido da API — o caller misturou padrões de argumentos mutuamente exclusivos.
+- **Quando é lançado:** Quando `BrUtils.new` recebe um `Hash` de settings não-`nil` e qualquer argumento nomeado não-`nil` (`cpf:`, `cnpj:`, `cpf_formatter:`, …) ao mesmo tempo.
+- **Exemplo:**
+
+```ruby
+BrUtils.new({ cpf: { formatter: { hidden: true } } }, cnpj: { formatter: { hidden: true } })
+# lança BrUtils::InvalidArgumentCombinationError
+```
+
+- **Como resgatar:**
+
+```ruby
+rescue BrUtils::InvalidArgumentCombinationError
+ # combinação inválida de assinatura desta gem
+
+rescue ArgumentError
+ # erros nativos de argumento, incluindo InvalidArgumentCombinationError desta gem
+```
+
+##### Granularidade de rescue
+
+Cada nível é mostrado como seu próprio exemplo isolado (não os una em uma única escada de `rescue` — um handler nativo amplo tornaria cláusulas mais estreitas inalcançáveis).
+
+```ruby
+require 'br-utilities'
+
+# 1) Classe nativa única — captura erros de uso indevido desse tipo,
+# incluindo os não-biblioteca já tratados em outro ponto do código do consumidor.
+begin
+ BrUtils.new('not-a-hash')
+rescue TypeError
+ # BrUtils::TypeMismatchError e qualquer outro TypeError (biblioteca ou não)
+end
+
+begin
+ BrUtils.new({ cpf: {} }, cnpj: CnpjUtils.new)
+rescue ArgumentError
+ # BrUtils::InvalidArgumentCombinationError e qualquer outro ArgumentError (biblioteca ou não)
+end
+```
+
+```ruby
+require 'br-utilities'
+
+# 2) BrUtils::DomainError — não aplicável: esta gem não define DomainError
+# (nem folhas de domínio). Falhas de domínio vêm apenas dos pacotes incluídos.
+# begin
+# BrUtils.new(cpf: { formatter: { hidden_start: -1 } })
+# rescue BrUtils::DomainError # NameError — constante não definida
+# end
+```
+
+```ruby
+require 'br-utilities'
+
+# 3) BrUtils::Error — captura tudo que esta gem lança, independentemente da ancestralidade nativa.
+# Não captura CpfFmt::*, CnpjGen::* ou outros erros dos pacotes incluídos.
+begin
+ BrUtils.new('not-a-hash')
+rescue BrUtils::Error
+ # todo erro customizado que inclui BrUtils::Error
+end
+```
+
+```ruby
+require 'br-utilities'
+
+# 4) Classe folha específica — captura apenas aquele modo de falha exato.
+begin
+ BrUtils.new('not-a-hash')
+rescue BrUtils::TypeMismatchError
+ # apenas BrUtils::TypeMismatchError
+end
+```
+
+#### Propagados dos pacotes incluídos
+
+`BrUtils` não redefine tipos de exceção de domínio. Construção, setters e chamadas de métodos de domínio lançam os mesmos erros de [`cpf-utilities`](../cpf-utilities/README.pt.md) e [`cnpj-utilities`](../cnpj-utilities/README.pt.md):
+
+- **Formatação de CPF**: `CpfFmt::TypeMismatchError`, `CpfFmt::OutOfRangeError`, `CpfFmt::ValidationError`, `CpfFmt::InvalidLengthError` (passado a `on_fail`, não lançado por `#format`) e classes relacionadas.
+- **Geração de CPF**: `CpfGen::TypeMismatchError`, `CpfGen::ValidationError` e classes relacionadas.
+- **Validação de CPF**: `CpfVal::TypeMismatchError` e classes relacionadas.
+- **Formatação de CNPJ**: `CnpjFmt::TypeMismatchError`, `CnpjFmt::OutOfRangeError`, `CnpjFmt::ValidationError`, `CnpjFmt::InvalidLengthError` (passado a `on_fail`) e classes relacionadas.
+- **Geração de CNPJ**: `CnpjGen::TypeMismatchError`, `CnpjGen::ValidationError` e classes relacionadas.
+- **Validação de CNPJ**: `CnpjVal::TypeMismatchError`, `CnpjVal::ValidationError` e classes relacionadas.
+
+Tipos de opção inválidos são tipicamente subclasses de **`TypeError`** (`*::TypeMismatchError`); valores de opção inválidos são erros de domínio sob a hierarquia `DomainError` de cada pacote. Falhas de validação de CPF e CNPJ retornam `false`. Falhas de comprimento na formatação são tratadas por **`on_fail`** (padrão retorna string vazia).
+
+```ruby
+require 'br-utilities'
+
+begin
+ BrUtils.new.cnpj.format(12_345)
+rescue CnpjFmt::TypeMismatchError => e
+ puts e.message
+end
+
+begin
+ BrUtils.new.cnpj.is_valid(12_345_678_000_198)
+rescue CnpjVal::TypeMismatchError => e
+ puts e.message
+end
+
+# on_fail customizado para comprimento inválido
+custom_fail = ->(value, _exception) { "Invalid: #{value}" }
+
+BrUtils.cpf.format('short', on_fail: custom_fail) # => "Invalid: short"
+BrUtils.cnpj.format('short', on_fail: custom_fail) # => "Invalid: short"
+BrUtils.cpf.format('short') # => "" (on_fail padrão)
+```
+
+Para listas exaustivas de exceções e comportamento em casos extremos, consulte o README de cada [pacote incluído](#pacotes-incluídos).
+
+### Pacotes incluídos
+
+| Pacote | Principais recursos | README |
+|---------|----------------|--------|
+| [`cpf-utilities`](https://rubygems.org/gems/cpf-utilities) | `CpfUtils`, `CpfFormatter`, `CpfGenerator`, `CpfValidator`, `CpfFmt.cpf_fmt`, `CpfGen.cpf_gen`, `CpfVal.cpf_val` | [docs](../cpf-utilities/README.pt.md) |
+| [`cnpj-utilities`](https://rubygems.org/gems/cnpj-utilities) | `CnpjUtils`, `CnpjFormatter`, `CnpjGenerator`, `CnpjValidator`, `CnpjFmt.cnpj_fmt`, `CnpjGen.cnpj_gen`, `CnpjVal.cnpj_val` | [docs](../cnpj-utilities/README.pt.md) |
+
+Todos os acima são puxados como dependências de **`br-utilities`**. Demos interativas: [CPF](https://cpf-utils.vercel.app/) e [CNPJ](https://cnpj-utils.vercel.app/).
+
+## Contribuição e suporte
+
+Contribuições são bem-vindas! Consulte as [Diretrizes de contribuição](https://github.com/LacusSolutions/br-utils-ruby/blob/main/CONTRIBUTING.md). Se o projeto for útil para você, considere:
+
+- ⭐ Dar uma estrela no repositório
+- 🤝 Contribuir com código
+- 💡 [Sugerir novas funcionalidades](https://github.com/LacusSolutions/br-utils-ruby/issues)
+- 🐛 [Reportar bugs](https://github.com/LacusSolutions/br-utils-ruby/issues)
+
+## Licença
+
+Este projeto está sob a licença MIT — veja o arquivo [LICENSE](https://github.com/LacusSolutions/br-utils-ruby/blob/main/LICENSE).
+
+## Changelog
+
+Veja o [CHANGELOG](./CHANGELOG.md) para alterações e histórico de versões.
+
+---
+
+Feito com ❤️ por [Lacus Solutions](https://github.com/LacusSolutions)
diff --git a/packages/br-utilities/CHANGELOG.md b/packages/br-utilities/CHANGELOG.md
index f2eea00..87656f0 100644
--- a/packages/br-utilities/CHANGELOG.md
+++ b/packages/br-utilities/CHANGELOG.md
@@ -1 +1,17 @@
# br-utilities
+
+## 1.0.0
+
+### 🚀 Stable Version Released!
+
+Unified toolkit to deal with Brazilian documents (CPF and CNPJ): validation, formatting, and generation of valid IDs. Main features:
+
+- **Unified façade**: `BrUtils` aggregates configurable `CpfUtils` and `CnpjUtils` behind `#cpf` / `#cnpj` accessors (with setters; `nil` resets to defaults).
+- **Constructor flexibility**: accept a settings `Hash` or keyword args (`cpf:` / `cnpj:` nested mappings, or flat `cpf_formatter:` / `cnpj_validator:`-style kwargs; nested wins when both present).
+- **Quick helpers**: `BrUtils.cpf` / `.cnpj` alias mutable `BrUtils::DEFAULT` (process-wide; prefer `BrUtils.new` under concurrency).
+- **Two-tier re-exports**: main classes at the façade root (`BrUtils::CpfFormatter`, `BrUtils::CnpjValidator`, …); full sibling surface under `BrUtils::CpfFmt` / `CpfUtils` / `CnpjFmt` / ….
+- **One install**: depends on `cpf-utilities` and `cnpj-utilities` so both domains ship without requiring each gem separately.
+- **Alphanumeric CNPJ**: CNPJ path supports 14-character alphanumeric IDs via `cnpj-utilities` (numeric CPF via `cpf-utilities`).
+- **Structured errors**: `BrUtils::TypeMismatchError` / `InvalidArgumentCombinationError` (+ `BrUtils::Error` marker) for façade misuse; domain errors propagate from nested packages.
+
+For detailed usage and API reference, see the [README](./README.md).
diff --git a/packages/br-utilities/Gemfile b/packages/br-utilities/Gemfile
index 2d266a0..c6122eb 100644
--- a/packages/br-utilities/Gemfile
+++ b/packages/br-utilities/Gemfile
@@ -4,9 +4,6 @@ source 'https://rubygems.org'
gemspec
-gem 'cnpj-utilities', path: '../cnpj-utilities'
-gem 'cpf-utilities', path: '../cpf-utilities'
-
group :test do
gem 'rake', '~> 13.2'
gem 'rspec', '~> 3.13'
diff --git a/packages/br-utilities/README.md b/packages/br-utilities/README.md
new file mode 100644
index 0000000..abc1e1d
--- /dev/null
+++ b/packages/br-utilities/README.md
@@ -0,0 +1,707 @@
+
+
+[](https://rubygems.org/gems/br-utilities)
+[](https://rubygems.org/gems/br-utilities)
+[](https://www.ruby-lang.org/)
+[](https://github.com/LacusSolutions/br-utils-ruby/actions)
+[](https://github.com/LacusSolutions/br-utils-ruby)
+[](https://github.com/LacusSolutions/br-utils-ruby/blob/main/LICENSE)
+
+> 🚀 **Full support for the [new alphanumeric CNPJ format](https://github.com/user-attachments/files/23937961/calculodvcnpjalfanaumerico.pdf).**
+
+> 🌎 [Acessar documentação em português](./README.pt.md)
+
+A Ruby toolkit to handle the main operations with Brazilian-related data: CPF (Individual's Taxpayer ID) and CNPJ (Business Tax ID). It wraps [`cpf-utilities`](https://rubygems.org/gems/cpf-utilities) and [`cnpj-utilities`](https://rubygems.org/gems/cnpj-utilities) in a single façade class (`BrUtils`).
+
+## Ruby Support
+
+|  |  |  |  |  |
+| --- | --- | --- | --- | --- |
+| Passing ✔ | Passing ✔ | Passing ✔ | Passing ✔ | Passing ✔ |
+
+Requires Ruby **≥ 3.1** (see `required_ruby_version` in the gemspec).
+
+## Features
+
+- ✅ **Unified top-level API**: Class helpers `BrUtils.cpf` / `.cnpj` alias `BrUtils::DEFAULT`; each domain offers `format`, `generate`, and `is_valid`
+- ✅ **Bundled domains**: [`cpf-utilities`](https://rubygems.org/gems/cpf-utilities) and [`cnpj-utilities`](https://rubygems.org/gems/cnpj-utilities) installed together
+- ✅ **Alphanumeric CNPJ**: Full support for the new alphanumeric CNPJ format (introduced in 2026)
+- ✅ **Reusable instance**: `BrUtils` class with optional default CPF and CNPJ settings (nested mappings, flat component kwargs, or pre-built utils instances)
+- ✅ **Two-tier access**: Prefer main-class shortcuts at the façade root (`BrUtils::CpfFormatter`, `BrUtils::CnpjValidator`, …); Options, helpers, and errors live under nested package modules (`BrUtils::CpfFmt`, `BrUtils::CnpjUtils`, …). Root siblings (`CpfUtils`, `CnpjUtils`, `CpfFmt`, …) still work
+- ✅ **Per-call overrides**: Configure defaults on the façade / domain utils; override options on a single `format` / `generate` / `is_valid` call
+- ✅ **Error handling**: Domain errors propagate unchanged from the bundled packages; this gem defines `BrUtils::TypeMismatchError` and `BrUtils::InvalidArgumentCombinationError` for API misuse
+
+## Installation
+
+Install the gem directly:
+
+```bash
+gem install br-utilities
+```
+
+Or add it to your `Gemfile` and run `bundle install`:
+
+```ruby
+gem 'br-utilities'
+```
+
+This installs **`br-utilities`** together with [`cpf-utilities`](https://rubygems.org/gems/cpf-utilities) and [`cnpj-utilities`](https://rubygems.org/gems/cnpj-utilities) (which in turn pull in the CPF and CNPJ component packages). You do **not** need separate `gem install` / `gem` lines for the domain packages when using **`br-utilities`**.
+
+## Require
+
+```ruby
+require 'br-utilities'
+```
+
+## Quick Start
+
+Prefer the aggregator class helpers (`BrUtils.cpf` / `BrUtils.cnpj`) for one-off calls — they forward to `BrUtils::DEFAULT`:
+
+```ruby
+require 'br-utilities'
+
+cpf = '12345678909'
+cnpj = '03603568000195'
+
+# CPF (personal ID)
+BrUtils.cpf.format(cpf) # => "123.456.789-09"
+BrUtils.cpf.generate(format: true) # => e.g. "478.442.410-55"
+BrUtils.cpf.is_valid('123.456.789-09') # => true
+
+# CNPJ (business ID)
+BrUtils.cnpj.format(cnpj) # => "03.603.568/0001-95"
+BrUtils.cnpj.generate(format: true) # => e.g. "AB.123.CDE/0001-55"
+BrUtils.cnpj.is_valid('98765432000198') # => true
+```
+
+**With domain aggregators:**
+
+```ruby
+require 'br-utilities'
+
+cpf = '12345678909'
+cnpj = '03603568000195'
+
+CpfUtils.format(cpf) # => "123.456.789-09"
+CnpjUtils.format(cnpj) # => "03.603.568/0001-95"
+CpfUtils.is_valid(cpf) # => true
+CnpjUtils.is_valid(cnpj) # => true
+```
+
+**With functional helpers** (root sibling modules, loaded by this gem):
+
+```ruby
+require 'br-utilities'
+
+cpf = '12345678909'
+cnpj = '03603568000195'
+
+CpfFmt.cpf_fmt(cpf) # => "123.456.789-09"
+CpfVal.cpf_val(cpf) # => true
+CnpjFmt.cnpj_fmt(cnpj) # => "03.603.568/0001-95"
+CnpjVal.cnpj_val(cnpj) # => true
+```
+
+## Usage
+
+You can work in these equivalent ways:
+
+1. **`BrUtils.cpf` / `.cnpj`** — class helpers for quick one-off calls (forward to `DEFAULT`).
+2. **`BrUtils::DEFAULT`** — mutable shared singleton (same object the class helpers use; process-wide / not thread-isolated).
+3. **`BrUtils.new`** — configurable instance with shared defaults across both CPF and CNPJ domains.
+4. **Domain aggregators** — `CpfUtils` / `CnpjUtils` (or `BrUtils::CpfUtils` / `BrUtils::CnpjUtils`) directly.
+5. **Main classes under `BrUtils`** — `BrUtils::CpfFormatter`, `BrUtils::CnpjGenerator`, and related shortcuts.
+6. **Nested package modules** — Options, helpers, errors, and types via `BrUtils::CpfFmt` / `CpfGen` / `CpfVal` / `CnpjFmt` / `CnpjGen` / `CnpjVal` / `CpfUtils` / `CnpjUtils`.
+7. **Root sibling modules** (still supported) — `CpfFmt`, `CnpjUtils`, and the rest unchanged.
+
+All approaches expose the same options and behavior within each domain. For exhaustive option tables and component-specific details, see the README of each [bundled package](#bundled-packages).
+
+### Class helpers (`BrUtils.cpf` / `.cnpj`)
+
+These class methods return the same domain utils instances as `BrUtils::DEFAULT`. Prefer them for one-off calls:
+
+```ruby
+BrUtils.cpf.format('12345678909')
+BrUtils.cpf.generate(format: true)
+BrUtils.cpf.is_valid('12345678909')
+
+BrUtils.cnpj.format('03603568000195')
+BrUtils.cnpj.generate(type: 'numeric')
+BrUtils.cnpj.is_valid('98765432000198')
+```
+
+### `BrUtils::DEFAULT` (default instance)
+
+`BrUtils::DEFAULT` is the pre-built, **mutable** singleton behind the class helpers (parity with the JS default export / Python `br_utils`). Its configuration is **process-wide and shared across threads**: mutating it (e.g. `DEFAULT.cpf = …`) affects subsequent `BrUtils.cpf` / `.cnpj` calls for every caller in the process. Prefer `BrUtils.new` or per-call options for concurrent or isolated work; custom instances stay independent of `DEFAULT`:
+
+```ruby
+BrUtils::DEFAULT.cpf = CpfUtils.new(formatter: { dash_key: '|' })
+BrUtils.cpf.format('12345678909') # => "123.456.789|09"
+
+custom = BrUtils.new
+custom.cpf.format('12345678909') # => "123.456.789-09" (unaffected)
+```
+
+### `BrUtils` (class)
+
+For custom default CPF or CNPJ utils, create your own instance:
+
+```ruby
+require 'br-utilities'
+
+utils = BrUtils.new(
+ cpf: {
+ formatter: { hidden: true, hidden_key: '#' },
+ generator: { format: true }
+ },
+ cnpj: {
+ formatter: { hidden: true },
+ generator: { type: 'numeric', format: true },
+ validator: { type: 'numeric' }
+ }
+)
+
+utils.cpf.format('12345678909') # => "123.###.###-##"
+utils.cpf.generate # => e.g. "005.265.352-88"
+utils.cnpj.format('03603568000195') # => "03.603.***/****-**"
+utils.cnpj.generate # => e.g. "73.008.535/0005-06"
+
+# Access or replace internal domain instances
+utils.cpf # => CpfUtils
+utils.cnpj # => CnpjUtils
+```
+
+- **`BrUtils.new(settings = nil, **keywords)`**: Optional settings. Pass either a settings `Hash` with `:cpf` and/or `:cnpj` keys, **or** the same keys (plus flat component kwargs) as keyword arguments — not both (passing both raises `BrUtils::InvalidArgumentCombinationError`).
+ - **`:cpf` / `:cnpj`**: A pre-built `CpfUtils` / `CnpjUtils` instance **or** a configuration `Hash` spread into the corresponding utils constructor. Within that `Hash`, each resource key (`:formatter`, `:generator`, and `:validator` for CNPJ) accepts either an options object or a mapping of option values.
+ - **`:cpf_formatter`**, **`:cpf_generator`**, **`:cnpj_formatter`**, **`:cnpj_generator`**, **`:cnpj_validator`**: Flat convenience arguments when only individual components need customization. They are ignored when the corresponding `:cpf` or `:cnpj` argument is provided.
+- **`#cpf`**, **`#cnpj`**: Accessors (getters and setters) for the domain utils instances. Setters accept a utils instance, a configuration `Hash`, or `nil` to reset to defaults (replaces the entire instance; does not merge).
+
+Flat constructor options (alternative to nested `:cpf` / `:cnpj` mappings):
+
+```ruby
+require 'br-utilities'
+
+utils = BrUtils.new(
+ cpf_formatter: CpfFmt::CpfFormatterOptions.new(hidden: true, hidden_key: '#'),
+ cpf_generator: CpfGen::CpfGeneratorOptions.new(format: true),
+ cnpj_formatter: CnpjFmt::CnpjFormatterOptions.new(hidden: true, hidden_key: '#'),
+ cnpj_generator: CnpjGen::CnpjGeneratorOptions.new(format: true, type: 'numeric'),
+ cnpj_validator: CnpjVal::CnpjValidatorOptions.new(type: 'numeric')
+)
+```
+
+Passing a settings `Hash` positional argument together with any keyword raises:
+
+```ruby
+BrUtils.new({ cpf: {} }, cnpj: CnpjUtils.new)
+# raises BrUtils::InvalidArgumentCombinationError
+```
+
+### Instance defaults and per-call overrides
+
+```ruby
+require 'br-utilities'
+
+utils = BrUtils.new(
+ cpf: {
+ formatter: { hidden: true, hidden_key: '#' },
+ generator: { format: true }
+ },
+ cnpj: {
+ formatter: { hidden: true, hidden_key: '#' },
+ generator: { format: true },
+ validator: { type: 'numeric' }
+ }
+)
+
+cpf = '12345678909'
+cnpj = '03603568000195'
+
+utils.cpf.format(cpf) # => "123.###.###-##"
+utils.cpf.format(cpf, hidden: false) # this call only: unmasked
+utils.cpf.generate(format: false) # this call only: compact output
+
+utils.cnpj.format(cnpj) # => "03.603.###/####-##"
+utils.cnpj.format(cnpj, hidden: false) # this call only: unmasked
+utils.cnpj.is_valid('1QB5UKALPYFP59') # => false (instance validator is numeric-only)
+utils.cnpj.is_valid( # => true for this call
+ '1QB5UKALPYFP59',
+ type: 'alphanumeric'
+)
+```
+
+Passing a `CnpjFmt::CnpjFormatterOptions`, `CnpjGen::CnpjGeneratorOptions`, or `CnpjVal::CnpjValidatorOptions` instance into the `BrUtils` constructor stores that object by reference — mutating it later affects subsequent calls with no per-call override.
+
+To change a single nested option without replacing the whole domain utils, mutate via the domain accessors (e.g. `utils.cpf.formatter.options.hidden = true`).
+
+### CPF operations
+
+CPF methods are accessed via `BrUtils.cpf`, `utils.cpf`, `CpfUtils`, or the `CpfFmt` / `CpfGen` / `CpfVal` helpers. CPF uses the API from [`cpf-utilities`](../cpf-utilities/README.md).
+
+#### Formatting (`#format` / `CpfFmt.cpf_fmt`)
+
+| Option | Type | Default | Description |
+|--------|------|---------|-------------|
+| `hidden` | `Boolean` | `false` | When `true`, mask digits in `hidden_start`–`hidden_end` with `hidden_key` |
+| `hidden_key` | `String` | `'*'` | Character(s) used to replace masked digits |
+| `hidden_start` | `Integer` | `3` | Start index (0–10, inclusive) of the range to hide |
+| `hidden_end` | `Integer` | `10` | End index (0–10, inclusive) of the range to hide |
+| `dot_key` | `String` | `'.'` | Dot delimiter (e.g. in `123.456.789`) |
+| `dash_key` | `String` | `'-'` | Dash delimiter (e.g. before check digits `…-09`) |
+| `escape` | `Boolean` | `false` | When `true`, escape HTML special characters in the result |
+| `encode` | `Boolean` | `false` | When `true`, URL-encode the result (similar to JavaScript `encodeURIComponent`) |
+| `on_fail` | `Proc` / callable | returns `''` | Callback when sanitized input length ≠ 11; return value is used as result |
+
+Default **`on_fail`** returns an empty string. Invalid length does **not** raise from `#format`.
+
+```ruby
+require 'br-utilities'
+
+cpf = '12345678909'
+
+BrUtils.cpf.format(cpf) # => "123.456.789-09"
+BrUtils.cpf.format(cpf, hidden: true, hidden_key: '#') # => "123.###.###-##"
+BrUtils.cpf.format(cpf, dot_key: '', dash_key: '_') # => "123456789_09"
+
+CpfFmt.cpf_fmt(cpf, hidden: true) # => "123.***.***-**"
+```
+
+#### Generation (`#generate` / `CpfGen.cpf_gen`)
+
+| Option | Type | Default | Description |
+|--------|------|---------|-------------|
+| `format` | `Boolean` | `false` | When `true`, return the generated CPF in standard format (`000.000.000-00`) |
+| `prefix` | `String` | `''` | Partial start string (0–9 digits). Non-digits are stripped; missing characters are generated and check digits computed. Prefixes longer than 9 digits are truncated silently. |
+
+Prefix rules: the base (first 9 digits) cannot be all zeros; 9 repeated digits (e.g. `999999999`) are not allowed.
+
+```ruby
+require 'br-utilities'
+
+BrUtils.cpf.generate # => e.g. "11508890048"
+BrUtils.cpf.generate(format: true) # => e.g. "661.134.831-00"
+BrUtils.cpf.generate(prefix: '123456789') # => "12345678909"
+CpfGen.cpf_gen(prefix: '123456789', format: true) # => "123.456.789-09"
+```
+
+#### Validation (`#is_valid` / `CpfVal.cpf_val`)
+
+Accepts formatted or unformatted CPF strings (or an `Array` of strings). Returns **`true`** or **`false`** without raising for invalid CPF. No validator options exist.
+
+```ruby
+require 'br-utilities'
+
+BrUtils.cpf.is_valid('12345678909') # => true
+BrUtils.cpf.is_valid('123.456.789-09') # => true
+BrUtils.cpf.is_valid('12345678900') # => false
+CpfVal.cpf_val('12345678909') # => true
+```
+
+### CNPJ operations
+
+CNPJ methods are accessed via `BrUtils.cnpj`, `utils.cnpj`, `CnpjUtils`, or the `CnpjFmt` / `CnpjGen` / `CnpjVal` helpers. CNPJ uses the API from [`cnpj-utilities`](../cnpj-utilities/README.md).
+
+#### Formatting (`#format` / `CnpjFmt.cnpj_fmt`)
+
+| Option | Type | Default | Description |
+|--------|------|---------|-------------|
+| `hidden` | `Boolean` | `false` | When `true`, mask characters in `hidden_start`–`hidden_end` with `hidden_key` |
+| `hidden_key` | `String` | `'*'` | Character(s) used to replace masked characters |
+| `hidden_start` | `Integer` | `5` | Start index (0–13, inclusive) of the range to hide |
+| `hidden_end` | `Integer` | `13` | End index (0–13, inclusive) of the range to hide |
+| `dot_key` | `String` | `'.'` | Dot delimiter (e.g. in `12.345.678`) |
+| `slash_key` | `String` | `'/'` | Slash delimiter (e.g. before branch `…/0001-90`) |
+| `dash_key` | `String` | `'-'` | Dash delimiter (e.g. before check digits `…-90`) |
+| `escape` | `Boolean` | `false` | When `true`, escape HTML special characters in the result |
+| `encode` | `Boolean` | `false` | When `true`, URL-encode the result (similar to JavaScript `encodeURIComponent`) |
+| `on_fail` | `Proc` / callable | returns `''` | Callback when sanitized input length ≠ 14; return value is used as result |
+
+Default **`on_fail`** returns an empty string. Wrong input types raise **`CnpjFmt::TypeMismatchError`**.
+
+```ruby
+require 'br-utilities'
+
+cnpj = '03603568000195'
+
+BrUtils.cnpj.format(cnpj) # => "03.603.568/0001-95"
+BrUtils.cnpj.format('12ABC34500DE99') # => "12.ABC.345/00DE-99"
+BrUtils.cnpj.format( # => "03.603.###/####-##"
+ cnpj,
+ hidden: true,
+ hidden_key: '#'
+)
+BrUtils.cnpj.format( # => "03603568|0001_95"
+ cnpj,
+ dot_key: '',
+ slash_key: '|',
+ dash_key: '_'
+)
+
+CnpjFmt.cnpj_fmt(cnpj) # => "03.603.568/0001-95"
+```
+
+#### Generation (`#generate` / `CnpjGen.cnpj_gen`)
+
+| Option | Type | Default | Description |
+|--------|------|---------|-------------|
+| `format` | `Boolean` | `false` | When `true`, return the generated CNPJ in standard format (`00.000.000/0000-00`) |
+| `prefix` | `String` | `''` | Partial start string (0–12 alphanumeric chars). Missing characters are generated and check digits computed. |
+| `type` | `String` | `'alphanumeric'` | Character set for the randomly generated part: `'numeric'`, `'alphabetic'`, or `'alphanumeric'`. **Check digits are always numeric.** |
+
+Prefix rules: base ID (first 8 chars) and branch ID (chars 9–12) cannot be all zeros; 12 repeated digits (e.g. `111111111111`) are also not allowed.
+
+```ruby
+require 'br-utilities'
+
+BrUtils.cnpj.generate # => e.g. "1GJTR3J3XSSA96"
+BrUtils.cnpj.generate(format: true) # => e.g. "V1.J0V.8WE/DVZ7-50"
+BrUtils.cnpj.generate( # => e.g. "12345678855883"
+ prefix: '12345678',
+ type: 'numeric'
+)
+CnpjGen.cnpj_gen(type: 'numeric') # => e.g. "65453043000178"
+```
+
+#### Validation (`#is_valid` / `CnpjVal.cnpj_val`)
+
+| Option | Type | Default | Description |
+|--------|------|---------|-------------|
+| `case_sensitive` | `Boolean` | `true` | When `false`, lowercase letters are accepted for alphanumeric CNPJ (input is uppercased before validation). |
+| `type` | `String` | `'alphanumeric'` | `'numeric'`: only digits (0–9); `'alphanumeric'`: digits and letters (0–9, A–Z). |
+
+```ruby
+require 'br-utilities'
+
+BrUtils.cnpj.is_valid('98765432000198') # => true
+BrUtils.cnpj.is_valid('98765432000199') # => false
+BrUtils.cnpj.is_valid('1QB5UKALPYFP59') # => true
+BrUtils.cnpj.is_valid('1QB5UKALpyfp59') # => false
+BrUtils.cnpj.is_valid( # => true
+ '1QB5UKALpyfp59',
+ case_sensitive: false
+)
+BrUtils.cnpj.is_valid( # => false
+ '1QB5UKALPYFP59',
+ type: 'numeric'
+)
+
+CnpjVal.cnpj_val('98765432000198') # => true
+CnpjVal.cnpj_val('1QB5UKALpyfp59', case_sensitive: false) # => true
+CnpjVal.cnpj_val('1QB5UKALPYFP59', type: 'numeric') # => false
+```
+
+Invalid CNPJ returns **`false`** without raising. Wrong input types raise **`CnpjVal::TypeMismatchError`**.
+
+### Domain aggregators (standalone)
+
+Use `CpfUtils` or `CnpjUtils` directly when you only need one domain:
+
+```ruby
+require 'br-utilities'
+
+cpf_utils = CpfUtils.new(
+ formatter: { hidden: true },
+ generator: { format: true }
+)
+
+cnpj_utils = CnpjUtils.new(
+ formatter: { hidden: true },
+ generator: { format: true },
+ validator: { type: 'numeric' }
+)
+
+cpf_utils.format('12345678909') # => "123.***.***-**"
+cnpj_utils.format('03603568000195') # => "03.603.***/****-**"
+```
+
+### Accessing components
+
+Each domain aggregator exposes its internal formatter, generator, and validator:
+
+```ruby
+require 'br-utilities'
+
+utils = BrUtils.new
+
+utils.cpf.formatter.format('12345678909', hidden: true) # => "123.***.***-**"
+utils.cpf.generator.generate(format: true) # => e.g. "545.507.690-68"
+utils.cpf.validator.is_valid('12345678909') # => true
+
+utils.cnpj.formatter.format('12ABC34500DE99') # => "12.ABC.345/00DE-99"
+utils.cnpj.generator.generate(format: true) # => e.g. "8O.BE5.2KL/UI0Y-06"
+utils.cnpj.validator.is_valid('03603568000195') # => true
+```
+
+### Using component classes and nested modules
+
+Preferred paths after `require 'br-utilities'`:
+
+```ruby
+require 'br-utilities'
+
+# Main classes at the façade root
+formatter = BrUtils::CpfFormatter.new(hidden: true)
+generator = BrUtils::CnpjGenerator.new(type: 'numeric')
+validator = BrUtils::CnpjValidator.new
+
+formatter.format('12345678909') # => "123.***.***-**"
+
+# Options, helpers, and errors under nested package modules
+options = BrUtils::CpfFmt::CpfFormatterOptions.new(dash_key: '|')
+BrUtils::CpfFmt.cpf_fmt('12345678909') # => "123.456.789-09"
+
+begin
+ BrUtils::CnpjFmt.cnpj_fmt(12_345)
+rescue BrUtils::CnpjFmt::TypeMismatchError
+ # wrong input type
+end
+```
+
+Root siblings remain supported (same objects as the nests):
+
+```ruby
+CpfFmt.cpf_fmt('12345678909', dash_key: '|') # => "123.456.789|09"
+CpfGen.cpf_gen(format: true) # => e.g. "478.442.410-55"
+CpfVal.cpf_val('12345678909') # => true
+CnpjFmt.cnpj_fmt('01ABC234000X56', slash_key: '|') # => "01.ABC.234|000X-56"
+CnpjGen.cnpj_gen(type: 'numeric') # => e.g. "65453043000178"
+CnpjVal.cnpj_val('9JN7MGLJZXIO50') # => true
+```
+
+See [`cpf-utilities`](../cpf-utilities/README.md) and [`cnpj-utilities`](../cnpj-utilities/README.md) for full option and error details.
+
+### Mixing styles
+
+Use `BrUtils` where a shared configuration helps, and standalone components or helpers elsewhere — they are the same underlying classes:
+
+```ruby
+require 'br-utilities'
+
+utils = BrUtils.new(cnpj: { validator: { type: 'numeric' } })
+
+# Via façade
+utils.cpf.format('12345678909') # => "123.456.789-09"
+
+# Via component returned by the façade
+utils.cnpj.formatter.format('12ABC34500DE99') # => "12.ABC.345/00DE-99"
+
+# Via a separate component instance
+BrUtils::CnpjFormatter.new.format('03603568000195') # => "03.603.568/0001-95"
+
+# Via functional helpers
+CpfFmt.cpf_fmt('12345678909') # => "123.456.789-09"
+CnpjVal.cnpj_val('98.765.432/0001-98') # => true
+```
+
+## API
+
+### Exports
+
+After `require 'br-utilities'`:
+
+- **`BrUtils`**: Façade class to create an instance with optional default CPF and CNPJ utils settings.
+- **`BrUtils.cpf` / `.cnpj`**: Class helpers that forward to `BrUtils::DEFAULT` domain accessors.
+- **`BrUtils::DEFAULT`**: Mutable pre-built `BrUtils` instance (same object the class helpers use). Process-wide / shared across threads — prefer `BrUtils.new` or per-call options under concurrency.
+- **`BrUtils::VERSION`**: Gem version string.
+- **Main-class shortcuts**: `BrUtils::CpfFormatter`, `BrUtils::CpfFormatterOptions`, `BrUtils::CpfGenerator`, `BrUtils::CpfGeneratorOptions`, `BrUtils::CpfValidator`, `BrUtils::CnpjFormatter`, `BrUtils::CnpjFormatterOptions`, `BrUtils::CnpjGenerator`, `BrUtils::CnpjGeneratorOptions`, `BrUtils::CnpjValidator`, `BrUtils::CnpjValidatorOptions` (same objects as the sibling classes). Error-marker shortcuts: `BrUtils::CpfFormatterError`, `BrUtils::CpfGeneratorError`, `BrUtils::CpfValidatorError`, `BrUtils::CnpjFormatterError`, `BrUtils::CnpjGeneratorError`, `BrUtils::CnpjValidatorError`.
+- **Nested package modules**: `BrUtils::CpfUtils`, `BrUtils::CnpjUtils`, `BrUtils::CpfFmt`, `BrUtils::CpfGen`, `BrUtils::CpfVal`, `BrUtils::CnpjFmt`, `BrUtils::CnpjGen`, `BrUtils::CnpjVal` — full sibling surface (Options, helpers, errors, types).
+- **Root sibling modules** (still supported): `CpfUtils`, `CnpjUtils`, `CpfFmt`, `CpfGen`, `CpfVal`, `CnpjFmt`, `CnpjGen`, `CnpjVal` — same objects as the nests.
+
+### Errors & Exceptions
+
+`BrUtils` defines only API-misuse errors for this gem’s argument rules. Domain errors are raised by the bundled packages and propagate unchanged.
+
+#### Defined by `br-utilities`
+
+Errors defined by this gem are **API misuse** only (wrong type or invalid argument combination). Every custom error includes the `BrUtils::Error` marker module. This gem defines **no** `BrUtils::DomainError` and no domain leaves — domain failures come only from the [bundled packages](#propagated-from-bundled-packages) and keep those packages’ namespaces (`CpfFmt::…`, `CnpjGen::…`, …).
+
+`rescue BrUtils::Error` catches **only** errors this gem raises. It does **not** catch component errors that propagate unchanged.
+
+##### Summary
+
+| Class | Inherits from | Category | Trigger condition |
+|-------|---------------|----------|-------------------|
+| `BrUtils::InvalidArgumentCombinationError` | `BrUtils::InvalidArgumentCombinationError < ArgumentError < StandardError` (+ `include BrUtils::Error`) | API misuse | Non-`nil` settings `Hash` passed together with any non-`nil` keyword argument |
+| `BrUtils::TypeMismatchError` | `BrUtils::TypeMismatchError < TypeError < StandardError` (+ `include BrUtils::Error`) | API misuse | Non-`nil` `settings` argument to `BrUtils.new` is not a `Hash` |
+
+##### `BrUtils::Error` (marker module)
+
+- **Inheritance:** module marker mixed into every custom error this gem raises via `include` (not a class).
+- **Category:** N/A (rescue target only) — not a failure mode by itself.
+- **When it is raised:** Never raised directly; included by every custom error this gem raises.
+- **Example:** N/A
+- **How to rescue it:**
+
+```ruby
+rescue BrUtils::Error
+ # TypeMismatchError, InvalidArgumentCombinationError from this gem only
+ # (not CpfFmt::*, CnpjGen::*, or other bundled-package errors)
+```
+
+##### `BrUtils::TypeMismatchError`
+
+- **Inheritance:** `BrUtils::TypeMismatchError < TypeError < StandardError` (includes `BrUtils::Error`)
+- **Category:** API misuse — the caller passed a value of the wrong type.
+- **When it is raised:** Raised when `BrUtils.new` receives a non-`nil` `settings` argument that is not a `Hash`.
+- **Example:**
+
+```ruby
+BrUtils.new('not-a-hash') # raises BrUtils::TypeMismatchError
+BrUtils.new(false) # raises BrUtils::TypeMismatchError (false is non-nil)
+```
+
+- **How to rescue it:**
+
+```ruby
+rescue BrUtils::TypeMismatchError
+ # this gem's type-contract violation
+
+rescue TypeError
+ # native type errors, including this gem's TypeMismatchError
+```
+
+##### `BrUtils::InvalidArgumentCombinationError`
+
+- **Inheritance:** `BrUtils::InvalidArgumentCombinationError < ArgumentError < StandardError` (includes `BrUtils::Error`)
+- **Category:** API misuse — the caller mixed mutually exclusive argument patterns.
+- **When it is raised:** Raised when `BrUtils.new` receives both a non-`nil` settings `Hash` and any non-`nil` keyword argument (`cpf:`, `cnpj:`, `cpf_formatter:`, …) at the same time.
+- **Example:**
+
+```ruby
+BrUtils.new({ cpf: { formatter: { hidden: true } } }, cnpj: { formatter: { hidden: true } })
+# raises BrUtils::InvalidArgumentCombinationError
+```
+
+- **How to rescue it:**
+
+```ruby
+rescue BrUtils::InvalidArgumentCombinationError
+ # this gem's invalid signature combination
+
+rescue ArgumentError
+ # native argument errors, including this gem's InvalidArgumentCombinationError
+```
+
+##### Rescue granularity
+
+Each level is shown as its own standalone example (do not merge them into one `rescue` ladder — a broad native handler would make narrower clauses unreachable).
+
+```ruby
+require 'br-utilities'
+
+# 1) Single native class — catches misuse errors of that kind,
+# including non-library ones already handled elsewhere in the consumer's code.
+begin
+ BrUtils.new('not-a-hash')
+rescue TypeError
+ # BrUtils::TypeMismatchError and any other TypeError (library or not)
+end
+
+begin
+ BrUtils.new({ cpf: {} }, cnpj: CnpjUtils.new)
+rescue ArgumentError
+ # BrUtils::InvalidArgumentCombinationError and any other ArgumentError (library or not)
+end
+```
+
+```ruby
+require 'br-utilities'
+
+# 2) BrUtils::DomainError — not applicable: this gem defines no DomainError
+# (and no domain leaves). Domain failures come from bundled packages only.
+# begin
+# BrUtils.new(cpf: { formatter: { hidden_start: -1 } })
+# rescue BrUtils::DomainError # NameError — constant is not defined
+# end
+```
+
+```ruby
+require 'br-utilities'
+
+# 3) BrUtils::Error — catches everything this gem raises, regardless of native ancestry.
+# Does not catch CpfFmt::*, CnpjGen::*, or other bundled-package errors.
+begin
+ BrUtils.new('not-a-hash')
+rescue BrUtils::Error
+ # every custom error that includes BrUtils::Error
+end
+```
+
+```ruby
+require 'br-utilities'
+
+# 4) Specific leaf class — catches only that exact failure mode.
+begin
+ BrUtils.new('not-a-hash')
+rescue BrUtils::TypeMismatchError
+ # only BrUtils::TypeMismatchError
+end
+```
+
+#### Propagated from bundled packages
+
+`BrUtils` does not redefine domain exception types. Construction, setters, and domain method calls raise the same errors as [`cpf-utilities`](../cpf-utilities/README.md) and [`cnpj-utilities`](../cnpj-utilities/README.md):
+
+- **CPF formatting**: `CpfFmt::TypeMismatchError`, `CpfFmt::OutOfRangeError`, `CpfFmt::ValidationError`, `CpfFmt::InvalidLengthError` (passed to `on_fail`, not raised by `#format`), and related classes.
+- **CPF generation**: `CpfGen::TypeMismatchError`, `CpfGen::ValidationError`, and related classes.
+- **CPF validation**: `CpfVal::TypeMismatchError` and related classes.
+- **CNPJ formatting**: `CnpjFmt::TypeMismatchError`, `CnpjFmt::OutOfRangeError`, `CnpjFmt::ValidationError`, `CnpjFmt::InvalidLengthError` (passed to `on_fail`), and related classes.
+- **CNPJ generation**: `CnpjGen::TypeMismatchError`, `CnpjGen::ValidationError`, and related classes.
+- **CNPJ validation**: `CnpjVal::TypeMismatchError`, `CnpjVal::ValidationError`, and related classes.
+
+Invalid option types are typically **`TypeError`** subclasses (`*::TypeMismatchError`); invalid option values are domain errors under each package’s `DomainError` hierarchy. CPF and CNPJ validation failures return `false`. Formatting length failures are handled by **`on_fail`** (default returns an empty string).
+
+```ruby
+require 'br-utilities'
+
+begin
+ BrUtils.new.cnpj.format(12_345)
+rescue CnpjFmt::TypeMismatchError => e
+ puts e.message
+end
+
+begin
+ BrUtils.new.cnpj.is_valid(12_345_678_000_198)
+rescue CnpjVal::TypeMismatchError => e
+ puts e.message
+end
+
+# Custom on_fail for invalid length
+custom_fail = ->(value, _exception) { "Invalid: #{value}" }
+
+BrUtils.cpf.format('short', on_fail: custom_fail) # => "Invalid: short"
+BrUtils.cnpj.format('short', on_fail: custom_fail) # => "Invalid: short"
+BrUtils.cpf.format('short') # => "" (default on_fail)
+```
+
+For exhaustive exception lists and edge-case behavior, see each [bundled package](#bundled-packages) README.
+
+### Bundled packages
+
+| Package | Main resources | README |
+|---------|----------------|--------|
+| [`cpf-utilities`](https://rubygems.org/gems/cpf-utilities) | `CpfUtils`, `CpfFormatter`, `CpfGenerator`, `CpfValidator`, `CpfFmt.cpf_fmt`, `CpfGen.cpf_gen`, `CpfVal.cpf_val` | [docs](../cpf-utilities/README.md) |
+| [`cnpj-utilities`](https://rubygems.org/gems/cnpj-utilities) | `CnpjUtils`, `CnpjFormatter`, `CnpjGenerator`, `CnpjValidator`, `CnpjFmt.cnpj_fmt`, `CnpjGen.cnpj_gen`, `CnpjVal.cnpj_val` | [docs](../cnpj-utilities/README.md) |
+
+All of the above are pulled in as dependencies of **`br-utilities`**. Interactive demos: [CPF](https://cpf-utils.vercel.app/) and [CNPJ](https://cnpj-utils.vercel.app/).
+
+## Contribution & Support
+
+We welcome contributions! Please see our [Contributing Guidelines](https://github.com/LacusSolutions/br-utils-ruby/blob/main/CONTRIBUTING.md) for details. If you find this project helpful, please consider:
+
+- ⭐ Starring the repository
+- 🤝 Contributing to the codebase
+- 💡 [Suggesting new features](https://github.com/LacusSolutions/br-utils-ruby/issues)
+- 🐛 [Reporting bugs](https://github.com/LacusSolutions/br-utils-ruby/issues)
+
+## License
+
+This project is licensed under the MIT License — see the [LICENSE](https://github.com/LacusSolutions/br-utils-ruby/blob/main/LICENSE) file for details.
+
+## Changelog
+
+See [CHANGELOG](./CHANGELOG.md) for a list of changes and version history.
+
+---
+
+Made with ❤️ by [Lacus Solutions](https://github.com/LacusSolutions)
diff --git a/packages/br-utilities/README.pt.md b/packages/br-utilities/README.pt.md
new file mode 100644
index 0000000..40d50ac
--- /dev/null
+++ b/packages/br-utilities/README.pt.md
@@ -0,0 +1,700 @@
+
+
+> 🚀 **Suporte total ao [novo formato alfanumérico de CNPJ](https://github.com/user-attachments/files/23937961/calculodvcnpjalfanaumerico.pdf).**
+
+> 🌎 [Access documentation in English](./README.md)
+
+Kit em Ruby para as principais operações com dados brasileiros: CPF (Cadastro de Pessoa Física) e CNPJ (Cadastro Nacional da Pessoa Jurídica). Envolve [`cpf-utilities`](https://rubygems.org/gems/cpf-utilities) e [`cnpj-utilities`](https://rubygems.org/gems/cnpj-utilities) em uma única classe fachada (`BrUtils`).
+
+## Suporte a Ruby
+
+|  |  |  |  |  |
+| --- | --- | --- | --- | --- |
+| Passing ✔ | Passing ✔ | Passing ✔ | Passing ✔ | Passing ✔ |
+
+Requer Ruby **≥ 3.1** (veja `required_ruby_version` no gemspec).
+
+## Recursos
+
+- ✅ **API unificada de alto nível**: Helpers de classe `BrUtils.cpf` / `.cnpj` alias de `BrUtils::DEFAULT`; cada domínio oferece `format`, `generate` e `is_valid`
+- ✅ **Domínios empacotados**: [`cpf-utilities`](https://rubygems.org/gems/cpf-utilities) e [`cnpj-utilities`](https://rubygems.org/gems/cnpj-utilities) instalados juntos
+- ✅ **CNPJ alfanumérico**: Suporte completo ao novo formato alfanumérico de CNPJ (a partir de 2026)
+- ✅ **Instância reutilizável**: Classe `BrUtils` com configurações padrão opcionais de CPF e CNPJ (mapeamentos aninhados, kwargs planos de componentes ou instâncias prontas de utils)
+- ✅ **Acesso em dois níveis**: Prefira atalhos de classes principais na raiz da fachada (`BrUtils::CpfFormatter`, `BrUtils::CnpjValidator`, …); Options, helpers e erros ficam nos módulos aninhados (`BrUtils::CpfFmt`, `BrUtils::CnpjUtils`, …). Os irmãos na raiz (`CpfUtils`, `CnpjUtils`, `CpfFmt`, …) continuam funcionando
+- ✅ **Sobrescritas por chamada**: Configure padrões na fachada / utils de domínio; sobrescreva opções em uma única chamada de `format` / `generate` / `is_valid`
+- ✅ **Tratamento de erros**: Erros de domínio propagam inalterados dos pacotes incluídos; esta gem define `BrUtils::TypeMismatchError` e `BrUtils::InvalidArgumentCombinationError` para uso indevido da API
+
+## Instalação
+
+Instale a gem diretamente:
+
+```bash
+gem install br-utilities
+```
+
+Ou adicione ao seu `Gemfile` e execute `bundle install`:
+
+```ruby
+gem 'br-utilities'
+```
+
+Isso instala **`br-utilities`** junto com [`cpf-utilities`](https://rubygems.org/gems/cpf-utilities) e [`cnpj-utilities`](https://rubygems.org/gems/cnpj-utilities) (que por sua vez trazem os pacotes componentes de CPF e CNPJ). Você **não** precisa de `gem install` / linhas `gem` separados para os pacotes de domínio ao usar **`br-utilities`**.
+
+## Require
+
+```ruby
+require 'br-utilities'
+```
+
+## Início rápido
+
+Prefira os helpers de classe do agregador (`BrUtils.cpf` / `BrUtils.cnpj`) para chamadas pontuais — eles encaminham para `BrUtils::DEFAULT`:
+
+```ruby
+require 'br-utilities'
+
+cpf = '12345678909'
+cnpj = '03603568000195'
+
+# CPF (pessoa física)
+BrUtils.cpf.format(cpf) # => "123.456.789-09"
+BrUtils.cpf.generate(format: true) # => ex.: "478.442.410-55"
+BrUtils.cpf.is_valid('123.456.789-09') # => true
+
+# CNPJ (pessoa jurídica)
+BrUtils.cnpj.format(cnpj) # => "03.603.568/0001-95"
+BrUtils.cnpj.generate(format: true) # => ex.: "AB.123.CDE/0001-55"
+BrUtils.cnpj.is_valid('98765432000198') # => true
+```
+
+**Com agregadores de domínio:**
+
+```ruby
+require 'br-utilities'
+
+cpf = '12345678909'
+cnpj = '03603568000195'
+
+CpfUtils.format(cpf) # => "123.456.789-09"
+CnpjUtils.format(cnpj) # => "03.603.568/0001-95"
+CpfUtils.is_valid(cpf) # => true
+CnpjUtils.is_valid(cnpj) # => true
+```
+
+**Com helpers funcionais** (módulos irmãos na raiz, carregados por esta gem):
+
+```ruby
+require 'br-utilities'
+
+cpf = '12345678909'
+cnpj = '03603568000195'
+
+CpfFmt.cpf_fmt(cpf) # => "123.456.789-09"
+CpfVal.cpf_val(cpf) # => true
+CnpjFmt.cnpj_fmt(cnpj) # => "03.603.568/0001-95"
+CnpjVal.cnpj_val(cnpj) # => true
+```
+
+## Utilização
+
+Você pode trabalhar destas formas equivalentes:
+
+1. **`BrUtils.cpf` / `.cnpj`** — helpers de classe para chamadas rápidas (encaminham para `DEFAULT`).
+2. **`BrUtils::DEFAULT`** — singleton compartilhado mutável (o mesmo objeto usado pelos helpers de classe; em todo o processo / não isolado por thread).
+3. **`BrUtils.new`** — instância configurável com padrões compartilhados entre os domínios CPF e CNPJ.
+4. **Agregadores de domínio** — `CpfUtils` / `CnpjUtils` (ou `BrUtils::CpfUtils` / `BrUtils::CnpjUtils`) diretamente.
+5. **Classes principais sob `BrUtils`** — `BrUtils::CpfFormatter`, `BrUtils::CnpjGenerator` e atalhos relacionados.
+6. **Módulos aninhados do pacote** — Options, helpers, erros e tipos via `BrUtils::CpfFmt` / `CpfGen` / `CpfVal` / `CnpjFmt` / `CnpjGen` / `CnpjVal` / `CpfUtils` / `CnpjUtils`.
+7. **Módulos irmãos na raiz** (ainda suportados) — `CpfFmt`, `CnpjUtils` e demais inalterados.
+
+Todas as abordagens expõem as mesmas opções e comportamento dentro de cada domínio. Para tabelas de opções exaustivas e detalhes específicos de cada componente, consulte o README de cada [pacote incluído](#pacotes-incluídos).
+
+### Helpers de classe (`BrUtils.cpf` / `.cnpj`)
+
+Esses métodos de classe retornam as mesmas instâncias de utils de domínio que `BrUtils::DEFAULT`. Prefira-os para chamadas pontuais:
+
+```ruby
+BrUtils.cpf.format('12345678909')
+BrUtils.cpf.generate(format: true)
+BrUtils.cpf.is_valid('12345678909')
+
+BrUtils.cnpj.format('03603568000195')
+BrUtils.cnpj.generate(type: 'numeric')
+BrUtils.cnpj.is_valid('98765432000198')
+```
+
+### `BrUtils::DEFAULT` (instância padrão)
+
+`BrUtils::DEFAULT` é o singleton pré-construído e **mutável** por trás dos helpers de classe (paridade com a exportação padrão do JS / `br_utils` do Python). Sua configuração é **em todo o processo e compartilhada entre threads**: mutá-lo (ex.: `DEFAULT.cpf = …`) afeta chamadas subsequentes de `BrUtils.cpf` / `.cnpj` para todos os callers no processo. Prefira `BrUtils.new` ou opções por chamada para trabalho concorrente ou isolado; instâncias customizadas permanecem independentes de `DEFAULT`:
+
+```ruby
+BrUtils::DEFAULT.cpf = CpfUtils.new(formatter: { dash_key: '|' })
+BrUtils.cpf.format('12345678909') # => "123.456.789|09"
+
+custom = BrUtils.new
+custom.cpf.format('12345678909') # => "123.456.789-09" (não afetado)
+```
+
+### `BrUtils` (classe)
+
+Para utils de CPF ou CNPJ padrão customizados, crie sua própria instância:
+
+```ruby
+require 'br-utilities'
+
+utils = BrUtils.new(
+ cpf: {
+ formatter: { hidden: true, hidden_key: '#' },
+ generator: { format: true }
+ },
+ cnpj: {
+ formatter: { hidden: true },
+ generator: { type: 'numeric', format: true },
+ validator: { type: 'numeric' }
+ }
+)
+
+utils.cpf.format('12345678909') # => "123.###.###-##"
+utils.cpf.generate # => ex.: "005.265.352-88"
+utils.cnpj.format('03603568000195') # => "03.603.***/****-**"
+utils.cnpj.generate # => ex.: "73.008.535/0005-06"
+
+# Acessar ou substituir instâncias internas de domínio
+utils.cpf # => CpfUtils
+utils.cnpj # => CnpjUtils
+```
+
+- **`BrUtils.new(settings = nil, **keywords)`**: Configurações opcionais. Passe um `Hash` de settings com chaves `:cpf` e/ou `:cnpj`, **ou** as mesmas chaves (mais kwargs planos de componentes) como argumentos nomeados — não ambos (passar ambos lança `BrUtils::InvalidArgumentCombinationError`).
+ - **`:cpf` / `:cnpj`**: Uma instância pronta de `CpfUtils` / `CnpjUtils` **ou** um `Hash` de configuração repassado ao construtor do utils correspondente. Dentro desse `Hash`, cada chave de recurso (`:formatter`, `:generator` e `:validator` para CNPJ) aceita um objeto de opções ou um mapeamento de valores de opção.
+ - **`:cpf_formatter`**, **`:cpf_generator`**, **`:cnpj_formatter`**, **`:cnpj_generator`**, **`:cnpj_validator`**: Argumentos planos de conveniência quando apenas componentes individuais precisam de customização. São ignorados quando o argumento `:cpf` ou `:cnpj` correspondente é fornecido.
+- **`#cpf`**, **`#cnpj`**: Acessores (getters e setters) das instâncias de utils de domínio. Os setters aceitam uma instância de utils, um `Hash` de configuração ou `nil` para voltar aos padrões (substitui a instância inteira; não faz merge).
+
+Opções planas no construtor (alternativa aos mapeamentos aninhados `:cpf` / `:cnpj`):
+
+```ruby
+require 'br-utilities'
+
+utils = BrUtils.new(
+ cpf_formatter: CpfFmt::CpfFormatterOptions.new(hidden: true, hidden_key: '#'),
+ cpf_generator: CpfGen::CpfGeneratorOptions.new(format: true),
+ cnpj_formatter: CnpjFmt::CnpjFormatterOptions.new(hidden: true, hidden_key: '#'),
+ cnpj_generator: CnpjGen::CnpjGeneratorOptions.new(format: true, type: 'numeric'),
+ cnpj_validator: CnpjVal::CnpjValidatorOptions.new(type: 'numeric')
+)
+```
+
+Passar um `Hash` de settings posicional junto com qualquer palavra-chave lança:
+
+```ruby
+BrUtils.new({ cpf: {} }, cnpj: CnpjUtils.new)
+# lança BrUtils::InvalidArgumentCombinationError
+```
+
+### Padrões da instância e sobrescritas por chamada
+
+```ruby
+require 'br-utilities'
+
+utils = BrUtils.new(
+ cpf: {
+ formatter: { hidden: true, hidden_key: '#' },
+ generator: { format: true }
+ },
+ cnpj: {
+ formatter: { hidden: true, hidden_key: '#' },
+ generator: { format: true },
+ validator: { type: 'numeric' }
+ }
+)
+
+cpf = '12345678909'
+cnpj = '03603568000195'
+
+utils.cpf.format(cpf) # => "123.###.###-##"
+utils.cpf.format(cpf, hidden: false) # só nesta chamada: sem máscara
+utils.cpf.generate(format: false) # só nesta chamada: saída compacta
+
+utils.cnpj.format(cnpj) # => "03.603.###/####-##"
+utils.cnpj.format(cnpj, hidden: false) # só nesta chamada: sem máscara
+utils.cnpj.is_valid('1QB5UKALPYFP59') # => false (validador da instância é só numérico)
+utils.cnpj.is_valid( # => true nesta chamada
+ '1QB5UKALPYFP59',
+ type: 'alphanumeric'
+)
+```
+
+Passar uma instância de `CnpjFmt::CnpjFormatterOptions`, `CnpjGen::CnpjGeneratorOptions` ou `CnpjVal::CnpjValidatorOptions` ao construtor de `BrUtils` armazena esse objeto por referência — mutá-lo depois afeta chamadas subsequentes sem sobrescrita por chamada.
+
+Para alterar uma única opção aninhada sem substituir o utils de domínio inteiro, mute via os acessores de domínio (ex.: `utils.cpf.formatter.options.hidden = true`).
+
+### Operações de CPF
+
+Os métodos de CPF são acessados via `BrUtils.cpf`, `utils.cpf`, `CpfUtils` ou os helpers `CpfFmt` / `CpfGen` / `CpfVal`. O CPF usa a API de [`cpf-utilities`](../cpf-utilities/README.pt.md).
+
+#### Formatação (`#format` / `CpfFmt.cpf_fmt`)
+
+| Opção | Tipo | Padrão | Descrição |
+|--------|------|---------|-------------|
+| `hidden` | `Boolean` | `false` | Se `true`, mascara dígitos entre `hidden_start` e `hidden_end` com `hidden_key` |
+| `hidden_key` | `String` | `'*'` | Caractere(s) usados para substituir dígitos mascarados |
+| `hidden_start` | `Integer` | `3` | Índice inicial (0–10, inclusivo) do intervalo a ocultar |
+| `hidden_end` | `Integer` | `10` | Índice final (0–10, inclusivo) do intervalo a ocultar |
+| `dot_key` | `String` | `'.'` | Delimitador de ponto (ex.: em `123.456.789`) |
+| `dash_key` | `String` | `'-'` | Delimitador de hífen (ex.: antes dos dígitos verificadores `…-09`) |
+| `escape` | `Boolean` | `false` | Se `true`, escapa caracteres especiais HTML no resultado |
+| `encode` | `Boolean` | `false` | Se `true`, codifica o resultado para URL (similar ao `encodeURIComponent` do JavaScript) |
+| `on_fail` | `Proc` / invocável | retorna `''` | Callback quando o tamanho da entrada sanitizada ≠ 11; o retorno é usado como resultado |
+
+O **`on_fail`** padrão retorna uma string vazia. Comprimento inválido **não** lança exceção em `#format`.
+
+```ruby
+require 'br-utilities'
+
+cpf = '12345678909'
+
+BrUtils.cpf.format(cpf) # => "123.456.789-09"
+BrUtils.cpf.format(cpf, hidden: true, hidden_key: '#') # => "123.###.###-##"
+BrUtils.cpf.format(cpf, dot_key: '', dash_key: '_') # => "123456789_09"
+
+CpfFmt.cpf_fmt(cpf, hidden: true) # => "123.***.***-**"
+```
+
+#### Geração (`#generate` / `CpfGen.cpf_gen`)
+
+| Opção | Tipo | Padrão | Descrição |
+|--------|------|---------|-------------|
+| `format` | `Boolean` | `false` | Se `true`, retorna o CPF gerado no formato padrão (`000.000.000-00`) |
+| `prefix` | `String` | `''` | String parcial inicial (0–9 dígitos). Não dígitos são removidos; caracteres faltantes são gerados e os dígitos verificadores calculados. Prefixos com mais de 9 dígitos são truncados silenciosamente. |
+
+Regras de prefixo: a base (primeiros 9 dígitos) não pode ser toda zeros; 9 dígitos repetidos (ex.: `999999999`) não são permitidos.
+
+```ruby
+require 'br-utilities'
+
+BrUtils.cpf.generate # => ex.: "11508890048"
+BrUtils.cpf.generate(format: true) # => ex.: "661.134.831-00"
+BrUtils.cpf.generate(prefix: '123456789') # => "12345678909"
+CpfGen.cpf_gen(prefix: '123456789', format: true) # => "123.456.789-09"
+```
+
+#### Validação (`#is_valid` / `CpfVal.cpf_val`)
+
+Aceita CPF formatado ou não (ou um `Array` de strings). Retorna **`true`** ou **`false`** sem lançar exceção para CPF inválido. Não há opções de validador.
+
+```ruby
+require 'br-utilities'
+
+BrUtils.cpf.is_valid('12345678909') # => true
+BrUtils.cpf.is_valid('123.456.789-09') # => true
+BrUtils.cpf.is_valid('12345678900') # => false
+CpfVal.cpf_val('12345678909') # => true
+```
+
+### Operações de CNPJ
+
+Os métodos de CNPJ são acessados via `BrUtils.cnpj`, `utils.cnpj`, `CnpjUtils` ou os helpers `CnpjFmt` / `CnpjGen` / `CnpjVal`. O CNPJ usa a API de [`cnpj-utilities`](../cnpj-utilities/README.pt.md).
+
+#### Formatação (`#format` / `CnpjFmt.cnpj_fmt`)
+
+| Opção | Tipo | Padrão | Descrição |
+|--------|------|---------|-------------|
+| `hidden` | `Boolean` | `false` | Se `true`, mascara caracteres entre `hidden_start` e `hidden_end` com `hidden_key` |
+| `hidden_key` | `String` | `'*'` | Caractere(s) usados para substituir caracteres mascarados |
+| `hidden_start` | `Integer` | `5` | Índice inicial (0–13, inclusivo) do intervalo a ocultar |
+| `hidden_end` | `Integer` | `13` | Índice final (0–13, inclusivo) do intervalo a ocultar |
+| `dot_key` | `String` | `'.'` | Delimitador de ponto (ex.: em `12.345.678`) |
+| `slash_key` | `String` | `'/'` | Delimitador de barra (ex.: antes da filial `…/0001-90`) |
+| `dash_key` | `String` | `'-'` | Delimitador de hífen (ex.: antes dos dígitos verificadores `…-90`) |
+| `escape` | `Boolean` | `false` | Se `true`, escapa caracteres especiais HTML no resultado |
+| `encode` | `Boolean` | `false` | Se `true`, codifica o resultado para URL (similar ao `encodeURIComponent` do JavaScript) |
+| `on_fail` | `Proc` / invocável | retorna `''` | Callback quando o tamanho da entrada sanitizada ≠ 14; o retorno é usado como resultado |
+
+O **`on_fail`** padrão retorna uma string vazia. Tipos de entrada incorretos lançam **`CnpjFmt::TypeMismatchError`**.
+
+```ruby
+require 'br-utilities'
+
+cnpj = '03603568000195'
+
+BrUtils.cnpj.format(cnpj) # => "03.603.568/0001-95"
+BrUtils.cnpj.format('12ABC34500DE99') # => "12.ABC.345/00DE-99"
+BrUtils.cnpj.format( # => "03.603.###/####-##"
+ cnpj,
+ hidden: true,
+ hidden_key: '#'
+)
+BrUtils.cnpj.format( # => "03603568|0001_95"
+ cnpj,
+ dot_key: '',
+ slash_key: '|',
+ dash_key: '_'
+)
+
+CnpjFmt.cnpj_fmt(cnpj) # => "03.603.568/0001-95"
+```
+
+#### Geração (`#generate` / `CnpjGen.cnpj_gen`)
+
+| Opção | Tipo | Padrão | Descrição |
+|--------|------|---------|-------------|
+| `format` | `Boolean` | `false` | Se `true`, retorna o CNPJ gerado no formato padrão (`00.000.000/0000-00`) |
+| `prefix` | `String` | `''` | String parcial inicial (0–12 caracteres alfanuméricos). Caracteres faltantes são gerados e os dígitos verificadores calculados. |
+| `type` | `String` | `'alphanumeric'` | Conjunto de caracteres para a parte gerada aleatoriamente: `'numeric'`, `'alphabetic'` ou `'alphanumeric'`. **Os dígitos verificadores são sempre numéricos.** |
+
+Regras de prefixo: o ID base (primeiros 8 caracteres) e o ID da filial (caracteres 9–12) não podem ser todos zeros; 12 dígitos repetidos (ex.: `111111111111`) também não são permitidos.
+
+```ruby
+require 'br-utilities'
+
+BrUtils.cnpj.generate # => ex.: "1GJTR3J3XSSA96"
+BrUtils.cnpj.generate(format: true) # => ex.: "V1.J0V.8WE/DVZ7-50"
+BrUtils.cnpj.generate( # => ex.: "12345678855883"
+ prefix: '12345678',
+ type: 'numeric'
+)
+CnpjGen.cnpj_gen(type: 'numeric') # => ex.: "65453043000178"
+```
+
+#### Validação (`#is_valid` / `CnpjVal.cnpj_val`)
+
+| Opção | Tipo | Padrão | Descrição |
+|--------|------|---------|-------------|
+| `case_sensitive` | `Boolean` | `true` | Se `false`, letras minúsculas são aceitas para CNPJ alfanumérico (a entrada é convertida para maiúsculas antes da validação). |
+| `type` | `String` | `'alphanumeric'` | `'numeric'`: apenas dígitos (0–9); `'alphanumeric'`: dígitos e letras (0–9, A–Z). |
+
+```ruby
+require 'br-utilities'
+
+BrUtils.cnpj.is_valid('98765432000198') # => true
+BrUtils.cnpj.is_valid('98765432000199') # => false
+BrUtils.cnpj.is_valid('1QB5UKALPYFP59') # => true
+BrUtils.cnpj.is_valid('1QB5UKALpyfp59') # => false
+BrUtils.cnpj.is_valid( # => true
+ '1QB5UKALpyfp59',
+ case_sensitive: false
+)
+BrUtils.cnpj.is_valid( # => false
+ '1QB5UKALPYFP59',
+ type: 'numeric'
+)
+
+CnpjVal.cnpj_val('98765432000198') # => true
+CnpjVal.cnpj_val('1QB5UKALpyfp59', case_sensitive: false) # => true
+CnpjVal.cnpj_val('1QB5UKALPYFP59', type: 'numeric') # => false
+```
+
+CNPJ inválido retorna **`false`** sem lançar exceção. Tipos de entrada incorretos lançam **`CnpjVal::TypeMismatchError`**.
+
+### Agregadores de domínio (isolados)
+
+Use `CpfUtils` ou `CnpjUtils` diretamente quando precisar de apenas um domínio:
+
+```ruby
+require 'br-utilities'
+
+cpf_utils = CpfUtils.new(
+ formatter: { hidden: true },
+ generator: { format: true }
+)
+
+cnpj_utils = CnpjUtils.new(
+ formatter: { hidden: true },
+ generator: { format: true },
+ validator: { type: 'numeric' }
+)
+
+cpf_utils.format('12345678909') # => "123.***.***-**"
+cnpj_utils.format('03603568000195') # => "03.603.***/****-**"
+```
+
+### Acessando componentes
+
+Cada agregador de domínio expõe seu formatador, gerador e validador internos:
+
+```ruby
+require 'br-utilities'
+
+utils = BrUtils.new
+
+utils.cpf.formatter.format('12345678909', hidden: true) # => "123.***.***-**"
+utils.cpf.generator.generate(format: true) # => ex.: "545.507.690-68"
+utils.cpf.validator.is_valid('12345678909') # => true
+
+utils.cnpj.formatter.format('12ABC34500DE99') # => "12.ABC.345/00DE-99"
+utils.cnpj.generator.generate(format: true) # => ex.: "8O.BE5.2KL/UI0Y-06"
+utils.cnpj.validator.is_valid('03603568000195') # => true
+```
+
+### Usando classes componentes e módulos aninhados
+
+Caminhos preferidos após `require 'br-utilities'`:
+
+```ruby
+require 'br-utilities'
+
+# Classes principais na raiz da fachada
+formatter = BrUtils::CpfFormatter.new(hidden: true)
+generator = BrUtils::CnpjGenerator.new(type: 'numeric')
+validator = BrUtils::CnpjValidator.new
+
+formatter.format('12345678909') # => "123.***.***-**"
+
+# Options, helpers e erros nos módulos aninhados do pacote
+options = BrUtils::CpfFmt::CpfFormatterOptions.new(dash_key: '|')
+BrUtils::CpfFmt.cpf_fmt('12345678909') # => "123.456.789-09"
+
+begin
+ BrUtils::CnpjFmt.cnpj_fmt(12_345)
+rescue BrUtils::CnpjFmt::TypeMismatchError
+ # tipo de entrada incorreto
+end
+```
+
+Os irmãos na raiz continuam suportados (os mesmos objetos dos nests):
+
+```ruby
+CpfFmt.cpf_fmt('12345678909', dash_key: '|') # => "123.456.789|09"
+CpfGen.cpf_gen(format: true) # => ex.: "478.442.410-55"
+CpfVal.cpf_val('12345678909') # => true
+CnpjFmt.cnpj_fmt('01ABC234000X56', slash_key: '|') # => "01.ABC.234|000X-56"
+CnpjGen.cnpj_gen(type: 'numeric') # => ex.: "65453043000178"
+CnpjVal.cnpj_val('9JN7MGLJZXIO50') # => true
+```
+
+Consulte [`cpf-utilities`](../cpf-utilities/README.pt.md) e [`cnpj-utilities`](../cnpj-utilities/README.pt.md) para detalhes completos de opções e erros.
+
+### Misturando estilos
+
+Use `BrUtils` onde uma configuração compartilhada ajuda, e componentes ou helpers isolados em outros pontos — são as mesmas classes subjacentes:
+
+```ruby
+require 'br-utilities'
+
+utils = BrUtils.new(cnpj: { validator: { type: 'numeric' } })
+
+# Via fachada
+utils.cpf.format('12345678909') # => "123.456.789-09"
+
+# Via componente retornado pela fachada
+utils.cnpj.formatter.format('12ABC34500DE99') # => "12.ABC.345/00DE-99"
+
+# Via instância de componente separada
+BrUtils::CnpjFormatter.new.format('03603568000195') # => "03.603.568/0001-95"
+
+# Via helpers funcionais
+CpfFmt.cpf_fmt('12345678909') # => "123.456.789-09"
+CnpjVal.cnpj_val('98.765.432/0001-98') # => true
+```
+
+## API
+
+### Exportações
+
+Após `require 'br-utilities'`:
+
+- **`BrUtils`**: Classe fachada para criar uma instância com configurações opcionais dos utils de CPF e CNPJ.
+- **`BrUtils.cpf` / `.cnpj`**: Helpers de classe que encaminham para os acessores de domínio de `BrUtils::DEFAULT`.
+- **`BrUtils::DEFAULT`**: Instância pré-construída e mutável de `BrUtils` (o mesmo objeto usado pelos helpers de classe). Em todo o processo / compartilhada entre threads — prefira `BrUtils.new` ou opções por chamada sob concorrência.
+- **`BrUtils::VERSION`**: String da versão da gem.
+- **Atalhos de classes principais**: `BrUtils::CpfFormatter`, `BrUtils::CpfFormatterOptions`, `BrUtils::CpfGenerator`, `BrUtils::CpfGeneratorOptions`, `BrUtils::CpfValidator`, `BrUtils::CnpjFormatter`, `BrUtils::CnpjFormatterOptions`, `BrUtils::CnpjGenerator`, `BrUtils::CnpjGeneratorOptions`, `BrUtils::CnpjValidator`, `BrUtils::CnpjValidatorOptions` (os mesmos objetos das classes irmãs). Atalhos de marcadores de erro: `BrUtils::CpfFormatterError`, `BrUtils::CpfGeneratorError`, `BrUtils::CpfValidatorError`, `BrUtils::CnpjFormatterError`, `BrUtils::CnpjGeneratorError`, `BrUtils::CnpjValidatorError`.
+- **Módulos aninhados do pacote**: `BrUtils::CpfUtils`, `BrUtils::CnpjUtils`, `BrUtils::CpfFmt`, `BrUtils::CpfGen`, `BrUtils::CpfVal`, `BrUtils::CnpjFmt`, `BrUtils::CnpjGen`, `BrUtils::CnpjVal` — superfície completa dos irmãos (Options, helpers, erros, tipos).
+- **Módulos irmãos na raiz** (ainda suportados): `CpfUtils`, `CnpjUtils`, `CpfFmt`, `CpfGen`, `CpfVal`, `CnpjFmt`, `CnpjGen`, `CnpjVal` — os mesmos objetos dos nests.
+
+### Erros e exceções
+
+`BrUtils` define apenas erros de uso indevido da API para as regras de argumentos desta gem. Erros de domínio são lançados pelos pacotes incluídos e propagam inalterados.
+
+#### Definidos por `br-utilities`
+
+Os erros definidos por esta gem são **apenas uso indevido da API** (tipo errado ou combinação inválida de argumentos). Todo erro customizado inclui o módulo marcador `BrUtils::Error`. Esta gem **não** define `BrUtils::DomainError` nem folhas de domínio — falhas de domínio vêm apenas dos [pacotes incluídos](#propagados-dos-pacotes-incluídos) e mantêm os namespaces desses pacotes (`CpfFmt::…`, `CnpjGen::…`, …).
+
+`rescue BrUtils::Error` captura **apenas** erros que esta gem lança. **Não** captura erros de componentes que propagam inalterados.
+
+##### Resumo
+
+| Classe | Herda de | Categoria | Condição de disparo |
+|-------|---------------|----------|-------------------|
+| `BrUtils::InvalidArgumentCombinationError` | `BrUtils::InvalidArgumentCombinationError < ArgumentError < StandardError` (+ `include BrUtils::Error`) | Uso indevido da API | `Hash` de settings não-`nil` passado junto com qualquer argumento nomeado não-`nil` |
+| `BrUtils::TypeMismatchError` | `BrUtils::TypeMismatchError < TypeError < StandardError` (+ `include BrUtils::Error`) | Uso indevido da API | Argumento `settings` não-`nil` em `BrUtils.new` não é um `Hash` |
+
+##### `BrUtils::Error` (módulo marcador)
+
+- **Herança:** módulo marcador misturado em todo erro customizado que esta gem lança via `include` (não é uma classe).
+- **Categoria:** N/A (apenas alvo de rescue) — não é um modo de falha por si só.
+- **Quando é lançado:** Nunca lançado diretamente; incluído por todo erro customizado que esta gem lança.
+- **Exemplo:** N/A
+- **Como resgatar:**
+
+```ruby
+rescue BrUtils::Error
+ # TypeMismatchError, InvalidArgumentCombinationError apenas desta gem
+ # (não CpfFmt::*, CnpjGen::* ou outros erros dos pacotes incluídos)
+```
+
+##### `BrUtils::TypeMismatchError`
+
+- **Herança:** `BrUtils::TypeMismatchError < TypeError < StandardError` (inclui `BrUtils::Error`)
+- **Categoria:** Uso indevido da API — o caller passou um valor do tipo errado.
+- **Quando é lançado:** Quando `BrUtils.new` recebe um argumento `settings` não-`nil` que não é um `Hash`.
+- **Exemplo:**
+
+```ruby
+BrUtils.new('not-a-hash') # lança BrUtils::TypeMismatchError
+BrUtils.new(false) # lança BrUtils::TypeMismatchError (false é não-nil)
+```
+
+- **Como resgatar:**
+
+```ruby
+rescue BrUtils::TypeMismatchError
+ # violação de contrato de tipo desta gem
+
+rescue TypeError
+ # erros nativos de tipo, incluindo TypeMismatchError desta gem
+```
+
+##### `BrUtils::InvalidArgumentCombinationError`
+
+- **Herança:** `BrUtils::InvalidArgumentCombinationError < ArgumentError < StandardError` (inclui `BrUtils::Error`)
+- **Categoria:** Uso indevido da API — o caller misturou padrões de argumentos mutuamente exclusivos.
+- **Quando é lançado:** Quando `BrUtils.new` recebe um `Hash` de settings não-`nil` e qualquer argumento nomeado não-`nil` (`cpf:`, `cnpj:`, `cpf_formatter:`, …) ao mesmo tempo.
+- **Exemplo:**
+
+```ruby
+BrUtils.new({ cpf: { formatter: { hidden: true } } }, cnpj: { formatter: { hidden: true } })
+# lança BrUtils::InvalidArgumentCombinationError
+```
+
+- **Como resgatar:**
+
+```ruby
+rescue BrUtils::InvalidArgumentCombinationError
+ # combinação inválida de assinatura desta gem
+
+rescue ArgumentError
+ # erros nativos de argumento, incluindo InvalidArgumentCombinationError desta gem
+```
+
+##### Granularidade de rescue
+
+Cada nível é mostrado como seu próprio exemplo isolado (não os una em uma única escada de `rescue` — um handler nativo amplo tornaria cláusulas mais estreitas inalcançáveis).
+
+```ruby
+require 'br-utilities'
+
+# 1) Classe nativa única — captura erros de uso indevido desse tipo,
+# incluindo os não-biblioteca já tratados em outro ponto do código do consumidor.
+begin
+ BrUtils.new('not-a-hash')
+rescue TypeError
+ # BrUtils::TypeMismatchError e qualquer outro TypeError (biblioteca ou não)
+end
+
+begin
+ BrUtils.new({ cpf: {} }, cnpj: CnpjUtils.new)
+rescue ArgumentError
+ # BrUtils::InvalidArgumentCombinationError e qualquer outro ArgumentError (biblioteca ou não)
+end
+```
+
+```ruby
+require 'br-utilities'
+
+# 2) BrUtils::DomainError — não aplicável: esta gem não define DomainError
+# (nem folhas de domínio). Falhas de domínio vêm apenas dos pacotes incluídos.
+# begin
+# BrUtils.new(cpf: { formatter: { hidden_start: -1 } })
+# rescue BrUtils::DomainError # NameError — constante não definida
+# end
+```
+
+```ruby
+require 'br-utilities'
+
+# 3) BrUtils::Error — captura tudo que esta gem lança, independentemente da ancestralidade nativa.
+# Não captura CpfFmt::*, CnpjGen::* ou outros erros dos pacotes incluídos.
+begin
+ BrUtils.new('not-a-hash')
+rescue BrUtils::Error
+ # todo erro customizado que inclui BrUtils::Error
+end
+```
+
+```ruby
+require 'br-utilities'
+
+# 4) Classe folha específica — captura apenas aquele modo de falha exato.
+begin
+ BrUtils.new('not-a-hash')
+rescue BrUtils::TypeMismatchError
+ # apenas BrUtils::TypeMismatchError
+end
+```
+
+#### Propagados dos pacotes incluídos
+
+`BrUtils` não redefine tipos de exceção de domínio. Construção, setters e chamadas de métodos de domínio lançam os mesmos erros de [`cpf-utilities`](../cpf-utilities/README.pt.md) e [`cnpj-utilities`](../cnpj-utilities/README.pt.md):
+
+- **Formatação de CPF**: `CpfFmt::TypeMismatchError`, `CpfFmt::OutOfRangeError`, `CpfFmt::ValidationError`, `CpfFmt::InvalidLengthError` (passado a `on_fail`, não lançado por `#format`) e classes relacionadas.
+- **Geração de CPF**: `CpfGen::TypeMismatchError`, `CpfGen::ValidationError` e classes relacionadas.
+- **Validação de CPF**: `CpfVal::TypeMismatchError` e classes relacionadas.
+- **Formatação de CNPJ**: `CnpjFmt::TypeMismatchError`, `CnpjFmt::OutOfRangeError`, `CnpjFmt::ValidationError`, `CnpjFmt::InvalidLengthError` (passado a `on_fail`) e classes relacionadas.
+- **Geração de CNPJ**: `CnpjGen::TypeMismatchError`, `CnpjGen::ValidationError` e classes relacionadas.
+- **Validação de CNPJ**: `CnpjVal::TypeMismatchError`, `CnpjVal::ValidationError` e classes relacionadas.
+
+Tipos de opção inválidos são tipicamente subclasses de **`TypeError`** (`*::TypeMismatchError`); valores de opção inválidos são erros de domínio sob a hierarquia `DomainError` de cada pacote. Falhas de validação de CPF e CNPJ retornam `false`. Falhas de comprimento na formatação são tratadas por **`on_fail`** (padrão retorna string vazia).
+
+```ruby
+require 'br-utilities'
+
+begin
+ BrUtils.new.cnpj.format(12_345)
+rescue CnpjFmt::TypeMismatchError => e
+ puts e.message
+end
+
+begin
+ BrUtils.new.cnpj.is_valid(12_345_678_000_198)
+rescue CnpjVal::TypeMismatchError => e
+ puts e.message
+end
+
+# on_fail customizado para comprimento inválido
+custom_fail = ->(value, _exception) { "Invalid: #{value}" }
+
+BrUtils.cpf.format('short', on_fail: custom_fail) # => "Invalid: short"
+BrUtils.cnpj.format('short', on_fail: custom_fail) # => "Invalid: short"
+BrUtils.cpf.format('short') # => "" (on_fail padrão)
+```
+
+Para listas exaustivas de exceções e comportamento em casos extremos, consulte o README de cada [pacote incluído](#pacotes-incluídos).
+
+### Pacotes incluídos
+
+| Pacote | Principais recursos | README |
+|---------|----------------|--------|
+| [`cpf-utilities`](https://rubygems.org/gems/cpf-utilities) | `CpfUtils`, `CpfFormatter`, `CpfGenerator`, `CpfValidator`, `CpfFmt.cpf_fmt`, `CpfGen.cpf_gen`, `CpfVal.cpf_val` | [docs](../cpf-utilities/README.pt.md) |
+| [`cnpj-utilities`](https://rubygems.org/gems/cnpj-utilities) | `CnpjUtils`, `CnpjFormatter`, `CnpjGenerator`, `CnpjValidator`, `CnpjFmt.cnpj_fmt`, `CnpjGen.cnpj_gen`, `CnpjVal.cnpj_val` | [docs](../cnpj-utilities/README.pt.md) |
+
+Todos os acima são puxados como dependências de **`br-utilities`**. Demos interativas: [CPF](https://cpf-utils.vercel.app/) e [CNPJ](https://cnpj-utils.vercel.app/).
+
+## Contribuição e suporte
+
+Contribuições são bem-vindas! Consulte as [Diretrizes de contribuição](https://github.com/LacusSolutions/br-utils-ruby/blob/main/CONTRIBUTING.md). Se o projeto for útil para você, considere:
+
+- ⭐ Dar uma estrela no repositório
+- 🤝 Contribuir com código
+- 💡 [Sugerir novas funcionalidades](https://github.com/LacusSolutions/br-utils-ruby/issues)
+- 🐛 [Reportar bugs](https://github.com/LacusSolutions/br-utils-ruby/issues)
+
+## Licença
+
+Este projeto está sob a licença MIT — veja o arquivo [LICENSE](https://github.com/LacusSolutions/br-utils-ruby/blob/main/LICENSE).
+
+## Changelog
+
+Veja o [CHANGELOG](./CHANGELOG.md) para alterações e histórico de versões.
+
+---
+
+Feito com ❤️ por [Lacus Solutions](https://github.com/LacusSolutions)
diff --git a/packages/br-utilities/br-utilities.gemspec b/packages/br-utilities/br-utilities.gemspec
index addc19d..60438d3 100644
--- a/packages/br-utilities/br-utilities.gemspec
+++ b/packages/br-utilities/br-utilities.gemspec
@@ -6,8 +6,9 @@ Gem::Specification.new do |spec|
spec.name = 'br-utilities'
spec.version = BrUtils::VERSION
spec.authors = ['Julio L. Muller']
- spec.summary = 'Brazilian data utilities: CPF, CNPJ, and more'
- spec.description = 'Unified API for CPF/CNPJ format, generate, validate (Brazilian IDs).'
+ spec.email = ['juliolmuller@outlook.com']
+ spec.summary = 'Utilities to deal with Brazilian-related data'
+ spec.description = 'Utilities to deal with Brazilian-related data'
spec.homepage = 'https://github.com/LacusSolutions/br-utils-ruby'
spec.license = 'MIT'
spec.required_ruby_version = '>= 3.1'
@@ -15,6 +16,6 @@ Gem::Specification.new do |spec|
spec.metadata['rubygems_mfa_required'] = 'true'
spec.files = Dir['src/**/*'] + ['LICENSE', 'README.md', 'README.pt.md', 'CHANGELOG.md']
spec.require_paths = ['src']
- spec.add_dependency 'cnpj-utilities', '>= 0'
- spec.add_dependency 'cpf-utilities', '>= 0'
+ spec.add_dependency 'cnpj-utilities', '>= 1.0.0', '< 1.1.0'
+ spec.add_dependency 'cpf-utilities', '>= 1.0.0', '< 1.1.0'
end
diff --git a/packages/br-utilities/src/br-utilities.rb b/packages/br-utilities/src/br-utilities.rb
index ac51953..ef0846e 100644
--- a/packages/br-utilities/src/br-utilities.rb
+++ b/packages/br-utilities/src/br-utilities.rb
@@ -4,8 +4,36 @@
require 'cnpj-utilities'
require_relative 'br-utilities/version'
-module BrUtils
- def self.hello
- 'br-utilities'
- end
+# Entry point for the +br-utilities+ gem.
+#
+# Loads sibling packages (+cpf-utilities+, +cnpj-utilities+) and defines the
+# {BrUtils} façade class. +version.rb+ defines a placeholder module so the
+# gemspec can read {BrUtils::VERSION}; this file promotes it to the class
+# consumers instantiate.
+#
+# Two-tier access after +require 'br-utilities'+:
+#
+# - *Main shortcuts* at the façade root: {BrUtils::CpfFormatter},
+# {BrUtils::CnpjFormatter}, etc.
+# - *Package nests* for the full sibling surface (Options, helpers, errors,
+# types): {BrUtils::CpfFmt}, {BrUtils::CnpjUtils}, etc. (same objects as
+# +::CpfFmt+, +::CnpjUtils+, …).
+# - Root siblings (+CpfUtils+, +CnpjUtils+, +CpfFmt+, …) remain supported
+# unchanged.
+unless BrUtils.is_a?(Class)
+ version = BrUtils::VERSION
+ Object.send(:remove_const, :BrUtils)
+ BrUtils = Class.new
+ BrUtils.const_set(:VERSION, version)
end
+
+require_relative 'br-utilities/errors'
+require_relative 'br-utilities/br_utils'
+require_relative 'br-utilities/cpf_fmt'
+require_relative 'br-utilities/cpf_gen'
+require_relative 'br-utilities/cpf_val'
+require_relative 'br-utilities/cpf_utils'
+require_relative 'br-utilities/cnpj_fmt'
+require_relative 'br-utilities/cnpj_gen'
+require_relative 'br-utilities/cnpj_val'
+require_relative 'br-utilities/cnpj_utils'
diff --git a/packages/br-utilities/src/br-utilities/br_utils.rb b/packages/br-utilities/src/br-utilities/br_utils.rb
new file mode 100644
index 0000000..4680a7e
--- /dev/null
+++ b/packages/br-utilities/src/br-utilities/br_utils.rb
@@ -0,0 +1,268 @@
+# frozen_string_literal: true
+
+require 'cpf-utilities'
+require 'cnpj-utilities'
+
+require_relative 'errors'
+
+# Unified API for Brazilian-related data, like CPF (Cadastro de Pessoa Física)
+# and CNPJ (Cadastro Nacional da Pessoa Jurídica). Provides a unified interface
+# for formatting, generating, and validating data. Aggregates configurable
+# {CpfUtils} and {CnpjUtils} instances behind a single façade.
+#
+# Public API:
+#
+# - {BrUtils.cpf}, {BrUtils.cnpj} — class helpers that alias {BrUtils::DEFAULT}
+# (preferred quick path for domain accessors)
+# - {BrUtils::DEFAULT} — mutable process-wide singleton (JS/Python parity; not
+# thread-isolated — prefer {.new} under concurrency)
+# - {BrUtils#cpf}, {BrUtils#cnpj} — instance accessors with setters
+# - {BrUtils::VERSION}
+# - {BrUtils::InvalidArgumentCombinationError}, {BrUtils::TypeMismatchError}
+#
+# Two-tier access: main-class shortcuts ({BrUtils::CpfFormatter}, etc.) and
+# nested package modules ({BrUtils::CpfFmt}, {BrUtils::CpfUtils}, etc.). Root
+# siblings ({CpfUtils}, {CnpjUtils}, {CpfFmt}, …) remain loadable after
+# +require 'br-utilities'+.
+#
+# Mutating {BrUtils::DEFAULT} (e.g. via setters) affects subsequent class-helper
+# calls process-wide (shared across threads). Prefer {BrUtils.new} for concurrent
+# or isolated work. Custom instances are independent of +DEFAULT+.
+#
+# @example
+# require 'br-utilities'
+#
+# BrUtils.cpf.format('12345678909') # => "123.456.789-09"
+# BrUtils.cnpj.is_valid('91415732000793') # => true
+class BrUtils
+ SETTINGS_KEYS = %i[cpf cnpj].freeze
+ FLAT_KEYS = %i[cpf_formatter cpf_generator cnpj_formatter cnpj_generator cnpj_validator].freeze
+ KEYWORD_KEYS = (SETTINGS_KEYS + FLAT_KEYS).freeze
+
+ private_constant :SETTINGS_KEYS, :FLAT_KEYS, :KEYWORD_KEYS
+
+ # Internal helpers for resolving domain utils from settings / keyword arguments.
+ module Helpers
+ module_function
+
+ def resolve_settings(settings, keywords)
+ keyword_settings = compact_keywords(keywords)
+ raise_ambiguous_settings! if !settings.nil? && !keyword_settings.empty?
+ return normalize_settings(settings) unless settings.nil?
+
+ keyword_settings
+ end
+
+ def normalize_settings(settings)
+ raise TypeMismatchError, "BrUtils settings must be a Hash. Got #{settings.class}." unless settings.is_a?(Hash)
+
+ SETTINGS_KEYS.each_with_object({}) do |key, resolved|
+ if settings.key?(key)
+ resolved[key] = settings[key]
+ elsif settings.key?(key.to_s)
+ resolved[key] = settings[key.to_s]
+ end
+ end
+ end
+
+ def compact_keywords(keywords)
+ KEYWORD_KEYS.each_with_object({}) do |key, resolved|
+ value = keywords[key]
+ resolved[key] = value unless value.nil?
+ end
+ end
+
+ def resolve_cpf_utils(resolved)
+ return resolve_utils(CpfUtils, resolved[:cpf]) if resolved.key?(:cpf)
+ return CpfUtils.new unless cpf_flat?(resolved)
+
+ CpfUtils.new(formatter: resolved[:cpf_formatter], generator: resolved[:cpf_generator])
+ end
+
+ def cpf_flat?(resolved)
+ resolved.key?(:cpf_formatter) || resolved.key?(:cpf_generator)
+ end
+
+ def resolve_cnpj_utils(resolved)
+ return resolve_utils(CnpjUtils, resolved[:cnpj]) if resolved.key?(:cnpj)
+ return CnpjUtils.new unless cnpj_flat?(resolved)
+
+ CnpjUtils.new(
+ formatter: resolved[:cnpj_formatter],
+ generator: resolved[:cnpj_generator],
+ validator: resolved[:cnpj_validator]
+ )
+ end
+
+ def cnpj_flat?(resolved)
+ resolved.key?(:cnpj_formatter) || resolved.key?(:cnpj_generator) || resolved.key?(:cnpj_validator)
+ end
+
+ def resolve_utils(utils_cls, value)
+ return utils_cls.new if value.nil?
+ return value if value.is_a?(utils_cls)
+ return utils_cls.new(value) if value.is_a?(Hash)
+
+ # Duck-typed / test doubles: use the given object by reference (Python parity).
+ value
+ end
+
+ def raise_ambiguous_settings!
+ option_keywords = KEYWORD_KEYS.map { |key| "#{key}:" }.join(', ')
+
+ raise InvalidArgumentCombinationError,
+ 'Pass either a settings Hash to `settings`, or keyword arguments ' \
+ "(#{option_keywords}), not both."
+ end
+ end
+ private_constant :Helpers
+
+ # Creates a new {BrUtils} instance with customized options. All options are
+ # optional. If any option is omitted, it falls back to its default value.
+ #
+ # Each of +:cpf+ and +:cnpj+ accepts either a pre-built utils instance or a
+ # configuration {Hash} spread into the corresponding {CpfUtils} / {CnpjUtils}
+ # constructor. Within that Hash, each resource key (+formatter+, +generator+,
+ # and +validator+ for CNPJ) accepts either an options object or a mapping of
+ # option values.
+ #
+ # Flat +:cpf_formatter+ / +:cpf_generator+ and +:cnpj_formatter+ /
+ # +:cnpj_generator+ / +:cnpj_validator+ arguments are supported as a
+ # convenience when only individual components need customization. They are
+ # ignored when the corresponding +:cpf+ or +:cnpj+ argument is provided.
+ #
+ # +settings+ and the keyword arguments are never merged with each other: when
+ # +settings+ is given (a {Hash} with +:cpf+ and/or +:cnpj+ keys), it alone
+ # determines the domains; otherwise, the domains are built exclusively from the
+ # keyword arguments. Passing +settings+ together with any non-+nil+ keyword
+ # argument raises {InvalidArgumentCombinationError} instead of silently
+ # ignoring the keywords.
+ #
+ # @param settings [Hash, nil] settings Hash with +:cpf+ and/or +:cnpj+ keys
+ # (each a utils instance, settings Hash, or +nil+)
+ # @param keywords [Hash] +:cpf+, +:cnpj+, and/or flat component kwargs
+ # (mutually exclusive with +settings+)
+ # @raise [InvalidArgumentCombinationError] if +settings+ and a keyword argument
+ # are both given
+ # @raise [TypeMismatchError] if +settings+ is given and is not a +Hash+
+ # @raise [CnpjFmt::TypeMismatchError] if CNPJ formatter options have an invalid
+ # type
+ # @raise [CnpjFmt::OutOfRangeError] if CNPJ formatter +hidden_start+ or
+ # +hidden_end+ are out of valid range
+ # @raise [CnpjFmt::ValidationError] if any CNPJ formatter key option contains a
+ # disallowed character
+ # @raise [CnpjGen::TypeMismatchError] if CNPJ generator options have an invalid
+ # type
+ # @raise [CnpjGen::ValidationError] if CNPJ generator +prefix+ is invalid or
+ # +type+ is not allowed
+ # @raise [CnpjVal::TypeMismatchError] if CNPJ validator options have an invalid
+ # type
+ # @raise [CnpjVal::ValidationError] if CNPJ validator +type+ is not allowed
+ # @raise [CpfFmt::TypeMismatchError] if CPF formatter options have an invalid
+ # type
+ # @raise [CpfFmt::OutOfRangeError] if CPF formatter +hidden_start+ or
+ # +hidden_end+ are out of valid range
+ # @raise [CpfFmt::ValidationError] if any CPF formatter key option contains a
+ # disallowed character
+ # @raise [CpfGen::TypeMismatchError] if CPF generator options have an invalid
+ # type
+ # @raise [CpfGen::ValidationError] if CPF generator +prefix+ is invalid
+ def initialize(settings = nil, **keywords)
+ resolved = Helpers.resolve_settings(settings, keywords)
+
+ @cpf = Helpers.resolve_cpf_utils(resolved)
+ @cnpj = Helpers.resolve_cnpj_utils(resolved)
+ end
+
+ # Access the CPF utilities instance.
+ #
+ # @return [CpfUtils]
+ attr_reader :cpf
+
+ # Access the CNPJ utilities instance.
+ #
+ # @return [CnpjUtils]
+ attr_reader :cnpj
+
+ # Sets the active CPF utilities instance.
+ #
+ # It is flexible and can handle any of these inputs:
+ #
+ # 1. A complete new instance of {CpfUtils}
+ # 2. A {Hash} of {CpfUtils} component settings
+ # 3. A partial {Hash} with options for the CPF utilities
+ # 4. +nil+ creates a brand new instance of {CpfUtils} with the default options
+ #
+ # Note that this resets the CPF utilities instance completely. Any previous
+ # options will be overridden. To alter only a single option or a few options
+ # of the existing instance, access it directly and use the CPF utilities'
+ # setters and methods (e.g. +utils.cpf.formatter.options.hidden = true+).
+ #
+ # @param value [CpfUtils, Hash, nil]
+ # @raise [CpfFmt::TypeMismatchError] if formatter options have an invalid type
+ # @raise [CpfFmt::OutOfRangeError] if formatter +hidden_start+ or +hidden_end+
+ # are out of valid range
+ # @raise [CpfFmt::ValidationError] if any formatter key option contains a
+ # disallowed character
+ # @raise [CpfGen::TypeMismatchError] if generator options have an invalid type
+ # @raise [CpfGen::ValidationError] if generator +prefix+ is invalid
+ def cpf=(value)
+ @cpf = Helpers.resolve_utils(CpfUtils, value)
+ end
+
+ # Sets the active CNPJ utilities instance.
+ #
+ # It is flexible and can handle any of these inputs:
+ #
+ # 1. A complete new instance of {CnpjUtils}
+ # 2. A {Hash} of {CnpjUtils} component settings
+ # 3. A partial {Hash} with options for the CNPJ utilities
+ # 4. +nil+ creates a brand new instance of {CnpjUtils} with the default options
+ #
+ # Note that this resets the CNPJ utilities instance completely. Any previous
+ # options will be overridden. To alter only a single option or a few options
+ # of the existing instance, access it directly and use the CNPJ utilities'
+ # setters and methods (e.g. +utils.cnpj.generator.options.type = 'numeric'+).
+ #
+ # @param value [CnpjUtils, Hash, nil]
+ # @raise [CnpjFmt::TypeMismatchError] if formatter options have an invalid type
+ # @raise [CnpjFmt::OutOfRangeError] if formatter +hidden_start+ or +hidden_end+
+ # are out of valid range
+ # @raise [CnpjFmt::ValidationError] if any formatter key option contains a
+ # disallowed character
+ # @raise [CnpjGen::TypeMismatchError] if generator options have an invalid type
+ # @raise [CnpjGen::ValidationError] if generator +prefix+ is invalid or +type+
+ # is not allowed
+ # @raise [CnpjVal::TypeMismatchError] if validator options have an invalid type
+ # @raise [CnpjVal::ValidationError] if validator +type+ is not allowed
+ def cnpj=(value)
+ @cnpj = Helpers.resolve_utils(CnpjUtils, value)
+ end
+
+ # Default {BrUtils} instance with default CPF and CNPJ utilities (parity with
+ # the JS default export / Python +br_utils+ singleton). Configuration is
+ # process-wide and shared across threads: mutating this instance (e.g. via
+ # setters) affects subsequent {BrUtils.cpf} and {BrUtils.cnpj} calls for every
+ # caller in the process. Prefer {BrUtils.new} for threaded or isolated work.
+ DEFAULT = new
+
+ class << self
+ # Access the CPF utilities from {DEFAULT} (alias of {BrUtils#cpf} on that
+ # instance).
+ #
+ # @return [CpfUtils]
+ # @see BrUtils#cpf
+ def cpf
+ DEFAULT.cpf
+ end
+
+ # Access the CNPJ utilities from {DEFAULT} (alias of {BrUtils#cnpj} on that
+ # instance).
+ #
+ # @return [CnpjUtils]
+ # @see BrUtils#cnpj
+ def cnpj
+ DEFAULT.cnpj
+ end
+ end
+end
diff --git a/packages/br-utilities/src/br-utilities/cnpj_fmt.rb b/packages/br-utilities/src/br-utilities/cnpj_fmt.rb
new file mode 100644
index 0000000..a050fec
--- /dev/null
+++ b/packages/br-utilities/src/br-utilities/cnpj_fmt.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class BrUtils
+ # CNPJ formatting utilities re-exported from +cnpj-fmt+ (via +cnpj-utilities+).
+ #
+ # Nested package module — same object as +::CnpjFmt+ (Options, helpers, errors,
+ # types).
+ CnpjFmt = ::CnpjFmt
+
+ # Main-class shortcut for {CnpjFmt::CnpjFormatter}.
+ CnpjFormatter = CnpjFmt::CnpjFormatter
+
+ # Main-class shortcut for {CnpjFmt::CnpjFormatterOptions}.
+ CnpjFormatterOptions = CnpjFmt::CnpjFormatterOptions
+
+ # Main-class shortcut for {CnpjFmt::Error}.
+ CnpjFormatterError = CnpjFmt::Error
+end
diff --git a/packages/br-utilities/src/br-utilities/cnpj_gen.rb b/packages/br-utilities/src/br-utilities/cnpj_gen.rb
new file mode 100644
index 0000000..94c374e
--- /dev/null
+++ b/packages/br-utilities/src/br-utilities/cnpj_gen.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class BrUtils
+ # CNPJ generation utilities re-exported from +cnpj-gen+ (via +cnpj-utilities+).
+ #
+ # Nested package module — same object as +::CnpjGen+ (Options, helpers, errors,
+ # types).
+ CnpjGen = ::CnpjGen
+
+ # Main-class shortcut for {CnpjGen::CnpjGenerator}.
+ CnpjGenerator = CnpjGen::CnpjGenerator
+
+ # Main-class shortcut for {CnpjGen::CnpjGeneratorOptions}.
+ CnpjGeneratorOptions = CnpjGen::CnpjGeneratorOptions
+
+ # Main-class shortcut for {CnpjGen::Error}.
+ CnpjGeneratorError = CnpjGen::Error
+end
diff --git a/packages/br-utilities/src/br-utilities/cnpj_utils.rb b/packages/br-utilities/src/br-utilities/cnpj_utils.rb
new file mode 100644
index 0000000..369f195
--- /dev/null
+++ b/packages/br-utilities/src/br-utilities/cnpj_utils.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+class BrUtils
+ # CNPJ utilities re-exported from +cnpj-utilities+.
+ #
+ # Nested package class — same object as +::CnpjUtils+.
+ CnpjUtils = ::CnpjUtils
+end
diff --git a/packages/br-utilities/src/br-utilities/cnpj_val.rb b/packages/br-utilities/src/br-utilities/cnpj_val.rb
new file mode 100644
index 0000000..68713e3
--- /dev/null
+++ b/packages/br-utilities/src/br-utilities/cnpj_val.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class BrUtils
+ # CNPJ validation utilities re-exported from +cnpj-val+ (via +cnpj-utilities+).
+ #
+ # Nested package module — same object as +::CnpjVal+ (Options, helpers, errors,
+ # types).
+ CnpjVal = ::CnpjVal
+
+ # Main-class shortcut for {CnpjVal::CnpjValidator}.
+ CnpjValidator = CnpjVal::CnpjValidator
+
+ # Main-class shortcut for {CnpjVal::CnpjValidatorOptions}.
+ CnpjValidatorOptions = CnpjVal::CnpjValidatorOptions
+
+ # Main-class shortcut for {CnpjVal::Error}.
+ CnpjValidatorError = CnpjVal::Error
+end
diff --git a/packages/br-utilities/src/br-utilities/cpf_fmt.rb b/packages/br-utilities/src/br-utilities/cpf_fmt.rb
new file mode 100644
index 0000000..b1588db
--- /dev/null
+++ b/packages/br-utilities/src/br-utilities/cpf_fmt.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class BrUtils
+ # CPF formatting utilities re-exported from +cpf-fmt+ (via +cpf-utilities+).
+ #
+ # Nested package module — same object as +::CpfFmt+ (Options, helpers, errors,
+ # types).
+ CpfFmt = ::CpfFmt
+
+ # Main-class shortcut for {CpfFmt::CpfFormatter}.
+ CpfFormatter = CpfFmt::CpfFormatter
+
+ # Main-class shortcut for {CpfFmt::CpfFormatterOptions}.
+ CpfFormatterOptions = CpfFmt::CpfFormatterOptions
+
+ # Main-class shortcut for {CpfFmt::Error}.
+ CpfFormatterError = CpfFmt::Error
+end
diff --git a/packages/br-utilities/src/br-utilities/cpf_gen.rb b/packages/br-utilities/src/br-utilities/cpf_gen.rb
new file mode 100644
index 0000000..fbfe4b5
--- /dev/null
+++ b/packages/br-utilities/src/br-utilities/cpf_gen.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class BrUtils
+ # CPF generation utilities re-exported from +cpf-gen+ (via +cpf-utilities+).
+ #
+ # Nested package module — same object as +::CpfGen+ (Options, helpers, errors,
+ # types).
+ CpfGen = ::CpfGen
+
+ # Main-class shortcut for {CpfGen::CpfGenerator}.
+ CpfGenerator = CpfGen::CpfGenerator
+
+ # Main-class shortcut for {CpfGen::CpfGeneratorOptions}.
+ CpfGeneratorOptions = CpfGen::CpfGeneratorOptions
+
+ # Main-class shortcut for {CpfGen::Error}.
+ CpfGeneratorError = CpfGen::Error
+end
diff --git a/packages/br-utilities/src/br-utilities/cpf_utils.rb b/packages/br-utilities/src/br-utilities/cpf_utils.rb
new file mode 100644
index 0000000..d86704c
--- /dev/null
+++ b/packages/br-utilities/src/br-utilities/cpf_utils.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+class BrUtils
+ # CPF utilities re-exported from +cpf-utilities+.
+ #
+ # Nested package class — same object as +::CpfUtils+.
+ CpfUtils = ::CpfUtils
+end
diff --git a/packages/br-utilities/src/br-utilities/cpf_val.rb b/packages/br-utilities/src/br-utilities/cpf_val.rb
new file mode 100644
index 0000000..e27d3ed
--- /dev/null
+++ b/packages/br-utilities/src/br-utilities/cpf_val.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class BrUtils
+ # CPF validation utilities re-exported from +cpf-val+ (via +cpf-utilities+).
+ #
+ # Nested package module — same object as +::CpfVal+ (helpers, errors, types).
+ CpfVal = ::CpfVal
+
+ # Main-class shortcut for {CpfVal::CpfValidator}.
+ CpfValidator = CpfVal::CpfValidator
+
+ # Main-class shortcut for {CpfVal::Error}.
+ CpfValidatorError = CpfVal::Error
+end
diff --git a/packages/br-utilities/src/br-utilities/errors.rb b/packages/br-utilities/src/br-utilities/errors.rb
new file mode 100644
index 0000000..6619be7
--- /dev/null
+++ b/packages/br-utilities/src/br-utilities/errors.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class BrUtils
+ # Marker module mixed into every custom error raised by this library.
+ #
+ # Use +rescue BrUtils::Error+ to catch every library error regardless of
+ # native ancestry. Domain packages raise their own error hierarchies;
+ # this gem only defines the misuse errors it raises itself.
+ module Error; end
+
+ # API misuse error raised when an argument's runtime type does not match the
+ # type required by the API contract (for example, a non-Hash +settings+ value).
+ class TypeMismatchError < TypeError
+ include Error
+ end
+
+ # API misuse error raised when the combination of provided arguments does not
+ # match any valid overload-style signature (for example, a settings Hash
+ # together with keyword overrides).
+ class InvalidArgumentCombinationError < ArgumentError
+ include Error
+ end
+end
diff --git a/packages/br-utilities/src/br-utilities/version.rb b/packages/br-utilities/src/br-utilities/version.rb
index b8ff3f8..59b60db 100644
--- a/packages/br-utilities/src/br-utilities/version.rb
+++ b/packages/br-utilities/src/br-utilities/version.rb
@@ -1,5 +1,11 @@
# frozen_string_literal: true
+# Placeholder module so the gemspec (and any early require of this file) can read
+# {BrUtils::VERSION}. The gem entry point promotes +BrUtils+ to a class and
+# reopens it for the façade implementation.
module BrUtils
+ # Gem version string. Placeholder replaced at build/publish time.
+ #
+ # @return [String]
VERSION = '0.0.0'
end
diff --git a/packages/br-utilities/tests/br_utilities.spec.rb b/packages/br-utilities/tests/br_utilities.spec.rb
deleted file mode 100644
index c334532..0000000
--- a/packages/br-utilities/tests/br_utilities.spec.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe BrUtils do
- describe '.hello' do
- it 'returns br-utilities' do
- expect(BrUtils.hello).to eq('br-utilities')
- end
- end
-end
diff --git a/packages/br-utilities/tests/br_utils.spec.rb b/packages/br-utilities/tests/br_utils.spec.rb
new file mode 100644
index 0000000..6a02322
--- /dev/null
+++ b/packages/br-utilities/tests/br_utils.spec.rb
@@ -0,0 +1,1054 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+# Combined behavioural suite for BrUtils (JS / PHP / Python reference tests).
+#
+# Dropped cases (not meaningful in Ruby):
+# - js/packages/br-utils/tests/output.spec.ts — UMD/CJS/ESM bundles, .d.ts wiring,
+# global variable attachment, and export-string parsing (JS packaging only).
+# - JavaScript `undefined` nullish values — Ruby uses `nil` only.
+# - PHP getCpfUtils() / getCnpjUtils() accessor names — Ruby uses #cpf / #cnpj
+# (JS/Python parity per AGENTS.md).
+# - PHP read-only cpf/cnpj (no setters) — Ruby implements setters like JS/Python.
+# - PHP CnpjType / CnpjValidationType enums — Ruby uses string type values.
+# - PHP legacy CPF v1 InvalidArgumentException for formatter/generator options —
+# Ruby mirrors JS/Python v2 structured errors (CpfFmt::*/CpfGen::*).
+# - PHP phpunit/Cpf/* legacy CPF v1 component suites — Ruby aligns with
+# cpf-utilities v2, not PHP CPF v1.
+# - Python __slots__ / dynamic-attribute restriction — optional in Ruby; AGENTS.md
+# does not require freezing or slot-like attribute locking.
+# - BrUtils Options class fold/#set/nil-setter contracts — BrUtils has no Options
+# class (settings are a Hash or domain utils instances; Options live on
+# cpf-fmt / cnpj-fmt / … siblings).
+# - BrUtils.format / .generate / .is_valid class helpers — façade owns wiring only
+# (cpf/cnpj); domain operations live on CpfUtils / CnpjUtils (and their DEFAULT).
+
+def compact_options(**kwargs)
+ kwargs.compact
+end
+
+def expect_options_containing(actual, expected)
+ expected.each do |key, value|
+ expect(actual[key]).to eq(value)
+ end
+end
+
+CPF_FORMAT_FACTORIES = {
+ constructor_hash: lambda { |cpf, dot_key = nil, dash_key = nil|
+ utils = BrUtils.new(cpf: { formatter: compact_options(dot_key: dot_key, dash_key: dash_key) })
+ utils.cpf.format(cpf)
+ },
+ method_keywords: lambda { |cpf, dot_key = nil, dash_key = nil|
+ BrUtils.new.cpf.format(cpf, dot_key: dot_key, dash_key: dash_key)
+ }
+}.freeze
+
+CPF_GENERATE_FACTORIES = {
+ constructor_hash: lambda { |format: nil, prefix: nil|
+ utils = BrUtils.new(cpf: { generator: compact_options(format: format, prefix: prefix) })
+ utils.cpf.generate
+ },
+ method_keywords: lambda { |format: nil, prefix: nil|
+ BrUtils.new.cpf.generate(format: format, prefix: prefix)
+ }
+}.freeze
+
+CNPJ_FORMAT_FACTORIES = {
+ constructor_hash: lambda { |cnpj, slash_key = nil|
+ utils = BrUtils.new(cnpj: { formatter: compact_options(slash_key: slash_key) })
+ utils.cnpj.format(cnpj)
+ },
+ constructor_options: lambda { |cnpj, slash_key = nil|
+ options = CnpjFmt::CnpjFormatterOptions.new(compact_options(slash_key: slash_key))
+ utils = BrUtils.new(cnpj: { formatter: options })
+ utils.cnpj.format(cnpj)
+ },
+ method_keywords: lambda { |cnpj, slash_key = nil|
+ BrUtils.new.cnpj.format(cnpj, slash_key: slash_key)
+ },
+ method_options: lambda { |cnpj, slash_key = nil|
+ options = CnpjFmt::CnpjFormatterOptions.new(compact_options(slash_key: slash_key))
+ BrUtils.new.cnpj.format(cnpj, options)
+ }
+}.freeze
+
+CNPJ_GENERATE_FACTORIES = {
+ constructor_hash: lambda { |format: nil, prefix: nil, type: nil|
+ utils = BrUtils.new(
+ cnpj: { generator: compact_options(format: format, prefix: prefix, type: type) }
+ )
+ utils.cnpj.generate
+ },
+ constructor_options: lambda { |format: nil, prefix: nil, type: nil|
+ options = CnpjGen::CnpjGeneratorOptions.new(
+ compact_options(format: format, prefix: prefix, type: type)
+ )
+ utils = BrUtils.new(cnpj: { generator: options })
+ utils.cnpj.generate
+ },
+ method_keywords: lambda { |format: nil, prefix: nil, type: nil|
+ BrUtils.new.cnpj.generate(format: format, prefix: prefix, type: type)
+ },
+ method_options: lambda { |format: nil, prefix: nil, type: nil|
+ options = CnpjGen::CnpjGeneratorOptions.new(
+ compact_options(format: format, prefix: prefix, type: type)
+ )
+ BrUtils.new.cnpj.generate(options)
+ }
+}.freeze
+
+CNPJ_IS_VALID_FACTORIES = {
+ constructor_hash: lambda { |cnpj, type: nil, case_sensitive: nil|
+ utils = BrUtils.new(
+ cnpj: { validator: compact_options(type: type, case_sensitive: case_sensitive) }
+ )
+ utils.cnpj.is_valid(cnpj)
+ },
+ constructor_options: lambda { |cnpj, type: nil, case_sensitive: nil|
+ options = CnpjVal::CnpjValidatorOptions.new(
+ compact_options(type: type, case_sensitive: case_sensitive)
+ )
+ utils = BrUtils.new(cnpj: { validator: options })
+ utils.cnpj.is_valid(cnpj)
+ },
+ method_keywords: lambda { |cnpj, type: nil, case_sensitive: nil|
+ BrUtils.new.cnpj.is_valid(cnpj, type: type, case_sensitive: case_sensitive)
+ },
+ method_options: lambda { |cnpj, type: nil, case_sensitive: nil|
+ options = CnpjVal::CnpjValidatorOptions.new(
+ compact_options(type: type, case_sensitive: case_sensitive)
+ )
+ BrUtils.new.cnpj.is_valid(cnpj, options)
+ }
+}.freeze
+
+CPF_FORMAT_FACTORY_CONTEXTS = [
+ ['when options are passed to the constructor as a Hash', :constructor_hash],
+ ['when options are passed to #format as keywords', :method_keywords]
+].freeze
+
+CPF_GENERATE_FACTORY_CONTEXTS = [
+ ['when options are passed to the constructor as a Hash', :constructor_hash],
+ ['when options are passed to #generate as keywords', :method_keywords]
+].freeze
+
+CNPJ_FORMAT_FACTORY_CONTEXTS = [
+ ['when options are passed to the constructor as a Hash', :constructor_hash],
+ ['when options are passed to the constructor as CnpjFormatterOptions', :constructor_options],
+ ['when options are passed to #format as keywords', :method_keywords],
+ ['when options are passed to #format as CnpjFormatterOptions', :method_options]
+].freeze
+
+CNPJ_GENERATE_FACTORY_CONTEXTS = [
+ ['when options are passed to the constructor as a Hash', :constructor_hash],
+ ['when options are passed to the constructor as CnpjGeneratorOptions', :constructor_options],
+ ['when options are passed to #generate as keywords', :method_keywords],
+ ['when options are passed to #generate as CnpjGeneratorOptions', :method_options]
+].freeze
+
+CNPJ_IS_VALID_FACTORY_CONTEXTS = [
+ ['when options are passed to the constructor as a Hash', :constructor_hash],
+ ['when options are passed to the constructor as CnpjValidatorOptions', :constructor_options],
+ ['when options are passed to #is_valid as keywords', :method_keywords],
+ ['when options are passed to #is_valid as CnpjValidatorOptions', :method_options]
+].freeze
+
+RSpec.describe BrUtils do
+ def default_cpf_formatter_options_snapshot
+ CpfFmt::CpfFormatterOptions.new.all
+ end
+
+ def default_cpf_generator_options_snapshot
+ CpfGen::CpfGeneratorOptions.new.all
+ end
+
+ def default_cnpj_formatter_options_snapshot
+ CnpjFmt::CnpjFormatterOptions.new.all
+ end
+
+ def default_cnpj_generator_options_snapshot
+ CnpjGen::CnpjGeneratorOptions.new.all
+ end
+
+ def default_cnpj_validator_options_snapshot
+ CnpjVal::CnpjValidatorOptions.new.all
+ end
+
+ describe 'DEFAULT' do
+ it 'is an instance of BrUtils' do
+ expect(described_class::DEFAULT).to be_a(described_class)
+ end
+
+ it 'exposes cpf and cnpj domain utils' do
+ aggregate_failures do
+ expect(described_class::DEFAULT.cpf).to be_a(CpfUtils)
+ expect(described_class::DEFAULT.cnpj).to be_a(CnpjUtils)
+ end
+ end
+ end
+
+ describe 'class helpers' do
+ # BrUtils façade operations are domain accessors; class helpers forward to DEFAULT.
+ it 'exposes cpf and cnpj' do
+ aggregate_failures do
+ expect(described_class).to respond_to(:cpf)
+ expect(described_class).to respond_to(:cnpj)
+ end
+ end
+
+ context 'when calling through the class' do
+ it 'returns the same cpf as DEFAULT' do
+ expect(described_class.cpf).to equal(described_class::DEFAULT.cpf)
+ end
+
+ it 'returns the same cnpj as DEFAULT' do
+ expect(described_class.cnpj).to equal(described_class::DEFAULT.cnpj)
+ end
+ end
+
+ context 'when DEFAULT is mutated' do
+ around do |example|
+ original_cpf = described_class::DEFAULT.cpf
+ original_cnpj = described_class::DEFAULT.cnpj
+ example.run
+ described_class::DEFAULT.cpf = original_cpf
+ described_class::DEFAULT.cnpj = original_cnpj
+ end
+
+ it 'affects subsequent class helper cpf calls' do
+ replacement = CpfUtils.new(formatter: { dash_key: '|' })
+ described_class::DEFAULT.cpf = replacement
+
+ aggregate_failures do
+ expect(described_class.cpf).to equal(replacement)
+ expect(described_class.cpf.format('12345678909')).to eq('123.456.789|09')
+ end
+ end
+
+ it 'affects subsequent class helper cnpj calls' do
+ replacement = CnpjUtils.new(formatter: { slash_key: '|' })
+ described_class::DEFAULT.cnpj = replacement
+
+ aggregate_failures do
+ expect(described_class.cnpj).to equal(replacement)
+ expect(described_class.cnpj.format('01ABC234000X56')).to eq('01.ABC.234|000X-56')
+ end
+ end
+
+ it 'does not affect a custom instance' do
+ custom = described_class.new
+ described_class::DEFAULT.cpf = CpfUtils.new(formatter: { dash_key: '|' })
+ described_class::DEFAULT.cnpj = CnpjUtils.new(formatter: { slash_key: '|' })
+
+ aggregate_failures do
+ expect(custom.cpf.format('12345678909')).to eq('123.456.789-09')
+ expect(custom.cnpj.format('01ABC234000X56')).to eq('01.ABC.234/000X-56')
+ end
+ end
+ end
+ end
+
+ describe 'loaded sibling packages' do
+ it 'makes cpf-utilities symbols available' do
+ aggregate_failures do
+ expect(defined?(CpfUtils)).to eq('constant')
+ expect(defined?(CpfFmt::CpfFormatter)).to eq('constant')
+ expect(defined?(CpfGen::CpfGenerator)).to eq('constant')
+ expect(defined?(CpfVal::CpfValidator)).to eq('constant')
+ expect(CpfFmt).to respond_to(:cpf_fmt)
+ expect(CpfGen).to respond_to(:cpf_gen)
+ expect(CpfVal).to respond_to(:cpf_val)
+ end
+ end
+
+ it 'makes cnpj-utilities symbols available' do
+ aggregate_failures do
+ expect(defined?(CnpjUtils)).to eq('constant')
+ expect(defined?(CnpjFmt::CnpjFormatter)).to eq('constant')
+ expect(defined?(CnpjGen::CnpjGenerator)).to eq('constant')
+ expect(defined?(CnpjVal::CnpjValidator)).to eq('constant')
+ expect(CnpjFmt).to respond_to(:cnpj_fmt)
+ expect(CnpjGen).to respond_to(:cnpj_gen)
+ expect(CnpjVal).to respond_to(:cnpj_val)
+ end
+ end
+ end
+
+ describe 'two-tier BrUtils re-exports' do
+ it 'nests sibling modules as the same objects' do
+ aggregate_failures do
+ expect(described_class::CpfUtils).to equal(CpfUtils)
+ expect(described_class::CnpjUtils).to equal(CnpjUtils)
+ expect(described_class::CpfFmt).to equal(CpfFmt)
+ expect(described_class::CpfGen).to equal(CpfGen)
+ expect(described_class::CpfVal).to equal(CpfVal)
+ expect(described_class::CnpjFmt).to equal(CnpjFmt)
+ expect(described_class::CnpjGen).to equal(CnpjGen)
+ expect(described_class::CnpjVal).to equal(CnpjVal)
+ end
+ end
+
+ it 'aliases main cpf classes at the façade root' do
+ aggregate_failures do
+ expect(described_class::CpfFormatter).to equal(CpfFmt::CpfFormatter)
+ expect(described_class::CpfFormatterOptions).to equal(CpfFmt::CpfFormatterOptions)
+ expect(described_class::CpfGenerator).to equal(CpfGen::CpfGenerator)
+ expect(described_class::CpfGeneratorOptions).to equal(CpfGen::CpfGeneratorOptions)
+ expect(described_class::CpfValidator).to equal(CpfVal::CpfValidator)
+ end
+ end
+
+ it 'aliases main cnpj classes at the façade root' do
+ aggregate_failures do
+ expect(described_class::CnpjFormatter).to equal(CnpjFmt::CnpjFormatter)
+ expect(described_class::CnpjFormatterOptions).to equal(CnpjFmt::CnpjFormatterOptions)
+ expect(described_class::CnpjGenerator).to equal(CnpjGen::CnpjGenerator)
+ expect(described_class::CnpjGeneratorOptions).to equal(CnpjGen::CnpjGeneratorOptions)
+ expect(described_class::CnpjValidator).to equal(CnpjVal::CnpjValidator)
+ expect(described_class::CnpjValidatorOptions).to equal(CnpjVal::CnpjValidatorOptions)
+ end
+ end
+
+ context 'with nested surface smoke' do
+ it 'exposes Options through the nest' do
+ options = described_class::CpfFmt::CpfFormatterOptions.new(hidden: true)
+
+ expect(options.hidden).to be(true)
+ end
+
+ it 'exposes helpers through the nest' do
+ expect(described_class::CpfFmt.cpf_fmt('12345678909')).to eq('123.456.789-09')
+ end
+
+ it 'exposes an error class through the nest' do
+ expect(described_class::CnpjFmt::OutOfRangeError).to equal(CnpjFmt::OutOfRangeError)
+ end
+ end
+ end
+
+ describe '#initialize' do
+ context 'when called with no arguments' do
+ subject(:utils) { described_class.new }
+
+ it 'creates default domain utils instances' do
+ aggregate_failures do
+ expect(utils.cpf).to be_a(CpfUtils)
+ expect(utils.cnpj).to be_a(CnpjUtils)
+ end
+ end
+
+ it 'uses default domain component options' do
+ aggregate_failures do
+ expect_options_containing(
+ utils.cpf.formatter.options.all,
+ default_cpf_formatter_options_snapshot
+ )
+ expect_options_containing(
+ utils.cpf.generator.options.all,
+ default_cpf_generator_options_snapshot
+ )
+ expect_options_containing(
+ utils.cnpj.formatter.options.all,
+ default_cnpj_formatter_options_snapshot
+ )
+ expect_options_containing(
+ utils.cnpj.generator.options.all,
+ default_cnpj_generator_options_snapshot
+ )
+ expect_options_containing(
+ utils.cnpj.validator.options.all,
+ default_cnpj_validator_options_snapshot
+ )
+ end
+ end
+ end
+
+ context 'when called with instances of resources' do
+ it 'uses the passed CnpjUtils directly' do
+ cnpj_utils = CnpjUtils.new
+ utils = described_class.new(cnpj: cnpj_utils)
+
+ aggregate_failures do
+ expect(utils.cnpj).to be_a(CnpjUtils)
+ expect(utils.cnpj).to equal(cnpj_utils)
+ end
+ end
+
+ it 'uses the passed CpfUtils directly' do
+ cpf_utils = CpfUtils.new
+ utils = described_class.new(cpf: cpf_utils)
+
+ aggregate_failures do
+ expect(utils.cpf).to be_a(CpfUtils)
+ expect(utils.cpf).to equal(cpf_utils)
+ end
+ end
+
+ it 'uses the passed resources directly' do
+ cnpj_utils = CnpjUtils.new
+ cpf_utils = CpfUtils.new
+ utils = described_class.new(cnpj: cnpj_utils, cpf: cpf_utils)
+
+ aggregate_failures do
+ expect(utils.cnpj).to equal(cnpj_utils)
+ expect(utils.cpf).to equal(cpf_utils)
+ end
+ end
+ end
+
+ context 'when called with literal Hash parameters' do
+ let(:cnpj_utils_options) do
+ {
+ formatter: {
+ hidden: true,
+ hidden_key: '#',
+ hidden_start: 8,
+ hidden_end: 11,
+ dot_key: '_',
+ slash_key: '|',
+ dash_key: ' dv '
+ },
+ generator: {
+ format: true,
+ prefix: '12345678',
+ type: 'numeric'
+ },
+ validator: {
+ type: 'numeric'
+ }
+ }
+ end
+
+ let(:cpf_utils_options) do
+ {
+ formatter: {
+ hidden: true,
+ hidden_key: '#',
+ hidden_start: 8,
+ hidden_end: 10,
+ dot_key: '_',
+ dash_key: ' dv '
+ },
+ generator: {
+ format: true,
+ prefix: '12345678'
+ }
+ }
+ end
+
+ it 'builds CnpjUtils from nested option hashes' do
+ utils = described_class.new(cnpj: cnpj_utils_options)
+
+ aggregate_failures do
+ expect(utils.cnpj).to be_a(CnpjUtils)
+ expect(utils.cnpj.formatter).to be_a(CnpjFmt::CnpjFormatter)
+ expect_options_containing(utils.cnpj.formatter.options.all, cnpj_utils_options[:formatter])
+ expect(utils.cnpj.generator).to be_a(CnpjGen::CnpjGenerator)
+ expect_options_containing(utils.cnpj.generator.options.all, cnpj_utils_options[:generator])
+ expect(utils.cnpj.validator).to be_a(CnpjVal::CnpjValidator)
+ expect_options_containing(utils.cnpj.validator.options.all, cnpj_utils_options[:validator])
+ end
+ end
+
+ it 'builds CpfUtils from nested option hashes' do
+ utils = described_class.new(cpf: cpf_utils_options)
+
+ aggregate_failures do
+ expect(utils.cpf).to be_a(CpfUtils)
+ expect(utils.cpf.formatter).to be_a(CpfFmt::CpfFormatter)
+ expect_options_containing(utils.cpf.formatter.options.all, cpf_utils_options[:formatter])
+ expect(utils.cpf.generator).to be_a(CpfGen::CpfGenerator)
+ expect_options_containing(utils.cpf.generator.options.all, cpf_utils_options[:generator])
+ end
+ end
+ end
+
+ context 'when called with flat formatter and generator options' do
+ it 'applies cpf_formatter options' do
+ formatter_options = CpfFmt::CpfFormatterOptions.new(hidden: true, hidden_key: 'X')
+ utils = described_class.new(cpf_formatter: formatter_options)
+
+ aggregate_failures do
+ expect(utils.cpf.formatter.options).to equal(formatter_options)
+ expect(utils.cpf.format('12345678901')).to eq('123.XXX.XXX-XX')
+ end
+ end
+
+ it 'applies cpf_generator options' do
+ generator_options = CpfGen::CpfGeneratorOptions.new(format: true, prefix: '123456789')
+ utils = described_class.new(cpf_generator: generator_options)
+
+ aggregate_failures do
+ expect(utils.cpf.generator.options).to equal(generator_options)
+ expect(utils.cpf.generate).to start_with('123.456.789-')
+ end
+ end
+
+ it 'applies cnpj_formatter options' do
+ formatter_options = CnpjFmt::CnpjFormatterOptions.new(hidden: true, hidden_key: 'X')
+ utils = described_class.new(cnpj_formatter: formatter_options)
+
+ aggregate_failures do
+ expect(utils.cnpj.formatter.options).to equal(formatter_options)
+ expect(utils.cnpj.format('11222333000181')).to eq('11.222.XXX/XXXX-XX')
+ end
+ end
+
+ it 'applies cnpj_generator options' do
+ generator_options = CnpjGen::CnpjGeneratorOptions.new(format: true, prefix: '11222333')
+ utils = described_class.new(cnpj_generator: generator_options)
+
+ aggregate_failures do
+ expect(utils.cnpj.generator.options).to equal(generator_options)
+ expect(utils.cnpj.generate).to start_with('11.222.333/')
+ end
+ end
+
+ it 'applies all flat options together' do
+ cpf_fmt_opts = CpfFmt::CpfFormatterOptions.new(hidden: true)
+ cpf_gen_opts = CpfGen::CpfGeneratorOptions.new(format: true)
+ cnpj_fmt_opts = CnpjFmt::CnpjFormatterOptions.new(hidden: true)
+ cnpj_gen_opts = CnpjGen::CnpjGeneratorOptions.new(format: true)
+
+ utils = described_class.new(
+ cpf_formatter: cpf_fmt_opts,
+ cpf_generator: cpf_gen_opts,
+ cnpj_formatter: cnpj_fmt_opts,
+ cnpj_generator: cnpj_gen_opts
+ )
+
+ aggregate_failures do
+ expect(utils.cpf.formatter.options).to equal(cpf_fmt_opts)
+ expect(utils.cpf.generator.options).to equal(cpf_gen_opts)
+ expect(utils.cnpj.formatter.options).to equal(cnpj_fmt_opts)
+ expect(utils.cnpj.generator.options).to equal(cnpj_gen_opts)
+ end
+ end
+ end
+
+ context 'when called with nested settings hashes' do
+ it 'configures CPF formatter and generator from hashes' do
+ formatter_options = {
+ hidden: true,
+ hidden_key: '#',
+ hidden_start: 8,
+ hidden_end: 10,
+ dot_key: '_',
+ dash_key: ' dv '
+ }
+ generator_options = { format: true, prefix: '12345678' }
+
+ utils = described_class.new(
+ cpf: {
+ formatter: formatter_options,
+ generator: generator_options
+ }
+ )
+
+ aggregate_failures do
+ expect_options_containing(utils.cpf.formatter.options.all, formatter_options)
+ expect_options_containing(utils.cpf.generator.options.all, generator_options)
+ end
+ end
+
+ it 'configures CNPJ components from hashes' do
+ formatter_options = { slash_key: '|' }
+ generator_options = { format: true, prefix: '12345' }
+ validator_options = { type: 'numeric', case_sensitive: false }
+
+ utils = described_class.new(
+ cnpj: {
+ formatter: formatter_options,
+ generator: generator_options,
+ validator: validator_options
+ }
+ )
+
+ aggregate_failures do
+ expect_options_containing(utils.cnpj.formatter.options.all, formatter_options)
+ expect_options_containing(utils.cnpj.generator.options.all, generator_options)
+ expect_options_containing(utils.cnpj.validator.options.all, validator_options)
+ end
+ end
+
+ it 'keeps provided options instances by reference' do
+ cnpj_formatter_options = CnpjFmt::CnpjFormatterOptions.new
+ cnpj_generator_options = CnpjGen::CnpjGeneratorOptions.new
+ cnpj_validator_options = CnpjVal::CnpjValidatorOptions.new
+
+ utils = described_class.new(
+ cnpj: {
+ formatter: cnpj_formatter_options,
+ generator: cnpj_generator_options,
+ validator: cnpj_validator_options
+ }
+ )
+
+ aggregate_failures do
+ expect(utils.cnpj.formatter.options).to equal(cnpj_formatter_options)
+ expect(utils.cnpj.generator.options).to equal(cnpj_generator_options)
+ expect(utils.cnpj.validator.options).to equal(cnpj_validator_options)
+ end
+ end
+
+ it 'reflects later mutation of shared options' do
+ cnpj_formatter_options = CnpjFmt::CnpjFormatterOptions.new
+ utils = described_class.new(cnpj: { formatter: cnpj_formatter_options })
+
+ cnpj_formatter_options.dash_key = '|'
+
+ expect(utils.cnpj.formatter.options.all[:dash_key]).to eq('|')
+ end
+ end
+
+ context 'when called with a settings Hash' do
+ it 'adopts nested domain utils from the Hash' do
+ cpf_utils = CpfUtils.new
+ cnpj_utils = CnpjUtils.new
+ utils = described_class.new({ cpf: cpf_utils, cnpj: cnpj_utils })
+
+ aggregate_failures do
+ expect(utils.cpf).to equal(cpf_utils)
+ expect(utils.cnpj).to equal(cnpj_utils)
+ end
+ end
+ end
+
+ context 'when called with a non-Hash settings value' do
+ it 'raises TypeMismatchError for a string' do
+ expect { described_class.new('not-a-hash') }
+ .to raise_error(BrUtils::TypeMismatchError, /settings must be a Hash/)
+ end
+
+ it 'raises TypeMismatchError for false' do
+ expect { described_class.new(false) }
+ .to raise_error(BrUtils::TypeMismatchError, /settings must be a Hash/)
+ end
+
+ it 'raises TypeMismatchError for an array' do
+ expect { described_class.new([]) }
+ .to raise_error(BrUtils::TypeMismatchError, /settings must be a Hash/)
+ end
+ end
+
+ context 'when called with invalid options' do
+ it 'raises CPF formatter OutOfRangeError' do
+ expect { described_class.new(cpf: { formatter: { hidden_start: -1 } }) }
+ .to raise_error(CpfFmt::OutOfRangeError)
+ end
+
+ it 'raises CPF generator ValidationError' do
+ expect { described_class.new(cpf: { generator: { prefix: '000000000' } }) }
+ .to raise_error(CpfGen::ValidationError)
+ end
+
+ it 'raises CNPJ formatter OutOfRangeError' do
+ expect { described_class.new(cnpj: { formatter: { hidden_start: -1 } }) }
+ .to raise_error(CnpjFmt::OutOfRangeError)
+ end
+
+ it 'raises CNPJ formatter ValidationError' do
+ expect { described_class.new(cnpj: { formatter: { dash_key: "\u00e5" } }) }
+ .to raise_error(CnpjFmt::ValidationError)
+ end
+
+ it 'raises CNPJ generator ValidationError for prefix' do
+ expect { described_class.new(cnpj: { generator: { prefix: '00000000' } }) }
+ .to raise_error(CnpjGen::ValidationError)
+ end
+
+ it 'raises CNPJ generator ValidationError for type' do
+ expect { described_class.new(cnpj: { generator: { type: 'invalid' } }) }
+ .to raise_error(CnpjGen::ValidationError)
+ end
+
+ it 'raises CNPJ generator TypeMismatchError' do
+ expect { described_class.new(cnpj: { generator: { prefix: 123 } }) }
+ .to raise_error(CnpjGen::TypeMismatchError)
+ end
+
+ it 'raises CNPJ validator ValidationError' do
+ expect { described_class.new(cnpj: { validator: { type: 'invalid' } }) }
+ .to raise_error(CnpjVal::ValidationError)
+ end
+ end
+
+ context 'when called with both a settings Hash and keywords' do
+ it 'raises InvalidArgumentCombinationError' do
+ expect do
+ described_class.new({ cpf: {} }, cnpj: CnpjUtils.new)
+ end.to raise_error(BrUtils::InvalidArgumentCombinationError)
+ end
+
+ it 'raises for false settings with keywords' do
+ expect do
+ described_class.new(false, cpf: {})
+ end.to raise_error(BrUtils::InvalidArgumentCombinationError)
+ end
+ end
+ end
+
+ describe 'resource accessors' do
+ subject(:utils) { described_class.new }
+
+ it 'returns the CpfUtils instance' do
+ expect(utils.cpf).to be_a(CpfUtils)
+ end
+
+ it 'returns the CpfFormatter instance' do
+ expect(utils.cpf.formatter).to be_a(CpfFmt::CpfFormatter)
+ end
+
+ it 'returns the CpfGenerator instance' do
+ expect(utils.cpf.generator).to be_a(CpfGen::CpfGenerator)
+ end
+
+ it 'returns the CpfValidator instance' do
+ expect(utils.cpf.validator).to be_a(CpfVal::CpfValidator)
+ end
+
+ it 'returns the CnpjUtils instance' do
+ expect(utils.cnpj).to be_a(CnpjUtils)
+ end
+
+ it 'returns the CnpjFormatter instance' do
+ expect(utils.cnpj.formatter).to be_a(CnpjFmt::CnpjFormatter)
+ end
+
+ it 'returns the CnpjGenerator instance' do
+ expect(utils.cnpj.generator).to be_a(CnpjGen::CnpjGenerator)
+ end
+
+ it 'returns the CnpjValidator instance' do
+ expect(utils.cnpj.validator).to be_a(CnpjVal::CnpjValidator)
+ end
+ end
+
+ describe '#cnpj=' do
+ subject(:utils) { described_class.new }
+
+ context 'when called with a CnpjUtils instance' do
+ it 'sets the CnpjUtils instance' do
+ cnpj_utils = CnpjUtils.new
+ utils.cnpj = cnpj_utils
+
+ expect(utils.cnpj).to equal(cnpj_utils)
+ end
+ end
+
+ context 'when called with literal Hash parameters' do
+ let(:cnpj_utils_options) do
+ {
+ formatter: {
+ hidden: true,
+ hidden_key: '#',
+ hidden_start: 8,
+ hidden_end: 11,
+ dot_key: '_',
+ slash_key: '|',
+ dash_key: ' dv '
+ },
+ generator: {
+ format: true,
+ prefix: '12345678',
+ type: 'numeric'
+ },
+ validator: {
+ type: 'numeric'
+ }
+ }
+ end
+
+ it 'sets CnpjUtils built from options' do
+ utils.cnpj = cnpj_utils_options
+
+ aggregate_failures do
+ expect(utils.cnpj).to be_a(CnpjUtils)
+ expect_options_containing(utils.cnpj.formatter.options.all, cnpj_utils_options[:formatter])
+ expect_options_containing(utils.cnpj.generator.options.all, cnpj_utils_options[:generator])
+ expect_options_containing(utils.cnpj.validator.options.all, cnpj_utils_options[:validator])
+ end
+ end
+ end
+
+ context 'when called with nil' do
+ it 'creates a new default CnpjUtils' do
+ original = utils.cnpj
+ utils.cnpj = nil
+
+ aggregate_failures do
+ expect(utils.cnpj).to be_a(CnpjUtils)
+ expect(utils.cnpj).not_to equal(original)
+ end
+ end
+ end
+ end
+
+ describe '#cpf=' do
+ subject(:utils) { described_class.new }
+
+ context 'when called with a CpfUtils instance' do
+ it 'sets the CpfUtils instance' do
+ cpf_utils = CpfUtils.new
+ utils.cpf = cpf_utils
+
+ expect(utils.cpf).to equal(cpf_utils)
+ end
+ end
+
+ context 'when called with literal Hash parameters' do
+ let(:cpf_utils_options) do
+ {
+ formatter: {
+ hidden: true,
+ hidden_key: '#',
+ hidden_start: 8,
+ hidden_end: 10,
+ dot_key: '_',
+ dash_key: ' dv '
+ },
+ generator: {
+ format: true,
+ prefix: '12345678'
+ }
+ }
+ end
+
+ it 'sets CpfUtils built from options' do
+ utils.cpf = cpf_utils_options
+
+ aggregate_failures do
+ expect(utils.cpf).to be_a(CpfUtils)
+ expect_options_containing(utils.cpf.formatter.options.all, cpf_utils_options[:formatter])
+ expect_options_containing(utils.cpf.generator.options.all, cpf_utils_options[:generator])
+ end
+ end
+ end
+
+ context 'when called with nil' do
+ it 'creates a new default CpfUtils' do
+ original = utils.cpf
+ utils.cpf = nil
+
+ aggregate_failures do
+ expect(utils.cpf).to be_a(CpfUtils)
+ expect(utils.cpf).not_to equal(original)
+ end
+ end
+ end
+ end
+
+ describe 'CPF utils through BrUtils' do
+ describe '#format' do
+ CPF_FORMAT_FACTORY_CONTEXTS.each do |context_description, factory_key|
+ context context_description do
+ let(:format_fn) { CPF_FORMAT_FACTORIES.fetch(factory_key) }
+
+ it 'matches CpfFormatter#format behavior' do
+ input = '80976511061'
+ formatter = CpfFmt::CpfFormatter.new
+
+ expect(format_fn.call(input)).to eq(formatter.format(input))
+ end
+
+ it 'forwards formatting options' do
+ expect(format_fn.call('80976511061', '_', ' dv ')).to eq('809_765_110 dv 61')
+ end
+ end
+ end
+
+ context 'when constructor formatter defaults are set' do
+ it 'applies defaults when method options are omitted' do
+ utils = described_class.new(
+ cpf: {
+ formatter: {
+ hidden: true,
+ hidden_key: '#'
+ }
+ }
+ )
+
+ expect(utils.cpf.format('80976511061')).to include('#')
+ end
+ end
+
+ it 'formats a basic CPF string' do
+ expect(described_class.new.cpf.format('12345678901')).to eq('123.456.789-01')
+ end
+ end
+
+ describe '#generate' do
+ CPF_GENERATE_FACTORY_CONTEXTS.each do |context_description, factory_key|
+ context context_description do
+ let(:generate_fn) { CPF_GENERATE_FACTORIES.fetch(factory_key) }
+
+ it 'matches CpfGenerator length behavior' do
+ generator = CpfGen::CpfGenerator.new
+ result = generate_fn.call
+
+ aggregate_failures do
+ expect(result).to match(/\A\d{11}\z/)
+ expect(result.length).to eq(generator.generate.length)
+ end
+ end
+
+ it 'forwards generation options' do
+ result = generate_fn.call(format: true, prefix: '12345')
+
+ expect(result).to match(/\A123\.45\d\.\d{3}-\d{2}\z/)
+ end
+
+ it 'returns a deterministic CPF for a full prefix' do
+ prefix = '123456789'
+ results = Array.new(20) { generate_fn.call(prefix: prefix) }
+
+ expect(results.uniq.size).to eq(1)
+ end
+ end
+ end
+
+ it 'generates an 11-digit CPF' do
+ expect(described_class.new.cpf.generate.length).to eq(11)
+ end
+ end
+
+ describe '#is_valid' do
+ it 'returns true for a valid CPF' do
+ expect(described_class.new.cpf.is_valid('52998224725')).to be(true)
+ end
+
+ it 'returns false for an invalid CPF' do
+ expect(described_class.new.cpf.is_valid('12345678901')).to be(false)
+ end
+ end
+ end
+
+ describe 'CNPJ utils through BrUtils' do
+ describe '#format' do
+ CNPJ_FORMAT_FACTORY_CONTEXTS.each do |context_description, factory_key|
+ context context_description do
+ let(:format_fn) { CNPJ_FORMAT_FACTORIES.fetch(factory_key) }
+
+ it 'matches CnpjFormatter#format behavior' do
+ input = '91415732000793'
+ formatter = CnpjFmt::CnpjFormatter.new
+
+ expect(format_fn.call(input)).to eq(formatter.format(input))
+ end
+
+ it 'forwards formatting options' do
+ expect(format_fn.call('01ABC234000X56', '|')).to eq('01.ABC.234|000X-56')
+ end
+ end
+ end
+
+ context 'when constructor formatter defaults are set' do
+ it 'applies defaults when method options are omitted' do
+ utils = described_class.new(
+ cnpj: {
+ formatter: {
+ hidden: true,
+ hidden_key: '#'
+ }
+ }
+ )
+
+ expect(utils.cnpj.format('12ABC34500DE99')).to include('#')
+ end
+ end
+
+ it 'formats a basic CNPJ string' do
+ expect(described_class.new.cnpj.format('12345678000195')).to eq('12.345.678/0001-95')
+ end
+ end
+
+ describe '#generate' do
+ CNPJ_GENERATE_FACTORY_CONTEXTS.each do |context_description, factory_key|
+ context context_description do
+ let(:generate_fn) { CNPJ_GENERATE_FACTORIES.fetch(factory_key) }
+
+ it 'matches CnpjGenerator length behavior' do
+ generator = CnpjGen::CnpjGenerator.new
+ result = generate_fn.call
+
+ aggregate_failures do
+ expect(result).to match(/\A[0-9A-Z]{14}\z/)
+ expect(result.length).to eq(generator.generate.length)
+ end
+ end
+
+ it 'forwards generation options' do
+ result = generate_fn.call(format: true, prefix: '12345', type: 'numeric')
+
+ expect(result).to match(%r{\A12\.345\.\d{3}/\d{4}-\d{2}\z})
+ end
+
+ it 'returns a deterministic CNPJ for a full prefix' do
+ prefix = '123456780009'
+ results = Array.new(20) { generate_fn.call(prefix: prefix) }
+
+ expect(results.uniq.size).to eq(1)
+ end
+ end
+ end
+
+ it 'generates a 14-character CNPJ' do
+ expect(described_class.new.cnpj.generate.length).to eq(14)
+ end
+ end
+
+ describe '#is_valid' do
+ CNPJ_IS_VALID_FACTORY_CONTEXTS.each do |context_description, factory_key|
+ context context_description do
+ let(:is_valid_fn) { CNPJ_IS_VALID_FACTORIES.fetch(factory_key) }
+
+ it 'matches CnpjValidator#is_valid behavior' do
+ input = '91415732000793'
+ validator = CnpjVal::CnpjValidator.new
+
+ expect(is_valid_fn.call(input)).to eq(validator.is_valid(input))
+ end
+
+ it 'forwards validation options' do
+ input = '1QB5UKALPYFP59'
+
+ aggregate_failures do
+ expect(is_valid_fn.call(input, type: 'numeric')).to be(false)
+ expect(is_valid_fn.call(input, type: 'alphanumeric')).to be(true)
+ end
+ end
+
+ it 'validates formatted and unformatted strings' do
+ aggregate_failures do
+ expect(is_valid_fn.call('1QB5UKALPYFP59')).to be(true)
+ expect(is_valid_fn.call('1QB5.UKAL.PYF/P59')).to be(true)
+ expect(is_valid_fn.call('AB123CDE0001555')).to be(false)
+ end
+ end
+ end
+ end
+
+ it 'returns true for a valid numeric CNPJ' do
+ expect(described_class.new.cnpj.is_valid('11222333000181')).to be(true)
+ end
+
+ it 'returns false for an invalid CNPJ' do
+ expect(described_class.new.cnpj.is_valid('11111111111111')).to be(false)
+ end
+ end
+ end
+
+ describe 'package smoke' do
+ it 'exposes BrUtils as a class' do
+ aggregate_failures do
+ expect(described_class).to be_a(Class)
+ expect(described_class.new).to be_a(described_class)
+ end
+ end
+
+ it 'exposes a VERSION string' do
+ expect(described_class::VERSION).to be_a(String).and match(/\A\d+\.\d+\.\d+\z/)
+ end
+ end
+end