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,386 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Catálogo de monedas y divisas internacionales (Banxico)
|
|
3
|
+
|
|
4
|
+
Este módulo proporciona acceso al catálogo de monedas y divisas
|
|
5
|
+
internacionales utilizadas en operaciones cambiarias en México,
|
|
6
|
+
basado en códigos ISO 4217.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import TypedDict
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MonedaDivisa(TypedDict, total=False):
|
|
15
|
+
"""Estructura de una moneda o divisa"""
|
|
16
|
+
|
|
17
|
+
codigo_iso: str
|
|
18
|
+
numero_iso: str
|
|
19
|
+
moneda: str
|
|
20
|
+
pais: str
|
|
21
|
+
simbolo: str
|
|
22
|
+
decimales: int
|
|
23
|
+
moneda_nacional: bool
|
|
24
|
+
tipo_cambio_banxico: bool
|
|
25
|
+
tipo_cambio_fix: bool # Optional
|
|
26
|
+
activa: bool
|
|
27
|
+
notas: str # Optional
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class MonedasDivisas:
|
|
31
|
+
"""
|
|
32
|
+
Catálogo de monedas y divisas internacionales.
|
|
33
|
+
|
|
34
|
+
Incluye todas las monedas con las que Banco de México publica
|
|
35
|
+
tipos de cambio, más otras monedas internacionales relevantes.
|
|
36
|
+
|
|
37
|
+
Características:
|
|
38
|
+
- Códigos ISO 4217 (código y número)
|
|
39
|
+
- Símbolos y decimales de cada moneda
|
|
40
|
+
- Indicación de tipo de cambio publicado por Banxico
|
|
41
|
+
- Indicación de tipo de cambio FIX
|
|
42
|
+
- Agrupación por regiones geográficas
|
|
43
|
+
- Formateo de montos en cada moneda
|
|
44
|
+
|
|
45
|
+
Ejemplo:
|
|
46
|
+
>>> from catalogmx.catalogs.banxico import MonedasDivisas
|
|
47
|
+
>>>
|
|
48
|
+
>>> # Obtener información del dólar
|
|
49
|
+
>>> usd = MonedasDivisas.get_por_codigo("USD")
|
|
50
|
+
>>> print(f"{usd['moneda']}: {usd['simbolo']}")
|
|
51
|
+
>>>
|
|
52
|
+
>>> # Obtener monedas con tipo de cambio Banxico
|
|
53
|
+
>>> con_tc = MonedasDivisas.get_con_tipo_cambio_banxico()
|
|
54
|
+
>>> print(f"Monedas con TC Banxico: {len(con_tc)}")
|
|
55
|
+
>>>
|
|
56
|
+
>>> # Formatear monto
|
|
57
|
+
>>> formatted = MonedasDivisas.formatear_monto(1234.56, "USD")
|
|
58
|
+
>>> print(formatted) # "US$ 1,234.56"
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
_data: list[MonedaDivisa] | None = None
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def _load_data(cls) -> None:
|
|
65
|
+
"""Carga lazy de datos desde JSON"""
|
|
66
|
+
if cls._data is not None:
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
# Path: catalogmx/packages/python/catalogmx/catalogs/banxico/monedas_divisas.py
|
|
70
|
+
# Target: catalogmx/packages/shared-data/banxico/monedas_divisas.json
|
|
71
|
+
data_path = (
|
|
72
|
+
Path(__file__).parent.parent.parent.parent.parent
|
|
73
|
+
/ "shared-data"
|
|
74
|
+
/ "banxico"
|
|
75
|
+
/ "monedas_divisas.json"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
with open(data_path, encoding="utf-8") as f:
|
|
79
|
+
json_data = json.load(f)
|
|
80
|
+
cls._data = json_data["monedas"]
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def get_all(cls) -> list[MonedaDivisa]:
|
|
84
|
+
"""
|
|
85
|
+
Obtiene todas las monedas.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Lista completa de monedas y divisas
|
|
89
|
+
|
|
90
|
+
Ejemplo:
|
|
91
|
+
>>> monedas = MonedasDivisas.get_all()
|
|
92
|
+
>>> print(f"Total monedas: {len(monedas)}")
|
|
93
|
+
"""
|
|
94
|
+
cls._load_data()
|
|
95
|
+
return cls._data.copy() # type: ignore
|
|
96
|
+
|
|
97
|
+
@classmethod
|
|
98
|
+
def get_por_codigo(cls, codigo_iso: str) -> MonedaDivisa | None:
|
|
99
|
+
"""
|
|
100
|
+
Busca moneda por código ISO.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
codigo_iso: Código ISO 4217 de la moneda (ej: "USD", "EUR", "MXN")
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Información de la moneda o None si no existe
|
|
107
|
+
|
|
108
|
+
Ejemplo:
|
|
109
|
+
>>> usd = MonedasDivisas.get_por_codigo("USD")
|
|
110
|
+
>>> print(usd['moneda']) # "Dólar Estadounidense"
|
|
111
|
+
"""
|
|
112
|
+
cls._load_data()
|
|
113
|
+
codigo_upper = codigo_iso.upper()
|
|
114
|
+
for moneda in cls._data: # type: ignore
|
|
115
|
+
if moneda["codigo_iso"].upper() == codigo_upper:
|
|
116
|
+
return moneda
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
@classmethod
|
|
120
|
+
def get_por_pais(cls, pais: str) -> list[MonedaDivisa]:
|
|
121
|
+
"""
|
|
122
|
+
Busca monedas por país.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
pais: Nombre del país (búsqueda parcial, case-insensitive)
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Lista de monedas del país
|
|
129
|
+
|
|
130
|
+
Ejemplo:
|
|
131
|
+
>>> mexico = MonedasDivisas.get_por_pais("México")
|
|
132
|
+
>>> for m in mexico:
|
|
133
|
+
... print(f"{m['moneda']} ({m['codigo_iso']})")
|
|
134
|
+
"""
|
|
135
|
+
cls._load_data()
|
|
136
|
+
pais_lower = pais.lower()
|
|
137
|
+
return [m for m in cls._data if pais_lower in m["pais"].lower()] # type: ignore
|
|
138
|
+
|
|
139
|
+
@classmethod
|
|
140
|
+
def get_con_tipo_cambio_banxico(cls) -> list[MonedaDivisa]:
|
|
141
|
+
"""
|
|
142
|
+
Obtiene monedas con tipo de cambio publicado por Banxico.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Lista de monedas con tipo de cambio Banxico
|
|
146
|
+
|
|
147
|
+
Ejemplo:
|
|
148
|
+
>>> con_tc = MonedasDivisas.get_con_tipo_cambio_banxico()
|
|
149
|
+
>>> for m in con_tc:
|
|
150
|
+
... print(f"{m['codigo_iso']}: {m['moneda']}")
|
|
151
|
+
"""
|
|
152
|
+
cls._load_data()
|
|
153
|
+
return [m for m in cls._data if m["tipo_cambio_banxico"]] # type: ignore
|
|
154
|
+
|
|
155
|
+
@classmethod
|
|
156
|
+
def get_con_tipo_cambio_fix(cls) -> list[MonedaDivisa]:
|
|
157
|
+
"""
|
|
158
|
+
Obtiene monedas con tipo de cambio FIX.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Lista de monedas con tipo de cambio FIX
|
|
162
|
+
|
|
163
|
+
Ejemplo:
|
|
164
|
+
>>> fix = MonedasDivisas.get_con_tipo_cambio_fix()
|
|
165
|
+
>>> for m in fix:
|
|
166
|
+
... print(f"{m['codigo_iso']}: {m['moneda']}")
|
|
167
|
+
"""
|
|
168
|
+
cls._load_data()
|
|
169
|
+
return [m for m in cls._data if m.get("tipo_cambio_fix", False)] # type: ignore
|
|
170
|
+
|
|
171
|
+
@classmethod
|
|
172
|
+
def get_por_region(cls, region: str) -> list[MonedaDivisa]:
|
|
173
|
+
"""
|
|
174
|
+
Obtiene monedas de una región específica.
|
|
175
|
+
|
|
176
|
+
Regiones soportadas:
|
|
177
|
+
- America del Norte
|
|
178
|
+
- America Latina
|
|
179
|
+
- Europa
|
|
180
|
+
- Asia-Pacifico
|
|
181
|
+
- Africa
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
region: Nombre de la región
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
Lista de monedas de esa región
|
|
188
|
+
|
|
189
|
+
Ejemplo:
|
|
190
|
+
>>> latam = MonedasDivisas.get_por_region("America Latina")
|
|
191
|
+
>>> for m in latam:
|
|
192
|
+
... print(f"{m['codigo_iso']}: {m['pais']}")
|
|
193
|
+
"""
|
|
194
|
+
cls._load_data()
|
|
195
|
+
|
|
196
|
+
regiones: dict[str, list[str]] = {
|
|
197
|
+
"America del Norte": ["USD", "CAD", "MXN"],
|
|
198
|
+
"America Latina": ["ARS", "BRL", "CLP", "COP", "PEN", "GTQ", "CRC", "UYU", "VES"],
|
|
199
|
+
"Europa": ["EUR", "GBP", "CHF", "SEK", "NOK", "DKK", "RUB"],
|
|
200
|
+
"Asia-Pacifico": ["JPY", "CNY", "AUD", "NZD", "SGD", "HKD", "INR", "KRW"],
|
|
201
|
+
"Africa": ["ZAR"],
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
codigos = regiones.get(region, [])
|
|
205
|
+
return [m for m in cls._data if m["codigo_iso"] in codigos] # type: ignore
|
|
206
|
+
|
|
207
|
+
@classmethod
|
|
208
|
+
def get_principales(cls) -> list[MonedaDivisa]:
|
|
209
|
+
"""
|
|
210
|
+
Obtiene monedas principales para operaciones en México.
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
Lista de monedas principales (MXN, USD, EUR, CAD, GBP, JPY, CHF)
|
|
214
|
+
|
|
215
|
+
Ejemplo:
|
|
216
|
+
>>> principales = MonedasDivisas.get_principales()
|
|
217
|
+
>>> for m in principales:
|
|
218
|
+
... print(f"{m['codigo_iso']}: {m['moneda']}")
|
|
219
|
+
"""
|
|
220
|
+
cls._load_data()
|
|
221
|
+
principales = ["MXN", "USD", "EUR", "CAD", "GBP", "JPY", "CHF"]
|
|
222
|
+
return [m for m in cls._data if m["codigo_iso"] in principales] # type: ignore
|
|
223
|
+
|
|
224
|
+
@classmethod
|
|
225
|
+
def get_latam(cls) -> list[MonedaDivisa]:
|
|
226
|
+
"""
|
|
227
|
+
Obtiene monedas latinoamericanas.
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
Lista de monedas de América Latina
|
|
231
|
+
|
|
232
|
+
Ejemplo:
|
|
233
|
+
>>> latam = MonedasDivisas.get_latam()
|
|
234
|
+
>>> for m in latam:
|
|
235
|
+
... print(f"{m['codigo_iso']}: {m['pais']}")
|
|
236
|
+
"""
|
|
237
|
+
cls._load_data()
|
|
238
|
+
latam = ["MXN", "ARS", "BRL", "CLP", "COP", "PEN", "GTQ", "CRC", "UYU", "VES"]
|
|
239
|
+
return [m for m in cls._data if m["codigo_iso"] in latam] # type: ignore
|
|
240
|
+
|
|
241
|
+
@classmethod
|
|
242
|
+
def validar_codigo_iso(cls, codigo: str) -> bool:
|
|
243
|
+
"""
|
|
244
|
+
Valida código ISO de moneda.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
codigo: Código ISO a validar
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
True si el código existe, False en caso contrario
|
|
251
|
+
|
|
252
|
+
Ejemplo:
|
|
253
|
+
>>> MonedasDivisas.validar_codigo_iso("USD") # True
|
|
254
|
+
>>> MonedasDivisas.validar_codigo_iso("XXX") # False
|
|
255
|
+
"""
|
|
256
|
+
cls._load_data()
|
|
257
|
+
codigo_upper = codigo.upper()
|
|
258
|
+
return any(m["codigo_iso"].upper() == codigo_upper for m in cls._data) # type: ignore
|
|
259
|
+
|
|
260
|
+
@classmethod
|
|
261
|
+
def get_formato_moneda(cls, codigo_iso: str) -> dict[str, str | int] | None:
|
|
262
|
+
"""
|
|
263
|
+
Obtiene información de formato de moneda.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
codigo_iso: Código ISO de la moneda
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
Diccionario con símbolo, decimales y formato de ejemplo
|
|
270
|
+
|
|
271
|
+
Ejemplo:
|
|
272
|
+
>>> formato = MonedasDivisas.get_formato_moneda("USD")
|
|
273
|
+
>>> print(formato['formato_ejemplo']) # "US$ 1234.56"
|
|
274
|
+
"""
|
|
275
|
+
moneda = cls.get_por_codigo(codigo_iso)
|
|
276
|
+
if not moneda:
|
|
277
|
+
return None
|
|
278
|
+
|
|
279
|
+
ejemplo_monto = 1234.56
|
|
280
|
+
if moneda["decimales"] == 0:
|
|
281
|
+
monto_formateado = str(round(ejemplo_monto))
|
|
282
|
+
else:
|
|
283
|
+
monto_formateado = f"{ejemplo_monto:.{moneda['decimales']}f}"
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
"simbolo": moneda["simbolo"],
|
|
287
|
+
"decimales": moneda["decimales"],
|
|
288
|
+
"formato_ejemplo": f"{moneda['simbolo']} {monto_formateado}",
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
@classmethod
|
|
292
|
+
def formatear_monto(cls, monto: float, codigo_iso: str) -> str:
|
|
293
|
+
"""
|
|
294
|
+
Formatea monto en una moneda específica.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
monto: Monto a formatear
|
|
298
|
+
codigo_iso: Código ISO de la moneda
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
Monto formateado con símbolo de moneda
|
|
302
|
+
|
|
303
|
+
Ejemplo:
|
|
304
|
+
>>> MonedasDivisas.formatear_monto(1234.56, "USD")
|
|
305
|
+
"US$ 1,234.56"
|
|
306
|
+
>>> MonedasDivisas.formatear_monto(1234.56, "JPY")
|
|
307
|
+
"¥ 1,235"
|
|
308
|
+
"""
|
|
309
|
+
moneda = cls.get_por_codigo(codigo_iso)
|
|
310
|
+
if not moneda:
|
|
311
|
+
return str(monto)
|
|
312
|
+
|
|
313
|
+
if moneda["decimales"] == 0:
|
|
314
|
+
monto_formateado = f"{round(monto):,.0f}".replace(",", " ")
|
|
315
|
+
else:
|
|
316
|
+
monto_formateado = f"{monto:,.{moneda['decimales']}f}".replace(",", " ")
|
|
317
|
+
|
|
318
|
+
return f"{moneda['simbolo']} {monto_formateado}"
|
|
319
|
+
|
|
320
|
+
@classmethod
|
|
321
|
+
def get_mxn(cls) -> MonedaDivisa | None:
|
|
322
|
+
"""Obtiene peso mexicano (MXN)."""
|
|
323
|
+
return cls.get_por_codigo("MXN")
|
|
324
|
+
|
|
325
|
+
@classmethod
|
|
326
|
+
def get_usd(cls) -> MonedaDivisa | None:
|
|
327
|
+
"""Obtiene dólar estadounidense (USD)."""
|
|
328
|
+
return cls.get_por_codigo("USD")
|
|
329
|
+
|
|
330
|
+
@classmethod
|
|
331
|
+
def get_eur(cls) -> MonedaDivisa | None:
|
|
332
|
+
"""Obtiene euro (EUR)."""
|
|
333
|
+
return cls.get_por_codigo("EUR")
|
|
334
|
+
|
|
335
|
+
@classmethod
|
|
336
|
+
def buscar_por_nombre(cls, nombre: str) -> list[MonedaDivisa]:
|
|
337
|
+
"""
|
|
338
|
+
Busca monedas por nombre.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
nombre: Texto a buscar en el nombre de la moneda
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
Lista de monedas que coinciden
|
|
345
|
+
|
|
346
|
+
Ejemplo:
|
|
347
|
+
>>> dolares = MonedasDivisas.buscar_por_nombre("dólar")
|
|
348
|
+
>>> for m in dolares:
|
|
349
|
+
... print(f"{m['codigo_iso']}: {m['moneda']}")
|
|
350
|
+
"""
|
|
351
|
+
cls._load_data()
|
|
352
|
+
nombre_lower = nombre.lower()
|
|
353
|
+
return [m for m in cls._data if nombre_lower in m["moneda"].lower()] # type: ignore
|
|
354
|
+
|
|
355
|
+
@classmethod
|
|
356
|
+
def get_activas(cls) -> list[MonedaDivisa]:
|
|
357
|
+
"""
|
|
358
|
+
Obtiene monedas activas.
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
Lista de monedas activas
|
|
362
|
+
|
|
363
|
+
Ejemplo:
|
|
364
|
+
>>> activas = MonedasDivisas.get_activas()
|
|
365
|
+
>>> print(f"Monedas activas: {len(activas)}")
|
|
366
|
+
"""
|
|
367
|
+
cls._load_data()
|
|
368
|
+
return [m for m in cls._data if m["activa"]] # type: ignore
|
|
369
|
+
|
|
370
|
+
@classmethod
|
|
371
|
+
def get_info_tipo_cambio_fix(cls) -> dict[str, str]:
|
|
372
|
+
"""
|
|
373
|
+
Información del tipo de cambio FIX.
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
Diccionario con información del tipo de cambio FIX
|
|
377
|
+
|
|
378
|
+
Ejemplo:
|
|
379
|
+
>>> info = MonedasDivisas.get_info_tipo_cambio_fix()
|
|
380
|
+
>>> print(info['horario'])
|
|
381
|
+
"""
|
|
382
|
+
return {
|
|
383
|
+
"descripcion": "Tipo de cambio FIX determinado por Banco de México - Promedio ponderado de cotizaciones del mercado de cambios al mayoreo",
|
|
384
|
+
"horario": "12:00 hrs (mediodía) tiempo de la Ciudad de México",
|
|
385
|
+
"uso": "Referencia oficial para liquidación de obligaciones denominadas en dólares",
|
|
386
|
+
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"""
|
|
2
|
+
UDI (Unidades de Inversión) Catalog
|
|
3
|
+
|
|
4
|
+
This module provides access to UDI values from Banco de México.
|
|
5
|
+
UDIs are inflation-indexed investment units used in Mexico.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class UDICatalog:
|
|
14
|
+
"""
|
|
15
|
+
Catalog of UDI (Unidades de Inversión) values
|
|
16
|
+
|
|
17
|
+
UDIs are inflation-indexed investment units maintained by Banco de México.
|
|
18
|
+
They are commonly used for mortgage loans and other long-term financial obligations.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
_data: list[dict] | None = None
|
|
22
|
+
_by_fecha: dict[str, dict] | None = None
|
|
23
|
+
_mensual: dict[str, dict] | None = None
|
|
24
|
+
_anual: dict[int, dict] | None = None
|
|
25
|
+
_daily: list[dict] | None = None
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def _load_data(cls) -> None:
|
|
29
|
+
"""Load UDI data from JSON file"""
|
|
30
|
+
if cls._data is None:
|
|
31
|
+
# Path: catalogmx/packages/python/catalogmx/catalogs/banxico/udis.py
|
|
32
|
+
# Target: catalogmx/packages/shared-data/banxico/udis.json
|
|
33
|
+
current_file = Path(__file__)
|
|
34
|
+
shared_data_path = (
|
|
35
|
+
current_file.parent.parent.parent.parent.parent
|
|
36
|
+
/ "shared-data"
|
|
37
|
+
/ "banxico"
|
|
38
|
+
/ "udis.json"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
with open(shared_data_path, encoding="utf-8") as f:
|
|
42
|
+
cls._data = json.load(f)
|
|
43
|
+
|
|
44
|
+
if cls._by_fecha is not None:
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
cls._by_fecha = {}
|
|
48
|
+
cls._mensual = {}
|
|
49
|
+
cls._anual = {}
|
|
50
|
+
daily: list[dict] = []
|
|
51
|
+
|
|
52
|
+
for record in cls._data:
|
|
53
|
+
fecha = record.get("fecha")
|
|
54
|
+
if not fecha:
|
|
55
|
+
continue
|
|
56
|
+
|
|
57
|
+
existing = cls._by_fecha.get(fecha)
|
|
58
|
+
if existing is None or (
|
|
59
|
+
record.get("tipo") == "diario" and existing.get("tipo") != "diario"
|
|
60
|
+
):
|
|
61
|
+
cls._by_fecha[fecha] = record
|
|
62
|
+
|
|
63
|
+
if record.get("tipo") == "diario":
|
|
64
|
+
daily.append(record)
|
|
65
|
+
elif record.get("tipo") == "promedio_mensual":
|
|
66
|
+
key = f"{record.get('año')}-{int(record.get('mes', 0)):02d}"
|
|
67
|
+
cls._mensual[key] = record
|
|
68
|
+
elif record.get("tipo") == "promedio_anual":
|
|
69
|
+
cls._anual[int(record.get("año"))] = record
|
|
70
|
+
|
|
71
|
+
daily.sort(key=lambda r: r["fecha"])
|
|
72
|
+
cls._daily = daily
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
def get_data(cls) -> list[dict]:
|
|
76
|
+
"""
|
|
77
|
+
Get all UDI data
|
|
78
|
+
|
|
79
|
+
:return: List of all UDI records
|
|
80
|
+
"""
|
|
81
|
+
cls._load_data()
|
|
82
|
+
return cls._data.copy()
|
|
83
|
+
|
|
84
|
+
@classmethod
|
|
85
|
+
def _get_by_fecha(cls, fecha: str) -> dict | None:
|
|
86
|
+
cls._load_data()
|
|
87
|
+
record = cls._by_fecha.get(fecha)
|
|
88
|
+
if record:
|
|
89
|
+
return record
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
anio, mes, _dia = fecha.split("-")
|
|
93
|
+
mensual = cls._mensual.get(f"{int(anio)}-{int(mes):02d}") if cls._mensual else None
|
|
94
|
+
return mensual
|
|
95
|
+
except ValueError:
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
@classmethod
|
|
99
|
+
def get_por_fecha(cls, fecha: str) -> dict | None:
|
|
100
|
+
"""
|
|
101
|
+
Get UDI value for a specific date
|
|
102
|
+
|
|
103
|
+
:param fecha: Date string in YYYY-MM-DD format
|
|
104
|
+
:return: UDI record or None if not found
|
|
105
|
+
"""
|
|
106
|
+
record = cls._get_by_fecha(fecha)
|
|
107
|
+
return record.copy() if record else None
|
|
108
|
+
|
|
109
|
+
@classmethod
|
|
110
|
+
def get_por_mes(cls, anio: int, mes: int) -> dict | None:
|
|
111
|
+
"""
|
|
112
|
+
Get monthly average UDI value
|
|
113
|
+
|
|
114
|
+
:param anio: Year (e.g., 2024)
|
|
115
|
+
:param mes: Month (1-12)
|
|
116
|
+
:return: UDI record with monthly average or None if not found
|
|
117
|
+
"""
|
|
118
|
+
cls._load_data()
|
|
119
|
+
key = f"{anio}-{mes:02d}"
|
|
120
|
+
record = cls._mensual.get(key) if cls._mensual else None
|
|
121
|
+
return record.copy() if record else None
|
|
122
|
+
|
|
123
|
+
@classmethod
|
|
124
|
+
def get_promedio_anual(cls, anio: int) -> dict | None:
|
|
125
|
+
"""
|
|
126
|
+
Get annual average UDI value
|
|
127
|
+
|
|
128
|
+
:param anio: Year (e.g., 2024)
|
|
129
|
+
:return: UDI record with annual average or None if not found
|
|
130
|
+
"""
|
|
131
|
+
cls._load_data()
|
|
132
|
+
|
|
133
|
+
record = cls._anual.get(anio) if cls._anual else None
|
|
134
|
+
return record.copy() if record else None
|
|
135
|
+
|
|
136
|
+
@classmethod
|
|
137
|
+
def get_por_anio(cls, anio: int) -> list[dict]:
|
|
138
|
+
"""Return the daily UDI series for a given year."""
|
|
139
|
+
cls._load_data()
|
|
140
|
+
|
|
141
|
+
source = (
|
|
142
|
+
cls._daily
|
|
143
|
+
if cls._daily
|
|
144
|
+
else [r for r in cls._data if r.get("tipo") == "promedio_mensual"]
|
|
145
|
+
)
|
|
146
|
+
return [record.copy() for record in source if record.get("año") == anio]
|
|
147
|
+
|
|
148
|
+
@classmethod
|
|
149
|
+
def get_actual(cls) -> dict | None:
|
|
150
|
+
"""
|
|
151
|
+
Get most recent UDI value
|
|
152
|
+
|
|
153
|
+
:return: Latest UDI record
|
|
154
|
+
"""
|
|
155
|
+
cls._load_data()
|
|
156
|
+
|
|
157
|
+
if cls._daily:
|
|
158
|
+
return cls._daily[-1].copy()
|
|
159
|
+
|
|
160
|
+
if not cls._data:
|
|
161
|
+
return None
|
|
162
|
+
|
|
163
|
+
record = max(cls._data, key=lambda r: r.get("fecha", ""), default=None)
|
|
164
|
+
return record.copy() if record else None
|
|
165
|
+
|
|
166
|
+
@classmethod
|
|
167
|
+
def _get_valor_cercano(cls, fecha: str) -> dict | None:
|
|
168
|
+
cls._load_data()
|
|
169
|
+
objetivo = datetime.fromisoformat(fecha)
|
|
170
|
+
|
|
171
|
+
candidatos = (
|
|
172
|
+
cls._daily
|
|
173
|
+
if cls._daily
|
|
174
|
+
else [r for r in cls._data if r.get("tipo") == "promedio_mensual"]
|
|
175
|
+
)
|
|
176
|
+
if not candidatos:
|
|
177
|
+
return None
|
|
178
|
+
|
|
179
|
+
def _diff(record: dict) -> float:
|
|
180
|
+
return abs((datetime.fromisoformat(record["fecha"]) - objetivo).total_seconds())
|
|
181
|
+
|
|
182
|
+
return min(candidatos, key=_diff)
|
|
183
|
+
|
|
184
|
+
@classmethod
|
|
185
|
+
def pesos_a_udis(cls, pesos: float, fecha: str) -> float | None:
|
|
186
|
+
"""
|
|
187
|
+
Convert Mexican pesos to UDIs
|
|
188
|
+
|
|
189
|
+
:param pesos: Amount in Mexican pesos
|
|
190
|
+
:param fecha: Date string in YYYY-MM-DD format
|
|
191
|
+
:return: Amount in UDIs or None if UDI value not found
|
|
192
|
+
"""
|
|
193
|
+
record = cls.get_por_fecha(fecha)
|
|
194
|
+
if not record:
|
|
195
|
+
record = cls._get_valor_cercano(fecha)
|
|
196
|
+
if not record:
|
|
197
|
+
return None
|
|
198
|
+
|
|
199
|
+
valor_udi = record.get("valor")
|
|
200
|
+
if not valor_udi:
|
|
201
|
+
return None
|
|
202
|
+
|
|
203
|
+
return pesos / valor_udi
|
|
204
|
+
|
|
205
|
+
@classmethod
|
|
206
|
+
def udis_a_pesos(cls, udis: float, fecha: str) -> float | None:
|
|
207
|
+
"""
|
|
208
|
+
Convert UDIs to Mexican pesos
|
|
209
|
+
|
|
210
|
+
:param udis: Amount in UDIs
|
|
211
|
+
:param fecha: Date string in YYYY-MM-DD format
|
|
212
|
+
:return: Amount in Mexican pesos or None if UDI value not found
|
|
213
|
+
"""
|
|
214
|
+
record = cls.get_por_fecha(fecha)
|
|
215
|
+
if not record:
|
|
216
|
+
record = cls._get_valor_cercano(fecha)
|
|
217
|
+
if not record:
|
|
218
|
+
return None
|
|
219
|
+
|
|
220
|
+
valor_udi = record.get("valor")
|
|
221
|
+
if not valor_udi:
|
|
222
|
+
return None
|
|
223
|
+
|
|
224
|
+
return udis * valor_udi
|
|
225
|
+
|
|
226
|
+
@classmethod
|
|
227
|
+
def calcular_variacion(cls, fecha_inicio: str, fecha_fin: str) -> float | None:
|
|
228
|
+
"""
|
|
229
|
+
Calculate percentage variation between two dates
|
|
230
|
+
|
|
231
|
+
:param fecha_inicio: Start date (YYYY-MM-DD)
|
|
232
|
+
:param fecha_fin: End date (YYYY-MM-DD)
|
|
233
|
+
:return: Percentage variation or None if values not found
|
|
234
|
+
"""
|
|
235
|
+
record_inicio = cls.get_por_fecha(fecha_inicio) or cls._get_valor_cercano(fecha_inicio)
|
|
236
|
+
record_fin = cls.get_por_fecha(fecha_fin) or cls._get_valor_cercano(fecha_fin)
|
|
237
|
+
|
|
238
|
+
if not record_inicio or not record_fin:
|
|
239
|
+
return None
|
|
240
|
+
|
|
241
|
+
valor_inicio = record_inicio.get("valor")
|
|
242
|
+
valor_fin = record_fin.get("valor")
|
|
243
|
+
|
|
244
|
+
if not valor_inicio or not valor_fin:
|
|
245
|
+
return None
|
|
246
|
+
|
|
247
|
+
return ((valor_fin - valor_inicio) / valor_inicio) * 100
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
# Convenience functions
|
|
251
|
+
def get_udi_actual() -> dict | None:
|
|
252
|
+
"""Get most recent UDI value"""
|
|
253
|
+
record = UDICatalog.get_actual()
|
|
254
|
+
return record.copy() if record else None
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def get_udi_por_fecha(fecha: str) -> dict | None:
|
|
258
|
+
"""Get UDI value for a specific date"""
|
|
259
|
+
return UDICatalog.get_por_fecha(fecha)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def pesos_a_udis(pesos: float, fecha: str) -> float | None:
|
|
263
|
+
"""Convert pesos to UDIs"""
|
|
264
|
+
return UDICatalog.pesos_a_udis(pesos, fecha)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def udis_a_pesos(udis: float, fecha: str) -> float | None:
|
|
268
|
+
"""Convert UDIs to pesos"""
|
|
269
|
+
return UDICatalog.udis_a_pesos(udis, fecha)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
# Export commonly used functions and classes
|
|
273
|
+
__all__ = [
|
|
274
|
+
"UDICatalog",
|
|
275
|
+
"get_udi_actual",
|
|
276
|
+
"get_udi_por_fecha",
|
|
277
|
+
"pesos_a_udis",
|
|
278
|
+
"udis_a_pesos",
|
|
279
|
+
]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""
|
|
2
|
+
catalogmx.catalogs.ift - Catálogos del IFT
|
|
3
|
+
|
|
4
|
+
Catálogos del Instituto Federal de Telecomunicaciones:
|
|
5
|
+
- CodigosLADACatalog: Plan de numeración telefónica (códigos LADA)
|
|
6
|
+
- OperadoresMovilesCatalog: Operadores de telefonía móvil
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .codigos_lada import CodigosLADACatalog
|
|
10
|
+
from .operadores_moviles import OperadoresMovilesCatalog
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"CodigosLADACatalog",
|
|
14
|
+
"OperadoresMovilesCatalog",
|
|
15
|
+
]
|