sat_rfc 0.1.0 → 0.2.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: d546b4ca5245b4d86c98325bf2872a14268e945b8b51c0f6bee2c26e7dc9b1af
4
- data.tar.gz: 6e129b4c04bfc6bb2ec397609bc408c8368094a49bc4a292fb561ff0f9bc9088
3
+ metadata.gz: d4c2e78c0e8c7490a0cc48c4f10c775d7061e6734ca28502c8c01d28ab8fd193
4
+ data.tar.gz: d192b112b798c0a9337d3261b61627e481630e3f5420d9748bd042212a8021a0
5
5
  SHA512:
6
- metadata.gz: 69a47948fcfa654cacfe91d7abd919704b44be4e3fa1690a078ca48a90dec715e5635b9781103b4e3f5e8944b891063361746567b9368d14ad2267e64411fcb9
7
- data.tar.gz: 928596bf9a5c0fcfd3426b839e8db8254e90c76b892ed785159a688e3d721ccc0771308f057fd04ea06daacf76e52fbd8be5cdf04a9b65c667be073e695180d6
6
+ metadata.gz: 4f6469fc80b3cd16c0a5b007bc04970217af152a220675a73054887b1f2392b65593f875a6c36c933372acf6debd6098b1dd4706bfe1a913b9a8fb3aab04702b
7
+ data.tar.gz: 6e4eb8bb3fe4548f46959ac830bae2701c67e21e1bbeee5e913fe5b5b34a0fcbde88a289ed09becb8764ccecd32bde5d9d952607528075d88e8f90636b4a516c
data/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # sat_rfc
2
2
 
