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 +7 -0
- data/.gitignore +14 -0
- data/.rspec +3 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +18 -0
- data/.yardopts +9 -0
- data/Gemfile +34 -0
- data/Guardfile +26 -0
- data/LICENSE.txt +22 -0
- data/MX-ID.gemspec +25 -0
- data/README.md +113 -0
- data/REFERENCIAS.md +8 -0
- data/Rakefile +15 -0
- data/lib/MX/ID.rb +13 -0
- data/lib/MX/ID/RFC.rb +424 -0
- data/lib/MX/ID/auxiliar.rb +58 -0
- data/lib/MX/ID/version.rb +6 -0
- data/lib/MX/numero_romano.rb +45 -0
- data/spec/MX/ID/RFC_spec.rb +415 -0
- data/spec/spec_helper.rb +20 -0
- metadata +97 -0
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
data/.rspec
ADDED
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
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
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
|