cnpj-dv 0.0.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e41c3c9ce1d2a8c96fe2c35183bb28dcaf629590313a5d769b2fe18d6f3a97c3
4
- data.tar.gz: f9b596418d1be76f103142bde7675ea23ce6c2ca588d4acc176419e7e901072f
3
+ metadata.gz: 589e0576ee9e9cc2ddfeaf94c5f745792880885d7ea8dfcfb0953695b32a8981
4
+ data.tar.gz: 6662f13dfc238ea843cb79b14d1aaaf526c311e31f63dfc47937846a82ea3a2f
5
5
  SHA512:
6
- metadata.gz: a3c72b3f1757dca405986fbd44232620cdf6cd3bb200c26f3b2a7121b7b7a917aa1432afc0770bdba46a395b56cbfd712c13f6186f9329f7274729912e98b7db
7
- data.tar.gz: b495d166c64353bf05c6a4c9dcbd351646eb360021fde40f4cf85b9a70412d8b2390249106e2c80cfa15bfe70f9bf69fd691fff68e15a648b2774bc436c9f8bb
6
+ metadata.gz: a3dbe7e29ebf3299714c285c90fbf170c6fb8a41e028e9fe64216d411233505cb7bb0230369e82d1a71dd14829c1b0031a046fefd21b96f0589af82af34bba09
7
+ data.tar.gz: 3a9f8a328af8c39185dc607db7529da03596235aa194ac57ad2fcdf623b5aad7f4a54124f6926f727a79fd89e9872212b21027a8e707e48d7d7961f902c1b86f
data/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ # cnpj-dv
2
+
3
+ ## 1.0.0
4
+
5
+ ### 🚀 Stable Version Released!
6
+
7
+ Utility class to calculate check digits on CNPJ (Brazilian Business Tax ID). Main features:
8
+
9
+ - **Flexible input**: Accepts a `String` or `Array` of strings (formatted or raw).
10
+ - **Format agnostic**: Automatically strips non-alphanumeric characters and uppercases letters before processing.
11
+ - **Alphanumeric CNPJ**: Supports letters `A–Z` in the base; check digits remain numeric via modulo-11.
12
+ - **Lazy evaluation**: `CnpjDV::CnpjCheckDigits` computes `first`, `second`, `both`, and `cnpj` only on first access, then caches.
13
+ - **Eligibility gates**: Rejects wrong types, invalid length, all-zero base/branch IDs, and repeated numeric digits.
14
+ - **Error handling**: `CnpjCheckDigitsInputTypeError` (`TypeError`) plus length/invalid `StandardError` subclasses with structured attributes.
15
+ - **Minimal dependencies**: Requires `lacus-utils` for reusable utilities.
16
+
17
+ For detailed usage and API reference, see the [README](./README.md).
data/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Julio L. Muller
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,223 @@
1
+ cnpj-dv for Ruby
2
+
3
+ [Gem Version](https://rubygems.org/gems/cnpj-dv)
4
+ [Downloads Count](https://rubygems.org/gems/cnpj-dv)
5
+ [Ruby Version](https://www.ruby-lang.org/)
6
+ [Test Status](https://github.com/LacusSolutions/br-utils-ruby/actions)
7
+ [Last Update Date](https://github.com/LacusSolutions/br-utils-ruby)
8
+ [Project License](https://github.com/LacusSolutions/br-utils-ruby/blob/main/LICENSE)
9
+
10
+ > 🚀 **Full support for the [new alphanumeric CNPJ format](https://github.com/user-attachments/files/23937961/calculodvcnpjalfanaumerico.pdf).**
11
+
12
+ > 🌎 [Acessar documentação em português](https://github.com/LacusSolutions/br-utils-ruby/blob/main/packages/cnpj-dv/README.pt.md)
13
+
14
+ A Ruby utility to calculate check digits on CNPJ (Brazilian Business Tax ID).
15
+
16
+ ## Ruby Support
17
+
18
+
19
+ | Ruby 3.2 | Ruby 3.3 | Ruby 3.4 |
20
+ | --------- | --------- | --------- |
21
+ | Passing ✔ | Passing ✔ | Passing ✔ |
22
+
23
+
24
+
25
+
26
+ ## Features
27
+
28
+ - ✅ **Alphanumeric CNPJ**: Full support for the new alphanumeric CNPJ format (introduced in 2026)
29
+ - ✅ **Flexible input**: Accepts `String` or `Array` of strings
30
+ - ✅ **Format agnostic**: Strips non-alphanumeric characters from string input and uppercases letters
31
+ - ✅ **Auto-expansion**: Multi-character strings in arrays are joined and parsed like a single string
32
+ - ✅ **Input validation**: Rejects ineligible CNPJs (all-zero base ID `00000000`, all-zero branch `0000`, or 12 numeric-only repeated digits)
33
+ - ✅ **Lazy evaluation**: Check digits are calculated only when accessed (via methods)
34
+ - ✅ **Caching**: Calculated values are cached for subsequent access
35
+ - ✅ **Minimal dependencies**: Only `[lacus-utils](https://rubygems.org/gems/lacus-utils)`
36
+ - ✅ **Error handling**: Specific types for type, length, and invalid CNPJ scenarios (`TypeError` vs `StandardError` semantics)
37
+
38
+
39
+
40
+ ## Installation
41
+
42
+ Install the gem directly:
43
+
44
+ ```bash
45
+ gem install cnpj-dv
46
+ ```
47
+
48
+ Or add it to your `Gemfile` and run `bundle install`:
49
+
50
+ ```ruby
51
+ gem 'cnpj-dv'
52
+ ```
53
+
54
+
55
+
56
+ ## Require
57
+
58
+ ```ruby
59
+ require 'cnpj-dv'
60
+ ```
61
+
62
+
63
+
64
+ ## Quick Start
65
+
66
+ ```ruby
67
+ require 'cnpj-dv'
68
+
69
+ check_digits = CnpjDV::CnpjCheckDigits.new('914157320007')
70
+
71
+ check_digits.first # => '9'
72
+ check_digits.second # => '3'
73
+ check_digits.both # => '93'
74
+ check_digits.cnpj # => '91415732000793'
75
+ ```
76
+
77
+ With alphanumeric CNPJ (new format):
78
+
79
+ ```ruby
80
+ require 'cnpj-dv'
81
+
82
+ check_digits = CnpjDV::CnpjCheckDigits.new('MGKGMJ9X0001')
83
+
84
+ check_digits.first # => '6'
85
+ check_digits.second # => '8'
86
+ check_digits.both # => '68'
87
+ check_digits.cnpj # => 'MGKGMJ9X000168'
88
+ ```
89
+
90
+
91
+
92
+ ## Usage
93
+
94
+ The main resource of this package is the class `CnpjDV::CnpjCheckDigits`. Through an instance, you access CNPJ check-digit information:
95
+
96
+ - `initialize`: `CnpjDV::CnpjCheckDigits.new(cnpj_input)` — `cnpj_input` must be a `String` or an `Array` of strings. After sanitization, the value must have 12–14 alphanumeric characters (formatting stripped from strings; letters uppercased). Only the **first 12** characters are used as the base; if you pass 13 or 14 characters (e.g. a full CNPJ including prior check digits), characters 13–14 are **ignored** and the digits are recalculated. There are **no options**, keyword arguments, or configuration objects — the constructor takes only the CNPJ input.
97
+ - `first`: First check digit (13th character of the full CNPJ). Lazy, cached.
98
+ - `second`: Second check digit (14th character of the full CNPJ). Lazy, cached.
99
+ - `both`: Both check digits concatenated as a string.
100
+ - `cnpj`: The complete CNPJ as a string of 14 characters (12 base characters + 2 check digits).
101
+
102
+
103
+
104
+ ### Input formats
105
+
106
+ The `CnpjCheckDigits` class accepts multiple input formats:
107
+
108
+ **String input:** raw digits and/or letters, or formatted CNPJ (e.g. `91.415.732/0007-93`, `MG.KGM.J9X/0001-68`). Non-alphanumeric characters are removed; lowercase letters are uppercased.
109
+
110
+ **Array of strings:** each element must be a string; values are concatenated and then parsed like a single string (e.g. `['9','1','4',…]`, `['9141','5732','0007']`, `['MG','KGM','J9X','0001']`). Non-string elements are not allowed.
111
+
112
+ ```ruby
113
+ require 'cnpj-dv'
114
+
115
+ # String — plain, formatted, or with existing check digits (only first 12 chars used)
116
+ CnpjDV::CnpjCheckDigits.new('914157320007')
117
+ CnpjDV::CnpjCheckDigits.new('91.415.732/0007')
118
+ CnpjDV::CnpjCheckDigits.new('91415732000793')
119
+
120
+ # Array of strings — single- or multi-character elements
121
+ CnpjDV::CnpjCheckDigits.new(%w[9 1 4 1 5 7 3 2 0 0 0 7])
122
+ CnpjDV::CnpjCheckDigits.new(%w[9141 5732 0007])
123
+ CnpjDV::CnpjCheckDigits.new(%w[MG KGM J9X 0001])
124
+ ```
125
+
126
+
127
+
128
+ ### Errors & exceptions handling
129
+
130
+ This package uses **TypeError vs StandardError** semantics: *type errors* indicate incorrect API use (e.g. wrong type); *exceptions* indicate invalid or ineligible data (e.g. invalid length or business rules). You can rescue specific classes or use the base classes.
131
+
132
+ - `CnpjDV::CnpjCheckDigitsTypeError` — base class for type errors; extends Ruby’s `TypeError`
133
+ - `CnpjDV::CnpjCheckDigitsInputTypeError` — input is not `String` or `Array` of strings (or array contains a non-string element)
134
+ - `CnpjDV::CnpjCheckDigitsException` — base class for data/flow exceptions; extends `StandardError`
135
+ - `CnpjDV::CnpjCheckDigitsInputLengthException` — sanitized length is not 12–14
136
+ - `CnpjDV::CnpjCheckDigitsInputInvalidException` — base ID `00000000`, branch ID `0000`, or 12 identical numeric digits (repeated-digit pattern)
137
+
138
+ ```ruby
139
+ require 'cnpj-dv'
140
+
141
+ # Input type (e.g. integer not allowed)
142
+ begin
143
+ CnpjDV::CnpjCheckDigits.new(12_345_678_000_100)
144
+ rescue CnpjDV::CnpjCheckDigitsInputTypeError => e
145
+ puts e.message
146
+ # => CNPJ input must be of type string or string[]. Got integer number.
147
+ end
148
+
149
+ # Length (must be 12–14 alphanumeric characters after sanitization)
150
+ begin
151
+ CnpjDV::CnpjCheckDigits.new('12345678901')
152
+ rescue CnpjDV::CnpjCheckDigitsInputLengthException => e
153
+ puts e.message
154
+ # => CNPJ input "12345678901" does not contain 12 to 14 characters. Got 11.
155
+ end
156
+
157
+ # Invalid (e.g. all-zero base or branch, or repeated numeric digits)
158
+ begin
159
+ CnpjDV::CnpjCheckDigits.new('000000000001')
160
+ rescue CnpjDV::CnpjCheckDigitsInputInvalidException => e
161
+ puts e.message
162
+ # => CNPJ input "000000000001" is invalid. Base ID "00000000" is not eligible.
163
+ end
164
+
165
+ # Any data exception from the package
166
+ begin
167
+ CnpjDV::CnpjCheckDigits.new('000000000001')
168
+ rescue CnpjDV::CnpjCheckDigitsException => e
169
+ puts e.message
170
+ end
171
+ ```
172
+
173
+ Notable attributes on raised errors:
174
+
175
+ - `CnpjCheckDigitsInputTypeError`: `actual_input`, `actual_type`, `expected_type`
176
+ - `CnpjCheckDigitsInputLengthException`: `actual_input`, `evaluated_input`, `min_expected_length`, `max_expected_length`
177
+ - `CnpjCheckDigitsInputInvalidException`: `actual_input`, `reason`
178
+
179
+
180
+
181
+ ### Other available resources
182
+
183
+ After `require 'cnpj-dv'`:
184
+
185
+ - `CnpjDV::CNPJ_MIN_LENGTH`: `12`
186
+ - `CnpjDV::CNPJ_MAX_LENGTH`: `14`
187
+ - **Exceptions**: see above
188
+
189
+
190
+
191
+ ## Calculation algorithm
192
+
193
+ The package computes check digits with the official Brazilian modulo-11 rules extended to alphanumeric characters:
194
+
195
+ 1. **Character value:** each character contributes `ord(character) − 48` (so `0`–`9` stay 0–9; letters use their ASCII offset from `0`).
196
+ 2. **Weights:** from **right to left**, multiply by weights that cycle **2, 3, 4, 5, 6, 7, 8, 9**, then repeat from 2.
197
+ 3. **First check digit (13th position):** apply steps 1–2 to the first **12** base characters; let `r = sum % 11`. The digit is `0` if `r < 2`, otherwise `11 − r`.
198
+ 4. **Second check digit (14th position):** apply steps 1–2 to the first 12 characters **plus** the first check digit; same formula for `r`.
199
+
200
+
201
+
202
+ ## Contribution & Support
203
+
204
+ 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:
205
+
206
+ - ⭐ Starring the repository
207
+ - 🤝 Contributing to the codebase
208
+ - 💡 [Suggesting new features](https://github.com/LacusSolutions/br-utils-ruby/issues)
209
+ - 🐛 [Reporting bugs](https://github.com/LacusSolutions/br-utils-ruby/issues)
210
+
211
+
212
+
213
+ ## License
214
+
215
+ This project is licensed under the MIT License — see the [LICENSE](https://github.com/LacusSolutions/br-utils-ruby/blob/main/LICENSE) file for details.
216
+
217
+ ## Changelog
218
+
219
+ See [CHANGELOG](./CHANGELOG.md) for a list of changes and version history.
220
+
221
+ ---
222
+
223
+ Made with ❤️ by [Lacus Solutions](https://github.com/LacusSolutions)
data/README.pt.md ADDED
@@ -0,0 +1,186 @@
1
+ ![cnpj-dv para Ruby](https://br-utils.vercel.app/img/cover_cnpj-dv.jpg)
2
+
3
+ > 🚀 **Suporte total ao [novo formato alfanumérico de CNPJ](https://github.com/user-attachments/files/23937961/calculodvcnpjalfanaumerico.pdf).**
4
+
5
+ > 🌎 [Access documentation in English](https://github.com/LacusSolutions/br-utils-ruby/blob/main/packages/cnpj-dv/README.md)
6
+
7
+ Utilitário em Ruby para calcular os dígitos verificadores de CNPJ (Cadastro Nacional da Pessoa Jurídica).
8
+
9
+ ## Recursos
10
+
11
+ - ✅ **CNPJ alfanumérico**: Suporte completo ao novo formato alfanumérico de CNPJ (a partir de 2026)
12
+ - ✅ **Entrada flexível**: Aceita `String` ou `Array` de strings
13
+ - ✅ **Agnóstico ao formato**: Remove caracteres não alfanuméricos da entrada em string e converte letras para maiúsculas
14
+ - ✅ **Junção em array**: Strings com vários caracteres em arrays são concatenadas e interpretadas como uma única sequência
15
+ - ✅ **Validação de entrada**: Rejeita CNPJs inelegíveis (base toda zero `00000000`, filial `0000`, ou 12 dígitos numéricos repetidos)
16
+ - ✅ **Avaliação lazy**: Dígitos verificadores são calculados apenas quando acessados (via métodos)
17
+ - ✅ **Cache**: Valores calculados são armazenados em cache para acessos subsequentes
18
+ - ✅ **Dependências mínimas**: Apenas [`lacus-utils`](https://rubygems.org/gems/lacus-utils)
19
+ - ✅ **Tratamento de erros**: Tipos específicos para tipo, tamanho e CNPJ inválido (semântica `TypeError` vs `StandardError`)
20
+
21
+ ## Instalação
22
+
23
+ Instale a gem diretamente:
24
+
25
+ ```bash
26
+ gem install cnpj-dv
27
+ ```
28
+
29
+ Ou adicione ao seu `Gemfile` e execute `bundle install`:
30
+
31
+ ```ruby
32
+ gem 'cnpj-dv'
33
+ ```
34
+
35
+ ## Require
36
+
37
+ ```ruby
38
+ require 'cnpj-dv'
39
+ ```
40
+
41
+ ## Início rápido
42
+
43
+ ```ruby
44
+ require 'cnpj-dv'
45
+
46
+ check_digits = CnpjDV::CnpjCheckDigits.new('914157320007')
47
+
48
+ check_digits.first # => '9'
49
+ check_digits.second # => '3'
50
+ check_digits.both # => '93'
51
+ check_digits.cnpj # => '91415732000793'
52
+ ```
53
+
54
+ Com CNPJ alfanumérico (novo formato):
55
+
56
+ ```ruby
57
+ require 'cnpj-dv'
58
+
59
+ check_digits = CnpjDV::CnpjCheckDigits.new('MGKGMJ9X0001')
60
+
61
+ check_digits.first # => '6'
62
+ check_digits.second # => '8'
63
+ check_digits.both # => '68'
64
+ check_digits.cnpj # => 'MGKGMJ9X000168'
65
+ ```
66
+
67
+ ## Utilização
68
+
69
+ O principal recurso deste pacote é a classe `CnpjDV::CnpjCheckDigits`. Por meio da instância, você acessa as informações dos dígitos verificadores do CNPJ:
70
+
71
+ - **`initialize`**: `CnpjDV::CnpjCheckDigits.new(cnpj_input)` — `cnpj_input` deve ser `String` ou `Array` de strings. Após a sanitização, o valor deve ter 12–14 caracteres alfanuméricos (formatação removida em strings; letras em maiúsculas). Apenas os **primeiros 12** caracteres entram como base; com 13 ou 14 caracteres (ex.: CNPJ completo com DV anteriores), os caracteres 13 e 14 são **ignorados** e os dígitos são recalculados. Não há **opções**, argumentos nomeados nem objeto de configuração — o construtor recebe apenas a entrada de CNPJ.
72
+ - **`first`**: Primeiro dígito verificador (13º caractere do CNPJ completo). Lazy, em cache.
73
+ - **`second`**: Segundo dígito verificador (14º caractere do CNPJ completo). Lazy, em cache.
74
+ - **`both`**: Ambos os dígitos verificadores concatenados em uma string.
75
+ - **`cnpj`**: O CNPJ completo como string de 14 caracteres (12 da base + 2 dígitos verificadores).
76
+
77
+ ### Formatos de entrada
78
+
79
+ A classe `CnpjCheckDigits` aceita múltiplos formatos de entrada:
80
+
81
+ **String:** dígitos e/ou letras crus, ou CNPJ formatado (ex.: `91.415.732/0007-93`, `MG.KGM.J9X/0001-68`). Caracteres não alfanuméricos são removidos; letras minúsculas viram maiúsculas.
82
+
83
+ **Array de strings:** cada elemento deve ser string; os valores são concatenados e interpretados como uma única string (ex.: `['9','1','4',…]`, `['9141','5732','0007']`, `['MG','KGM','J9X','0001']`). Elementos que não são strings não são permitidos.
84
+
85
+ ```ruby
86
+ require 'cnpj-dv'
87
+
88
+ # String — crua, formatada ou com DV existentes (apenas os 12 primeiros caracteres são usados)
89
+ CnpjDV::CnpjCheckDigits.new('914157320007')
90
+ CnpjDV::CnpjCheckDigits.new('91.415.732/0007')
91
+ CnpjDV::CnpjCheckDigits.new('91415732000793')
92
+
93
+ # Array de strings — elementos de um ou vários caracteres
94
+ CnpjDV::CnpjCheckDigits.new(%w[9 1 4 1 5 7 3 2 0 0 0 7])
95
+ CnpjDV::CnpjCheckDigits.new(%w[9141 5732 0007])
96
+ CnpjDV::CnpjCheckDigits.new(%w[MG KGM J9X 0001])
97
+ ```
98
+
99
+ ### Erros e exceções
100
+
101
+ Este pacote usa a distinção **TypeError vs StandardError**: *erros de tipo* indicam uso incorreto da API (ex.: tipo errado); *exceções* indicam dados inválidos ou inelegíveis (ex.: tamanho ou regras de negócio). Você pode resgatar classes específicas ou as classes base.
102
+
103
+ - **`CnpjDV::CnpjCheckDigitsTypeError`** — classe base para erros de tipo; estende o `TypeError` do Ruby
104
+ - **`CnpjDV::CnpjCheckDigitsInputTypeError`** — entrada não é `String` nem `Array` de strings (ou o array contém elemento que não é string)
105
+ - **`CnpjDV::CnpjCheckDigitsException`** — classe base para exceções de dados/fluxo; estende `StandardError`
106
+ - **`CnpjDV::CnpjCheckDigitsInputLengthException`** — tamanho após sanitização não é 12–14
107
+ - **`CnpjDV::CnpjCheckDigitsInputInvalidException`** — base `00000000`, filial `0000`, ou 12 dígitos numéricos idênticos (padrão de repetição)
108
+
109
+ ```ruby
110
+ require 'cnpj-dv'
111
+
112
+ # Tipo de entrada (ex.: inteiro não permitido)
113
+ begin
114
+ CnpjDV::CnpjCheckDigits.new(12_345_678_000_100)
115
+ rescue CnpjDV::CnpjCheckDigitsInputTypeError => e
116
+ puts e.message
117
+ # => CNPJ input must be of type string or string[]. Got integer number.
118
+ end
119
+
120
+ # Tamanho (deve ser 12–14 caracteres alfanuméricos após sanitização)
121
+ begin
122
+ CnpjDV::CnpjCheckDigits.new('12345678901')
123
+ rescue CnpjDV::CnpjCheckDigitsInputLengthException => e
124
+ puts e.message
125
+ # => CNPJ input "12345678901" does not contain 12 to 14 characters. Got 11.
126
+ end
127
+
128
+ # Inválido (ex.: base ou filial zeradas, ou dígitos numéricos repetidos)
129
+ begin
130
+ CnpjDV::CnpjCheckDigits.new('000000000001')
131
+ rescue CnpjDV::CnpjCheckDigitsInputInvalidException => e
132
+ puts e.message
133
+ # => CNPJ input "000000000001" is invalid. Base ID "00000000" is not eligible.
134
+ end
135
+
136
+ # Qualquer exceção de dados do pacote
137
+ begin
138
+ CnpjDV::CnpjCheckDigits.new('000000000001')
139
+ rescue CnpjDV::CnpjCheckDigitsException => e
140
+ puts e.message
141
+ end
142
+ ```
143
+
144
+ Atributos relevantes nos erros:
145
+
146
+ - `CnpjCheckDigitsInputTypeError`: `actual_input`, `actual_type`, `expected_type`
147
+ - `CnpjCheckDigitsInputLengthException`: `actual_input`, `evaluated_input`, `min_expected_length`, `max_expected_length`
148
+ - `CnpjCheckDigitsInputInvalidException`: `actual_input`, `reason`
149
+
150
+ ### Outros recursos disponíveis
151
+
152
+ Após `require 'cnpj-dv'`:
153
+
154
+ - **`CnpjDV::CNPJ_MIN_LENGTH`**: `12`
155
+ - **`CnpjDV::CNPJ_MAX_LENGTH`**: `14`
156
+ - **Exceções**: veja acima
157
+
158
+ ## Algoritmo de cálculo
159
+
160
+ O pacote calcula os dígitos verificadores com as regras oficiais brasileiras de módulo 11 estendidas a caracteres alfanuméricos:
161
+
162
+ 1. **Valor do caractere:** cada caractere contribui com `ord(caractere) − 48` (assim `0`–`9` permanecem 0–9; letras usam o deslocamento ASCII em relação a `0`).
163
+ 2. **Pesos:** da **direita para a esquerda**, multiplicar pelos pesos que ciclam **2, 3, 4, 5, 6, 7, 8, 9** e voltam a 2.
164
+ 3. **Primeiro dígito verificador (13ª posição):** aplicar os itens 1–2 aos **primeiros 12** caracteres da base; seja `r = soma % 11`. O dígito é `0` se `r < 2`, senão `11 − r`.
165
+ 4. **Segundo dígito verificador (14ª posição):** aplicar os itens 1–2 aos 12 primeiros caracteres **mais** o primeiro dígito verificador; mesma fórmula para `r`.
166
+
167
+ ## Contribuição e suporte
168
+
169
+ 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:
170
+
171
+ - ⭐ Dar uma estrela no repositório
172
+ - 🤝 Contribuir com código
173
+ - 💡 [Sugerir novas funcionalidades](https://github.com/LacusSolutions/br-utils-ruby/issues)
174
+ - 🐛 [Reportar bugs](https://github.com/LacusSolutions/br-utils-ruby/issues)
175
+
176
+ ## Licença
177
+
178
+ Este projeto está sob a licença MIT — veja o arquivo [LICENSE](https://github.com/LacusSolutions/br-utils-ruby/blob/main/LICENSE).
179
+
180
+ ## Changelog
181
+
182
+ Veja o [CHANGELOG](./CHANGELOG.md) para alterações e histórico de versões.
183
+
184
+ ---
185
+
186
+ Feito com ❤️ por [Lacus Solutions](https://github.com/LacusSolutions)
@@ -0,0 +1,242 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'exceptions'
4
+
5
+ module CnpjDV
6
+ # Minimum number of characters required for the CNPJ check digits calculation.
7
+ CNPJ_MIN_LENGTH = 12
8
+
9
+ # Maximum number of characters accepted as input for the CNPJ check digits
10
+ # calculation.
11
+ CNPJ_MAX_LENGTH = 14
12
+
13
+ CNPJ_BASE_ID_LENGTH = 8
14
+ CNPJ_INVALID_BASE_ID = '0' * CNPJ_BASE_ID_LENGTH
15
+ CNPJ_BRANCH_ID_LENGTH = 4
16
+ CNPJ_INVALID_BRANCH_ID = '0' * CNPJ_BRANCH_ID_LENGTH
17
+
18
+ DELTA_FACTOR = '0'.ord
19
+ WEIGHTS = [2, 3, 4, 5, 6, 7, 8, 9].freeze
20
+ DIGIT_CHARS = '0123456789'
21
+
22
+ private_constant :CNPJ_BASE_ID_LENGTH, :CNPJ_INVALID_BASE_ID,
23
+ :CNPJ_BRANCH_ID_LENGTH, :CNPJ_INVALID_BRANCH_ID,
24
+ :DELTA_FACTOR, :WEIGHTS, :DIGIT_CHARS
25
+
26
+ # Calculates and exposes CNPJ check digits from a valid base input.
27
+ #
28
+ # Validates length, base ID, branch ID and rejects repeated numeric digits.
29
+ #
30
+ # @example Numeric base
31
+ # check_digits = CnpjDV::CnpjCheckDigits.new('914157320007')
32
+ # check_digits.first # => '9'
33
+ # check_digits.second # => '3'
34
+ # check_digits.both # => '93'
35
+ # check_digits.cnpj # => '91415732000793'
36
+ #
37
+ # @example Alphanumeric base
38
+ # check_digits = CnpjDV::CnpjCheckDigits.new('MGKGMJ9X0001')
39
+ # check_digits.first # => '6'
40
+ # check_digits.second # => '8'
41
+ # check_digits.both # => '68'
42
+ # check_digits.cnpj # => 'MGKGMJ9X000168'
43
+ class CnpjCheckDigits
44
+ # Creates a calculator for the given CNPJ base (12 to 14 characters).
45
+ #
46
+ # @param cnpj_input [String, Array<String>] alphanumeric CNPJ with or without
47
+ # formatting, or an array of strings
48
+ # @raise [CnpjCheckDigitsInputTypeError] when input is not a +String+ or
49
+ # +Array<String>+
50
+ # @raise [CnpjCheckDigitsInputLengthException] when character count is not
51
+ # between 12 and 14
52
+ # @raise [CnpjCheckDigitsInputInvalidException] when base ID is all zero
53
+ # (+00.000.000+), branch ID is all zero (+0000+) or all digits are numeric
54
+ # the same (repeated digits, e.g. +77.777.777/7777-...+)
55
+ def initialize(cnpj_input)
56
+ parsed_input = parse_input(cnpj_input)
57
+
58
+ validate_length(parsed_input, cnpj_input)
59
+ validate_base_id(parsed_input, cnpj_input)
60
+ validate_branch_id(parsed_input, cnpj_input)
61
+ validate_non_repeated_digits(parsed_input, cnpj_input)
62
+
63
+ @cnpj_chars = parsed_input[0, CNPJ_MIN_LENGTH]
64
+ @cached_first_digit = nil
65
+ @cached_second_digit = nil
66
+ end
67
+
68
+ # First check digit (13th character of the full CNPJ).
69
+ #
70
+ # @return [String] a single numeric character (+"0"+–+"9"+)
71
+ def first
72
+ @cached_first_digit = _calculate(@cnpj_chars) if @cached_first_digit.nil?
73
+
74
+ DIGIT_CHARS[@cached_first_digit]
75
+ end
76
+
77
+ # Second check digit (14th character of the full CNPJ).
78
+ #
79
+ # @return [String] a single numeric character (+"0"+–+"9"+)
80
+ def second
81
+ @cached_second_digit = _calculate([*@cnpj_chars, first]) if @cached_second_digit.nil?
82
+
83
+ DIGIT_CHARS[@cached_second_digit]
84
+ end
85
+
86
+ # Both check digits concatenated (13th and 14th characters).
87
+ #
88
+ # @return [String] two-character numeric string
89
+ def both
90
+ first + second
91
+ end
92
+
93
+ # Full 14-character CNPJ (base 12 characters concatenated with the 2 check
94
+ # digits).
95
+ #
96
+ # @return [String] 14-character CNPJ (base may contain letters + numeric DVs)
97
+ def cnpj
98
+ @cnpj_chars.join + both
99
+ end
100
+
101
+ # Protected (not private) so test spy subclasses can override and call +super+.
102
+ # Leading underscore matches the cross-language helper name (`_calculate`).
103
+ protected
104
+
105
+ # Computes a single check digit using the standard CNPJ modulo-11 algorithm.
106
+ #
107
+ # @param cnpj_sequence [Array<String>] characters used in the weighted sum
108
+ # @return [Integer] check digit in the range 0–9
109
+ def _calculate(cnpj_sequence)
110
+ length = cnpj_sequence.length
111
+ sum_result = 0
112
+
113
+ (length - 1).downto(0) do |index|
114
+ char_value = cnpj_sequence[index].ord - DELTA_FACTOR
115
+ sum_result += char_value * WEIGHTS[(length - 1 - index) % 8]
116
+ end
117
+
118
+ remainder = sum_result % 11
119
+
120
+ remainder < 2 ? 0 : 11 - remainder
121
+ end
122
+
123
+ private
124
+
125
+ # Parses a string or an array of strings into alphanumeric characters.
126
+ #
127
+ # @param cnpj_input [Object] candidate CNPJ input
128
+ # @return [Array<String>] uppercase alphanumeric characters
129
+ # @raise [CnpjCheckDigitsInputTypeError] when input is not a +String+ or
130
+ # +Array<String>+
131
+ def parse_input(cnpj_input)
132
+ return parse_string_input(cnpj_input) if cnpj_input.is_a?(String)
133
+ return parse_array_input(cnpj_input) if cnpj_input.is_a?(Array)
134
+
135
+ raise CnpjCheckDigitsInputTypeError.new(cnpj_input, 'string or string[]')
136
+ end
137
+
138
+ # Strips non-alphanumeric characters and uppercases the remainder.
139
+ #
140
+ # @param cnpj_string [String] raw or formatted CNPJ string
141
+ # @return [Array<String>] uppercase alphanumeric characters
142
+ def parse_string_input(cnpj_string)
143
+ result = []
144
+
145
+ cnpj_string.each_char do |char|
146
+ code = char.ord
147
+ if code.between?(48, 57) || code.between?(65, 90)
148
+ result << char
149
+ elsif code.between?(97, 122)
150
+ result << (code - 32).chr
151
+ end
152
+ end
153
+
154
+ result
155
+ end
156
+
157
+ # Concatenates an array of strings and normalizes the result.
158
+ #
159
+ # @param cnpj_array [Array] candidate array of string chunks
160
+ # @return [Array<String>] uppercase alphanumeric characters
161
+ # @raise [CnpjCheckDigitsInputTypeError] when input is not a +String+ or
162
+ # +Array<String>+
163
+ def parse_array_input(cnpj_array)
164
+ return [] if cnpj_array.empty?
165
+
166
+ raise CnpjCheckDigitsInputTypeError.new(cnpj_array, 'string or string[]') unless cnpj_array.all?(String)
167
+
168
+ parse_string_input(cnpj_array.join)
169
+ end
170
+
171
+ # Ensures character count is between {CNPJ_MIN_LENGTH} and
172
+ # {CNPJ_MAX_LENGTH}.
173
+ #
174
+ # @param cnpj_chars [Array<String>] normalized characters
175
+ # @param original_input [String, Array<String>] original caller input
176
+ # @raise [CnpjCheckDigitsInputLengthException] when character count is not
177
+ # between 12 and 14
178
+ def validate_length(cnpj_chars, original_input)
179
+ chars_count = cnpj_chars.length
180
+
181
+ return if chars_count.between?(CNPJ_MIN_LENGTH, CNPJ_MAX_LENGTH)
182
+
183
+ raise CnpjCheckDigitsInputLengthException.new(
184
+ original_input,
185
+ cnpj_chars.join,
186
+ CNPJ_MIN_LENGTH,
187
+ CNPJ_MAX_LENGTH
188
+ )
189
+ end
190
+
191
+ # Rejects base ID (first 8 digits) when it is all zeros.
192
+ #
193
+ # @param cnpj_chars [Array<String>] normalized characters
194
+ # @param original_input [String, Array<String>] original caller input
195
+ # @raise [CnpjCheckDigitsInputInvalidException] when base ID is all zeros
196
+ # (+00.000.000+)
197
+ def validate_base_id(cnpj_chars, original_input)
198
+ return unless cnpj_chars[0, CNPJ_BASE_ID_LENGTH].all? { |char| char == '0' }
199
+
200
+ raise CnpjCheckDigitsInputInvalidException.new(
201
+ original_input,
202
+ "Base ID \"#{CNPJ_INVALID_BASE_ID}\" is not eligible."
203
+ )
204
+ end
205
+
206
+ # Rejects branch ID (digits 9–12) when it is all zeros.
207
+ #
208
+ # @param cnpj_chars [Array<String>] normalized characters
209
+ # @param original_input [String, Array<String>] original caller input
210
+ # @raise [CnpjCheckDigitsInputInvalidException] when branch ID is all zeros
211
+ # (+0000+)
212
+ def validate_branch_id(cnpj_chars, original_input)
213
+ branch_start = CNPJ_BASE_ID_LENGTH
214
+ branch_end = branch_start + CNPJ_BRANCH_ID_LENGTH
215
+ branch_id = cnpj_chars[branch_start...branch_end]
216
+
217
+ return unless branch_id.all? { |char| char == '0' }
218
+
219
+ raise CnpjCheckDigitsInputInvalidException.new(
220
+ original_input,
221
+ "Branch ID \"#{CNPJ_INVALID_BRANCH_ID}\" is not eligible."
222
+ )
223
+ end
224
+
225
+ # Rejects inputs where all first 12 characters are the same numeric digit.
226
+ #
227
+ # @param cnpj_chars [Array<String>] normalized characters
228
+ # @param original_input [String, Array<String>] original caller input
229
+ # @raise [CnpjCheckDigitsInputInvalidException] when all digits are numeric
230
+ # the same (repeated digits, e.g. +77.777.777/7777-...+)
231
+ def validate_non_repeated_digits(cnpj_chars, original_input)
232
+ first_char = cnpj_chars[0]
233
+ return unless first_char.match?(/\A\d\z/)
234
+ return unless cnpj_chars[1, CNPJ_MIN_LENGTH - 1].all? { |char| char == first_char }
235
+
236
+ raise CnpjCheckDigitsInputInvalidException.new(
237
+ original_input,
238
+ 'Repeated digits are not considered valid.'
239
+ )
240
+ end
241
+ end
242
+ end
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'lacus-utils'
5
+
6
+ module CnpjDV
7
+ # Formats the original input for inclusion in an exception message.
8
+ module FormatActualInput
9
+ module_function
10
+
11
+ # @param actual_input [Object] the original input value
12
+ # @return [String] a quoted string, or compact JSON for arrays
13
+ def call(actual_input)
14
+ return "\"#{actual_input}\"" if actual_input.is_a?(String)
15
+
16
+ JSON.generate(actual_input)
17
+ end
18
+ end
19
+ private_constant :FormatActualInput
20
+
21
+ # Base error for all `cnpj-dv` type-related errors.
22
+ #
23
+ # This class extends the native {TypeError} and serves as the base for all
24
+ # type validation errors in {CnpjCheckDigits}.
25
+ class CnpjCheckDigitsTypeError < TypeError
26
+ # @return [Object] the offending input value
27
+ attr_reader :actual_input
28
+
29
+ # @return [String] human-readable type of {#actual_input}
30
+ attr_reader :actual_type
31
+
32
+ # @return [String] description of the expected type
33
+ attr_reader :expected_type
34
+
35
+ # @param actual_input [Object] the offending input value
36
+ # @param actual_type [String] human-readable type of +actual_input+
37
+ # @param expected_type [String] description of the expected type
38
+ # @param message [String] error message
39
+ def initialize(actual_input, actual_type, expected_type, message)
40
+ super(message)
41
+ @actual_input = actual_input
42
+ @actual_type = actual_type
43
+ @expected_type = expected_type
44
+ end
45
+ end
46
+
47
+ # Error raised when the input provided to {CnpjCheckDigits} is not of the
48
+ # expected type (+String+ or +Array<String>+). The error message includes both
49
+ # the actual type of the input and the expected type.
50
+ class CnpjCheckDigitsInputTypeError < CnpjCheckDigitsTypeError
51
+ # @param actual_input [Object] the offending input value (the whole array when
52
+ # a non-string element is found)
53
+ # @param expected_type [String] description of the expected type (e.g.
54
+ # +"string or string[]"+)
55
+ def initialize(actual_input, expected_type)
56
+ actual_type = LacusUtils.describe_type(actual_input)
57
+
58
+ super(
59
+ actual_input,
60
+ actual_type,
61
+ expected_type,
62
+ "CNPJ input must be of type #{expected_type}. Got #{actual_type}."
63
+ )
64
+ end
65
+ end
66
+
67
+ # Base exception for all `cnpj-dv` rules-related errors.
68
+ #
69
+ # This class extends the native {StandardError} and serves as the base for all
70
+ # non-type-related errors in {CnpjCheckDigits}. It is suitable for validation
71
+ # errors, range errors, and other business logic exceptions that are not
72
+ # strictly type-related.
73
+ class CnpjCheckDigitsException < StandardError
74
+ end
75
+
76
+ # Error raised when the input (after optional processing) does not have the
77
+ # required length to calculate the check digits. A valid CNPJ input must
78
+ # contain between 12 and 14 alphanumeric characters. The error message
79
+ # distinguishes between the original input and the evaluated one (which strips
80
+ # punctuation characters).
81
+ class CnpjCheckDigitsInputLengthException < CnpjCheckDigitsException
82
+ # @return [String, Array<String>] the original input
83
+ attr_reader :actual_input
84
+
85
+ # @return [String] the normalized alphanumeric string
86
+ attr_reader :evaluated_input
87
+
88
+ # @return [Integer] minimum expected length (12)
89
+ attr_reader :min_expected_length
90
+
91
+ # @return [Integer] maximum expected length (14)
92
+ attr_reader :max_expected_length
93
+
94
+ # @param actual_input [String, Array<String>] the original input
95
+ # @param evaluated_input [String] the normalized alphanumeric string
96
+ # @param min_expected_length [Integer] minimum expected length
97
+ # @param max_expected_length [Integer] maximum expected length
98
+ def initialize(actual_input, evaluated_input, min_expected_length, max_expected_length)
99
+ super(build_message(actual_input, evaluated_input, min_expected_length, max_expected_length))
100
+ @actual_input = actual_input
101
+ @evaluated_input = evaluated_input
102
+ @min_expected_length = min_expected_length
103
+ @max_expected_length = max_expected_length
104
+ end
105
+
106
+ private
107
+
108
+ def build_message(actual_input, evaluated_input, min_len, max_len)
109
+ fmt_actual = FormatActualInput.call(actual_input)
110
+ fmt_evaluated =
111
+ if actual_input == evaluated_input
112
+ evaluated_input.length.to_s
113
+ else
114
+ "#{evaluated_input.length} in \"#{evaluated_input}\""
115
+ end
116
+
117
+ "CNPJ input #{fmt_actual} does not contain #{min_len} to #{max_len} characters. " \
118
+ "Got #{fmt_evaluated}."
119
+ end
120
+ end
121
+
122
+ # Exception raised when the CNPJ input contains invalid character sequences,
123
+ # like all digits are repeated. This is a business logic exception and it is
124
+ # highly recommended that users of the library catch it and handle it
125
+ # appropriately.
126
+ class CnpjCheckDigitsInputInvalidException < CnpjCheckDigitsException
127
+ # @return [String, Array<String>] the original input
128
+ attr_reader :actual_input
129
+
130
+ # @return [String] human-readable reason why the input is invalid
131
+ attr_reader :reason
132
+
133
+ # @param actual_input [String, Array<String>] the original input
134
+ # @param reason [String] human-readable reason why the input is invalid
135
+ def initialize(actual_input, reason)
136
+ super("CNPJ input #{FormatActualInput.call(actual_input)} is invalid. #{reason}")
137
+ @actual_input = actual_input
138
+ @reason = reason
139
+ end
140
+ end
141
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module CnpjDv
4
- VERSION = '0.0.0'
3
+ module CnpjDV
4
+ VERSION = '1.0.0'
5
5
  end
data/src/cnpj-dv.rb CHANGED
@@ -1,9 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'cnpj-dv/version'
4
+ require_relative 'cnpj-dv/exceptions'
5
+ require_relative 'cnpj-dv/cnpj_check_digits'
4
6
 
5
- module CnpjDv
6
- def self.hello
7
- 'cnpj-dv'
8
- end
7
+ # Check-digit calculation for Brazilian CNPJ (numeric and alphanumeric formats).
8
+ #
9
+ # The package distinguishes between **errors** and **exceptions**:
10
+ #
11
+ # - {CnpjDV::CnpjCheckDigitsTypeError} (extends the native {TypeError})
12
+ # signals incorrect API usage (the input is of the wrong *type*).
13
+ # - {CnpjDV::CnpjCheckDigitsException} (extends the native {StandardError})
14
+ # signals invalid or ineligible data (right type, bad value).
15
+ #
16
+ # Public API:
17
+ #
18
+ # - {CnpjDV::CnpjCheckDigits}
19
+ # - {CnpjDV::CNPJ_MIN_LENGTH}, {CnpjDV::CNPJ_MAX_LENGTH}
20
+ # - Exception hierarchy under {CnpjDV::CnpjCheckDigitsTypeError} /
21
+ # {CnpjDV::CnpjCheckDigitsException}
22
+ #
23
+ # @example
24
+ # require 'cnpj-dv'
25
+ #
26
+ # check_digits = CnpjDV::CnpjCheckDigits.new('914157320007')
27
+ # check_digits.cnpj # => '91415732000793'
28
+ module CnpjDV
9
29
  end
metadata CHANGED
@@ -1,27 +1,55 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cnpj-dv
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julio L. Muller
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-03-04 00:00:00.000000000 Z
12
- dependencies: []
13
- description:
11
+ date: 2026-07-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: lacus-utils
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 1.1.0
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: 2.0.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 1.1.0
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: 2.0.0
33
+ description: Utility to calculate check digits on CNPJ (Brazilian Business Tax ID)
14
34
  email:
35
+ - juliolmuller@outlook.com
15
36
  executables: []
16
37
  extensions: []
17
38
  extra_rdoc_files: []
18
39
  files:
40
+ - CHANGELOG.md
41
+ - LICENSE
42
+ - README.md
43
+ - README.pt.md
19
44
  - src/cnpj-dv.rb
45
+ - src/cnpj-dv/cnpj_check_digits.rb
46
+ - src/cnpj-dv/exceptions.rb
20
47
  - src/cnpj-dv/version.rb
21
48
  homepage: https://github.com/LacusSolutions/br-utils-ruby
22
49
  licenses:
23
50
  - MIT
24
51
  metadata:
52
+ source_code_uri: https://github.com/LacusSolutions/br-utils-ruby
25
53
  rubygems_mfa_required: 'true'
26
54
  post_install_message:
27
55
  rdoc_options: []
@@ -31,7 +59,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
31
59
  requirements:
32
60
  - - ">="
33
61
  - !ruby/object:Gem::Version
34
- version: '3.2'
62
+ version: '3.1'
35
63
  required_rubygems_version: !ruby/object:Gem::Requirement
36
64
  requirements:
37
65
  - - ">="
@@ -41,5 +69,5 @@ requirements: []
41
69
  rubygems_version: 3.4.19
42
70
  signing_key:
43
71
  specification_version: 4
44
- summary: Check-digit calculation for CNPJ (Brazilian company ID)
72
+ summary: Check-digit calculation for CNPJ (Brazilian Business Tax ID)
45
73
  test_files: []