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.
Files changed (81) hide show
  1. catalogmx/__init__.py +56 -0
  2. catalogmx/catalogs/__init__.py +5 -0
  3. catalogmx/catalogs/banxico/__init__.py +24 -0
  4. catalogmx/catalogs/banxico/banks.py +136 -0
  5. catalogmx/catalogs/banxico/codigos_plaza.py +287 -0
  6. catalogmx/catalogs/banxico/instituciones_financieras.py +338 -0
  7. catalogmx/catalogs/banxico/monedas_divisas.py +386 -0
  8. catalogmx/catalogs/banxico/udis.py +279 -0
  9. catalogmx/catalogs/ift/__init__.py +15 -0
  10. catalogmx/catalogs/ift/codigos_lada.py +426 -0
  11. catalogmx/catalogs/ift/operadores_moviles.py +315 -0
  12. catalogmx/catalogs/inegi/__init__.py +21 -0
  13. catalogmx/catalogs/inegi/localidades.py +207 -0
  14. catalogmx/catalogs/inegi/municipios.py +73 -0
  15. catalogmx/catalogs/inegi/municipios_completo.py +236 -0
  16. catalogmx/catalogs/inegi/states.py +148 -0
  17. catalogmx/catalogs/mexico/__init__.py +17 -0
  18. catalogmx/catalogs/mexico/hoy_no_circula.py +215 -0
  19. catalogmx/catalogs/mexico/placas_formatos.py +184 -0
  20. catalogmx/catalogs/mexico/salarios_minimos.py +156 -0
  21. catalogmx/catalogs/mexico/uma.py +207 -0
  22. catalogmx/catalogs/sat/__init__.py +13 -0
  23. catalogmx/catalogs/sat/carta_porte/__init__.py +19 -0
  24. catalogmx/catalogs/sat/carta_porte/aeropuertos.py +76 -0
  25. catalogmx/catalogs/sat/carta_porte/carreteras.py +59 -0
  26. catalogmx/catalogs/sat/carta_porte/config_autotransporte.py +54 -0
  27. catalogmx/catalogs/sat/carta_porte/material_peligroso.py +66 -0
  28. catalogmx/catalogs/sat/carta_porte/puertos_maritimos.py +63 -0
  29. catalogmx/catalogs/sat/carta_porte/tipo_embalaje.py +48 -0
  30. catalogmx/catalogs/sat/carta_porte/tipo_permiso.py +54 -0
  31. catalogmx/catalogs/sat/cfdi_4/__init__.py +42 -0
  32. catalogmx/catalogs/sat/cfdi_4/clave_prod_serv.py +383 -0
  33. catalogmx/catalogs/sat/cfdi_4/clave_unidad.py +298 -0
  34. catalogmx/catalogs/sat/cfdi_4/exportacion.py +45 -0
  35. catalogmx/catalogs/sat/cfdi_4/forma_pago.py +45 -0
  36. catalogmx/catalogs/sat/cfdi_4/impuesto.py +57 -0
  37. catalogmx/catalogs/sat/cfdi_4/meses.py +34 -0
  38. catalogmx/catalogs/sat/cfdi_4/metodo_pago.py +45 -0
  39. catalogmx/catalogs/sat/cfdi_4/objeto_imp.py +45 -0
  40. catalogmx/catalogs/sat/cfdi_4/periodicidad.py +34 -0
  41. catalogmx/catalogs/sat/cfdi_4/regimen_fiscal.py +57 -0
  42. catalogmx/catalogs/sat/cfdi_4/tasa_o_cuota.py +42 -0
  43. catalogmx/catalogs/sat/cfdi_4/tipo_comprobante.py +45 -0
  44. catalogmx/catalogs/sat/cfdi_4/tipo_factor.py +34 -0
  45. catalogmx/catalogs/sat/cfdi_4/tipo_relacion.py +45 -0
  46. catalogmx/catalogs/sat/cfdi_4/uso_cfdi.py +45 -0
  47. catalogmx/catalogs/sat/comercio_exterior/__init__.py +39 -0
  48. catalogmx/catalogs/sat/comercio_exterior/claves_pedimento.py +77 -0
  49. catalogmx/catalogs/sat/comercio_exterior/estados.py +122 -0
  50. catalogmx/catalogs/sat/comercio_exterior/incoterms.py +226 -0
  51. catalogmx/catalogs/sat/comercio_exterior/monedas.py +107 -0
  52. catalogmx/catalogs/sat/comercio_exterior/motivos_traslado.py +54 -0
  53. catalogmx/catalogs/sat/comercio_exterior/paises.py +88 -0
  54. catalogmx/catalogs/sat/comercio_exterior/registro_ident_trib.py +76 -0
  55. catalogmx/catalogs/sat/comercio_exterior/unidades_aduana.py +54 -0
  56. catalogmx/catalogs/sat/comercio_exterior/validator.py +212 -0
  57. catalogmx/catalogs/sat/nomina/__init__.py +19 -0
  58. catalogmx/catalogs/sat/nomina/banco.py +50 -0
  59. catalogmx/catalogs/sat/nomina/periodicidad_pago.py +48 -0
  60. catalogmx/catalogs/sat/nomina/riesgo_puesto.py +56 -0
  61. catalogmx/catalogs/sat/nomina/tipo_contrato.py +47 -0
  62. catalogmx/catalogs/sat/nomina/tipo_jornada.py +42 -0
  63. catalogmx/catalogs/sat/nomina/tipo_nomina.py +52 -0
  64. catalogmx/catalogs/sat/nomina/tipo_regimen.py +47 -0
  65. catalogmx/catalogs/sepomex/__init__.py +5 -0
  66. catalogmx/catalogs/sepomex/codigos_postales.py +184 -0
  67. catalogmx/cli.py +185 -0
  68. catalogmx/helpers.py +324 -0
  69. catalogmx/utils/text.py +55 -0
  70. catalogmx/validators/__init__.py +0 -0
  71. catalogmx/validators/clabe.py +233 -0
  72. catalogmx/validators/curp.py +623 -0
  73. catalogmx/validators/nss.py +255 -0
  74. catalogmx/validators/rfc.py +1004 -0
  75. catalogmx-0.3.0.dist-info/METADATA +644 -0
  76. catalogmx-0.3.0.dist-info/RECORD +81 -0
  77. catalogmx-0.3.0.dist-info/WHEEL +5 -0
  78. catalogmx-0.3.0.dist-info/entry_points.txt +2 -0
  79. catalogmx-0.3.0.dist-info/licenses/AUTHORS.rst +5 -0
  80. catalogmx-0.3.0.dist-info/licenses/LICENSE +19 -0
  81. catalogmx-0.3.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,426 @@
