Repository: joke2k/faker

Issue:
Feature: Elector code for es_MX ssn provider
* Faker version: 18.11.2
* OS: NA

The National Electoral Institute in Mexico issues a unique identifier code for electors, it would be great if the library could fake those.

### Steps to reproduce

NA

### Expected behavior

```python
>>> fake.elector_code()
"GLVLMR80070509M900"
```

### Actual behavior

It's not implemented 😅 



Discussion:

Resolving patch:
diff --git a/faker/providers/ssn/es_MX/__init__.py b/faker/providers/ssn/es_MX/__init__.py
--- a/faker/providers/ssn/es_MX/__init__.py
+++ b/faker/providers/ssn/es_MX/__init__.py
@@ -7,9 +7,16 @@
 
 import random
 import string
+from typing import Optional
 
 from .. import Provider as BaseProvider
 
+try:
+    from typing import Literal  # type: ignore
+except ImportError:
+    from typing_extensions import Literal  # type: ignore
+
+
 ALPHABET = string.ascii_uppercase
 ALPHANUMERIC = string.digits + ALPHABET
 VOWELS = "AEIOU"
@@ -224,3 +231,30 @@ def rfc(self, natural: bool = True) -> str:
         random_rfc = name_initials + birth_date + disambiguation_code
 
         return random_rfc
+
+    def elector_code(self, gender: Literal["H", "M"] = None) -> str:
+        """
+        Unique elector code issued by INE (Instituto Nacional Electoral) in Mexico.
+
+        :param gender: Gender for which to generate the code. Will be randomly
+            selected if not provided.
+        :type gender: str
+        :return: a random INE elector code
+
+        :sample:
+        :sample: gender='M'
+        """
+        if gender and gender not in ("H", "M"):
+            raise ValueError("Gender must be 'H' or 'M'")
+
+        gender = gender or random.choice("HM")
+
+        consonants = "".join(random.choices(CONSONANTS, k=6))
+
+        birthday = self.generator.date_of_birth()
+        birth_date = birthday.strftime("%y%m%d")
+
+        entity = random.randint(1, 33)
+        disambiguation_code = "".join(random.choices(string.digits, k=3))
+
+        return f"{consonants}{birth_date}{entity:02d}{gender}{disambiguation_code}"


Test patch:
diff --git a/tests/providers/test_ssn.py b/tests/providers/test_ssn.py
--- a/tests/providers/test_ssn.py
+++ b/tests/providers/test_ssn.py
@@ -630,8 +630,8 @@ def setUp(self):
         Faker.seed(0)
 
 
-class TestEsMX(unittest.TestCase):
-    def setUp(self):
+class TestEsMX:
+    def setup_method(self):
         self.fake = Faker("es_MX")
         Faker.seed(0)
 
@@ -665,6 +665,26 @@ def test_rfc_legal(self):
             assert len(rfc) == 12
             assert re.search(r"^[A-Z]{3}\d{6}[0-9A-Z]{3}$", rfc)
 
+    @pytest.mark.parametrize(
+        "gender,pattern",
+        [
+            ("M", r"^[A-Z]{6}\d{8}M\d{3}$"),
+            ("H", r"^[A-Z]{6}\d{8}H\d{3}$"),
+            (None, r"^[A-Z]{6}\d{8}[HM]\d{3}$"),
+        ],
+        ids=["woman", "man", "any"],
+    )
+    def test_elector_code(self, gender, pattern):
+        for _ in range(100):
+            elector_code = self.fake.elector_code(gender=gender)
+
+            assert len(elector_code) == 18
+            assert re.search(pattern, elector_code)
+
+    def test_elector_code_unsupported_gender(self):
+        with pytest.raises(ValueError, match="Gender must be"):
+            self.fake.elector_code("Z")
+
 
 class TestEsCL(unittest.TestCase):
     def setUp(self):