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,184 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Mexican License Plates Formats Catalog
|
|
3
|
+
|
|
4
|
+
This module provides validation patterns and formats for Mexican vehicle license plates
|
|
5
|
+
according to NOM-001-SCT-2-2016.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import re
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PlacasFormatosCatalog:
|
|
14
|
+
"""
|
|
15
|
+
Catalog of Mexican license plate formats
|
|
16
|
+
|
|
17
|
+
Provides validation and detection for 35 official vehicle plate formats
|
|
18
|
+
defined by NOM-001-SCT-2-2016.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
_data: list[dict] | None = None
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def _load_data(cls) -> None:
|
|
25
|
+
"""Load license plate formats from JSON file"""
|
|
26
|
+
if cls._data is None:
|
|
27
|
+
# Path: catalogmx/packages/python/catalogmx/catalogs/mexico/placas_formatos.py
|
|
28
|
+
# Target: catalogmx/packages/shared-data/mexico/placas_formatos.json
|
|
29
|
+
current_file = Path(__file__)
|
|
30
|
+
shared_data_path = (
|
|
31
|
+
current_file.parent.parent.parent.parent.parent
|
|
32
|
+
/ "shared-data"
|
|
33
|
+
/ "mexico"
|
|
34
|
+
/ "placas_formatos.json"
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
with open(shared_data_path, encoding="utf-8") as f:
|
|
38
|
+
cls._data = json.load(f)
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def get_data(cls) -> list[dict]:
|
|
42
|
+
"""
|
|
43
|
+
Get all license plate formats
|
|
44
|
+
|
|
45
|
+
:return: List of all plate format dictionaries
|
|
46
|
+
"""
|
|
47
|
+
cls._load_data()
|
|
48
|
+
return cls._data.copy()
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def validate_placa(cls, placa: str) -> bool:
|
|
52
|
+
"""
|
|
53
|
+
Validate a license plate against all known active formats
|
|
54
|
+
|
|
55
|
+
:param placa: License plate string to validate
|
|
56
|
+
:return: True if valid, False otherwise
|
|
57
|
+
"""
|
|
58
|
+
cls._load_data()
|
|
59
|
+
normalized_placa = placa.upper().strip()
|
|
60
|
+
|
|
61
|
+
for formato in cls._data:
|
|
62
|
+
if formato.get("activo", True):
|
|
63
|
+
pattern = formato["pattern"]
|
|
64
|
+
if re.match(pattern, normalized_placa):
|
|
65
|
+
return True
|
|
66
|
+
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def get_formatos_por_estado(cls, estado: str) -> list[dict]:
|
|
71
|
+
"""
|
|
72
|
+
Get all formats for a specific state
|
|
73
|
+
|
|
74
|
+
:param estado: State name (e.g., 'Jalisco', 'Nacional')
|
|
75
|
+
:return: List of formats for the state
|
|
76
|
+
"""
|
|
77
|
+
cls._load_data()
|
|
78
|
+
estado_lower = estado.lower()
|
|
79
|
+
|
|
80
|
+
return [
|
|
81
|
+
f
|
|
82
|
+
for f in cls._data
|
|
83
|
+
if estado_lower in f["estado"].lower() or estado_lower == "nacional"
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
@classmethod
|
|
87
|
+
def get_formatos_por_tipo(cls, tipo: str) -> list[dict]:
|
|
88
|
+
"""
|
|
89
|
+
Get all active formats by type
|
|
90
|
+
|
|
91
|
+
:param tipo: Plate type (e.g., 'particular', 'diplomatico', 'militar_ejercito')
|
|
92
|
+
:return: List of formats of the specified type
|
|
93
|
+
"""
|
|
94
|
+
cls._load_data()
|
|
95
|
+
|
|
96
|
+
return [f for f in cls._data if f.get("tipo") == tipo and f.get("activo", True)]
|
|
97
|
+
|
|
98
|
+
@classmethod
|
|
99
|
+
def detect_formato(cls, placa: str) -> dict | None:
|
|
100
|
+
"""
|
|
101
|
+
Detect the format of a given license plate
|
|
102
|
+
|
|
103
|
+
:param placa: License plate string
|
|
104
|
+
:return: Format dictionary if detected, None otherwise
|
|
105
|
+
"""
|
|
106
|
+
cls._load_data()
|
|
107
|
+
normalized_placa = placa.upper().strip()
|
|
108
|
+
|
|
109
|
+
for formato in cls._data:
|
|
110
|
+
pattern = formato["pattern"]
|
|
111
|
+
if re.match(pattern, normalized_placa):
|
|
112
|
+
return formato.copy()
|
|
113
|
+
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
def get_formatos_activos(cls) -> list[dict]:
|
|
118
|
+
"""
|
|
119
|
+
Get all active formats
|
|
120
|
+
|
|
121
|
+
:return: List of active plate formats
|
|
122
|
+
"""
|
|
123
|
+
cls._load_data()
|
|
124
|
+
|
|
125
|
+
return [f for f in cls._data if f.get("activo", True)]
|
|
126
|
+
|
|
127
|
+
@classmethod
|
|
128
|
+
def is_diplomatica(cls, placa: str) -> bool:
|
|
129
|
+
"""
|
|
130
|
+
Check if a plate is diplomatic
|
|
131
|
+
|
|
132
|
+
:param placa: License plate string
|
|
133
|
+
:return: True if diplomatic, False otherwise
|
|
134
|
+
"""
|
|
135
|
+
formato = cls.detect_formato(placa)
|
|
136
|
+
return formato.get("tipo") == "diplomatico" if formato else False
|
|
137
|
+
|
|
138
|
+
@classmethod
|
|
139
|
+
def is_federal(cls, placa: str) -> bool:
|
|
140
|
+
"""
|
|
141
|
+
Check if a plate is federal (government, military, or federal service)
|
|
142
|
+
|
|
143
|
+
:param placa: License plate string
|
|
144
|
+
:return: True if federal, False otherwise
|
|
145
|
+
"""
|
|
146
|
+
formato = cls.detect_formato(placa)
|
|
147
|
+
if not formato:
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
tipo = formato.get("tipo")
|
|
151
|
+
federal_types = [
|
|
152
|
+
"gobierno_federal",
|
|
153
|
+
"servicio_publico_federal",
|
|
154
|
+
"carga_federal",
|
|
155
|
+
"policia_federal",
|
|
156
|
+
"remolque_federal",
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
return tipo in federal_types
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# Convenience functions for direct access
|
|
163
|
+
def validate_placa(placa: str) -> bool:
|
|
164
|
+
"""Validate a license plate"""
|
|
165
|
+
return PlacasFormatosCatalog.validate_placa(placa)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def detect_formato(placa: str) -> dict | None:
|
|
169
|
+
"""Detect the format of a license plate"""
|
|
170
|
+
return PlacasFormatosCatalog.detect_formato(placa)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def get_formatos_activos() -> list[dict]:
|
|
174
|
+
"""Get all active plate formats"""
|
|
175
|
+
return PlacasFormatosCatalog.get_formatos_activos()
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
# Export commonly used functions and classes
|
|
179
|
+
__all__ = [
|
|
180
|
+
"PlacasFormatosCatalog",
|
|
181
|
+
"validate_placa",
|
|
182
|
+
"detect_formato",
|
|
183
|
+
"get_formatos_activos",
|
|
184
|
+
]
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Mexican Minimum Wage Catalog
|
|
3
|
+
|
|
4
|
+
This module provides access to historical minimum wage data for Mexico.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SalariosMinimos:
|
|
12
|
+
"""
|
|
13
|
+
Catalog of Mexican minimum wages
|
|
14
|
+
|
|
15
|
+
Provides historical minimum wage data from 2010 to present,
|
|
16
|
+
with separate values for border zone and rest of country.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
_data: list[dict] | None = None
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def _load_data(cls) -> None:
|
|
23
|
+
"""Load minimum wage data from JSON file"""
|
|
24
|
+
if cls._data is None:
|
|
25
|
+
# Path: catalogmx/packages/python/catalogmx/catalogs/mexico/salarios_minimos.py
|
|
26
|
+
# Target: catalogmx/packages/shared-data/mexico/salarios_minimos.json
|
|
27
|
+
current_file = Path(__file__)
|
|
28
|
+
shared_data_path = (
|
|
29
|
+
current_file.parent.parent.parent.parent.parent
|
|
30
|
+
/ "shared-data"
|
|
31
|
+
/ "mexico"
|
|
32
|
+
/ "salarios_minimos.json"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
with open(shared_data_path, encoding="utf-8") as f:
|
|
36
|
+
cls._data = json.load(f)
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def get_data(cls) -> list[dict]:
|
|
40
|
+
"""
|
|
41
|
+
Get all minimum wage data
|
|
42
|
+
|
|
43
|
+
:return: List of all minimum wage records
|
|
44
|
+
"""
|
|
45
|
+
cls._load_data()
|
|
46
|
+
return cls._data.copy()
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
def get_por_anio(cls, anio: int) -> dict | None:
|
|
50
|
+
"""
|
|
51
|
+
Get minimum wage for a specific year
|
|
52
|
+
|
|
53
|
+
:param anio: Year (e.g., 2024)
|
|
54
|
+
:return: Minimum wage record or None if not found
|
|
55
|
+
"""
|
|
56
|
+
cls._load_data()
|
|
57
|
+
|
|
58
|
+
for record in cls._data:
|
|
59
|
+
if record["año"] == anio:
|
|
60
|
+
return record.copy()
|
|
61
|
+
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def get_actual(cls) -> dict | None:
|
|
66
|
+
"""
|
|
67
|
+
Get current minimum wage (latest year in data)
|
|
68
|
+
|
|
69
|
+
:return: Latest minimum wage record
|
|
70
|
+
"""
|
|
71
|
+
cls._load_data()
|
|
72
|
+
|
|
73
|
+
if not cls._data:
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
# Data is sorted by year descending, so first element is most recent
|
|
77
|
+
return cls._data[0].copy()
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def calcular_mensual(cls, diario: float, dias: int = 30) -> float:
|
|
81
|
+
"""
|
|
82
|
+
Calculate monthly wage from daily wage
|
|
83
|
+
|
|
84
|
+
:param diario: Daily wage amount
|
|
85
|
+
:param dias: Number of days (default 30)
|
|
86
|
+
:return: Monthly wage
|
|
87
|
+
"""
|
|
88
|
+
return diario * dias
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
def calcular_anual(cls, diario: float, dias: int = 365) -> float:
|
|
92
|
+
"""
|
|
93
|
+
Calculate annual wage from daily wage
|
|
94
|
+
|
|
95
|
+
:param diario: Daily wage amount
|
|
96
|
+
:param dias: Number of days (default 365)
|
|
97
|
+
:return: Annual wage
|
|
98
|
+
"""
|
|
99
|
+
return diario * dias
|
|
100
|
+
|
|
101
|
+
@classmethod
|
|
102
|
+
def get_por_zona(cls, anio: int, zona_frontera: bool = False) -> float | None:
|
|
103
|
+
"""
|
|
104
|
+
Get minimum wage for specific zone and year
|
|
105
|
+
|
|
106
|
+
:param anio: Year
|
|
107
|
+
:param zona_frontera: True for border zone, False for rest of country
|
|
108
|
+
:return: Daily wage amount or None if not found
|
|
109
|
+
"""
|
|
110
|
+
record = cls.get_por_anio(anio)
|
|
111
|
+
if not record:
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
if zona_frontera:
|
|
115
|
+
return record.get("zona_frontera_norte")
|
|
116
|
+
else:
|
|
117
|
+
return record.get("resto_pais")
|
|
118
|
+
|
|
119
|
+
@classmethod
|
|
120
|
+
def get_incremento(cls, anio: int) -> float | None:
|
|
121
|
+
"""
|
|
122
|
+
Get percentage increment for a specific year
|
|
123
|
+
|
|
124
|
+
:param anio: Year
|
|
125
|
+
:return: Percentage increment or None if not found
|
|
126
|
+
"""
|
|
127
|
+
record = cls.get_por_anio(anio)
|
|
128
|
+
if not record:
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
return record.get("incremento_porcentual")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# Convenience functions
|
|
135
|
+
def get_salario_actual() -> dict | None:
|
|
136
|
+
"""Get current minimum wage"""
|
|
137
|
+
return SalariosMinimos.get_actual()
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def get_salario_por_anio(anio: int) -> dict | None:
|
|
141
|
+
"""Get minimum wage for a specific year"""
|
|
142
|
+
return SalariosMinimos.get_por_anio(anio)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def calcular_mensual(diario: float, dias: int = 30) -> float:
|
|
146
|
+
"""Calculate monthly wage from daily wage"""
|
|
147
|
+
return SalariosMinimos.calcular_mensual(diario, dias)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# Export commonly used functions and classes
|
|
151
|
+
__all__ = [
|
|
152
|
+
"SalariosMinimos",
|
|
153
|
+
"get_salario_actual",
|
|
154
|
+
"get_salario_por_anio",
|
|
155
|
+
"calcular_mensual",
|
|
156
|
+
]
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""
|
|
2
|
+
UMA (Unidad de Medida y Actualización) Catalog
|
|
3
|
+
|
|
4
|
+
This module provides access to UMA values, which are used as reference
|
|
5
|
+
units for fines, taxes, and other obligations in Mexico.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from .salarios_minimos import SalariosMinimos
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class UMACatalog:
|
|
15
|
+
"""
|
|
16
|
+
Catalog of UMA (Unidad de Medida y Actualización) values
|
|
17
|
+
|
|
18
|
+
UMA is a reference economic unit used in Mexico for calculating
|
|
19
|
+
fines, taxes, social security contributions, and other obligations.
|
|
20
|
+
It was introduced in 2016 to replace the minimum wage as a reference unit.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
_data: list[dict] | None = None
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def _load_data(cls) -> None:
|
|
27
|
+
"""Load UMA data from JSON file"""
|
|
28
|
+
if cls._data is None:
|
|
29
|
+
# Path: catalogmx/packages/python/catalogmx/catalogs/mexico/uma.py
|
|
30
|
+
# Target: catalogmx/packages/shared-data/mexico/uma.json
|
|
31
|
+
current_file = Path(__file__)
|
|
32
|
+
shared_data_path = (
|
|
33
|
+
current_file.parent.parent.parent.parent.parent
|
|
34
|
+
/ "shared-data"
|
|
35
|
+
/ "mexico"
|
|
36
|
+
/ "uma.json"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
with open(shared_data_path, encoding="utf-8") as f:
|
|
40
|
+
cls._data = json.load(f)
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def get_data(cls) -> list[dict]:
|
|
44
|
+
"""
|
|
45
|
+
Get all UMA data
|
|
46
|
+
|
|
47
|
+
:return: List of all UMA records
|
|
48
|
+
"""
|
|
49
|
+
cls._load_data()
|
|
50
|
+
return cls._data.copy()
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def get_por_anio(cls, anio: int) -> dict | None:
|
|
54
|
+
"""
|
|
55
|
+
Get UMA values for a specific year
|
|
56
|
+
|
|
57
|
+
:param anio: Year (e.g., 2024)
|
|
58
|
+
:return: UMA record or None if not found
|
|
59
|
+
"""
|
|
60
|
+
cls._load_data()
|
|
61
|
+
|
|
62
|
+
for record in cls._data:
|
|
63
|
+
if record["año"] == anio:
|
|
64
|
+
return record.copy()
|
|
65
|
+
|
|
66
|
+
# Fallback to salary minimum equivalence for pre-2017 years
|
|
67
|
+
salario = SalariosMinimos.get_por_anio(anio)
|
|
68
|
+
if not salario:
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
diario = salario.get("uma_equivalente_diario") or salario.get("resto_pais")
|
|
72
|
+
if diario is None:
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
mensual = salario.get("uma_equivalente_mensual")
|
|
76
|
+
if mensual is None:
|
|
77
|
+
mensual = round(diario * 30.4, 2)
|
|
78
|
+
|
|
79
|
+
anual = salario.get("uma_equivalente_anual")
|
|
80
|
+
if anual is None:
|
|
81
|
+
anual = round(diario * 365, 2)
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
"año": anio,
|
|
85
|
+
"vigencia_inicio": salario.get("vigencia_inicio", f"{anio}-01-01"),
|
|
86
|
+
"vigencia_fin": f"{anio}-12-31",
|
|
87
|
+
"valor_diario": round(diario, 2),
|
|
88
|
+
"valor_mensual": mensual,
|
|
89
|
+
"valor_anual": anual,
|
|
90
|
+
"moneda": salario.get("moneda", "MXN"),
|
|
91
|
+
"publicacion_dof": salario.get("vigencia_inicio", f"{anio}-01-01"),
|
|
92
|
+
"incremento_porcentual": None,
|
|
93
|
+
"notas": "Equivalencia de UMA derivada del salario mínimo vigente antes de 2017",
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
def get_actual(cls) -> dict | None:
|
|
98
|
+
"""
|
|
99
|
+
Get current UMA values (latest year in data)
|
|
100
|
+
|
|
101
|
+
:return: Latest UMA record
|
|
102
|
+
"""
|
|
103
|
+
cls._load_data()
|
|
104
|
+
|
|
105
|
+
if not cls._data:
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
# Data is sorted by year descending, so first element is most recent
|
|
109
|
+
return cls._data[0].copy()
|
|
110
|
+
|
|
111
|
+
@classmethod
|
|
112
|
+
def get_valor(cls, anio: int, tipo: str = "diario") -> float | None:
|
|
113
|
+
"""
|
|
114
|
+
Get UMA value for specific year and type
|
|
115
|
+
|
|
116
|
+
:param anio: Year
|
|
117
|
+
:param tipo: Type of value ('diario', 'mensual', 'anual')
|
|
118
|
+
:return: UMA value or None if not found
|
|
119
|
+
"""
|
|
120
|
+
record = cls.get_por_anio(anio)
|
|
121
|
+
if not record:
|
|
122
|
+
return None
|
|
123
|
+
|
|
124
|
+
tipo_map = {"diario": "valor_diario", "mensual": "valor_mensual", "anual": "valor_anual"}
|
|
125
|
+
|
|
126
|
+
field = tipo_map.get(tipo.lower())
|
|
127
|
+
if not field:
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
return record.get(field)
|
|
131
|
+
|
|
132
|
+
@classmethod
|
|
133
|
+
def calcular_umas(cls, monto: float, anio: int, tipo: str = "diario") -> float | None:
|
|
134
|
+
"""
|
|
135
|
+
Calculate how many UMAs a given amount represents
|
|
136
|
+
|
|
137
|
+
:param monto: Amount in Mexican pesos
|
|
138
|
+
:param anio: Year for UMA value
|
|
139
|
+
:param tipo: Type of UMA ('diario', 'mensual', 'anual')
|
|
140
|
+
:return: Number of UMAs or None if UMA value not found
|
|
141
|
+
"""
|
|
142
|
+
valor_uma = cls.get_valor(anio, tipo)
|
|
143
|
+
if not valor_uma:
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
return monto / valor_uma
|
|
147
|
+
|
|
148
|
+
@classmethod
|
|
149
|
+
def calcular_monto(cls, umas: float, anio: int, tipo: str = "diario") -> float | None:
|
|
150
|
+
"""
|
|
151
|
+
Calculate the peso amount for a given number of UMAs
|
|
152
|
+
|
|
153
|
+
:param umas: Number of UMAs
|
|
154
|
+
:param anio: Year for UMA value
|
|
155
|
+
:param tipo: Type of UMA ('diario', 'mensual', 'anual')
|
|
156
|
+
:return: Amount in Mexican pesos or None if UMA value not found
|
|
157
|
+
"""
|
|
158
|
+
valor_uma = cls.get_valor(anio, tipo)
|
|
159
|
+
if not valor_uma:
|
|
160
|
+
return None
|
|
161
|
+
|
|
162
|
+
return umas * valor_uma
|
|
163
|
+
|
|
164
|
+
@classmethod
|
|
165
|
+
def get_incremento(cls, anio: int) -> float | None:
|
|
166
|
+
"""
|
|
167
|
+
Get percentage increment for a specific year
|
|
168
|
+
|
|
169
|
+
:param anio: Year
|
|
170
|
+
:return: Percentage increment or None if not found
|
|
171
|
+
"""
|
|
172
|
+
record = cls.get_por_anio(anio)
|
|
173
|
+
if not record:
|
|
174
|
+
return None
|
|
175
|
+
|
|
176
|
+
return record.get("incremento_porcentual")
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# Convenience functions
|
|
180
|
+
def get_uma_actual() -> dict | None:
|
|
181
|
+
"""Get current UMA values"""
|
|
182
|
+
return UMACatalog.get_actual()
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def get_uma_por_anio(anio: int) -> dict | None:
|
|
186
|
+
"""Get UMA values for a specific year"""
|
|
187
|
+
return UMACatalog.get_por_anio(anio)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def calcular_umas(monto: float, anio: int, tipo: str = "diario") -> float | None:
|
|
191
|
+
"""Calculate how many UMAs a given amount represents"""
|
|
192
|
+
return UMACatalog.calcular_umas(monto, anio, tipo)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def calcular_monto(umas: float, anio: int, tipo: str = "diario") -> float | None:
|
|
196
|
+
"""Calculate peso amount for a given number of UMAs"""
|
|
197
|
+
return UMACatalog.calcular_monto(umas, anio, tipo)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
# Export commonly used functions and classes
|
|
201
|
+
__all__ = [
|
|
202
|
+
"UMACatalog",
|
|
203
|
+
"get_uma_actual",
|
|
204
|
+
"get_uma_por_anio",
|
|
205
|
+
"calcular_umas",
|
|
206
|
+
"calcular_monto",
|
|
207
|
+
]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""
|
|
2
|
+
catalogmx.catalogs.sat - Catálogos del SAT
|
|
3
|
+
|
|
4
|
+
Módulos disponibles:
|
|
5
|
+
- cfdi_4: Catálogos para CFDI 4.0 (Anexo 20)
|
|
6
|
+
- comercio_exterior: Catálogos para Complemento de Comercio Exterior 2.0
|
|
7
|
+
- carta_porte: Catálogos para Complemento de Carta Porte 3.0
|
|
8
|
+
- nomina: Catálogos para Complemento de Nómina 1.2
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from . import carta_porte, cfdi_4, comercio_exterior, nomina
|
|
12
|
+
|
|
13
|
+
__all__ = ["cfdi_4", "comercio_exterior", "carta_porte", "nomina"]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Catálogos SAT Carta Porte 3.0"""
|
|
2
|
+
|
|
3
|
+
from .aeropuertos import AeropuertosCatalog
|
|
4
|
+
from .carreteras import CarreterasCatalog
|
|
5
|
+
from .config_autotransporte import ConfigAutotransporteCatalog
|
|
6
|
+
from .material_peligroso import MaterialPeligrosoCatalog
|
|
7
|
+
from .puertos_maritimos import PuertosMaritimos
|
|
8
|
+
from .tipo_embalaje import TipoEmbalajeCatalog
|
|
9
|
+
from .tipo_permiso import TipoPermisoCatalog
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"AeropuertosCatalog",
|
|
13
|
+
"PuertosMaritimos",
|
|
14
|
+
"TipoPermisoCatalog",
|
|
15
|
+
"ConfigAutotransporteCatalog",
|
|
16
|
+
"TipoEmbalajeCatalog",
|
|
17
|
+
"CarreterasCatalog",
|
|
18
|
+
"MaterialPeligrosoCatalog",
|
|
19
|
+
]
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Catálogo c_CodigoTransporteAereo - Aeropuertos"""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from catalogmx.utils.text import normalize_text
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AeropuertosCatalog:
|
|
10
|
+
_data: list[dict] | None = None
|
|
11
|
+
_by_code: dict[str, dict] | None = None
|
|
12
|
+
_by_iata: dict[str, dict] | None = None
|
|
13
|
+
_by_icao: dict[str, dict] | None = None
|
|
14
|
+
|
|
15
|
+
@classmethod
|
|
16
|
+
def _load_data(cls) -> None:
|
|
17
|
+
if cls._data is None:
|
|
18
|
+
path = (
|
|
19
|
+
Path(__file__).parent.parent.parent.parent.parent.parent
|
|
20
|
+
/ "shared-data"
|
|
21
|
+
/ "sat"
|
|
22
|
+
/ "carta_porte_3"
|
|
23
|
+
/ "aeropuertos.json"
|
|
24
|
+
)
|
|
25
|
+
with open(path, encoding="utf-8") as f:
|
|
26
|
+
cls._data = json.load(f)
|
|
27
|
+
cls._by_code = {item["code"]: item for item in cls._data}
|
|
28
|
+
cls._by_iata = {item["iata"]: item for item in cls._data}
|
|
29
|
+
cls._by_icao = {item["icao"]: item for item in cls._data}
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def get_aeropuerto(cls, code: str) -> dict | None:
|
|
33
|
+
"""Obtiene aeropuerto por código SAT"""
|
|
34
|
+
cls._load_data()
|
|
35
|
+
return cls._by_code.get(code)
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def get_by_iata(cls, iata: str) -> dict | None:
|
|
39
|
+
"""Obtiene aeropuerto por código IATA"""
|
|
40
|
+
cls._load_data()
|
|
41
|
+
return cls._by_iata.get(iata)
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def get_by_icao(cls, icao: str) -> dict | None:
|
|
45
|
+
"""Obtiene aeropuerto por código ICAO"""
|
|
46
|
+
cls._load_data()
|
|
47
|
+
return cls._by_icao.get(icao)
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def is_valid(cls, code: str) -> bool:
|
|
51
|
+
"""Verifica si un código de aeropuerto es válido"""
|
|
52
|
+
return cls.get_aeropuerto(code) is not None
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def get_all(cls) -> list[dict]:
|
|
56
|
+
"""Obtiene todos los aeropuertos"""
|
|
57
|
+
cls._load_data()
|
|
58
|
+
return cls._data.copy()
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def get_by_state(cls, state: str) -> list[dict]:
|
|
62
|
+
"""Obtiene aeropuertos por estado (insensible a acentos)"""
|
|
63
|
+
cls._load_data()
|
|
64
|
+
state_normalized = normalize_text(state)
|
|
65
|
+
return [
|
|
66
|
+
a
|
|
67
|
+
for a in cls._data
|
|
68
|
+
if normalize_text(a.get("estado", a.get("state", ""))) == state_normalized
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
def search_by_name(cls, name: str) -> list[dict]:
|
|
73
|
+
"""Busca aeropuertos por nombre (insensible a acentos)"""
|
|
74
|
+
cls._load_data()
|
|
75
|
+
name_normalized = normalize_text(name)
|
|
76
|
+
return [a for a in cls._data if name_normalized in normalize_text(a["name"])]
|