CurpMX 0.3.0 → 1.0.1

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: 7819449ad32b97f499f11eb10f68db65583939637084330439c750c898e11e98
4
- data.tar.gz: 604fb5e29eeef5d75879de5ae325430012484253765239724d8e95c882a6fcf1
3
+ metadata.gz: 0b322b557bf790e15b26b0d5d950f370e1e56d458e49e74490746d2eac61433f
4
+ data.tar.gz: 7af4e03b90b08fab6cfedfa9d49484219ba48bb5eeff27f9bf587ac93b9a1d8b
5
5
  SHA512:
6
- metadata.gz: f1b4c3b213baa582c13107df963805b42b3a52a18fec062f4689e638b35f9d3160c62bd76d277da365841298b1ec4ee2de505adc118377e32781748d7d4ca46f
7
- data.tar.gz: c9f9256bcdde887f8ec5f0d328cc1e5f67876b264acaaa940d55639071b9c0a7e666a7420964b9041b08c200794cad0da552c80bea8ea53bdfca1f997c8ef604
6
+ metadata.gz: f2133105cf7c325814b4a485abe0d457e2f3f35f6e5acc5b266f7b1346d6fa6eab620f5070ed50ccba8c9ffb4b92e73ddbc41d172dc460a34826db6af0c68e56
7
+ data.tar.gz: 39322eb5225a390fcaa7e9ab2d8119d1db09d62aaaa247268466f79c9ba69d873c5359f693baa2034cab76eb17872edde252b09ed6affc97c465669ebbc517d3
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,13 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.0
3
+
4
+ Style/StringLiterals:
5
+ Enabled: true
6
+ EnforcedStyle: double_quotes
7
+
8
+ Style/StringLiteralsInInterpolation:
9
+ Enabled: true
10
+ EnforcedStyle: double_quotes
11
+
12
+ Layout/LineLength:
13
+ Max: 120
data/CHANGELOG.md ADDED
@@ -0,0 +1,49 @@
1
+ # Changelog
2
+
3
+ Formato basado en [Keep a Changelog](https://keepachangelog.com/es-ES/1.0.0/),
4
+ y este proyecto sigue [Versionado Semántico](https://semver.org/lang/es/).
5
+
6
+ ## [1.0.1] - 2026-07-04
7
+
8
+ ### Cambiado
9
+ - La gema se publica bajo el nombre existente `CurpMX` (RubyGems rechaza
10
+ `curp_mx` por ser demasiado similar). La ruta de `require` sigue siendo
11
+ `curp_mx`.
12
+
13
+ ## [1.0.0] - 2026-07-04
14
+
15
+ Primera versión funcional y estable. `CurpMx::Validator` valida la formación de
16
+ un CURP conforme al Instructivo Normativo (DOF 18-10-2021) y a las Reglas para
17
+ la ejecución de los procedimientos para la asignación de la CURP.
18
+
19
+ ### Agregado
20
+ - Validación del dígito verificador (posición 18) con el algoritmo del RENAPO,
21
+ disponible también como `CurpMx::Validator.check_digit`.
22
+ - Aceptación del marcador de sexo `X` (CURPs de género no binario).
23
+ - Aceptación de CURPs anteriores y posteriores al año 2000 (homoclave `0-9` o
24
+ `A-J` en la posición 17).
25
+ - Código de entidad `NE` (nacido en el extranjero).
26
+ - Normalización de la entrada a mayúsculas.
27
+ - Manejo seguro de entradas `nil` o que no son cadenas (regresan `format` en
28
+ lugar de lanzar una excepción).
29
+
30
+ ### Cambiado
31
+ - Catálogo de palabras altisonantes ampliado a las 82 entradas del Anexo 01.
32
+ - Catálogo de entidades corregido a los 33 códigos del Anexo 03 (se eliminó el
33
+ código inexistente `CX`; la Ciudad de México es `DF`).
34
+ - Reescritura interna del validador: búsquedas con `Set`, extracción por
35
+ posición fija y `String#match?`, sin asignar `MatchData`. La validación
36
+ completa es ~2x más rápida que la implementación previa.
37
+
38
+ ### Eliminado
39
+ - Dependencia de `parslet`. La gema ya no tiene dependencias en tiempo de
40
+ ejecución (solo la librería estándar de Ruby).
41
+
42
+ ### Corregido
43
+ - Los errores de `state` y `problematic_name` lanzaban `NoMethodError` porque
44
+ el arreglo de errores nunca se inicializaba.
45
+ - El formato rechazaba todo CURP emitido a partir del año 2000 (posición 17 con
46
+ letra).
47
+
48
+ [1.0.1]: https://github.com/hslzr/curp_mx/releases/tag/v1.0.1
49
+ [1.0.0]: https://github.com/hslzr/curp_mx/releases/tag/v1.0.0
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 TODO: Write your name
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,178 @@
1
+ # CurpMx
2
+
3
+ Librería para validar CURPs. Nada complicado: esencialmente un regex más un
4
+ par de comprobaciones que regresan una lista de errores, de haber alguno.
5
+
6
+ Sin dependencias en tiempo de ejecución (solo usa la librería estándar de Ruby).
7
+
8
+ ## Instalación
9
+
10
+ Agrega la gema a tu `Gemfile`:
11
+
12
+ ```ruby
13
+ gem "curp_mx"
14
+ ```
15
+
16
+ Y luego:
17
+
18
+ ```bash
19
+ bundle install
20
+ ```
21
+
22
+ O instálala directamente:
23
+
24
+ ```bash
25
+ gem install curp_mx
26
+ ```
27
+
28
+ ## Uso
29
+
30
+ ### Validación rápida
31
+
32
+ Cuando solo te interesa saber si el CURP es válido o no:
33
+
34
+ ```ruby
35
+ CurpMx::Validator.valid?("TOGG641009HJCRML99")
36
+ #=> true | false
37
+ ```
38
+
39
+ ### Validación a detalle
40
+
41
+ Cuando necesitas saber *por qué* un CURP es inválido:
42
+
43
+ ```ruby
44
+ validator = CurpMx::Validator.new("TOGG641009HZZRML99")
45
+ # El método #validate se llama automáticamente al inicializar.
46
+
47
+ validator.valid?
48
+ #=> false
49
+
50
+ validator.errors
51
+ #=> { :state => ["Invalid state: 'ZZ'"] }
52
+ ```
53
+
54
+ Si el formato no coincide con el de un CURP, la validación se detiene ahí y
55
+ solo se reporta el error de `format`:
56
+
57
+ ```ruby
58
+ CurpMx::Validator.new("no-es-un-curp").errors
59
+ #=> { :format => ["Invalid format"] }
60
+ ```
61
+
62
+ Un CURP válido regresa un hash de errores vacío:
63
+
64
+ ```ruby
65
+ validator = CurpMx::Validator.new("TOGG641009HJCRML99")
66
+ validator.valid? #=> true
67
+ validator.errors #=> {}
68
+ ```
69
+
70
+ ## Validaciones
71
+
72
+ `errors` es un `Hash` donde cada llave es el campo con problema y su valor es un
73
+ arreglo de mensajes.
74
+
75
+ | Llave | Significado |
76
+ |:--- |:---|
77
+ | `format` | El formato no coincide con el de un CURP |
78
+ | `state` | El estado no coincide con las abreviaciones del RENAPO |
79
+ | `problematic_name` | Las iniciales forman una palabra altisonante (ej. `CACA`) |
80
+ | `birth_day` | Día de nacimiento `<= 0` o `> 31` |
81
+ | `birth_month` | Mes de nacimiento `<= 0` o `> 12` |
82
+ | `birth_date` | Fecha de nacimiento inexistente (ej. `30/02/1989`) |
83
+ | `check_digit` | El dígito verificador (posición 18) no coincide con el calculado |
84
+
85
+ ## Notas
86
+
87
+ - Se aceptan los marcadores de sexo `H`, `M` y `X`.
88
+ - Se aceptan CURPs anteriores y posteriores al año 2000 (la homoclave puede ser
89
+ dígito o letra).
90
+ - Se valida el dígito verificador (posición 18) con el algoritmo del RENAPO.
91
+ También se puede calcular por separado a partir de los primeros 17 caracteres:
92
+
93
+ ```ruby
94
+ CurpMx::Validator.check_digit("BEBE900101HDFXXX0") #=> 7
95
+ ```
96
+
97
+ ## Dígito verificador
98
+
99
+ ### Cómo se dedujo
100
+
101
+ El algoritmo del dígito verificador **no aparece publicado** en el Instructivo
102
+ Normativo: éste solo describe la posición 18 como *"un carácter asignado […] a
103
+ través de la aplicación de un algoritmo que permite calcular y verificar la
104
+ correcta conformación de la clave"*, sin dar la fórmula ni la tabla de valores.
105
+
106
+ Por eso el algoritmo de esta gema es, en parte, **ingeniería inversa**. Se
107
+ partió del algoritmo estándar del RENAPO que circula públicamente y se confirmó
108
+ de forma empírica contra CURPs reales, válidas y conocidas: para cada una se
109
+ calculó el dígito a partir de sus primeros 17 caracteres y se comparó con el
110
+ carácter 18 real. Varias CURPs independientes coinciden, así que la confianza es
111
+ alta, pero conviene tenerlo presente: **es la única regla de la gema que no está
112
+ respaldada por una fuente oficial directa**, sino por verificación empírica.
113
+
114
+ Sugiero ampliamente hacer validaciones con CURPs propias para corroborar que, en
115
+ CURPs ya existentes y en circulación, este algoritmo siga siendo válido. Si tu
116
+ CURP es detectada como no válida puedes crear un issue en este repo para refinar
117
+ el cálculo.
118
+
119
+ ### Cómo funciona
120
+
121
+ Dados los primeros 17 caracteres del CURP:
122
+
123
+ 1. **Valor de cada carácter.** Los dígitos `0`–`9` valen `0`–`9`. Las letras
124
+ `A`–`N` valen `10`–`23`, y las letras `O`–`Z` valen `25`–`36`.
125
+
126
+ 2. **Suma ponderada.** Cada valor se multiplica por un peso descendente según su
127
+ posición: el primer carácter por `18`, el segundo por `17`, … y el carácter
128
+ 17 por `2`. Se suman todos los productos.
129
+
130
+ 3. **Complemento a 10.** El dígito verificador es `(10 - (suma mod 10)) mod 10`,
131
+ siempre un número del `0` al `9`.
132
+
133
+ Ejemplo con `BEBE900101HDFXXX0` (17 caracteres):
134
+
135
+ ```
136
+ B E B E 9 0 0 1 0 1 H D F X X X 0
137
+ 11 14 11 14 9 0 0 1 0 1 17 13 15 34 34 34 0 ← valor
138
+ ×18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 ← peso
139
+
140
+ suma = 1693 ; 1693 mod 10 = 3 ; (10 - 3) mod 10 = 7 → dígito = 7
141
+ ```
142
+
143
+ Por eso `BEBE900101HDFXXX07` es válido.
144
+
145
+ Internamente el cálculo se hace sobre los bytes ASCII (los tres tramos de
146
+ valores son contiguos), sin construir tablas ni objetos, para que sea rápido.
147
+
148
+ ## Desarrollo
149
+
150
+ ```bash
151
+ bin/setup # instala dependencias
152
+ bundle exec rspec # corre las pruebas
153
+ ```
154
+
155
+ ## Normativa
156
+ La gema en su estado actual valida la formación del CURP de acuerdo al
157
+ **Instructivo Normativo Para La Asignación De La Clave Única De Registro De
158
+ Población**, publicado en el Diario Oficial de la Federación el día 18 de
159
+ Octubre de 2021. El documento en formato PDF puede ser leído [en este
160
+ enlace](https://www.gob.mx/cms/uploads/attachment/file/337251/Instructivo_Normativo_para_la_Asignacion_de_la_CURP.pdf)
161
+
162
+ Sin embargo, cabe aclarar que **no soy abogado**, así que mis fuentes podrían no
163
+ ser las más precisas. Durante mi investigación, encontré además las [REGLAS PARA
164
+ LA EJECUCIÓN DE LOS PROCEDIMIENTOS PARA LA ASIGNACIÓN DE LA CLAVE ÚNICA DE
165
+ POBLACIÓN](https://www.gob.mx/cms/uploads/attachment/file/960109/Reglas_para_la_Ejecucion_de_los_Procedimientos_para_la_Asignacion_de_la_CURP.pdf),
166
+ que si bien no contradice el documento anterior, ésta tiene ejemplos más
167
+ precisos y un par de anexos para la validación de los datos – la lista de
168
+ palabras altisonantes en la página 86, por ejemplo.
169
+
170
+ La gema actual permite el uso de la letra `X` en el
171
+ género del individuo a pesar de no estar dentro de la normativa; esto debido a
172
+ un antecedente de [una CURP emitida en
173
+ 2023](https://quinto-poder.mx/orgullomx/2023/2/23/expiden-la-primera-curp-de-genero-no-binario-18792.html),
174
+ lo que implica que más CURPs con formato similar podrían existir allá afuera.
175
+
176
+ ## Licencia
177
+
178
+ Disponible como software libre bajo los términos de la [licencia MIT](LICENSE.txt).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
data/curp_mx.gemspec ADDED
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/curp_mx/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "CurpMX"
7
+ spec.version = CurpMx::VERSION
8
+ spec.authors = ["Salazar"]
9
+
10
+ spec.summary = "Parse and validate CURP (Clave Única de Registro de Población) from Mexico."
11
+ spec.description = "Parse and validate CURP (Clave Única de Registro de Población) from Mexico."
12
+ spec.homepage = "https://github.com/hslzr/curp_mx"
13
+ spec.required_ruby_version = ">= 3.0.0"
14
+
15
+ spec.metadata["homepage_uri"] = spec.homepage
16
+ spec.metadata["source_code_uri"] = spec.homepage
17
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md"
18
+
19
+ # Specify which files should be added to the gem when it is released.
20
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
+ spec.files = Dir.chdir(__dir__) do
22
+ `git ls-files -z`.split("\x0").reject do |f|
23
+ (File.expand_path(f) == __FILE__) ||
24
+ f.start_with?(*%w[bin/ test/ spec/ features/ .git appveyor Gemfile])
25
+ end
26
+ end
27
+ spec.bindir = "exe"
28
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
29
+ spec.require_paths = ["lib"]
30
+
31
+ # No runtime dependencies: validation is a regex + stdlib Date.
32
+
33
+ # For more information and examples about making a new gem, check out our
34
+ # guide at: https://bundler.io/guides/creating_gem.html
35
+ end
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+ require 'set'
5
+
6
+ module CurpMx
7
+ # Validates a CURP's format and a few data points in it.
8
+ #
9
+ # Hot-path notes: the format check uses String#match? (no MatchData
10
+ # allocation) and fields are read by fixed offset. Lookups go through
11
+ # frozen Sets, not Array scans. See spec + benchmarks.
12
+ class Validator
13
+ attr_reader :errors, :raw_input
14
+
15
+ # Format only — no captures. Fields are sliced by offset afterwards.
16
+ # 0-3 name initials 4-5 year 6-7 month 8-9 day
17
+ # 10 sex 11-12 state 13-15 consonants
18
+ # 16 homoclave 17 check digit
19
+ #
20
+ # Position 16 (homoclave/century): 0-9 for births before 2000, A-J for
21
+ # 2000 onward, per the Instructivo Normativo (DOF 18-10-2021).
22
+ # Sex accepts X (non-binary CURPs issued since 2023) beyond the H/M
23
+ # in that same text.
24
+ FORMAT = /\A[A-Z]{4}\d{2}[0-1]\d[0-3]\d[HMX][A-Z]{2}[^AEIOU]{3}[0-9A-J]\d\z/.freeze
25
+
26
+ # Entity codes for positions 12-13, from Anexo 03 "Catálogo de
27
+ # Entidades Federativas para la conformación de la CURP" of the
28
+ # Instructivo Normativo (DOF 18-10-2021). 32 entities (Mexico City is
29
+ # DF; there is no CX) plus NE for people born abroad.
30
+ STATES_RENAPO = %w[AS BC BS CC CL CM CS CH DF DG GT GR HG JC MC MN MS
31
+ NT NL OC PL QT QR SP SL SR TC TS TL VZ YN ZS NE].freeze
32
+
33
+ # Problematic name initials (RENAPO substitutes the 2nd letter with X
34
+ # when the first four letters spell one of these). Full catalog of 82
35
+ # words from Anexo 01 "Catálogo de palabras altisonantes" of the
36
+ # Instructivo Normativo (DOF 18-10-2021).
37
+ NAME_ISSUES = %w[BACA BAKA BUEI BUEY CACA CACO CAGA CAGO CAKA CAKO
38
+ COGE COGI COJA COJE COJI COJO COLA CULO FALO FETO
39
+ GETA GUEI GUEY JETA JOTO KACA KACO KAGA KAGO KAKA
40
+ KAKO KOGE KOGI KOJA KOJE KOJI KOJO KOLA KULO LILO
41
+ LOCA LOCO LOKA LOKO MAME MAMO MEAR MEAS MEON MIAR
42
+ MION MOCO MOKO MULA MULO NACA NACO ORIN PEDA PEDO
43
+ PENE PIPI PITO POPO PUTA PUTO QULO RATA ROBA ROBE
44
+ ROBO RUIN SENO TETA VACA VAGA VAGO VAKA VUEI VUEY
45
+ WUEI WUEY].freeze
46
+
47
+ # O(1) lookup copies of the constants above (the public constants stay
48
+ # as readable frozen Arrays).
49
+ STATES = STATES_RENAPO.to_set.freeze
50
+ WORDS = NAME_ISSUES.to_set.freeze
51
+
52
+ # Computes the RENAPO check digit (0-9) from the first 17 characters.
53
+ #
54
+ # Works on raw bytes: RENAPO's value table is contiguous in ASCII —
55
+ # '0'-'9' = 48-57 (=> 0-9), 'A'-'N' = 65-78 (=> 10-23), 'O'-'Z' =
56
+ # 79-90 (=> 25-36). The unused value 24 is Ñ's slot in the table;
57
+ # Ñ never appears in a CURP (RENAPO substitutes it), so we only need
58
+ # its offset (the -54 shift from 'O' on), not the character itself.
59
+ # Returns nil for input shorter than 17 bytes.
60
+ def self.check_digit(str)
61
+ return nil if str.bytesize < 17
62
+
63
+ sum = 0
64
+ 17.times do |i|
65
+ b = str.getbyte(i)
66
+ value = b < 58 ? b - 48 : (b < 79 ? b - 55 : b - 54)
67
+ sum += value * (18 - i)
68
+ end
69
+ (10 - sum % 10) % 10
70
+ end
71
+
72
+ def self.valid?(curp)
73
+ new(curp).valid?
74
+ end
75
+
76
+ def initialize(curp)
77
+ @raw_input = curp.is_a?(String) ? curp.upcase : curp
78
+ @errors = {}
79
+
80
+ validate
81
+ end
82
+
83
+ def valid?
84
+ @errors.empty?
85
+ end
86
+
87
+ def validate
88
+ unless @raw_input.is_a?(String) && FORMAT.match?(@raw_input)
89
+ add_error(:format, 'Invalid format')
90
+ return false
91
+ end
92
+
93
+ validate_state
94
+ validate_name_initials
95
+ validate_birth_date
96
+ validate_date_exists
97
+ validate_check_digit
98
+ end
99
+
100
+ private
101
+
102
+ def add_error(key, message)
103
+ (@errors[key] ||= []) << message
104
+ end
105
+
106
+ def validate_state
107
+ state = @raw_input[11, 2]
108
+ return if STATES.include?(state)
109
+
110
+ add_error(:state, "Invalid state: '#{state}'")
111
+ end
112
+
113
+ def validate_name_initials
114
+ initials = @raw_input[0, 4]
115
+ return unless WORDS.include?(initials)
116
+
117
+ add_error(:problematic_name, "Problematic name initials: '#{initials}'")
118
+ end
119
+
120
+ def validate_birth_date
121
+ day = @raw_input[8, 2].to_i
122
+ add_error(:birth_day, "Invalid birth day: '#{@raw_input[8, 2]}'") if day <= 0 || day > 31
123
+
124
+ month = @raw_input[6, 2].to_i
125
+ add_error(:birth_month, "Invalid birth month: '#{@raw_input[6, 2]}'") if month <= 0 || month > 12
126
+ end
127
+
128
+ def validate_date_exists
129
+ return if Date.valid_date?(birth_year, @raw_input[6, 2].to_i, @raw_input[8, 2].to_i)
130
+
131
+ add_error(:birth_date,
132
+ "Invalid birth date (YYYY-mm-dd): #{birth_year}-#{@raw_input[6, 2]}-#{@raw_input[8, 2]}")
133
+ end
134
+
135
+ def validate_check_digit
136
+ expected = self.class.check_digit(@raw_input)
137
+ return if expected && @raw_input[17].to_i == expected
138
+
139
+ add_error(:check_digit,
140
+ "Invalid check digit: expected '#{expected}', got '#{@raw_input[17]}'")
141
+ end
142
+
143
+ # Full 4-digit year, using the homoclave to pick the century: a digit
144
+ # at position 17 means <2000, a letter means >=2000. Keeps leap-year
145
+ # checks (e.g. Feb 29) correct. Letters sort after '9' in ASCII.
146
+ def birth_year
147
+ century = @raw_input[16] >= 'A' ? 2000 : 1900
148
+ century + @raw_input[4, 2].to_i
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CurpMx
4
+ VERSION = "1.0.1"
5
+ end
data/lib/curp_mx.rb CHANGED
@@ -1,124 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'date'
3
+ require_relative "curp_mx/version"
4
+ require_relative "curp_mx/validator"
4
5
 
5
6
  module CurpMx
6
- VERSION = '0.1.0'
7
-
8
- # Used to validate a CURPs format and a few data points in it
9
- class Validator
10
- attr_reader :errors, :raw_input
11
-
12
- # Basic CURP regex structure
13
- REGEX = /\A(?<father_initial>[A-Z]{2})
14
- (?<mother_initial>[A-Z]{1})
15
- (?<name_initial>[A-Z]{1})
16
- (?<birth_year>[0-9]{2})
17
- (?<birth_month>[0-1][0-9])
18
- (?<birth_day>[0-3][0-9])
19
- (?<sex>[HM])
20
- (?<state>[A-Z]{2})
21
- (?<father_consonant>[^AEIOU])
22
- (?<mother_consonant>[^AEIOU])
23
- (?<name_consonant>[^AEIOU])
24
- (?<key>[0-9]{2})\z/x.freeze
25
-
26
- # States' initials as listed in
27
- # Registro Nacional de Población (RENAPO)
28
- STATES_RENAPO = %w[AS BC BS CC CS CH CL CM DF CX DG GT GR HG JC MC MN MS
29
- NT NL OC PL QT QR SP SL SR TC TS TL VZ YN ZS].freeze
30
-
31
- # Problematic name initials
32
- NAME_ISSUES = %w[BACA LOCO BUEI BUEY MAME CACA MAMO
33
- CAGA MEAS CAGO MEON CAKA MIAR CAKO MION COGE
34
- MOCO COGI MOKO COJA MULA COJE MULO COJI NACA
35
- COJO NACO COLA PEDA CULO PEDO FALO PENE FETO
36
- PIPI GETA PITO GUEI POPO GUEY PUTA JETA PUTO
37
- JOTO QULO KACA RATA KACO ROBA KAGA ROBE KAGO
38
- ROBO KAKA RUIN KAKO SENO KOGE TETA KOGI VACA
39
- KOJA VAGA KOJE VAGO KOJI VAKA KOJO VUEI KOLA
40
- VUEY KULO WUEI LILO WUEY LOCA CACO MEAR].freeze
41
-
42
- def self.valid?(curp)
43
- new(curp).valid?
44
- end
45
-
46
- def initialize(curp)
47
- @raw_input = curp
48
- @errors = {}
49
-
50
- validate
51
- end
52
-
53
- def valid?
54
- @errors.empty?
55
- end
56
-
57
- def validate
58
- @md = REGEX.match(@raw_input)
59
-
60
- if @md.nil?
61
- @errors[:format] ||= []
62
- @errors[:format] << 'Invalid format'
63
- return false
64
- end
65
-
66
- validate_state
67
- validate_name_initials
68
- validate_birth_date
69
- validate_date_exists
70
- end
71
-
72
- private
73
-
74
- def validate_state
75
- return if STATES_RENAPO.include? @md[:state]
76
-
77
- @errors[:state] << "Invalid state: '#{@md[:state]}'"
78
- end
79
-
80
- def validate_name_initials
81
- return unless NAME_ISSUES.include?(name_initials)
82
-
83
- @errors[:problematic_name] << "Problematic name initials: '#{name_initials}'"
84
- end
85
-
86
- def validate_birth_date
87
- validate_birth_day
88
- validate_birth_month
89
- end
90
-
91
- def validate_birth_day
92
- birth_day = @md[:birth_day].to_i
93
- return unless birth_day <= 0 || birth_day > 31
94
-
95
- @errors[:birth_day] ||= []
96
- @errors[:birth_day] << "Invalid birth day: '#{@md[:birth_day]}'"
97
- end
98
-
99
- def validate_birth_month
100
- birth_month = @md[:birth_month].to_i
101
- return unless birth_month <= 0 || birth_month > 12
102
-
103
- @errors[:birth_month] ||= []
104
- @errors[:birth_month] << "Invalid birth month: '#{@md[:birth_month]}'"
105
- end
106
-
107
- def validate_date_exists
108
- date_str = "#{@md[:birth_year]}-#{@md[:birth_month]}-#{@md[:birth_day]}"
109
- return if valid_date?(date_str)
110
-
111
- @errors[:birth_date] ||= []
112
- @errors[:birth_date] << "Invalid birth date (YYYY-mm-dd): #{date_str}"
113
- end
114
-
115
- def name_initials
116
- [@md[:father_initial], @md[:mother_initial], @md[:name_initial]].join
117
- end
118
-
119
- def valid_date?(date_str)
120
- # Inner works: Date.valid_date? 2020, 7, 21
121
- Date.valid_date?(*date_str.split('-').map(&:to_i))
122
- end
123
- end
7
+ class Error < StandardError; end
124
8
  end
data/sig/curp_mx.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module CurpMx
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata CHANGED
@@ -1,28 +1,36 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: CurpMX
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Salazar
8
- autorequire:
9
- bindir: bin
8
+ bindir: exe
10
9
  cert_chain: []
11
- date: 2022-10-18 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies: []
13
- description: A simple validator for CURPs. Fast, easy, descriptive.
14
- email:
15
- - contacto@capisalazar.com
12
+ description: Parse and validate CURP (Clave Única de Registro de Población) from Mexico.
16
13
  executables: []
17
14
  extensions: []
18
15
  extra_rdoc_files: []
19
16
  files:
17
+ - ".rspec"
18
+ - ".rubocop.yml"
19
+ - CHANGELOG.md
20
+ - LICENSE.txt
21
+ - README.md
22
+ - Rakefile
23
+ - curp_mx.gemspec
20
24
  - lib/curp_mx.rb
21
- homepage:
22
- licenses:
23
- - MIT
24
- metadata: {}
25
- post_install_message:
25
+ - lib/curp_mx/validator.rb
26
+ - lib/curp_mx/version.rb
27
+ - sig/curp_mx.rbs
28
+ homepage: https://github.com/hslzr/curp_mx
29
+ licenses: []
30
+ metadata:
31
+ homepage_uri: https://github.com/hslzr/curp_mx
32
+ source_code_uri: https://github.com/hslzr/curp_mx
33
+ changelog_uri: https://github.com/hslzr/curp_mx/blob/master/CHANGELOG.md
26
34
  rdoc_options: []
27
35
  require_paths:
28
36
  - lib
@@ -30,15 +38,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
30
38
  requirements:
31
39
  - - ">="
32
40
  - !ruby/object:Gem::Version
33
- version: '0'
41
+ version: 3.0.0
34
42
  required_rubygems_version: !ruby/object:Gem::Requirement
35
43
  requirements:
36
44
  - - ">="
37
45
  - !ruby/object:Gem::Version
38
46
  version: '0'
39
47
  requirements: []
40
- rubygems_version: 3.2.3
41
- signing_key:
48
+ rubygems_version: 4.0.3
42
49
  specification_version: 4
43
- summary: Mexican CURP validation library
50
+ summary: Parse and validate CURP (Clave Única de Registro de Población) from Mexico.
44
51
  test_files: []