MX-ID 0.0.1.pre

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c97a7fd9b2ed4c6157b6fce6cddc179e066ad90f
4
+ data.tar.gz: aacdcbcff79b8083d67148574cd9c545337bfc3e
5
+ SHA512:
6
+ metadata.gz: 78cd1da4e9c78c109615a760db3e0fe8409e71feb6aed582cac69013b578dade9bf3794a0b0f176d3accd3d79842c1e8a21a23a73a034833686438cd46b25f42
7
+ data.tar.gz: 8369dccc130cd5a6063614d6a4e769fe3d5a60796b8305c7572b19f7860bc1832740e2e6e502d0ab08de3eb88d9631f5cfcc9b722d832ca4f90d622262dc3d90
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format progress
3
+ --require spec_helper
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ mx-id
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-2.2.0
data/.travis.yml ADDED
@@ -0,0 +1,18 @@
1
+ language: ruby
2
+
3
+ before_install:
4
+ - gem install bundler -v '>= 1.5.1'
5
+
6
+ bundler_args: --without ci_filter
7
+
8
+ rvm:
9
+ - 1.9.3
10
+ - 2.0.0
11
+ - 2.1
12
+ - 2.2
13
+ - ruby-head
14
+ - jruby-19mode
15
+
16
+ addons:
17
+ code_climate:
18
+ repo_token: 9fe9f9cbceefce33e5bbd46c28b97df24b01650457287a3d8a152315011ebf19
data/.yardopts ADDED
@@ -0,0 +1,9 @@
1
+ --title 'MX-ID'
2
+ --output-dir ./doc
3
+ --markup-provider redcarpet
4
+ --markup markdown
5
+ --protected --private
6
+ --readme README.md
7
+ lib/**/*.rb - LICENSE.txt REFERENCIAS.md
8
+
9
+
data/Gemfile ADDED
@@ -0,0 +1,34 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development, :ci_filter do
6
+ gem 'guard'
7
+ gem 'guard-bundler', require: false
8
+ gem 'guard-rspec', require: false
9
+ gem "pry"
10
+ gem "pry-remote"
11
+ gem "pry-byebug", platform: [:mri_20, :mri_21, :mri_22, :mri]
12
+ gem "pry-nav", platform: [:mri_19, :jruby]
13
+ gem 'terminal-notifier'
14
+ gem 'terminal-notifier-guard'
15
+ end
16
+
17
+ group :development, :test do
18
+ gem "bundler"
19
+ gem "rake"
20
+ gem "rspec", "~> 3.2"
21
+ end
22
+
23
+ group :test do
24
+ gem "coveralls", require: false
25
+ gem "codeclimate-test-reporter"
26
+ gem 'rspec-collection_matchers'
27
+ gem 'shoulda-matchers', require: false
28
+ end
29
+
30
+ group :doc do
31
+ gem 'yard'
32
+ gem 'redcarpet'
33
+ gem 'github-markup'
34
+ end
data/Guardfile ADDED
@@ -0,0 +1,26 @@
1
+ guard :rspec, cmd: "bundle exec rspec", cmd_additional_args: '-f progress', failed_mode: :focus do
2
+ require "guard/rspec/dsl"
3
+ dsl = Guard::RSpec::Dsl.new(self)
4
+
5
+ # RSpec files
6
+ rspec = dsl.rspec
7
+ watch(rspec.spec_helper) { rspec.spec_dir }
8
+ watch(rspec.spec_support) { rspec.spec_dir }
9
+ watch(rspec.spec_files)
10
+
11
+ # Ruby files
12
+ ruby = dsl.ruby
13
+ dsl.watch_spec_files_for(ruby.lib_files)
14
+ end
15
+
16
+ guard :bundler do
17
+ require 'guard/bundler'
18
+ require 'guard/bundler/verify'
19
+ helper = Guard::Bundler::Verify.new
20
+
21
+ files = ['Gemfile']
22
+ files += Dir['*.gemspec'] if files.any? { |f| helper.uses_gemspec?(f) }
23
+
24
+ # Assume files are symlinked from somewhere
25
+ files.each { |file| watch(helper.real_path(file)) }
26
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Pablo Ruiz
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/MX-ID.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'MX/ID/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "MX-ID"
8
+ spec.version = MX::ID::VERSION # `git describe --tags --abbrev=0`.strip
9
+ spec.authors = ["Pablo Ruiz"]
10
+ spec.email = ["pjruiz@maquech.com.mx"]
11
+ spec.summary = %q{Mexican government identifiers utilities / Utilerías para lidiar con identifcadores del gobierno mexicano}
12
+ spec.description = %q{Mexican government identifiers utilities / Utilerías para lidiar con identifcadores del gobierno mexicano}
13
+ spec.homepage = "https://github.com/Maquech/MX-ID"
14
+ spec.license = "MIT"
15
+ spec.post_install_message = "¡Viva México!"
16
+
17
+ spec.required_ruby_version = '>= 1.9.3'
18
+ spec.files = `git ls-files -z`.split("\x0")
19
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_dependency 'activesupport'
24
+ spec.add_dependency 'numbers_and_words'
25
+ end
data/README.md ADDED
@@ -0,0 +1,113 @@
1
+ # MX::ID
2
+
3
+ Utilites for validation of Mexican government identifiers.
4
+
5
+ ---
6
+
7
+ Utilerías para validación de identificadores del gobierno mexicano.
8
+
9
+ ## Why and what for / Por qué y para qué
10
+
11
+ This gem is conceived as an utility for proyects that involve identifiers issued by the Mexican government to its citizens and organizations.
12
+
13
+ For example, the R.F.C. (acronym in Spanish for Federal Taxpayer Registry) is an identifier issued by the Ministry of Tresasury and Public Credit for the different kind of taxpayers there is.
14
+
15
+ This project is not endorsed in any way by the Mexican government.
16
+
17
+
18
+ ---
19
+
20
+ Esta gema está pensada para usarse en proyectos que tienen que ver con las diversas formas en que el gobierno mexicano identifica a ciudadanos y organizaciones.
21
+
22
+ Por ejemplo, el Registro Federal de Contribuyentes (R.F.C.) es un identificador expedido por la Secretaría de Hacienda y Crédito Público (SHCP) a través del Servicio de Administración Tributaria (SAT) para los distintos tipos de contribuyentes que existen.
23
+
24
+ Este proyecto no está patrocinado de ninguna forma por el gobierno mexicano. Es un esfuerzo para contribuir a la sanidad mental de los programadores que tenemos que lidiar con estas cosas :)
25
+
26
+
27
+ ## Installation / Instalación
28
+
29
+ Add this line to your application's Gemfile:
30
+
31
+ ```ruby
32
+ gem 'mx-id'
33
+ ```
34
+
35
+ And then execute:
36
+
37
+ $ bundle
38
+
39
+ Or install it yourself as:
40
+
41
+ $ gem install mx-id
42
+
43
+ ---
44
+
45
+ Agrega esta línea al archivo Gemfile de tu aplicación:
46
+
47
+ ```ruby
48
+ gem 'mx-id'
49
+ ```
50
+
51
+ Luego ejecuta:
52
+
53
+ $ bundle
54
+
55
+ O instálalo tu mismo usando:
56
+
57
+ $ gem install mx-id
58
+
59
+
60
+ ## Usage / Uso
61
+
62
+ ### R.F.C. (Registro Federal de Contribuyentes)
63
+
64
+
65
+ El Registro Federal de Contribuyentes (R.F.C.) es un identificador expedido por la Secretaría de Hacienda y Crédito Público (SHCP) a través del Servicio de Administración Tributaria (SAT) para los distintos tipos de contribuyentes que existen. Este identificador se encuentra en la Cédula de Identificación Fiscal que el SAT expide al inscribirse en este registro.
66
+
67
+ El algoritmo aquí implementado para generar y validar un R.F.C. ha salido de varias fuentes en la red, pues al parecer el algoritmo para calcularlo ha cambiado con el paso del tiempo. Estas son algunas de los cambios que he notado:
68
+
69
+ 1. para personas morales registradas antes y después de 1998
70
+
71
+ Pendiente...
72
+
73
+
74
+ ## Contributing / Contribuir
75
+
76
+ 1. Fork it ( https://github.com/Maquech/MX-ID/fork )
77
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
78
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
79
+ 4. Push to the branch (`git push origin my-new-feature`)
80
+ 5. Create a new Pull Request
81
+
82
+ You can also submit technical observations on the algorithms here implemented alongside with references to official documentation that supports it.
83
+
84
+ ---
85
+
86
+ Puedes contribuir mandándonos tus observaciones sobre la implementación de los algoritmos aquí implementados o referencias a documentos oficiales que las respalde.
87
+
88
+ Ahora, diviérte con esta traducción (si sabes el término correcto en español, háznoslo saber por favor):
89
+
90
+ 1. Crea tu copia del repositorio ( https://github.com/Maquech/MX-ID/fork )
91
+ 2. Crea tu rama con la funcionalidad propuesta (`git checkout -b mi-nueva-funcionalidad-shubiduby`)
92
+ 3. Compromete tus cambios (`git commit -am 'Nueva funcionalidad shubiduby'`)
93
+ 4. Empuja la rama a tu repositorio (`git push origin mi-nueva-funcionalidad-shubiduby`)
94
+ 5. Crea una nueva petición de jalada|tirada|extracción|acarreo
95
+
96
+
97
+ ## Docs
98
+
99
+ Use `bundle exec yard server --port 8828 --reload` para generar la documentación. También revisa las [referencias](REFERENCIAS.md)
100
+
101
+
102
+ ## Code status / Estado del código
103
+
104
+ [![Build Status][Travis-CI-badge]][Travis-CI-url] [![Code Climate][Code Climate-badge]][Code Climate-url] [![Coverage Status][Coveralls-badge]][Coveralls-url]
105
+
106
+
107
+ [Travis-CI-badge]: https://travis-ci.org/Maquech/MX-ID.svg?branch=master
108
+ [Travis-CI-url]: https://travis-ci.org/Maquech/MX-ID
109
+ [Code Climate-badge]: https://codeclimate.com/github/Maquech/MX-ID/badges/gpa.svg
110
+ [Code Climate-url]: https://codeclimate.com/github/Maquech/MX-ID
111
+ [Coveralls-badge]: https://coveralls.io/repos/Maquech/MX-ID/badge.svg?branch=master
112
+ [Coveralls-url]: https://coveralls.io/r/Maquech/MX-ID?branch=master
113
+
data/REFERENCIAS.md ADDED
@@ -0,0 +1,8 @@
1
+ ## Referencias oficiales y no tan oficiales
2
+
3
+ ### Oficiales
4
+
5
+
6
+ ### No oficiales, pero confiables aparentemente
7
+
8
+ 1. [Solución Factible, especificación sobre el R.F.C](http://solucionfactible.com/sfic/capitulos/timbrado/rfc.jsp)
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ begin
2
+ require 'bundler/gem_tasks'
3
+ require 'rspec/core/rake_task'
4
+ require 'yard'
5
+ YARD::Rake::YardocTask.new
6
+
7
+ RSpec::Core::RakeTask.new(:spec, :tag) do |t, task_args|
8
+ t.rspec_opts = ["--tag #{task_args[:tag]}", "--format", "--color"]
9
+ end
10
+
11
+ task default: :spec
12
+ task test: :spec
13
+ rescue LoadError
14
+ end
15
+
data/lib/MX/ID.rb ADDED
@@ -0,0 +1,13 @@
1
+ # encoding: utf-8
2
+ #require "i18n"
3
+ require "numbers_and_words"
4
+ require "activesupport" unless defined?(ActiveSupport)
5
+ require "MX/numero_romano"
6
+ require "MX/ID/version"
7
+ require "MX/ID/auxiliar"
8
+ require "MX/ID/RFC"
9
+
10
+ if defined?(I18n) # Dependencia de numbers_and_words
11
+ I18n.enforce_available_locales = false
12
+ I18n.default_locale = :es
13
+ end
data/lib/MX/ID/RFC.rb ADDED
@@ -0,0 +1,424 @@
1
+ # encoding: utf-8
2
+ # @author Pablo Ruiz <pjruiz@maquech.com.mx>
3
+ # @org Maquech Technology Services
4
+ # @fecha 15/07/2014
5
+ #
6
+ # Herramientas para obtener y validar un Registro Federal de Contribuyentes para una persona moral o física.
7
+ # @see {http://es.wikipedia.org/wiki/Registro_Federal_de_Contribuyentes_(M%C3%A9xico)} R.F.C (Wikipedia)
8
+ #
9
+ class MX::ID::RFC
10
+ include MX::ID::Auxiliar
11
+
12
+ ### Tablas de los Anexos ###
13
+ ANEXO_I = {
14
+ " " => "00",
15
+ "0" => "00", "1" => "01", "2" => "02", "3" => "03", "4" => "04", "5" => "05", "6" => "06", "7" => "07", "8" => "08", "9" => "09",
16
+ "&" => "10",
17
+ "A" => "11", "B" => "12", "C" => "13", "D" => "14", "E" => "15", "F" => "16", "G" => "17", "H" => "18", "I" => "19", "J" => "21",
18
+ "K" => "22", "L" => "23", "M" => "24", "N" => "25", "O" => "26", "P" => "27", "Q" => "28", "R" => "29", "S" => "32", "T" => "33",
19
+ "U" => "34", "V" => "35", "W" => "36", "X" => "37", "Y" => "38", "Z" => "39", "Ñ" => "40"
20
+ }.freeze
21
+ ### Anexo II ###
22
+ ANEXO_II = {
23
+ "0" => "1", "1" => "2", "2" => "3", "3" => "4", "4" => "5", "5" => "6", "6" => "7", "7" => "8", "8" => "9",
24
+ "9" => "A", "10" => "B", "11" => "C", "12" => "D", "13" => "E", "14" => "F", "15" => "G", "16" => "H",
25
+ "17" => "I", "18" => "J", "19" => "K", "20" => "L", "21" => "M", "22" => "N", "23" => "P", "24" => "Q",
26
+ "25" => "R", "26" => "S", "27" => "T", "28" => "U", "29" => "V", "30" => "W", "31" => "X", "32" => "Y",
27
+ "33" => "Z"
28
+ }.freeze
29
+ ### Termina tablas Anexo II ###
30
+
31
+ ### Anexo III ###
32
+ ANEXO_III = {
33
+ "0" => 0, "1" => 1, "2" => 2, "3" => 3, "4" => 4, "5" => 5, "6" => 6, "7" => 7, "8" => 8, "9" => 9,
34
+ "A" => 10, "B" => 11, "C" => 12, "D" => 13, "E" => 14, "F" => 15, "G" => 16, "H" => 17, "I" => 18, "J" => 19,
35
+ "K" => 20, "L" => 21, "M" => 22, "N" => 23, "&" => 24, "O" => 25, "P" => 26, "Q" => 27, "R" => 28, "S" => 29,
36
+ "T" => 30, "U" => 31, "V" => 32, "W" => 33, "X" => 34, "Y" => 35, "Z" => 36, " " => 37, "Ñ" => 38
37
+ }.freeze
38
+ ### Termina tablas Anexo III ###
39
+
40
+ ### Anexo IV ###
41
+ ANEXO_IV = [
42
+ "BUEI", "BUEY", "CACA", "CACO", "CAGA", "CAGO", "CAKA", "CAKO", "COGE", "COJA", "COJE", "COJI", "COJO", "CULO",
43
+ "FETO", "GUEY", "JOTO", "KACA", "KACO", "KAGA", "KAGO", "KAKA", "KOGE", "KOJO", "KULO", "MAME", "MAMO", "MEAR",
44
+ "MEAS", "MEON", "MION", "MOCO", "MULA", "PEDA", "PEDO", "PENE", "PUTA", "PUTO", "QULO", "RATA", "RUIN"
45
+ ].freeze
46
+ ### Termina tablas Anexo IV ###
47
+
48
+ ### Anexo V ###
49
+ ANEXO_V_PERSONA_MORAL_COMPUESTAS = [
50
+ "A EN P", "S DE RL", "S EN C", "S EN C POR A", "S EN NC", "SA DE CV", "SA DE CV MI", "SA MI", "SOCIEDAD", "SOFOM ENR",
51
+ "SRL CV", "SRL CV MI", "SRL MI"
52
+ ].freeze
53
+
54
+ ANEXO_V_PERSONA_MORAL_SIMPLES = [
55
+ "A", "AL", "AND", "CIA", "CO", "COMPA&IA", "COMPA&ÍA", "COMPANY", "COMPAÑÍA", "COMPAÑIA", "CON", "COOP", "COOPERATIVA", "DE", "DEL", "E", "EL", "EN",
56
+ "LA", "LAS", "LOS", "MAC", "MC", "MI", "OF", "PARA", "POR", "SA", "SC", "SCL", "SCS", "SNC", "SOC", "SOCIEDAD", "SUS", "THE",
57
+ "VAN", "VON", "Y"
58
+ ].freeze
59
+
60
+ ANEXO_V_PERSONA_FISICA = [ "DE", "DEL", "LA", "LAS", "LOS", "MAC", "MC", "MI", "VAN", "VON", "Y" ].freeze
61
+ ### Termina tablas Anexo V ###
62
+
63
+ ### Anexo VI ###
64
+ ANEXO_VI_PERSONA_MORAL = {
65
+ '@' => 'ARROBA', "'" => 'APOSTROFE', '%' => 'PORCIENTO', '#' => 'NUMERO', '!' => 'ADMIRACION', '.' => 'PUNTO',
66
+ '$' => 'PESOS', '"' => 'COMILLAS', '-' => 'GUION', '/' => 'DIAGONAL', '+' => 'SUMA', '(' => 'ABRE PARENTESIS',
67
+ ')' => 'CIERRA PARENTESIS'
68
+ }.freeze
69
+
70
+ ANEXO_VI_PERSONA_FISICA = { "'" => 'APOSTROFE', '.' => 'PUNTO' }.freeze
71
+ ### Termina tablas Anexo VI ###
72
+
73
+ # Expresión regular para identificar los símbolos permitidos acorde al Anexo VI para personas Morales
74
+ #
75
+ REGEXP_SIMBOLO_PERSONA_MORAL = /#{ANEXO_VI_PERSONA_MORAL.keys.map{|k| Regexp.escape(k)}.join("|")}/.freeze
76
+
77
+ # Expresión regular para identificar los símbolos permitidos acorde al Anexo VI para personas Físicas
78
+ #
79
+ REGEXP_SIMBOLO_PERSONA_FISICA = /#{ANEXO_VI_PERSONA_FISICA.keys.map{|k| Regexp.escape(k)}.join("|")}/.freeze
80
+
81
+ # Vocales
82
+ #
83
+ VOCALES = ["A", "E", "I", "O", "U"].freeze
84
+ # Expresión regular para identificar vocales
85
+ #
86
+ REGEXP_VOCALES = /#{VOCALES.join("|")}/.freeze
87
+
88
+ # Consonantes
89
+ #
90
+ CONSONANTES = [ "B", "C", "D", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "V", "W", "X", "Y", "Z", "Ñ" ].freeze
91
+
92
+ # Expresión regular para identificar vocales
93
+ #
94
+ REGEXP_CONSONANTES = /#{CONSONANTES.join("|")}/.freeze
95
+
96
+ # Símbolos válidos para personas morales
97
+ SIMBOLOS_PERSONA_MORAL = ANEXO_VI_PERSONA_MORAL.keys.freeze
98
+
99
+ # Símbolos válidos para personas físicas
100
+ SIMBOLOS_PERSONA_FISICA = ANEXO_VI_PERSONA_FISICA.keys.freeze
101
+
102
+ # Expresión regular para validar R.F.C. de persona moral con grupos de captura con nombres
103
+ #
104
+ REGEXP_PERSONA_MORAL = /\A(?<iniciales>[A-ZÑ&]{3})(?<año>[0-9]{2})(?<mes>[0-1][0-9])(?<dia>[0-3][0-9])(?<homoclave>[A-Z0-9]{2})(?<verificador>[0-9A])\z/i.freeze
105
+
106
+ # Expresión regular para validar R.F.C. de persona física con grupos de captura con nombres
107
+ #
108
+ REGEXP_PERSONA_FISICA = /\A(?<iniciales>[A-ZÑ&]{4})(?<año>[0-9]{2})(?<mes>[0-1][0-9])(?<dia>[0-3][0-9])(?<homoclave>[A-Z0-9]{2})(?<verificador>[0-9A])\z/i.freeze
109
+
110
+ # Al parecer los RFCs de personas morales anteriores a este año tomaban en cuenta el tipo de razón social para calcular la clave diferenciadora de homonimia.
111
+ #
112
+ AÑO_CAMBIO_CALCULO_CVE_HOMONIMIA = 2008
113
+
114
+ # RFC para operaciones con el público en general
115
+ RFC_PUBLICO_GENERAL = "XAXX010101000".freeze
116
+
117
+ # RFC para operaciones con extranjeros
118
+ RFC_EXTRANJEROS = "XEXX010101000".freeze
119
+
120
+ class << self
121
+
122
+ # Valida el R.F.C. dado.
123
+ #
124
+ # @param [String] rfc Un R.F.C.
125
+ # @param [Date] fecha La fecha de nacimiento o de constitución.
126
+ # @param [String] msgs Mensajes de error. La cadena vacía por default.
127
+ # @param [Boolean] persona_fisica True para indicar que el R.F.C. corresponde a una persona física, false cuando corresponde a una persona moral. Default true.
128
+ # @return [Boolean] True si es válido el R.F.C., false de lo contrario.
129
+ #
130
+ def valido?(rfc, fecha = nil, msgs = "", persona_fisica = true)
131
+ msgs = "" if msgs.nil?
132
+ msg = []
133
+ unless rfc.blank?
134
+ return true if rfc == RFC_EXTRANJEROS or rfc == RFC_PUBLICO_GENERAL
135
+ errores = false
136
+ error_match = true
137
+ rfc.match(persona_fisica ? REGEXP_PERSONA_FISICA : REGEXP_PERSONA_MORAL) {|m|
138
+ errores = !fecha_coincide?(m[:dia], m[:mes], m[:año], fecha,
139
+ (persona_fisica ? "nacimiento" : "constitución"), msg)
140
+ if digito_verificador(rfc[0..-2]) != m[:verificador]
141
+ errores = true
142
+ msg << "El dígito verificador es incorrecto."
143
+ end
144
+ error_match = false
145
+ }
146
+ if error_match
147
+ longitud_rfc_valida = persona_fisica ? 13 : 12
148
+ if rfc.size != longitud_rfc_valida
149
+ msg << "No tiene la longitud correcta (#{longitud_rfc_valida} caracteres)."
150
+ else
151
+ msg << "No tiene el formato correcto."
152
+ end
153
+ end
154
+ msgs << msg.join(" ")
155
+ !errores && !error_match
156
+ else
157
+ msgs << "No debe estar vacío."
158
+ false
159
+ end
160
+ end
161
+
162
+ # Regresa el Registro Federal de Contribuyentes (R.F.C.) de una persona física de acuerdo a su nombre completo y fecha de nacimiento.
163
+ # Tomar en cuenta las siguientes condiciones para los parámetros:
164
+ # 1. Todos los parámetros deben escribirse en mayúsculas.
165
+ # 2. Convertir letras acentuadas a la letra sin acento.
166
+ # 3. Los símbolos permitidos son el punto "." y el apóstrofe "'".
167
+ # 4. La información debe tomarse tal cual se encuentra en el acta de nacimiento.
168
+ #
169
+ # @param [String] nombres Nombres de la persona física.
170
+ # @param [String] apellido_paterno Nombres completo, incluir artículos, preposiciones y conjunciones.
171
+ # @param [String] apellido_materno Apellido paterno completo, incluir artículos, preposiciones y conjunciones.
172
+ # @param [Date] fecha_nacimiento fecha de nacimiento.
173
+ # @return [String] El R.F.C. de la persona física con Homoclave y dígito verificador
174
+ #
175
+ def persona_fisica(nombres, apellido_paterno, apellido_materno, fecha_nacimiento)
176
+ nombres_limpios, apellido_paterno_limpio, apellido_materno_limpio = procesar_nombre_completo(nombres,
177
+ apellido_paterno, apellido_materno)
178
+ rfc = primeras_cuatro_letras(nombres_limpios, apellido_paterno_limpio, apellido_materno_limpio) +
179
+ fecha_con_formato(fecha_nacimiento) +
180
+ clave_diferenciadora_homonimia(nombre_reverso(nombres, apellido_paterno, apellido_materno))
181
+ rfc + digito_verificador(rfc)
182
+ end
183
+
184
+ # Regresa el Registro Federal de Contribuyentes (R.F.C.) de una persona moral de acuerdo a la denominación o razón social completa.
185
+ # Tomar en cuenta las siguientes condiciones para los parámetros:
186
+ # 1. Todos los parámetros deben escribirse en mayúsculas.
187
+ # 2. Convertir letras acentuadas a la letra sin acento.
188
+ # 3. @see ANEXO_VI_PERSONA_MORAL para los símbolos permitidos.
189
+ # 4. La información debe tomarse tal cual se encuentra en el acta constitutiva (incluir el tipo de sociedad, i.e. SA de CV).
190
+ #
191
+ # @param [String] razon_social Razón o denominación social completa, incluir artículos, preposiciones, conjunciones y tipo de sociedad.
192
+ # @param [Date] fecha_constitucion fecha de constitución de la empresa.
193
+ # @return [String] El R.F.C. de la persona moral con Homoclave y dígito verificador
194
+ #
195
+ def persona_moral(razon_social, fecha_constitucion)
196
+ rfc = primeras_tres_letras(razon_social) + fecha_con_formato(fecha_constitucion) +
197
+ clave_diferenciadora_homonimia(razon_social_para_clave_diferenciadora_homonimia(razon_social, fecha_constitucion))
198
+ rfc + digito_verificador(rfc)
199
+ end
200
+
201
+ # Método para obtención de la Clave Diferenciadora de Homonimia (Homoclave)
202
+ #
203
+ # @param [String] nombre_o_razon_social el nombre o la razón social conteniendo caracteres válidos.
204
+ # En caso de ser persona física el nombre debe estar acomodado como "Paterno Materno Nombres" y estar completo, i.e. 'DEL LEON RUIZ JOSE ANGEL'.
205
+ # En caso se ser persona moral incluir el tipo de sociedad, i.e. 'OPERACIONES EVE SA DE CV'
206
+ # @return [String] la Clave Diferenciadora de Homonimia (Homoclave)
207
+ #
208
+ def clave_diferenciadora_homonimia(nombre_o_razon_social)
209
+ cadena_anexo_1 = obtener_cadena_anexo_1(nombre_o_razon_social)
210
+ ultimas_tres_cifras_resultado = multiplicar_y_sumar(cadena_anexo_1).to_s[-3..-1].to_i
211
+ cociente_y_residuo = ultimas_tres_cifras_resultado.divmod(34)
212
+ return ANEXO_II[cociente_y_residuo[0].to_s] + ANEXO_II[cociente_y_residuo[1].to_s]
213
+ end
214
+
215
+ # Método para obtención del Dígito Verificador
216
+ #
217
+ # @param [String] rfc el R.F.C. a 11 ó 12 posiciones (persona moral o física respectivamente).
218
+ # @return [String] el Dígito Verificador.
219
+ #
220
+ def digito_verificador(rfc)
221
+ raise ArgumentError.new("la longitud del R.F.C. es incorrecta. Usar 11 o 12 caracteres.") unless rfc.size.between?(11,12)
222
+ rfc_12_posiciones = rfc.size == 11 ? " " + rfc : rfc
223
+ suma = 0
224
+ 13.downto(2){ |i| suma += ANEXO_III[rfc_12_posiciones[(i-13)*(-1)]].to_i * i }
225
+ residuo = suma % 11
226
+ return "0" if residuo.zero?
227
+ resta = 11 - residuo
228
+ resta == 10 ? "A" : resta.to_s
229
+ end
230
+
231
+ private
232
+
233
+ #### Métodos auxiliares para R.F.C. de personas morales
234
+
235
+ def razon_social_para_clave_diferenciadora_homonimia(razon_social, fecha_constitucion)
236
+ elementos = quitar_tipo_razon_social(razon_social).split(" ")
237
+ elementos = sustituir_abreviaturas(elementos)
238
+ elementos = sustituir_simbolos_individuales_persona_moral(elementos)
239
+ elementos = sustituir_numeros(elementos)
240
+ if fecha_constitucion.year < AÑO_CAMBIO_CALCULO_CVE_HOMONIMIA
241
+ elementos << obtener_tipo_sociedad(razon_social)
242
+ end
243
+ quitar_simbolos_persona_moral(elementos).join(" ")
244
+ end
245
+
246
+ def obtener_tipo_sociedad(razon_social)
247
+ tipo_sociedad = nil
248
+ regexp = /\A(#{ANEXO_V_PERSONA_MORAL_COMPUESTAS.join(")|(")})/
249
+ regexp.match(razon_social) do |m|
250
+ tipo_sociedad = m.captures.compact.first
251
+ end
252
+ return tipo_sociedad
253
+ end
254
+
255
+ def primeras_tres_letras(razon_social)
256
+ elementos = quitar_elementos_sobrantes_clave(razon_social)
257
+ elementos = sustituir_abreviaturas(elementos)
258
+ elementos = sustituir_simbolos_individuales_persona_moral(elementos)
259
+ elementos = quitar_simbolos_persona_moral(elementos)
260
+ elementos = sustituir_numeros(elementos)
261
+ obtener_tres_letras_iniciales(elementos)
262
+ end
263
+
264
+ def quitar_elementos_sobrantes_clave(razon_social)
265
+ elementos = quitar_tipo_razon_social(razon_social).split(" ")
266
+ for elemento in elementos # regla 9ª, 11ª
267
+ elementos.delete(elemento) if ANEXO_V_PERSONA_MORAL_SIMPLES.member?(elemento)
268
+ end
269
+ return elementos
270
+ end
271
+
272
+ def quitar_tipo_razon_social(razon_social)
273
+ regexp = /\A(#{ANEXO_V_PERSONA_MORAL_COMPUESTAS.join("\s?)|(")}\s?)/
274
+ razon_social.gsub(regexp, "").strip # regla 5ª
275
+ end
276
+
277
+ def sustituir_abreviaturas(elementos_razon_social)
278
+ elementos = elementos_razon_social.dup
279
+ sustituciones = {}
280
+ elementos_razon_social.each_with_index do |elemento, index|
281
+ nvo = abreviatura_a_palabras(elemento)
282
+ sustituciones[index] = nvo unless nvo.nil?
283
+ end
284
+ sustituciones.each_pair{|k,v| elementos[k] = v}
285
+ return elementos.join(" ").split(" ")
286
+ end
287
+
288
+ def abreviatura_a_palabras(elemento)
289
+ elemento.gsub(/\./, " ").strip if elemento =~ /\A[A-ZÑ]{1}\.([A-ZÑ]\.){0,2}/
290
+ end
291
+
292
+ def sustituir_simbolos_individuales_persona_moral(elementos_razon_social)
293
+ elementos = elementos_razon_social.dup
294
+ elementos_razon_social.each_with_index do |elemento, index|
295
+ elementos[index] = ANEXO_VI_PERSONA_MORAL[elemento] if ANEXO_VI_PERSONA_MORAL.member?(elemento)
296
+ end
297
+ return elementos
298
+ end
299
+
300
+ def quitar_simbolos_persona_moral(elementos_razon_social)
301
+ simbolos = ANEXO_VI_PERSONA_MORAL.keys.map{|s| Regexp.escape(s)}
302
+ elementos_razon_social.join(" ").gsub( /#{simbolos.join("|")}/, "").split(" ")
303
+ end
304
+
305
+ def sustituir_numeros(elementos_razon_social)
306
+ elementos = elementos_razon_social.dup
307
+ elementos_razon_social.each_with_index do |elemento, index|
308
+ if elemento =~ /\A[-+]?[0-9]+\z/
309
+ elementos[index] = numero_a_palabras(elemento.to_i).upcase
310
+ elsif NumeroRomano.romano?(elemento)
311
+ elementos[index] = numero_a_palabras(NumeroRomano.a_decimal(elemento)).upcase
312
+ else
313
+ next
314
+ end
315
+ end
316
+ return elementos
317
+ end
318
+
319
+ def obtener_tres_letras_iniciales(elementos_razon_social)
320
+ case elementos_razon_social.size
321
+ when 3
322
+ elementos_razon_social[0][0] + elementos_razon_social[1][0] + elementos_razon_social[2][0]
323
+ when 2
324
+ elementos_razon_social[0][0] + elementos_razon_social[1][0..1]
325
+ when 1
326
+ if elementos_razon_social[0].size >= 3
327
+ elementos_razon_social[0][0..2]
328
+ else
329
+ elementos_razon_social[0] + ("X" * (3 - elementos_razon_social[0].size)) # regla 8
330
+ end
331
+ end
332
+ end
333
+
334
+
335
+
336
+ #### Métodos auxiliares para R.F.C. de personas físicas
337
+
338
+ def nombre_reverso(nombres, apellido_paterno, apellido_materno)
339
+ apellidos = if en_blanco?(apellido_materno)
340
+ sustituir_simbolos_persona_fisica(apellido_paterno).strip
341
+ elsif en_blanco?(apellido_paterno)
342
+ sustituir_simbolos_persona_fisica(apellido_materno)
343
+ else
344
+ "#{sustituir_simbolos_persona_fisica(apellido_paterno).strip} #{sustituir_simbolos_persona_fisica(apellido_materno)}"
345
+ end
346
+ "#{apellidos} #{sustituir_simbolos_persona_fisica(nombres)}"
347
+ end
348
+
349
+ def procesar_nombre_completo(nombres, apellido_paterno, apellido_materno)
350
+ regexp = /\A(#{ANEXO_V_PERSONA_FISICA.join("\s)|(")}\s)/
351
+ nombres_limpios = sustituir_simbolos_persona_fisica(nombres.gsub(regexp, "")).strip
352
+ apellido_paterno_limpio = sustituir_simbolos_persona_fisica(apellido_paterno.gsub(regexp, "")).strip unless en_blanco?(apellido_paterno)
353
+ apellido_materno_limpio = sustituir_simbolos_persona_fisica(apellido_materno.gsub(regexp, "")).strip unless en_blanco?(apellido_materno)
354
+ [nombres_limpios, apellido_paterno_limpio, apellido_materno_limpio]
355
+ end
356
+
357
+ def primeras_cuatro_letras(nombres, apellido_paterno, apellido_materno)
358
+ if en_blanco?(apellido_materno)
359
+ regla_7(nombres, apellido_paterno)
360
+ elsif en_blanco?(apellido_paterno)
361
+ regla_7(nombres, apellido_materno)
362
+ elsif !apellido_paterno.nil? and apellido_paterno.size.between?(1,2)
363
+ regla_4(nombres, apellido_paterno, apellido_materno)
364
+ else
365
+ letras_apellido_paterno(apellido_paterno) + apellido_materno[0] + letra_nombres(nombres)
366
+ end
367
+ end
368
+
369
+ def sustituir_simbolos_persona_fisica(cadena)
370
+ sustitucion = cadena.dup
371
+ ANEXO_VI_PERSONA_FISICA.each_pair{ |llave, valor| sustitucion.gsub!(llave, valor[0]) }
372
+ return sustitucion
373
+ end
374
+
375
+ def filtro_cuatro_letras_convenientes(cuatro_letras)
376
+ ANEXO_IV.member?(cuatro_letras) ? cuatro_letras[0..2] + "X" : cuatro_letras
377
+ end
378
+
379
+ def letra_nombres(nombres)
380
+ # Primera letra primer nombre
381
+ obtener_primer_nombre_valido(nombres)[0]
382
+ end
383
+
384
+ def letras_apellido_paterno(apellido_paterno)
385
+ # Primera letra y primera vocal
386
+ primera_vocal = nil
387
+ apellido_paterno[1..-1].each_char do |c|
388
+ if VOCALES.member?(c)
389
+ primera_vocal = c
390
+ break
391
+ end
392
+ end
393
+ apellido_paterno[0] + (primera_vocal || "X")
394
+ end
395
+
396
+ def regla_4(nombres, apellido_paterno, apellido_materno)
397
+ apellido_paterno[0] + apellido_materno[0] + obtener_primer_nombre_valido(nombres)[0..1]
398
+ end
399
+
400
+ def regla_7(nombres, apellido)
401
+ apellido[0..1] + obtener_primer_nombre_valido(nombres)[0..1]
402
+ end
403
+
404
+ def obtener_cadena_anexo_1(nombre_o_razon_social)
405
+ cadena_anexo_1 = ""
406
+ nombre_o_razon_social.each_char{|c| cadena_anexo_1 << ANEXO_I[c]}
407
+ cadena_anexo_1.prepend("0") if cadena_anexo_1.size.even?
408
+ return cadena_anexo_1
409
+ end
410
+
411
+ def multiplicar_y_sumar(cadena_anexo_1)
412
+ suma = 0
413
+ cadena_anexo_1.split("").each_with_index do |actual, idx|
414
+ idx_siguiente = idx + 1
415
+ if idx_siguiente < cadena_anexo_1.size
416
+ siguiente = cadena_anexo_1[idx_siguiente]
417
+ suma += (actual + siguiente).to_i * siguiente.to_i
418
+ end
419
+ end
420
+ return suma
421
+ end
422
+
423
+ end
424
+ end