3
- Ruby gem to generate Mexican **RFC** tax identifiers for **natural persons** (personas físicas), following the [SAT](https://www.sat.gob.mx/) algorithm.
4
-
5
- Legal entity support (personas morales) is planned for a future release.
3
+ Ruby gem to generate Mexican **RFC** tax identifiers for **natural persons** (personas físicas) and **legal entities** (personas morales), following the SAT algorithm documented by the IFAI (Instituto Federal de Acceso a la Información). Natural person support is inspired by [rfc-facil](https://github.com/josketres/rfc-facil) and [rfc_facil](https://github.com/acrogenesis/rfc_facil).
6
4
 
7
5
  ## Installation
8
6
 
@@ -39,6 +37,32 @@ rfc = Rfc::Generator.new.for_natural_person(
39
37
  puts rfc # => "PEGJ900315PE9"
40
38
  ```
41
39
 
40
+ To inspect individual RFC parts, use the calculators directly:
41
+
42
+ ```ruby
43
+ name = "Juan Carlos"
44
+ first_last_name = "Perez"
45
+ second_last_name = "Garcia"
46
+
47
+ ten_digits = Rfc::NaturalTenDigitsCodeCalculator.new(
48
+ name: name,
49
+ first_last_name: first_last_name,
50
+ second_last_name: second_last_name,
51
+ day: 15, month: 3, year: 1990
52
+ ).calculate
53
+ # => "PEGJ900315"
54
+
55
+ homoclave = Rfc::HomoclaveCalculator.new(
56
+ name: name,
57
+ first_last_name: first_last_name,
58
+ second_last_name: second_last_name
59
+ ).calculate
60
+ # => "PE"
61
+
62
+ verification_digit = Rfc::VerificationDigitCalculator.new("#{ten_digits}#{homoclave}").calculate
63
+ # => "9"
64
+ ```
65
+
42
66
  You can also pass the birth date as separate values:
43
67
 
44
68
  ```ruby
@@ -54,6 +78,37 @@ Rfc::Generator.new.for_natural_person(
54
78
 
55
79
  `date_of_birth` accepts `DD/MM/YYYY` format or any string parseable by Ruby's `Date`.
56
80
 
81
+ ### Legal entity (persona moral)
82
+
83
+ ```ruby
84
+ rfc = Rfc::Generator.new.for_legal_entity(
85
+ legal_name: "Mu Networks S.A.P.I. de C.V.",
86
+ constitution_date: "10/07/2014"
87
+ )
88
+
89
+ puts rfc # => "MNP1407108U7"
90
+ ```
91
+
92
+ To inspect individual RFC parts:
93
+
94
+ ```ruby
95
+ legal_name = "Mu Networks S.A.P.I. de C.V."
96
+
97
+ ten_digits = Rfc::LegalEntityTenDigitsCodeCalculator.new(
98
+ legal_name: legal_name,
99
+ day: 10, month: 7, year: 2014
100
+ ).calculate
101
+ # => "MNP140710"
102
+
103
+ homoclave = Rfc::HomoclaveCalculator.new(legal_name: legal_name).calculate
104
+ # => "8U"
105
+
106
+ verification_digit = Rfc::VerificationDigitCalculator.new("#{ten_digits}#{homoclave}").calculate
107
+ # => "7"
108
+ ```
109
+
110
+ `constitution_date` accepts `DD/MM/YYYY` format or any string parseable by Ruby's `Date`.
111
+
57
112
  ### Errors
58
113
 
59
114
  ```ruby
@@ -83,6 +138,19 @@ Rfc::Generator.new.for_natural_person(
83
138
  )
84
139
  ```
85
140
 
141
+ ## Algorithm reference
142
+
143
+ This gem implements the RFC generation algorithm with homoclave for natural persons and legal entities, based on official documentation obtained through the IFAI.
144
+
145
+ - **INFOMEX folio:** `0610100135506`
146
+ - **Document:** [Algoritmo para generar el RFC con homoclave para personas físicas y morales (PDF)](https://www.mariovaldez.net/files/IFAI%200610100135506%20065%20Algoritmo%20para%20generar%20el%20RFC%20con%20homoclave%20para%20personas%20fisicas%20y%20morales.pdf)
147
+
148
+ ## Acknowledgments
149
+
150
+ ### Natural person (persona física)
151
+
152
+ Natural person support is inspired by [rfc-facil](https://github.com/josketres/rfc-facil) by [josketres](https://github.com/josketres) and [rfc_facil](https://github.com/acrogenesis/rfc_facil) by [acrogenesis](https://github.com/acrogenesis), following the SAT algorithm documented by the IFAI.
153
+
86
154
  ## Contributing
87
155
 
88
156
  Bug reports and pull requests are welcome on GitHub at [elosnaya/rfc](https://github.com/elosnaya/rfc).
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rfc
4
+ module DiscardableTerms
5
+ # Dictionary of discardable terms for legal entities (personas morales).
6
+ # Based on SAT specification.
7
+ #
8
+ # After normalization, terms like "S.A. de C.V." become "SA DE CV" (separate words),
9
+ # so individual abbreviations and words are listed here.
10
+ module LegalEntity
11
+ ARTICLES_AND_PREPOSITIONS = %w[
12
+ DE LA LAS DEL LOS Y EL
13
+ PARA POR AL E EN CON SUS
14
+ OF THE AND
15
+ ].freeze
16
+
17
+ COMPANY_TYPE_ABBREVIATIONS = %w[
18
+ SA SC SRL SCL SNC SCS CIA COOP SOC CO COMPANY
19
+ CV RL
20
+ ].freeze
21
+
22
+ SINGLE_LETTER_ABBREVIATIONS = %w[S A C V].freeze
23
+
24
+ COMPANY_TYPE_WORDS = %w[
25
+ COMPAÑIA SOCIEDAD COOPERATIVA
26
+ ANONIMA COMANDITA RESPONSABILIDAD LIMITADA
27
+ PROMOTORA INVERSION CAPITAL VARIABLE NOMBRE COLECTIVO
28
+ ].freeze
29
+
30
+ DISCARDABLE_TERMS = %w[MC VON MAC VAN MI].freeze
31
+
32
+ ALL = (
33
+ ARTICLES_AND_PREPOSITIONS +
34
+ COMPANY_TYPE_ABBREVIATIONS +
35
+ SINGLE_LETTER_ABBREVIATIONS +
36
+ COMPANY_TYPE_WORDS +
37
+ DISCARDABLE_TERMS
38
+ ).freeze
39
+ end
40
+ end
41
+ end
data/lib/rfc/generator.rb CHANGED
@@ -8,10 +8,12 @@ module Rfc
8
8
 
9
9
  def initialize(
10
10
  natural_ten_digits_calculator: NaturalTenDigitsCodeCalculator,
11
+ legal_entity_ten_digits_calculator: LegalEntityTenDigitsCodeCalculator,
11
12
  homoclave_calculator: HomoclaveCalculator,
12
13
  verification_digit_calculator: VerificationDigitCalculator
13
14
  )
14
15
  @natural_ten_digits_calculator = natural_ten_digits_calculator
16
+ @legal_entity_ten_digits_calculator = legal_entity_ten_digits_calculator
15
17
  @homoclave_calculator = homoclave_calculator
16
18
  @verification_digit_calculator = verification_digit_calculator
17
19
  end
@@ -47,6 +49,29 @@ module Rfc
47
49
  raise InvalidDateError, "Invalid date parameters: #{e.message}"
48
50
  end
49
51
 
52
+ def for_legal_entity(
53
+ legal_name:,
54
+ day: nil,
55
+ month: nil,
56
+ year: nil,
57
+ constitution_date: nil
58
+ )
59
+ normalized_day, normalized_month, normalized_year = normalize_date(day, month, year, constitution_date)
60
+
61
+ ten_digits_code = @legal_entity_ten_digits_calculator.new(
62
+ legal_name: legal_name,
63
+ day: normalized_day,
64
+ month: normalized_month,
65
+ year: normalized_year
66
+ ).calculate
67
+
68
+ homoclave = @homoclave_calculator.new(legal_name: legal_name).calculate
69
+
70
+ build_rfc(ten_digits_code, homoclave)
71
+ rescue ArgumentError => e
72
+ raise InvalidDateError, "Invalid date parameters: #{e.message}"
73
+ end
74
+
50
75
  private
51
76
 
52
77
  def build_rfc(ten_digits_code, homoclave)
@@ -15,10 +15,11 @@ module Rfc
15
15
  "Y" => "38", "Z" => "39", "Ñ" => "40"
16
16
  }.freeze
17
17
 
18
- def initialize(name:, first_last_name:, second_last_name:)
18
+ def initialize(name: nil, first_last_name: nil, second_last_name: nil, legal_name: nil)
19
19
  @name = name
20
20
  @first_last_name = first_last_name
21
21
  @second_last_name = second_last_name
22
+ @legal_name = legal_name
22
23
  end
23
24
 
24
25
  def calculate
@@ -73,9 +74,15 @@ module Rfc
73
74
  end
74
75
 
75
76
  def full_name_string
77
+ return @legal_name if present?(@legal_name)
78
+
76
79
  "#{@first_last_name} #{@second_last_name} #{@name}"
77
80
  end
78
81
 
82
+ def present?(value)
83
+ !value.nil? && !value.to_s.strip.empty?
84
+ end
85
+
79
86
  def add_missing_char_to_full_name(raw_full_name, missing_char)
80
87
  index = raw_full_name.index(missing_char)
81
88
  until index.nil?
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "i18n"
4
+
5
+ module Rfc
6
+ class LegalEntityTenDigitsCodeCalculator
7
+ DISCARDABLE_TERMS = DiscardableTerms::LegalEntity::ALL
8
+
9
+ def initialize(legal_name:, day:, month:, year:)
10
+ @legal_name = legal_name
11
+ @day = day
12
+ @month = month
13
+ @year = year
14
+ end
15
+
16
+ def calculate
17
+ name_code + birthday_code
18
+ end
19
+
20
+ private
21
+
22
+ def name_code
23
+ normalized_name = normalize(@legal_name)
24
+ all_words = normalized_name.split(/\s+/).reject(&:empty?)
25
+ significant_words = all_words.reject { |word| DISCARDABLE_TERMS.include?(word) }
26
+
27
+ words_to_use = significant_words.empty? ? all_words : significant_words
28
+
29
+ case words_to_use.length
30
+ when 0
31
+ raise ArgumentError, "Legal name is blank or invalid"
32
+ when 1
33
+ words_to_use[0][0..2]
34
+ when 2
35
+ "#{words_to_use[0][0]}#{words_to_use[1][0..1]}"
36
+ else
37
+ "#{words_to_use[0][0]}#{words_to_use[1][0]}#{words_to_use[2][0]}"
38
+ end
39
+ end
40
+
41
+ def birthday_code
42
+ "#{last_two_digits_of(@year)}#{formatted_in_two_digits(@month)}#{formatted_in_two_digits(@day)}"
43
+ end
44
+
45
+ def normalize(legal_name)
46
+ return legal_name if legal_name.nil? || legal_name.empty?
47
+
48
+ normalized = I18n.transliterate(legal_name).upcase
49
+ normalized.gsub(/[^A-Z0-9\s]/, " ")
50
+ end
51
+
52
+ def formatted_in_two_digits(number)
53
+ format("%02d", number)
54
+ end
55
+
56
+ def last_two_digits_of(number)
57
+ formatted_in_two_digits(number % 100)
58
+ end
59
+ end
60
+ end
@@ -81,7 +81,11 @@ module Rfc
81
81
  end
82
82
 
83
83
  def filter_name(name)
84
- normalize(name).strip.sub(/^(MA|MA\.|MARIA|JOSE)\s+/, "")
84
+ return name if name.nil? || name.empty?
85
+
86
+ normalized = I18n.transliterate(name).upcase.strip
87
+ normalized = normalized.sub(/^(MA|MA\.|MARIA|JOSE)\s+/, "")
88
+ remove_discardable_terms(normalized, DISCARDABLE_TERMS)
85
89
  end
86
90
 
87
91
  def first_letter_of(word)
data/lib/rfc/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rfc
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/sat_rfc.rb CHANGED
@@ -7,9 +7,11 @@ I18n.default_locale = :en
7
7
 
8
8
  require_relative "rfc/version"
9
9
  require_relative "rfc/discardable_terms/natural_person"
10
+ require_relative "rfc/discardable_terms/legal_entity"
10
11
  require_relative "rfc/verification_digit_calculator"
11
12
  require_relative "rfc/homoclave_calculator"
12
13
  require_relative "rfc/natural_ten_digits_code_calculator"
14
+ require_relative "rfc/legal_entity_ten_digits_code_calculator"
13
15
 
14
16
  module Rfc
15
17
  class Error < StandardError; end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sat_rfc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - elOsnaya
@@ -24,8 +24,10 @@ dependencies:
24
24
  - !ruby/object:Gem::Version
25
25
  version: '1.0'
26
26
  description: Ruby library that generates RFC tax identifiers for Mexican natural persons
27
- using the SAT algorithm. Supports name normalization, homoclave, and verification
28
- digit calculation.
27
+ and legal entities using the SAT algorithm documented by the IFAI (INFOMEX folio
28
+ 0610100135506). Natural person support is inspired by rfc-facil (josketres) and
29
+ rfc_facil (acrogenesis). Legal entity support is an original implementation by elOsnaya.
30
+ Supports name normalization, homoclave, and verification digit calculation.
29
31
  email:
30
32
  - luisosnet@gmail.com
31
33
  executables: []
@@ -36,9 +38,11 @@ files:
36
38
  - README.md
37
39
  - Rakefile
38
40
  - lib/rfc.rb
41
+ - lib/rfc/discardable_terms/legal_entity.rb
39
42
  - lib/rfc/discardable_terms/natural_person.rb
40
43
  - lib/rfc/generator.rb
41
44
  - lib/rfc/homoclave_calculator.rb
45
+ - lib/rfc/legal_entity_ten_digits_code_calculator.rb
42
46
  - lib/rfc/natural_ten_digits_code_calculator.rb
43
47
  - lib/rfc/verification_digit_calculator.rb
44
48
  - lib/rfc/version.rb
@@ -50,6 +54,7 @@ licenses:
50
54
  metadata:
51
55
  homepage_uri: https://github.com/elosnaya/rfc
52
56
  source_code_uri: https://github.com/elosnaya/rfc
57
+ documentation_uri: https://www.mariovaldez.net/files/IFAI%200610100135506%20065%20Algoritmo%20para%20generar%20el%20RFC%20con%20homoclave%20para%20personas%20fisicas%20y%20morales.pdf
53
58
  rdoc_options: []
54
59
  require_paths:
55
60
  - lib
@@ -66,5 +71,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
66
71
  requirements: []
67
72
  rubygems_version: 4.0.3
68
73
  specification_version: 4
69
- summary: Generate Mexican RFC codes for natural persons (personas físicas).
74
+ summary: Generate Mexican RFC codes for natural persons and legal entities.
70
75
  test_files: []