1
+ """
2
+ Catálogo de códigos LADA (plan de numeración telefónica) en México (IFT)
3
+
4
+ Este módulo proporciona acceso al catálogo completo de códigos LADA
5
+ del Instituto Federal de Telecomunicaciones (IFT).
6
+ """
7
+
8
+ import json
9
+ from pathlib import Path
10
+ from typing import TypedDict
11
+
12
+ from catalogmx.utils.text import normalize_text
13
+
14
+
15
+ class CodigoLADA(TypedDict):
16
+ """Estructura de un código LADA"""
17
+
18
+ lada: str
19
+ ciudad: str
20
+ estado: str
21
+ tipo: str # metropolitana | fronteriza | turistica | normal
22
+ region: str
23
+
24
+
25
+ class ValidacionNumero(TypedDict):
26
+ """Resultado de validación de número telefónico"""
27
+
28
+ valid: bool
29
+ lada: str | None
30
+ numero_local: str | None
31
+ ciudad: str | None
32
+ estado: str | None
33
+ error: str | None
34
+
35
+
36
+ class InfoNumero(TypedDict):
37
+ """Información detallada de un número telefónico"""
38
+
39
+ lada: str
40
+ ciudad: str
41
+ estado: str
42
+ tipo: str
43
+ region: str
44
+
45
+
46
+ class CodigosLADACatalog:
47
+ """
48
+ Catálogo de códigos LADA de México.
49
+
50
+ Proporciona métodos para búsqueda, validación y formato de números telefónicos.
51
+
52
+ Características:
53
+ - 231+ códigos LADA (expandible a 397 según plan IFT)
54
+ - Cobertura nacional (32 estados)
55
+ - Clasificación por tipo (metropolitana, fronteriza, turística, normal)
56
+ - Validación de números telefónicos de 10 dígitos
57
+ - Formateo automático de números
58
+
59
+ Ejemplo:
60
+ >>> from catalogmx.catalogs.ift import CodigosLADACatalog
61
+ >>>
62
+ >>> # Buscar por LADA
63
+ >>> codigo = CodigosLADACatalog.buscar_por_lada("33")
64
+ >>> print(codigo['ciudad']) # "Guadalajara"
65
+ >>>
66
+ >>> # Validar número telefónico
67
+ >>> info = CodigosLADACatalog.validar_numero("3312345678")
68
+ >>> print(info['valid']) # True
69
+ >>> print(info['ciudad']) # "Guadalajara"
70
+ """
71
+
72
+ _data: list[CodigoLADA] | None = None
73
+ _by_lada: dict[str, CodigoLADA] | None = None
74
+ _by_estado: dict[str, list[CodigoLADA]] | None = None
75
+ _by_tipo: dict[str, list[CodigoLADA]] | None = None
76
+ _by_region: dict[str, list[CodigoLADA]] | None = None
77
+
78
+ @classmethod
79
+ def _load_data(cls) -> None:
80
+ """Carga lazy de datos desde JSON"""
81
+ if cls._data is not None:
82
+ return
83
+
84
+ # Path: catalogmx/packages/python/catalogmx/catalogs/ift/codigos_lada.py
85
+ # Target: catalogmx/packages/shared-data/ift/codigos_lada.json
86
+ data_path = (
87
+ Path(__file__).parent.parent.parent.parent.parent
88
+ / "shared-data"
89
+ / "ift"
90
+ / "codigos_lada.json"
91
+ )
92
+
93
+ with open(data_path, encoding="utf-8") as f:
94
+ json_data = json.load(f)
95
+ cls._data = json_data["codigos"]
96
+
97
+ # Crear índices para búsquedas rápidas
98
+ cls._by_lada = {item["lada"]: item for item in cls._data}
99
+
100
+ # Índice por estado
101
+ cls._by_estado = {}
102
+ for item in cls._data:
103
+ estado = item["estado"].lower()
104
+ if estado not in cls._by_estado:
105
+ cls._by_estado[estado] = []
106
+ cls._by_estado[estado].append(item)
107
+
108
+ # Índice por tipo
109
+ cls._by_tipo = {}
110
+ for item in cls._data:
111
+ tipo = item["tipo"]
112
+ if tipo not in cls._by_tipo:
113
+ cls._by_tipo[tipo] = []
114
+ cls._by_tipo[tipo].append(item)
115
+
116
+ # Índice por región
117
+ cls._by_region = {}
118
+ for item in cls._data:
119
+ region = item["region"].lower()
120
+ if region not in cls._by_region:
121
+ cls._by_region[region] = []
122
+ cls._by_region[region].append(item)
123
+
124
+ @classmethod
125
+ def get_all(cls) -> list[CodigoLADA]:
126
+ """
127
+ Obtiene todos los códigos LADA.
128
+
129
+ Returns:
130
+ Lista completa de códigos LADA
131
+ """
132
+ cls._load_data()
133
+ return cls._data.copy() # type: ignore
134
+
135
+ @classmethod
136
+ def buscar_por_lada(cls, lada: str) -> CodigoLADA | None:
137
+ """
138
+ Busca un código LADA específico.
139
+
140
+ Args:
141
+ lada: Código LADA a buscar (ej: "33", "55", "664")
142
+
143
+ Returns:
144
+ Información del código LADA o None si no existe
145
+
146
+ Ejemplo:
147
+ >>> codigo = CodigosLADACatalog.buscar_por_lada("33")
148
+ >>> print(codigo['ciudad']) # "Guadalajara"
149
+ >>> print(codigo['estado']) # "Jalisco"
150
+ """
151
+ cls._load_data()
152
+ return cls._by_lada.get(lada) # type: ignore
153
+
154
+ @classmethod
155
+ def buscar_por_ciudad(cls, ciudad: str) -> list[CodigoLADA]:
156
+ """
157
+ Busca códigos LADA por nombre de ciudad (búsqueda parcial, insensible a acentos).
158
+
159
+ Args:
160
+ ciudad: Nombre o parte del nombre de la ciudad
161
+
162
+ Returns:
163
+ Lista de códigos LADA que coinciden
164
+
165
+ Ejemplo:
166
+ >>> # Búsqueda con o sin acentos funciona igual
167
+ >>> codigos = CodigosLADACatalog.buscar_por_ciudad("san jose")
168
+ >>> codigos = CodigosLADACatalog.buscar_por_ciudad("san josé") # mismo resultado
169
+ >>> for codigo in codigos:
170
+ ... print(f"{codigo['lada']} - {codigo['ciudad']}")
171
+ """
172
+ cls._load_data()
173
+ ciudad_normalized = normalize_text(ciudad)
174
+ return [
175
+ item
176
+ for item in cls._data # type: ignore
177
+ if ciudad_normalized in normalize_text(item["ciudad"])
178
+ ]
179
+
180
+ @classmethod
181
+ def get_por_estado(cls, estado: str) -> list[CodigoLADA]:
182
+ """
183
+ Obtiene todos los códigos LADA de un estado.
184
+
185
+ Args:
186
+ estado: Nombre del estado (ej: "Jalisco", "CDMX")
187
+
188
+ Returns:
189
+ Lista de códigos LADA del estado
190
+
191
+ Ejemplo:
192
+ >>> codigos = CodigosLADACatalog.get_por_estado("Jalisco")
193
+ >>> print(f"Jalisco tiene {len(codigos)} códigos LADA")
194
+ """
195
+ cls._load_data()
196
+ estado_lower = estado.lower()
197
+ return cls._by_estado.get(estado_lower, []).copy() # type: ignore
198
+
199
+ @classmethod
200
+ def get_por_tipo(cls, tipo: str) -> list[CodigoLADA]:
201
+ """
202
+ Obtiene códigos LADA por tipo.
203
+
204
+ Args:
205
+ tipo: Tipo de código ("metropolitana", "fronteriza", "turistica", "normal")
206
+
207
+ Returns:
208
+ Lista de códigos del tipo especificado
209
+
210
+ Ejemplo:
211
+ >>> metropolitanas = CodigosLADACatalog.get_por_tipo("metropolitana")
212
+ >>> for codigo in metropolitanas:
213
+ ... print(f"{codigo['lada']} - {codigo['ciudad']}")
214
+ """
215
+ cls._load_data()
216
+ return cls._by_tipo.get(tipo, []).copy() # type: ignore
217
+
218
+ @classmethod
219
+ def get_por_region(cls, region: str) -> list[CodigoLADA]:
220
+ """
221
+ Obtiene códigos LADA por región geográfica.
222
+
223
+ Args:
224
+ region: Región ("noroeste", "norte", "noreste", "occidente",
225
+ "centro", "golfo", "sur", "sureste")
226
+
227
+ Returns:
228
+ Lista de códigos de la región
229
+
230
+ Ejemplo:
231
+ >>> codigos_norte = CodigosLADACatalog.get_por_region("norte")
232
+ >>> print(f"Región norte: {len(codigos_norte)} códigos")
233
+ """
234
+ cls._load_data()
235
+ region_lower = region.lower()
236
+ return cls._by_region.get(region_lower, []).copy() # type: ignore
237
+
238
+ @classmethod
239
+ def get_metropolitanas(cls) -> list[CodigoLADA]:
240
+ """
241
+ Obtiene códigos LADA de zonas metropolitanas.
242
+
243
+ Returns:
244
+ Lista de códigos metropolitanos
245
+ """
246
+ return cls.get_por_tipo("metropolitana")
247
+
248
+ @classmethod
249
+ def get_fronterizas(cls) -> list[CodigoLADA]:
250
+ """
251
+ Obtiene códigos LADA de ciudades fronterizas.
252
+
253
+ Returns:
254
+ Lista de códigos fronterizos
255
+ """
256
+ return cls.get_por_tipo("fronteriza")
257
+
258
+ @classmethod
259
+ def get_turisticas(cls) -> list[CodigoLADA]:
260
+ """
261
+ Obtiene códigos LADA de destinos turísticos.
262
+
263
+ Returns:
264
+ Lista de códigos turísticos
265
+ """
266
+ return cls.get_por_tipo("turistica")
267
+
268
+ @classmethod
269
+ def validar_numero(cls, numero: str) -> ValidacionNumero:
270
+ """
271
+ Valida y analiza un número telefónico mexicano.
272
+
273
+ Desde agosto 2019, México usa un plan de marcación cerrado de 10 dígitos.
274
+ Este método valida el formato y extrae información geográfica.
275
+
276
+ Args:
277
+ numero: Número telefónico (puede contener espacios o guiones)
278
+
279
+ Returns:
280
+ Diccionario con validación e información del número
281
+
282
+ Ejemplo:
283
+ >>> info = CodigosLADACatalog.validar_numero("33 1234 5678")
284
+ >>> if info['valid']:
285
+ ... print(f"LADA: {info['lada']}")
286
+ ... print(f"Ciudad: {info['ciudad']}")
287
+ ... print(f"Número local: {info['numero_local']}")
288
+ """
289
+ cls._load_data()
290
+
291
+ # Limpiar número (eliminar espacios y guiones)
292
+ numero_limpio = numero.replace(" ", "").replace("-", "")
293
+
294
+ # Validar que sean 10 dígitos
295
+ if not numero_limpio.isdigit() or len(numero_limpio) != 10:
296
+ return {
297
+ "valid": False,
298
+ "lada": None,
299
+ "numero_local": None,
300
+ "ciudad": None,
301
+ "estado": None,
302
+ "error": "El número debe tener exactamente 10 dígitos",
303
+ }
304
+
305
+ # Intentar extraer LADA (primeros 2 o 3 dígitos)
306
+ # Primero intentar con 3 dígitos
307
+ lada = numero_limpio[:3]
308
+ codigo = cls._by_lada.get(lada) # type: ignore
309
+
310
+ # Si no se encuentra, intentar con 2 dígitos
311
+ if not codigo:
312
+ lada = numero_limpio[:2]
313
+ codigo = cls._by_lada.get(lada) # type: ignore
314
+
315
+ if codigo:
316
+ numero_local = numero_limpio[len(lada) :]
317
+ return {
318
+ "valid": True,
319
+ "lada": codigo["lada"],
320
+ "numero_local": numero_local,
321
+ "ciudad": codigo["ciudad"],
322
+ "estado": codigo["estado"],
323
+ "error": None,
324
+ }
325
+
326
+ return {
327
+ "valid": False,
328
+ "lada": lada,
329
+ "numero_local": None,
330
+ "ciudad": None,
331
+ "estado": None,
332
+ "error": f"Código LADA {lada} no encontrado en el catálogo",
333
+ }
334
+
335
+ @classmethod
336
+ def formatear_numero(cls, numero: str) -> str:
337
+ """
338
+ Formatea un número telefónico al formato estándar.
339
+
340
+ Args:
341
+ numero: Número telefónico sin formato
342
+
343
+ Returns:
344
+ Número formateado (LADA XXXX XXXX)
345
+
346
+ Ejemplo:
347
+ >>> formateado = CodigosLADACatalog.formatear_numero("3312345678")
348
+ >>> print(formateado) # "33 1234 5678"
349
+ """
350
+ validacion = cls.validar_numero(numero)
351
+
352
+ if not validacion["valid"] or not validacion["lada"] or not validacion["numero_local"]:
353
+ return numero
354
+
355
+ lada = validacion["lada"]
356
+ local = validacion["numero_local"]
357
+
358
+ # Formato: LADA XXXX XXXX
359
+ if len(local) == 7:
360
+ return f"{lada} {local[:3]} {local[3:]}"
361
+ elif len(local) == 8:
362
+ return f"{lada} {local[:4]} {local[4:]}"
363
+ else:
364
+ return f"{lada} {local}"
365
+
366
+ @classmethod
367
+ def get_info_numero(cls, numero: str) -> InfoNumero | None:
368
+ """
369
+ Obtiene información completa de un número telefónico.
370
+
371
+ Args:
372
+ numero: Número telefónico
373
+
374
+ Returns:
375
+ Información completa del número o None si es inválido
376
+
377
+ Ejemplo:
378
+ >>> info = CodigosLADACatalog.get_info_numero("3312345678")
379
+ >>> if info:
380
+ ... print(f"Ciudad: {info['ciudad']}")
381
+ ... print(f"Estado: {info['estado']}")
382
+ ... print(f"Tipo: {info['tipo']}")
383
+ ... print(f"Región: {info['region']}")
384
+ """
385
+ validacion = cls.validar_numero(numero)
386
+
387
+ if not validacion["valid"] or not validacion["lada"]:
388
+ return None
389
+
390
+ codigo = cls.buscar_por_lada(validacion["lada"])
391
+
392
+ if not codigo:
393
+ return None
394
+
395
+ return {
396
+ "lada": codigo["lada"],
397
+ "ciudad": codigo["ciudad"],
398
+ "estado": codigo["estado"],
399
+ "tipo": codigo["tipo"],
400
+ "region": codigo["region"],
401
+ }
402
+
403
+ @classmethod
404
+ def get_estadisticas(cls) -> dict[str, int | dict]:
405
+ """
406
+ Obtiene estadísticas del catálogo.
407
+
408
+ Returns:
409
+ Diccionario con estadísticas del catálogo
410
+
411
+ Ejemplo:
412
+ >>> stats = CodigosLADACatalog.get_estadisticas()
413
+ >>> print(f"Total códigos: {stats['total_codigos']}")
414
+ >>> print(f"Estados: {stats['estados_cubiertos']}")
415
+ """
416
+ cls._load_data()
417
+
418
+ return {
419
+ "total_codigos": len(cls._data), # type: ignore
420
+ "codigos_metropolitanos": len(cls.get_metropolitanas()),
421
+ "codigos_fronterizos": len(cls.get_fronterizas()),
422
+ "codigos_turisticos": len(cls.get_turisticas()),
423
+ "estados_cubiertos": len(cls._by_estado), # type: ignore
424
+ "regiones": list(cls._by_region.keys()), # type: ignore
425
+ "tipos": list(cls._by_tipo.keys()), # type: ignore
426
+ }