catalogmx 0.3.0__py3-none-any.whl
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.
- catalogmx/__init__.py +56 -0
- catalogmx/catalogs/__init__.py +5 -0
- catalogmx/catalogs/banxico/__init__.py +24 -0
- catalogmx/catalogs/banxico/banks.py +136 -0
- catalogmx/catalogs/banxico/codigos_plaza.py +287 -0
- catalogmx/catalogs/banxico/instituciones_financieras.py +338 -0
- catalogmx/catalogs/banxico/monedas_divisas.py +386 -0
- catalogmx/catalogs/banxico/udis.py +279 -0
- catalogmx/catalogs/ift/__init__.py +15 -0
- catalogmx/catalogs/ift/codigos_lada.py +426 -0
- catalogmx/catalogs/ift/operadores_moviles.py +315 -0
- catalogmx/catalogs/inegi/__init__.py +21 -0
- catalogmx/catalogs/inegi/localidades.py +207 -0
- catalogmx/catalogs/inegi/municipios.py +73 -0
- catalogmx/catalogs/inegi/municipios_completo.py +236 -0
- catalogmx/catalogs/inegi/states.py +148 -0
- catalogmx/catalogs/mexico/__init__.py +17 -0
- catalogmx/catalogs/mexico/hoy_no_circula.py +215 -0
- catalogmx/catalogs/mexico/placas_formatos.py +184 -0
- catalogmx/catalogs/mexico/salarios_minimos.py +156 -0
- catalogmx/catalogs/mexico/uma.py +207 -0
- catalogmx/catalogs/sat/__init__.py +13 -0
- catalogmx/catalogs/sat/carta_porte/__init__.py +19 -0
- catalogmx/catalogs/sat/carta_porte/aeropuertos.py +76 -0
- catalogmx/catalogs/sat/carta_porte/carreteras.py +59 -0
- catalogmx/catalogs/sat/carta_porte/config_autotransporte.py +54 -0
- catalogmx/catalogs/sat/carta_porte/material_peligroso.py +66 -0
- catalogmx/catalogs/sat/carta_porte/puertos_maritimos.py +63 -0
- catalogmx/catalogs/sat/carta_porte/tipo_embalaje.py +48 -0
- catalogmx/catalogs/sat/carta_porte/tipo_permiso.py +54 -0
- catalogmx/catalogs/sat/cfdi_4/__init__.py +42 -0
- catalogmx/catalogs/sat/cfdi_4/clave_prod_serv.py +383 -0
- catalogmx/catalogs/sat/cfdi_4/clave_unidad.py +298 -0
- catalogmx/catalogs/sat/cfdi_4/exportacion.py +45 -0
- catalogmx/catalogs/sat/cfdi_4/forma_pago.py +45 -0
- catalogmx/catalogs/sat/cfdi_4/impuesto.py +57 -0
- catalogmx/catalogs/sat/cfdi_4/meses.py +34 -0
- catalogmx/catalogs/sat/cfdi_4/metodo_pago.py +45 -0
- catalogmx/catalogs/sat/cfdi_4/objeto_imp.py +45 -0
- catalogmx/catalogs/sat/cfdi_4/periodicidad.py +34 -0
- catalogmx/catalogs/sat/cfdi_4/regimen_fiscal.py +57 -0
- catalogmx/catalogs/sat/cfdi_4/tasa_o_cuota.py +42 -0
- catalogmx/catalogs/sat/cfdi_4/tipo_comprobante.py +45 -0
- catalogmx/catalogs/sat/cfdi_4/tipo_factor.py +34 -0
- catalogmx/catalogs/sat/cfdi_4/tipo_relacion.py +45 -0
- catalogmx/catalogs/sat/cfdi_4/uso_cfdi.py +45 -0
- catalogmx/catalogs/sat/comercio_exterior/__init__.py +39 -0
- catalogmx/catalogs/sat/comercio_exterior/claves_pedimento.py +77 -0
- catalogmx/catalogs/sat/comercio_exterior/estados.py +122 -0
- catalogmx/catalogs/sat/comercio_exterior/incoterms.py +226 -0
- catalogmx/catalogs/sat/comercio_exterior/monedas.py +107 -0
- catalogmx/catalogs/sat/comercio_exterior/motivos_traslado.py +54 -0
- catalogmx/catalogs/sat/comercio_exterior/paises.py +88 -0
- catalogmx/catalogs/sat/comercio_exterior/registro_ident_trib.py +76 -0
- catalogmx/catalogs/sat/comercio_exterior/unidades_aduana.py +54 -0
- catalogmx/catalogs/sat/comercio_exterior/validator.py +212 -0
- catalogmx/catalogs/sat/nomina/__init__.py +19 -0
- catalogmx/catalogs/sat/nomina/banco.py +50 -0
- catalogmx/catalogs/sat/nomina/periodicidad_pago.py +48 -0
- catalogmx/catalogs/sat/nomina/riesgo_puesto.py +56 -0
- catalogmx/catalogs/sat/nomina/tipo_contrato.py +47 -0
- catalogmx/catalogs/sat/nomina/tipo_jornada.py +42 -0
- catalogmx/catalogs/sat/nomina/tipo_nomina.py +52 -0
- catalogmx/catalogs/sat/nomina/tipo_regimen.py +47 -0
- catalogmx/catalogs/sepomex/__init__.py +5 -0
- catalogmx/catalogs/sepomex/codigos_postales.py +184 -0
- catalogmx/cli.py +185 -0
- catalogmx/helpers.py +324 -0
- catalogmx/utils/text.py +55 -0
- catalogmx/validators/__init__.py +0 -0
- catalogmx/validators/clabe.py +233 -0
- catalogmx/validators/curp.py +623 -0
- catalogmx/validators/nss.py +255 -0
- catalogmx/validators/rfc.py +1004 -0
- catalogmx-0.3.0.dist-info/METADATA +644 -0
- catalogmx-0.3.0.dist-info/RECORD +81 -0
- catalogmx-0.3.0.dist-info/WHEEL +5 -0
- catalogmx-0.3.0.dist-info/entry_points.txt +2 -0
- catalogmx-0.3.0.dist-info/licenses/AUTHORS.rst +5 -0
- catalogmx-0.3.0.dist-info/licenses/LICENSE +19 -0
- catalogmx-0.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Catálogo c_TipoRegimen"""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TipoRegimenCatalog:
|
|
8
|
+
_data: list[dict] | None = None
|
|
9
|
+
_by_code: dict[str, dict] | None = None
|
|
10
|
+
|
|
11
|
+
@classmethod
|
|
12
|
+
def _load_data(cls) -> None:
|
|
13
|
+
if cls._data is None:
|
|
14
|
+
path = (
|
|
15
|
+
Path(__file__).parent.parent.parent.parent.parent.parent
|
|
16
|
+
/ "shared-data"
|
|
17
|
+
/ "sat"
|
|
18
|
+
/ "nomina_1.2"
|
|
19
|
+
/ "tipo_regimen.json"
|
|
20
|
+
)
|
|
21
|
+
with open(path, encoding="utf-8") as f:
|
|
22
|
+
data = json.load(f)
|
|
23
|
+
# Handle both list and dict formats
|
|
24
|
+
cls._data = data if isinstance(data, list) else data.get("regimenes", data)
|
|
25
|
+
cls._by_code = {item["code"]: item for item in cls._data}
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def get_regimen(cls, code: str) -> dict | None:
|
|
29
|
+
"""Obtiene tipo de régimen por código"""
|
|
30
|
+
cls._load_data()
|
|
31
|
+
return cls._by_code.get(code)
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def is_valid(cls, code: str) -> bool:
|
|
35
|
+
"""Verifica si un código de régimen es válido"""
|
|
36
|
+
return cls.get_regimen(code) is not None
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def get_all(cls) -> list[dict]:
|
|
40
|
+
"""Obtiene todos los tipos de régimen"""
|
|
41
|
+
cls._load_data()
|
|
42
|
+
return cls._data.copy()
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def is_asimilado(cls, code: str) -> bool:
|
|
46
|
+
"""Verifica si es régimen asimilado a salarios"""
|
|
47
|
+
return code in ["05", "06", "07", "08", "09", "10", "11", "12", "13"]
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""Catálogo de Códigos Postales SEPOMEX"""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from catalogmx.utils.text import normalize_text
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CodigosPostales:
|
|
10
|
+
_data: list[dict] | None = None
|
|
11
|
+
_by_cp: dict[str, list[dict]] | None = None
|
|
12
|
+
_by_estado: dict[str, list[dict]] | None = None
|
|
13
|
+
_by_estado_normalized: dict[str, list[dict]] | None = None
|
|
14
|
+
_by_municipio_normalized: dict[str, list[dict]] | None = None
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def _load_data(cls) -> None:
|
|
18
|
+
if cls._data is None:
|
|
19
|
+
# Path: catalogmx/packages/python/catalogmx/catalogs/sepomex/codigos_postales.py
|
|
20
|
+
# Target: catalogmx/packages/shared-data/sepomex/codigos_postales_completo.json
|
|
21
|
+
path = (
|
|
22
|
+
Path(__file__).parent.parent.parent.parent.parent
|
|
23
|
+
/ "shared-data"
|
|
24
|
+
/ "sepomex"
|
|
25
|
+
/ "codigos_postales_completo.json"
|
|
26
|
+
)
|
|
27
|
+
with open(path, encoding="utf-8") as f:
|
|
28
|
+
cls._data = json.load(f)
|
|
29
|
+
|
|
30
|
+
# Index by CP (can have multiple settlements)
|
|
31
|
+
cls._by_cp = {}
|
|
32
|
+
for item in cls._data:
|
|
33
|
+
cp = item["cp"]
|
|
34
|
+
if cp not in cls._by_cp:
|
|
35
|
+
cls._by_cp[cp] = []
|
|
36
|
+
cls._by_cp[cp].append(item)
|
|
37
|
+
|
|
38
|
+
# Index by estado
|
|
39
|
+
cls._by_estado = {}
|
|
40
|
+
for item in cls._data:
|
|
41
|
+
estado = item["estado"]
|
|
42
|
+
if estado not in cls._by_estado:
|
|
43
|
+
cls._by_estado[estado] = []
|
|
44
|
+
cls._by_estado[estado].append(item)
|
|
45
|
+
|
|
46
|
+
# Index by estado normalized (accent-insensitive)
|
|
47
|
+
cls._by_estado_normalized = {}
|
|
48
|
+
for item in cls._data:
|
|
49
|
+
estado_norm = normalize_text(item["estado"])
|
|
50
|
+
if estado_norm not in cls._by_estado_normalized:
|
|
51
|
+
cls._by_estado_normalized[estado_norm] = []
|
|
52
|
+
cls._by_estado_normalized[estado_norm].append(item)
|
|
53
|
+
|
|
54
|
+
# Index by municipio normalized (accent-insensitive)
|
|
55
|
+
cls._by_municipio_normalized = {}
|
|
56
|
+
for item in cls._data:
|
|
57
|
+
municipio_norm = normalize_text(item["municipio"])
|
|
58
|
+
if municipio_norm not in cls._by_municipio_normalized:
|
|
59
|
+
cls._by_municipio_normalized[municipio_norm] = []
|
|
60
|
+
cls._by_municipio_normalized[municipio_norm].append(item)
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
def get_by_cp(cls, cp: str) -> list[dict]:
|
|
64
|
+
"""Obtiene todos los asentamientos de un código postal"""
|
|
65
|
+
cls._load_data()
|
|
66
|
+
return cls._by_cp.get(cp, [])
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def is_valid(cls, cp: str) -> bool:
|
|
70
|
+
"""Verifica si un código postal existe"""
|
|
71
|
+
cls._load_data()
|
|
72
|
+
return cp in cls._by_cp
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
def get_by_estado(cls, estado: str) -> list[dict]:
|
|
76
|
+
"""Obtiene todos los códigos postales de un estado (insensible a acentos)"""
|
|
77
|
+
cls._load_data()
|
|
78
|
+
estado_normalized = normalize_text(estado)
|
|
79
|
+
return cls._by_estado_normalized.get(estado_normalized, [])
|
|
80
|
+
|
|
81
|
+
@classmethod
|
|
82
|
+
def get_by_municipio(cls, municipio: str) -> list[dict]:
|
|
83
|
+
"""Obtiene todos los códigos postales de un municipio (insensible a acentos)"""
|
|
84
|
+
cls._load_data()
|
|
85
|
+
municipio_normalized = normalize_text(municipio)
|
|
86
|
+
return cls._by_municipio_normalized.get(municipio_normalized, [])
|
|
87
|
+
|
|
88
|
+
@classmethod
|
|
89
|
+
def search_by_colonia(cls, colonia: str) -> list[dict]:
|
|
90
|
+
"""Busca códigos postales por nombre de colonia (insensible a acentos)"""
|
|
91
|
+
cls._load_data()
|
|
92
|
+
colonia_normalized = normalize_text(colonia)
|
|
93
|
+
return [
|
|
94
|
+
item for item in cls._data if colonia_normalized in normalize_text(item["asentamiento"])
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
@classmethod
|
|
98
|
+
def get_all(cls) -> list[dict]:
|
|
99
|
+
"""Obtiene todos los códigos postales"""
|
|
100
|
+
cls._load_data()
|
|
101
|
+
return cls._data.copy()
|
|
102
|
+
|
|
103
|
+
@classmethod
|
|
104
|
+
def get_municipio(cls, cp: str) -> str | None:
|
|
105
|
+
"""Obtiene el municipio de un código postal"""
|
|
106
|
+
settlements = cls.get_by_cp(cp)
|
|
107
|
+
return settlements[0]["municipio"] if settlements else None
|
|
108
|
+
|
|
109
|
+
@classmethod
|
|
110
|
+
def get_estado(cls, cp: str) -> str | None:
|
|
111
|
+
"""Obtiene el estado de un código postal"""
|
|
112
|
+
settlements = cls.get_by_cp(cp)
|
|
113
|
+
return settlements[0]["estado"] if settlements else None
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class CodigosPostalesSQLite:
|
|
117
|
+
_db_path = None
|
|
118
|
+
_connection = None
|
|
119
|
+
|
|
120
|
+
@classmethod
|
|
121
|
+
def _get_db_path(cls):
|
|
122
|
+
if cls._db_path is None:
|
|
123
|
+
cls._db_path = (
|
|
124
|
+
Path(__file__).parent.parent.parent.parent.parent
|
|
125
|
+
/ "shared-data"
|
|
126
|
+
/ "sqlite"
|
|
127
|
+
/ "sepomex.db"
|
|
128
|
+
)
|
|
129
|
+
return cls._db_path
|
|
130
|
+
|
|
131
|
+
@classmethod
|
|
132
|
+
def _get_connection(cls):
|
|
133
|
+
import sqlite3
|
|
134
|
+
|
|
135
|
+
if cls._connection is None:
|
|
136
|
+
path = cls._get_db_path()
|
|
137
|
+
if not path.exists():
|
|
138
|
+
raise FileNotFoundError(
|
|
139
|
+
f"Database not found at {path}. Please run the migration script."
|
|
140
|
+
)
|
|
141
|
+
cls._connection = sqlite3.connect(path)
|
|
142
|
+
cls._connection.row_factory = sqlite3.Row
|
|
143
|
+
return cls._connection
|
|
144
|
+
|
|
145
|
+
@classmethod
|
|
146
|
+
def _query(cls, query: str, params: tuple = ()):
|
|
147
|
+
conn = cls._get_connection()
|
|
148
|
+
cursor = conn.cursor()
|
|
149
|
+
cursor.execute(query, params)
|
|
150
|
+
rows = cursor.fetchall()
|
|
151
|
+
return [dict(row) for row in rows]
|
|
152
|
+
|
|
153
|
+
@classmethod
|
|
154
|
+
def get_by_cp(cls, cp: str) -> list[dict]:
|
|
155
|
+
"""Obtiene todos los asentamientos de un código postal desde SQLite"""
|
|
156
|
+
return cls._query("SELECT * FROM codigos_postales WHERE cp = ?", (cp,))
|
|
157
|
+
|
|
158
|
+
@classmethod
|
|
159
|
+
def is_valid(cls, cp: str) -> bool:
|
|
160
|
+
"""Verifica si un código postal existe en SQLite"""
|
|
161
|
+
result = cls._query("SELECT 1 FROM codigos_postales WHERE cp = ? LIMIT 1", (cp,))
|
|
162
|
+
return len(result) > 0
|
|
163
|
+
|
|
164
|
+
@classmethod
|
|
165
|
+
def get_by_estado(cls, estado: str) -> list[dict]:
|
|
166
|
+
"""Obtiene todos los códigos postales de un estado desde SQLite"""
|
|
167
|
+
return cls._query("SELECT * FROM codigos_postales WHERE estado = ?", (estado,))
|
|
168
|
+
|
|
169
|
+
@classmethod
|
|
170
|
+
def get_all(cls) -> list[dict]:
|
|
171
|
+
"""Obtiene todos los códigos postales desde SQLite"""
|
|
172
|
+
return cls._query("SELECT * FROM codigos_postales")
|
|
173
|
+
|
|
174
|
+
@classmethod
|
|
175
|
+
def get_municipio(cls, cp: str) -> str | None:
|
|
176
|
+
"""Obtiene el municipio de un código postal desde SQLite"""
|
|
177
|
+
settlements = cls.get_by_cp(cp)
|
|
178
|
+
return settlements[0]["municipio"] if settlements else None
|
|
179
|
+
|
|
180
|
+
@classmethod
|
|
181
|
+
def get_estado(cls, cp: str) -> str | None:
|
|
182
|
+
"""Obtiene el estado de un código postal desde SQLite"""
|
|
183
|
+
settlements = cls.get_by_cp(cp)
|
|
184
|
+
return settlements[0]["estado"] if settlements else None
|
catalogmx/cli.py
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module that contains the command line app.
|
|
3
|
+
|
|
4
|
+
Why does this file exist, and why not put this in __main__?
|
|
5
|
+
|
|
6
|
+
You might be tempted to import things from __main__ later, but that will cause
|
|
7
|
+
problems: the code will get executed twice:
|
|
8
|
+
|
|
9
|
+
- When you run `python -mrfcmx` python will execute
|
|
10
|
+
``__main__.py`` as a script. That means there won't be any
|
|
11
|
+
``rfcmx.__main__`` in ``sys.modules``.
|
|
12
|
+
- When you import __main__ it will get executed again (as a module) because
|
|
13
|
+
there's no ``rfcmx.__main__`` in ``sys.modules``.
|
|
14
|
+
|
|
15
|
+
Also see (1) from http://click.pocoo.org/5/setuptools/#setuptools-integration
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import datetime
|
|
19
|
+
|
|
20
|
+
import click
|
|
21
|
+
|
|
22
|
+
from catalogmx.validators.curp import CURPGenerator, CURPValidator
|
|
23
|
+
from catalogmx.validators.rfc import RFCGenerator, RFCValidator
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@click.group()
|
|
27
|
+
@click.version_option(version="0.2.0")
|
|
28
|
+
def main():
|
|
29
|
+
"""
|
|
30
|
+
Mexican RFC and CURP calculator and validator.
|
|
31
|
+
|
|
32
|
+
This tool helps you generate and validate:
|
|
33
|
+
- RFC (Registro Federal de Contribuyentes) for individuals and companies
|
|
34
|
+
- CURP (Clave Única de Registro de Población) for individuals
|
|
35
|
+
"""
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@main.group()
|
|
40
|
+
def rfc():
|
|
41
|
+
"""RFC (Registro Federal de Contribuyentes) commands"""
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@main.group()
|
|
46
|
+
def curp():
|
|
47
|
+
"""CURP (Clave Única de Registro de Población) commands"""
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@rfc.command("validate")
|
|
52
|
+
@click.argument("rfc_code")
|
|
53
|
+
def rfc_validate(rfc_code):
|
|
54
|
+
"""Validate an RFC code"""
|
|
55
|
+
validator = RFCValidator(rfc_code)
|
|
56
|
+
|
|
57
|
+
if validator.validate():
|
|
58
|
+
click.echo(click.style(f"✓ RFC {rfc_code} is valid", fg="green"))
|
|
59
|
+
tipo = validator.detect_fisica_moral()
|
|
60
|
+
click.echo(f" Type: {tipo}")
|
|
61
|
+
|
|
62
|
+
# Show validation details
|
|
63
|
+
validations = validator.validators()
|
|
64
|
+
click.echo("\n Validation details:")
|
|
65
|
+
for name, result in validations.items():
|
|
66
|
+
status = "✓" if result else "✗"
|
|
67
|
+
color = "green" if result else "red"
|
|
68
|
+
click.echo(f" {click.style(status, fg=color)} {name}")
|
|
69
|
+
else:
|
|
70
|
+
click.echo(click.style(f"✗ RFC {rfc_code} is invalid", fg="red"))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@rfc.command("generate-fisica")
|
|
74
|
+
@click.option("--nombre", "-n", required=True, help="First name(s)")
|
|
75
|
+
@click.option("--paterno", "-p", required=True, help="First surname (apellido paterno)")
|
|
76
|
+
@click.option("--materno", "-m", default="", help="Second surname (apellido materno)")
|
|
77
|
+
@click.option("--fecha", "-f", required=True, help="Birth date (YYYY-MM-DD)")
|
|
78
|
+
def rfc_generate_fisica(nombre, paterno, materno, fecha):
|
|
79
|
+
"""Generate RFC for Persona Física (individual)"""
|
|
80
|
+
try:
|
|
81
|
+
# Parse date
|
|
82
|
+
fecha_obj = datetime.datetime.strptime(fecha, "%Y-%m-%d").date()
|
|
83
|
+
|
|
84
|
+
# Generate RFC
|
|
85
|
+
rfc_code = RFCGenerator.generate_fisica(
|
|
86
|
+
nombre=nombre, paterno=paterno, materno=materno, fecha=fecha_obj
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
click.echo(click.style(f"\nGenerated RFC: {rfc_code}", fg="green", bold=True))
|
|
90
|
+
click.echo(f"\nName: {nombre} {paterno} {materno}")
|
|
91
|
+
click.echo(f"Birth date: {fecha}")
|
|
92
|
+
|
|
93
|
+
except ValueError as e:
|
|
94
|
+
click.echo(click.style(f"Error: {str(e)}", fg="red"))
|
|
95
|
+
except Exception as e:
|
|
96
|
+
click.echo(click.style(f"Unexpected error: {str(e)}", fg="red"))
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@rfc.command("generate-moral")
|
|
100
|
+
@click.option("--razon-social", "-r", required=True, help="Company name (razón social)")
|
|
101
|
+
@click.option("--fecha", "-f", required=True, help="Incorporation date (YYYY-MM-DD)")
|
|
102
|
+
def rfc_generate_moral(razon_social, fecha):
|
|
103
|
+
"""Generate RFC for Persona Moral (company/legal entity)"""
|
|
104
|
+
try:
|
|
105
|
+
# Parse date
|
|
106
|
+
fecha_obj = datetime.datetime.strptime(fecha, "%Y-%m-%d").date()
|
|
107
|
+
|
|
108
|
+
# Generate RFC
|
|
109
|
+
rfc_code = RFCGenerator.generate_moral(razon_social=razon_social, fecha=fecha_obj)
|
|
110
|
+
|
|
111
|
+
click.echo(click.style(f"\nGenerated RFC: {rfc_code}", fg="green", bold=True))
|
|
112
|
+
click.echo(f"\nCompany: {razon_social}")
|
|
113
|
+
click.echo(f"Incorporation date: {fecha}")
|
|
114
|
+
|
|
115
|
+
except ValueError as e:
|
|
116
|
+
click.echo(click.style(f"Error: {str(e)}", fg="red"))
|
|
117
|
+
except Exception as e:
|
|
118
|
+
click.echo(click.style(f"Unexpected error: {str(e)}", fg="red"))
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@curp.command("validate")
|
|
122
|
+
@click.argument("curp_code")
|
|
123
|
+
def curp_validate(curp_code):
|
|
124
|
+
"""Validate a CURP code"""
|
|
125
|
+
validator = CURPValidator(curp_code)
|
|
126
|
+
|
|
127
|
+
if validator.is_valid():
|
|
128
|
+
click.echo(click.style(f"✓ CURP {curp_code} is valid", fg="green"))
|
|
129
|
+
else:
|
|
130
|
+
click.echo(click.style(f"✗ CURP {curp_code} is invalid", fg="red"))
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@curp.command("generate")
|
|
134
|
+
@click.option("--nombre", "-n", required=True, help="First name(s)")
|
|
135
|
+
@click.option("--paterno", "-p", required=True, help="First surname (apellido paterno)")
|
|
136
|
+
@click.option("--materno", "-m", default="", help="Second surname (apellido materno)")
|
|
137
|
+
@click.option("--fecha", "-f", required=True, help="Birth date (YYYY-MM-DD)")
|
|
138
|
+
@click.option(
|
|
139
|
+
"--sexo",
|
|
140
|
+
"-s",
|
|
141
|
+
required=True,
|
|
142
|
+
type=click.Choice(["H", "M"], case_sensitive=False),
|
|
143
|
+
help="Gender: H (Hombre/Male) or M (Mujer/Female)",
|
|
144
|
+
)
|
|
145
|
+
@click.option("--estado", "-e", required=True, help="Birth state (e.g., Jalisco, CDMX, etc.)")
|
|
146
|
+
def curp_generate(nombre, paterno, materno, fecha, sexo, estado):
|
|
147
|
+
"""Generate CURP for an individual"""
|
|
148
|
+
try:
|
|
149
|
+
# Parse date
|
|
150
|
+
fecha_obj = datetime.datetime.strptime(fecha, "%Y-%m-%d").date()
|
|
151
|
+
|
|
152
|
+
# Generate CURP
|
|
153
|
+
generator = CURPGenerator(
|
|
154
|
+
nombre=nombre,
|
|
155
|
+
paterno=paterno,
|
|
156
|
+
materno=materno,
|
|
157
|
+
fecha_nacimiento=fecha_obj,
|
|
158
|
+
sexo=sexo.upper(),
|
|
159
|
+
estado=estado,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
curp_code = generator.curp
|
|
163
|
+
|
|
164
|
+
click.echo(click.style(f"\nGenerated CURP: {curp_code}", fg="green", bold=True))
|
|
165
|
+
click.echo(f"\nName: {nombre} {paterno} {materno}")
|
|
166
|
+
click.echo(f"Birth date: {fecha}")
|
|
167
|
+
click.echo(f"Gender: {sexo.upper()}")
|
|
168
|
+
click.echo(f"Birth state: {estado}")
|
|
169
|
+
|
|
170
|
+
# Show a note about homoclave
|
|
171
|
+
click.echo(
|
|
172
|
+
click.style(
|
|
173
|
+
'\nNote: The homoclave (last 2 characters) is a placeholder ("00").', fg="yellow"
|
|
174
|
+
)
|
|
175
|
+
)
|
|
176
|
+
click.echo(click.style("The official homoclave is assigned by RENAPO.", fg="yellow"))
|
|
177
|
+
|
|
178
|
+
except ValueError as e:
|
|
179
|
+
click.echo(click.style(f"Error: {str(e)}", fg="red"))
|
|
180
|
+
except Exception as e:
|
|
181
|
+
click.echo(click.style(f"Unexpected error: {str(e)}", fg="red"))
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
if __name__ == "__main__":
|
|
185
|
+
main()
|