catalogmx 0.3.0__py3-none-any.whl → 0.4.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 +133 -19
- catalogmx/calculators/__init__.py +113 -0
- catalogmx/calculators/costo_trabajador.py +213 -0
- catalogmx/calculators/impuestos.py +920 -0
- catalogmx/calculators/imss.py +370 -0
- catalogmx/calculators/isr.py +290 -0
- catalogmx/calculators/resico.py +154 -0
- catalogmx/catalogs/banxico/__init__.py +29 -3
- catalogmx/catalogs/banxico/cetes_sqlite.py +279 -0
- catalogmx/catalogs/banxico/inflacion_sqlite.py +302 -0
- catalogmx/catalogs/banxico/salarios_minimos_sqlite.py +295 -0
- catalogmx/catalogs/banxico/tiie_sqlite.py +279 -0
- catalogmx/catalogs/banxico/tipo_cambio_usd_sqlite.py +255 -0
- catalogmx/catalogs/banxico/udis_sqlite.py +332 -0
- catalogmx/catalogs/cnbv/__init__.py +9 -0
- catalogmx/catalogs/cnbv/sectores.py +173 -0
- catalogmx/catalogs/conapo/__init__.py +15 -0
- catalogmx/catalogs/conapo/sistema_urbano_nacional.py +50 -0
- catalogmx/catalogs/conapo/zonas_metropolitanas.py +230 -0
- catalogmx/catalogs/ift/__init__.py +1 -1
- catalogmx/catalogs/ift/codigos_lada.py +517 -313
- catalogmx/catalogs/inegi/__init__.py +17 -0
- catalogmx/catalogs/inegi/scian.py +127 -0
- catalogmx/catalogs/mexico/__init__.py +2 -0
- catalogmx/catalogs/mexico/giros_mercantiles.py +119 -0
- catalogmx/catalogs/sat/carta_porte/material_peligroso.py +5 -1
- catalogmx/catalogs/sat/cfdi_4/clave_prod_serv.py +78 -0
- catalogmx/catalogs/sat/cfdi_4/tasa_o_cuota.py +2 -1
- catalogmx/catalogs/sepomex/__init__.py +2 -1
- catalogmx/catalogs/sepomex/codigos_postales.py +30 -2
- catalogmx/catalogs/sepomex/codigos_postales_completo.py +261 -0
- catalogmx/cli.py +12 -9
- catalogmx/data/__init__.py +10 -0
- catalogmx/data/mexico_dynamic.sqlite3 +0 -0
- catalogmx/data/updater.py +362 -0
- catalogmx/generators/__init__.py +20 -0
- catalogmx/generators/identity.py +582 -0
- catalogmx/helpers.py +177 -3
- catalogmx/utils/__init__.py +29 -0
- catalogmx/utils/clabe_utils.py +417 -0
- catalogmx/utils/text.py +7 -1
- catalogmx/validators/clabe.py +52 -2
- catalogmx/validators/nss.py +32 -27
- catalogmx/validators/rfc.py +185 -52
- catalogmx-0.4.0.dist-info/METADATA +905 -0
- {catalogmx-0.3.0.dist-info → catalogmx-0.4.0.dist-info}/RECORD +51 -25
- {catalogmx-0.3.0.dist-info → catalogmx-0.4.0.dist-info}/WHEEL +1 -1
- catalogmx/catalogs/banxico/udis.py +0 -279
- catalogmx-0.3.0.dist-info/METADATA +0 -644
- {catalogmx-0.3.0.dist-info → catalogmx-0.4.0.dist-info}/entry_points.txt +0 -0
- {catalogmx-0.3.0.dist-info → catalogmx-0.4.0.dist-info}/licenses/AUTHORS.rst +0 -0
- {catalogmx-0.3.0.dist-info → catalogmx-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {catalogmx-0.3.0.dist-info → catalogmx-0.4.0.dist-info}/top_level.txt +0 -0
|
@@ -1,426 +1,630 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Catálogo de
|
|
2
|
+
Catálogo de Códigos LADA de México
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
Basado en el Plan de Numeración Nacional del IFT (Instituto Federal de Telecomunicaciones).
|
|
5
|
+
Incluye mapeo geográfico a códigos INEGI y zonas metropolitanas.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import json
|
|
9
|
+
import random
|
|
9
10
|
from pathlib import Path
|
|
10
|
-
from typing import TypedDict
|
|
11
11
|
|
|
12
|
-
from catalogmx.utils.text import normalize_text
|
|
13
12
|
|
|
13
|
+
class CodigosLADACatalog:
|
|
14
|
+
"""Catálogo de códigos LADA con mapeo geográfico"""
|
|
15
|
+
|
|
16
|
+
_data: list[dict] | None = None
|
|
17
|
+
_by_lada: dict[str, dict] | None = None
|
|
18
|
+
_by_municipio: dict[tuple[str, str], list[dict]] | None = None
|
|
19
|
+
_by_entidad: dict[str, list[dict]] | None = None
|
|
20
|
+
|
|
21
|
+
# Zonas metropolitanas principales (LADA compartida entre municipios de distintos estados)
|
|
22
|
+
ZONAS_METROPOLITANAS: dict[str, dict] = {
|
|
23
|
+
"55": { # CDMX - Valle de México
|
|
24
|
+
"nombre": "Zona Metropolitana del Valle de México",
|
|
25
|
+
"municipios": [
|
|
26
|
+
# CDMX (todos los municipios/alcaldías)
|
|
27
|
+
("09", None), # Toda la entidad 09
|
|
28
|
+
# Estado de México - municipios de Zona 5 IFT (60 municipios)
|
|
29
|
+
("15", "002"), # Acolman
|
|
30
|
+
("15", "009"), # Amecameca
|
|
31
|
+
("15", "010"), # Apaxco
|
|
32
|
+
("15", "011"), # Atenco
|
|
33
|
+
("15", "013"), # Atizapán de Zaragoza
|
|
34
|
+
("15", "015"), # Atlautla
|
|
35
|
+
("15", "016"), # Axapusco
|
|
36
|
+
("15", "017"), # Ayapango
|
|
37
|
+
("15", "020"), # Coacalco de Berriozábal
|
|
38
|
+
("15", "022"), # Cocotitlán
|
|
39
|
+
("15", "023"), # Coyotepec
|
|
40
|
+
("15", "024"), # Cuautitlán
|
|
41
|
+
("15", "025"), # Chalco
|
|
42
|
+
("15", "026"), # Chapa de Mota
|
|
43
|
+
("15", "028"), # Chiautla
|
|
44
|
+
("15", "029"), # Chicoloapan
|
|
45
|
+
("15", "030"), # Chiconcuac
|
|
46
|
+
("15", "031"), # Chimalhuacán
|
|
47
|
+
("15", "033"), # Ecatepec de Morelos
|
|
48
|
+
("15", "034"), # Ecatzingo
|
|
49
|
+
("15", "035"), # Huehuetoca
|
|
50
|
+
("15", "036"), # Hueypoxtla
|
|
51
|
+
("15", "037"), # Huixquilucan
|
|
52
|
+
("15", "038"), # Isidro Fabela
|
|
53
|
+
("15", "039"), # Ixtapaluca
|
|
54
|
+
("15", "044"), # Jaltenco
|
|
55
|
+
("15", "046"), # Jilotzingo
|
|
56
|
+
("15", "050"), # Juchitepec
|
|
57
|
+
("15", "053"), # Melchor Ocampo
|
|
58
|
+
("15", "057"), # Naucalpan de Juárez
|
|
59
|
+
("15", "058"), # Nezahualcóyotl
|
|
60
|
+
("15", "059"), # Nextlalpan
|
|
61
|
+
("15", "060"), # Nicolás Romero
|
|
62
|
+
("15", "061"), # Nopaltepec
|
|
63
|
+
("15", "065"), # Otumba
|
|
64
|
+
("15", "068"), # Ozumba
|
|
65
|
+
("15", "069"), # Papalotla
|
|
66
|
+
("15", "070"), # La Paz
|
|
67
|
+
("15", "075"), # San Martín de las Pirámides
|
|
68
|
+
("15", "081"), # Tecámac
|
|
69
|
+
("15", "083"), # Temamatla
|
|
70
|
+
("15", "084"), # Temascalapa
|
|
71
|
+
("15", "089"), # Tenango del Aire
|
|
72
|
+
("15", "091"), # Teoloyucan
|
|
73
|
+
("15", "092"), # Teotihuacán
|
|
74
|
+
("15", "093"), # Tepetlaoxtoc
|
|
75
|
+
("15", "094"), # Tepetlixpa
|
|
76
|
+
("15", "095"), # Tepotzotlán
|
|
77
|
+
("15", "096"), # Tequixquiac
|
|
78
|
+
("15", "099"), # Texcoco
|
|
79
|
+
("15", "100"), # Tezoyuca
|
|
80
|
+
("15", "103"), # Tlalmanalco
|
|
81
|
+
("15", "104"), # Tlalnepantla de Baz
|
|
82
|
+
("15", "108"), # Tultepec
|
|
83
|
+
("15", "109"), # Tultitlán
|
|
84
|
+
("15", "112"), # Villa del Carbón
|
|
85
|
+
("15", "120"), # Zumpango
|
|
86
|
+
("15", "121"), # Cuautitlán Izcalli
|
|
87
|
+
("15", "122"), # Valle de Chalco Solidaridad
|
|
88
|
+
("15", "125"), # Tonanitla
|
|
89
|
+
# Hidalgo
|
|
90
|
+
("13", "069"), # Tizayuca
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
"33": { # Guadalajara
|
|
94
|
+
"nombre": "Zona Metropolitana de Guadalajara",
|
|
95
|
+
"municipios": [
|
|
96
|
+
("14", "039"), # Guadalajara
|
|
97
|
+
("14", "070"), # El Salto
|
|
98
|
+
("14", "097"), # Tlajomulco de Zúñiga
|
|
99
|
+
("14", "098"), # Tlaquepaque
|
|
100
|
+
("14", "101"), # Tonalá
|
|
101
|
+
("14", "120"), # Zapopan
|
|
102
|
+
("14", "124"), # Juanacatlán
|
|
103
|
+
("14", "044"), # Ixtlahuacán de los Membrillos
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
"81": { # Monterrey
|
|
107
|
+
"nombre": "Zona Metropolitana de Monterrey",
|
|
108
|
+
"municipios": [
|
|
109
|
+
("19", "006"), # Apodaca
|
|
110
|
+
("19", "009"), # Cadereyta Jiménez
|
|
111
|
+
("19", "010"), # El Carmen
|
|
112
|
+
("19", "018"), # García
|
|
113
|
+
("19", "019"), # San Pedro Garza García
|
|
114
|
+
("19", "021"), # General Escobedo
|
|
115
|
+
("19", "026"), # Guadalupe
|
|
116
|
+
("19", "031"), # Juárez
|
|
117
|
+
("19", "039"), # Monterrey
|
|
118
|
+
("19", "045"), # Salinas Victoria
|
|
119
|
+
("19", "046"), # San Nicolás de los Garza
|
|
120
|
+
("19", "048"), # Santa Catarina
|
|
121
|
+
("19", "049"), # Santiago
|
|
122
|
+
],
|
|
123
|
+
},
|
|
124
|
+
"222": { # Puebla-Tlaxcala (también 220, 221)
|
|
125
|
+
"nombre": "Zona Metropolitana de Puebla-Tlaxcala",
|
|
126
|
+
"municipios": [
|
|
127
|
+
# Puebla
|
|
128
|
+
("21", "114"), # Puebla
|
|
129
|
+
("21", "015"), # Amozoc
|
|
130
|
+
("21", "034"), # Coronango
|
|
131
|
+
("21", "041"), # Cuautlancingo
|
|
132
|
+
("21", "074"), # Huejotzingo
|
|
133
|
+
("21", "090"), # Juan C. Bonilla
|
|
134
|
+
("21", "106"), # Ocoyucan
|
|
135
|
+
("21", "119"), # San Andrés Cholula
|
|
136
|
+
("21", "132"), # San Martín Texmelucan
|
|
137
|
+
("21", "140"), # San Pedro Cholula
|
|
138
|
+
# Tlaxcala
|
|
139
|
+
("29", "022"), # Mazatecochco de José María Morelos
|
|
140
|
+
("29", "024"), # Papalotla de Xicohténcatl
|
|
141
|
+
("29", "028"), # San Pablo del Monte
|
|
142
|
+
("29", "032"), # Tenancingo
|
|
143
|
+
("29", "033"), # Teolocholco
|
|
144
|
+
("29", "034"), # Tepeyanco
|
|
145
|
+
("29", "038"), # Xicohtzinco
|
|
146
|
+
("29", "044"), # Zacatelco
|
|
147
|
+
],
|
|
148
|
+
},
|
|
149
|
+
"664": { # Tijuana-Tecate-Rosarito (también 661, 663, 665)
|
|
150
|
+
"nombre": "Zona Metropolitana de Tijuana",
|
|
151
|
+
"municipios": [
|
|
152
|
+
# Baja California
|
|
153
|
+
("02", "004"), # Tijuana
|
|
154
|
+
("02", "003"), # Tecate
|
|
155
|
+
("02", "005"), # Playas de Rosarito
|
|
156
|
+
],
|
|
157
|
+
},
|
|
158
|
+
"656": { # Ciudad Juárez (también 657)
|
|
159
|
+
"nombre": "Zona Metropolitana de Ciudad Juárez",
|
|
160
|
+
"municipios": [
|
|
161
|
+
# Chihuahua
|
|
162
|
+
("08", "037"), # Juárez
|
|
163
|
+
("08", "031"), # Guadalupe (Chihuahua)
|
|
164
|
+
("08", "051"), # Praxedis G. Guerrero
|
|
165
|
+
],
|
|
166
|
+
},
|
|
167
|
+
"999": { # Mérida (también 990)
|
|
168
|
+
"nombre": "Zona Metropolitana de Mérida",
|
|
169
|
+
"municipios": [
|
|
170
|
+
# Yucatán
|
|
171
|
+
("31", "050"), # Mérida
|
|
172
|
+
("31", "041"), # Kanasín
|
|
173
|
+
("31", "100"), # Ucú
|
|
174
|
+
("31", "101"), # Umán
|
|
175
|
+
("31", "013"), # Conkal
|
|
176
|
+
("31", "059"), # Progreso
|
|
177
|
+
],
|
|
178
|
+
},
|
|
179
|
+
"998": { # Cancún-Riviera Maya
|
|
180
|
+
"nombre": "Zona Metropolitana de Cancún",
|
|
181
|
+
"municipios": [
|
|
182
|
+
# Quintana Roo
|
|
183
|
+
("23", "005"), # Benito Juárez (Cancún)
|
|
184
|
+
("23", "008"), # Solidaridad (Playa del Carmen)
|
|
185
|
+
("23", "009"), # Tulum
|
|
186
|
+
("23", "004"), # Isla Mujeres
|
|
187
|
+
("23", "011"), # Puerto Morelos
|
|
188
|
+
],
|
|
189
|
+
},
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
# Prefijos asociados a cada zona metropolitana
|
|
193
|
+
# Solo CDMX (55), GDL (33) y MTY (81) son de 2 dígitos
|
|
194
|
+
# Las expansiones son específicas por ciudad, NO por patrón de zona
|
|
195
|
+
LADAS_METROPOLITANAS: dict[str, list[str]] = {
|
|
196
|
+
"55": ["55", "56"], # CDMX: 55 + 56x (expansión móvil)
|
|
197
|
+
"33": ["33"], # Guadalajara
|
|
198
|
+
"81": ["81"], # Monterrey
|
|
199
|
+
"222": ["220", "221", "222"], # Puebla-Tlaxcala (expansiones)
|
|
200
|
+
"664": ["663", "664"], # Tijuana (663 overlay 2018)
|
|
201
|
+
"656": ["656", "657"], # Ciudad Juárez (657 overlay)
|
|
202
|
+
"999": ["990", "999"], # Mérida (990 expansión)
|
|
203
|
+
"998": ["998"], # Cancún
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
# Lookup de zona metropolitana: (cve_entidad, cve_municipio) -> LADA
|
|
207
|
+
_zm_lookup: dict[tuple[str, str | None], str] | None = None
|
|
14
208
|
|
|
15
|
-
|
|
16
|
-
|
|
209
|
+
@classmethod
|
|
210
|
+
def _load_data(cls) -> None:
|
|
211
|
+
"""Carga los datos del catálogo"""
|
|
212
|
+
if cls._data is not None:
|
|
213
|
+
return
|
|
17
214
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
tipo: str # metropolitana | fronteriza | turistica | normal
|
|
22
|
-
region: str
|
|
215
|
+
data_path = (
|
|
216
|
+
Path(__file__).resolve().parents[4] / "shared-data" / "ift" / "codigos_lada.json"
|
|
217
|
+
)
|
|
23
218
|
|
|
219
|
+
with open(data_path, encoding="utf-8") as f:
|
|
220
|
+
raw = json.load(f)
|
|
24
221
|
|
|
25
|
-
|
|
26
|
-
"""Resultado de validación de número telefónico"""
|
|
222
|
+
cls._data = raw.get("codigos", raw) if isinstance(raw, dict) else raw
|
|
27
223
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
numero_local: str | None
|
|
31
|
-
ciudad: str | None
|
|
32
|
-
estado: str | None
|
|
33
|
-
error: str | None
|
|
224
|
+
if not cls._data:
|
|
225
|
+
cls._data = []
|
|
34
226
|
|
|
227
|
+
# Build indexes
|
|
228
|
+
cls._by_lada = {c["lada"]: c for c in cls._data}
|
|
229
|
+
cls._by_municipio = {}
|
|
230
|
+
cls._by_entidad = {}
|
|
35
231
|
|
|
36
|
-
|
|
37
|
-
|
|
232
|
+
for c in cls._data:
|
|
233
|
+
ent = c.get("cve_entidad")
|
|
234
|
+
mun = c.get("cve_municipio")
|
|
38
235
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
236
|
+
if ent and mun:
|
|
237
|
+
key = (ent, mun)
|
|
238
|
+
if key not in cls._by_municipio:
|
|
239
|
+
cls._by_municipio[key] = []
|
|
240
|
+
cls._by_municipio[key].append(c)
|
|
44
241
|
|
|
242
|
+
if ent:
|
|
243
|
+
if ent not in cls._by_entidad:
|
|
244
|
+
cls._by_entidad[ent] = []
|
|
245
|
+
cls._by_entidad[ent].append(c)
|
|
45
246
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
|
247
|
+
# Build ZM lookup
|
|
248
|
+
cls._zm_lookup = {}
|
|
249
|
+
for lada, zm in cls.ZONAS_METROPOLITANAS.items():
|
|
250
|
+
for cve_ent, cve_mun in zm["municipios"]:
|
|
251
|
+
cls._zm_lookup[(cve_ent, cve_mun)] = lada
|
|
77
252
|
|
|
78
253
|
@classmethod
|
|
79
|
-
def
|
|
80
|
-
"""
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
)
|
|
254
|
+
def get_all(cls) -> list[dict]:
|
|
255
|
+
"""Obtiene todos los códigos LADA"""
|
|
256
|
+
cls._load_data()
|
|
257
|
+
return cls._data.copy() if cls._data else []
|
|
92
258
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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)
|
|
259
|
+
@classmethod
|
|
260
|
+
def buscar_por_lada(cls, lada: str) -> dict | None:
|
|
261
|
+
"""Busca información por código LADA"""
|
|
262
|
+
cls._load_data()
|
|
263
|
+
return cls._by_lada.get(lada) if cls._by_lada else None
|
|
123
264
|
|
|
124
265
|
@classmethod
|
|
125
|
-
def
|
|
126
|
-
"""
|
|
127
|
-
|
|
266
|
+
def buscar_por_ciudad(cls, ciudad: str) -> list[dict]:
|
|
267
|
+
"""Busca códigos LADA por nombre de ciudad"""
|
|
268
|
+
cls._load_data()
|
|
269
|
+
if not cls._data:
|
|
270
|
+
return []
|
|
271
|
+
ciudad_upper = ciudad.upper()
|
|
272
|
+
return [c for c in cls._data if ciudad_upper in c.get("ciudad", "").upper()]
|
|
128
273
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
"""
|
|
274
|
+
@classmethod
|
|
275
|
+
def get_por_estado(cls, estado: str) -> list[dict]:
|
|
276
|
+
"""Obtiene códigos LADA por estado"""
|
|
132
277
|
cls._load_data()
|
|
133
|
-
|
|
278
|
+
if not cls._data:
|
|
279
|
+
return []
|
|
280
|
+
estado_upper = estado.upper()
|
|
281
|
+
return [c for c in cls._data if estado_upper in c.get("estado", "").upper()]
|
|
134
282
|
|
|
135
283
|
@classmethod
|
|
136
|
-
def
|
|
137
|
-
"""
|
|
138
|
-
|
|
284
|
+
def get_por_cve_entidad(cls, cve_entidad: str) -> list[dict]:
|
|
285
|
+
"""Obtiene códigos LADA por clave de entidad INEGI"""
|
|
286
|
+
cls._load_data()
|
|
287
|
+
return cls._by_entidad.get(cve_entidad, []).copy() if cls._by_entidad else []
|
|
139
288
|
|
|
140
|
-
|
|
141
|
-
|
|
289
|
+
@classmethod
|
|
290
|
+
def get_por_municipio(cls, cve_entidad: str, cve_municipio: str) -> list[dict]:
|
|
291
|
+
"""Obtiene códigos LADA por municipio INEGI"""
|
|
292
|
+
cls._load_data()
|
|
293
|
+
key = (cve_entidad, cve_municipio)
|
|
294
|
+
return cls._by_municipio.get(key, []).copy() if cls._by_municipio else []
|
|
142
295
|
|
|
143
|
-
|
|
144
|
-
|
|
296
|
+
@classmethod
|
|
297
|
+
def get_por_tipo(cls, tipo: str) -> list[dict]:
|
|
298
|
+
"""Obtiene códigos LADA por tipo (metropolitana, fronteriza, turistica, normal)"""
|
|
299
|
+
cls._load_data()
|
|
300
|
+
if not cls._data:
|
|
301
|
+
return []
|
|
302
|
+
return [c for c in cls._data if c.get("tipo") == tipo]
|
|
145
303
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
>>> print(codigo['estado']) # "Jalisco"
|
|
150
|
-
"""
|
|
304
|
+
@classmethod
|
|
305
|
+
def get_por_region(cls, region: str) -> list[dict]:
|
|
306
|
+
"""Obtiene códigos LADA por región"""
|
|
151
307
|
cls._load_data()
|
|
152
|
-
|
|
308
|
+
if not cls._data:
|
|
309
|
+
return []
|
|
310
|
+
return [c for c in cls._data if c.get("region") == region]
|
|
153
311
|
|
|
154
312
|
@classmethod
|
|
155
|
-
def
|
|
313
|
+
def get_prefijos_por_municipio(cls, cve_entidad: str, cve_municipio: str) -> list[str]:
|
|
156
314
|
"""
|
|
157
|
-
|
|
315
|
+
Obtiene todos los prefijos telefónicos probables para un municipio.
|
|
316
|
+
|
|
317
|
+
Retorna todos los prefijos que podrían corresponder a ese municipio,
|
|
318
|
+
incluyendo expansiones metropolitanas. Los teléfonos siempre se
|
|
319
|
+
completan a 10 dígitos.
|
|
158
320
|
|
|
159
321
|
Args:
|
|
160
|
-
|
|
322
|
+
cve_entidad: Clave de entidad INEGI (01-32)
|
|
323
|
+
cve_municipio: Clave de municipio INEGI (001-570)
|
|
161
324
|
|
|
162
325
|
Returns:
|
|
163
|
-
Lista de
|
|
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']}")
|
|
326
|
+
Lista de prefijos (ej: ["55", "561", "562", ...])
|
|
171
327
|
"""
|
|
172
328
|
cls._load_data()
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
329
|
+
prefijos: list[str] = []
|
|
330
|
+
|
|
331
|
+
# 1. Verificar si pertenece a una zona metropolitana con LADAs asociadas
|
|
332
|
+
if cls._zm_lookup:
|
|
333
|
+
# Buscar por municipio específico
|
|
334
|
+
lada = cls._zm_lookup.get((cve_entidad, cve_municipio))
|
|
335
|
+
if lada and lada in cls.LADAS_METROPOLITANAS:
|
|
336
|
+
prefijos.extend(cls.LADAS_METROPOLITANAS[lada])
|
|
337
|
+
|
|
338
|
+
# Buscar si toda la entidad está en una ZM (ej: CDMX)
|
|
339
|
+
if not prefijos:
|
|
340
|
+
lada = cls._zm_lookup.get((cve_entidad, None))
|
|
341
|
+
if lada and lada in cls.LADAS_METROPOLITANAS:
|
|
342
|
+
prefijos.extend(cls.LADAS_METROPOLITANAS[lada])
|
|
343
|
+
|
|
344
|
+
# 2. Agregar LADAs directamente mapeadas al municipio
|
|
345
|
+
if cls._by_municipio:
|
|
346
|
+
for c in cls._by_municipio.get((cve_entidad, cve_municipio), []):
|
|
347
|
+
if c["lada"] not in prefijos:
|
|
348
|
+
prefijos.append(c["lada"])
|
|
349
|
+
|
|
350
|
+
# 3. Si no hay nada, buscar por entidad (estado)
|
|
351
|
+
if not prefijos and cls._by_entidad:
|
|
352
|
+
for c in cls._by_entidad.get(cve_entidad, []):
|
|
353
|
+
if c["lada"] not in prefijos:
|
|
354
|
+
prefijos.append(c["lada"])
|
|
355
|
+
break # Solo el principal del estado
|
|
356
|
+
|
|
357
|
+
return prefijos
|
|
179
358
|
|
|
180
359
|
@classmethod
|
|
181
|
-
def
|
|
360
|
+
def get_lada_for_location(
|
|
361
|
+
cls, cve_entidad: str, cve_municipio: str | None = None
|
|
362
|
+
) -> dict | None:
|
|
182
363
|
"""
|
|
183
|
-
Obtiene
|
|
364
|
+
Obtiene el código LADA más apropiado para una ubicación geográfica.
|
|
365
|
+
|
|
366
|
+
Prioridad:
|
|
367
|
+
1. Zona metropolitana (si el municipio pertenece a una)
|
|
368
|
+
2. Municipio específico
|
|
369
|
+
3. Estado (ciudad principal)
|
|
184
370
|
|
|
185
371
|
Args:
|
|
186
|
-
|
|
372
|
+
cve_entidad: Clave de entidad INEGI (01-32)
|
|
373
|
+
cve_municipio: Clave de municipio INEGI (opcional)
|
|
187
374
|
|
|
188
375
|
Returns:
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
Ejemplo:
|
|
192
|
-
>>> codigos = CodigosLADACatalog.get_por_estado("Jalisco")
|
|
193
|
-
>>> print(f"Jalisco tiene {len(codigos)} códigos LADA")
|
|
376
|
+
Dict con información del LADA o None
|
|
194
377
|
"""
|
|
195
378
|
cls._load_data()
|
|
196
|
-
|
|
197
|
-
|
|
379
|
+
|
|
380
|
+
# Priority 1: Check zona metropolitana
|
|
381
|
+
if cls._zm_lookup:
|
|
382
|
+
# Check specific municipality
|
|
383
|
+
if cve_municipio:
|
|
384
|
+
lada = cls._zm_lookup.get((cve_entidad, cve_municipio))
|
|
385
|
+
if lada and cls._by_lada:
|
|
386
|
+
return cls._by_lada.get(lada)
|
|
387
|
+
# Check if entire state is in ZM
|
|
388
|
+
lada = cls._zm_lookup.get((cve_entidad, None))
|
|
389
|
+
if lada and cls._by_lada:
|
|
390
|
+
return cls._by_lada.get(lada)
|
|
391
|
+
|
|
392
|
+
# Priority 2: Specific municipality
|
|
393
|
+
if cve_municipio and cls._by_municipio:
|
|
394
|
+
candidates = cls._by_municipio.get((cve_entidad, cve_municipio), [])
|
|
395
|
+
if candidates:
|
|
396
|
+
# Prefer metropolitana type
|
|
397
|
+
for c in candidates:
|
|
398
|
+
if c.get("tipo") == "metropolitana":
|
|
399
|
+
return c
|
|
400
|
+
return candidates[0]
|
|
401
|
+
|
|
402
|
+
# Priority 3: State level - prefer metropolitana
|
|
403
|
+
if cls._by_entidad:
|
|
404
|
+
candidates = cls._by_entidad.get(cve_entidad, [])
|
|
405
|
+
if candidates:
|
|
406
|
+
# Sort by type priority
|
|
407
|
+
tipo_priority = {
|
|
408
|
+
"metropolitana": 0,
|
|
409
|
+
"turistica": 1,
|
|
410
|
+
"fronteriza": 2,
|
|
411
|
+
"normal": 3,
|
|
412
|
+
}
|
|
413
|
+
candidates.sort(key=lambda x: tipo_priority.get(x.get("tipo", ""), 99))
|
|
414
|
+
return candidates[0]
|
|
415
|
+
|
|
416
|
+
return None
|
|
198
417
|
|
|
199
418
|
@classmethod
|
|
200
|
-
def
|
|
419
|
+
def get_municipios_por_lada(cls, lada: str) -> list[tuple[str, str | None]]:
|
|
201
420
|
"""
|
|
202
|
-
Obtiene
|
|
421
|
+
Obtiene todos los municipios INEGI asociados a una LADA.
|
|
422
|
+
|
|
423
|
+
Para LADAs metropolitanas, retorna todos los municipios de la zona.
|
|
424
|
+
Para LADAs regulares, retorna el municipio principal del catálogo.
|
|
203
425
|
|
|
204
426
|
Args:
|
|
205
|
-
|
|
427
|
+
lada: Código LADA (ej: "55", "222", "561")
|
|
206
428
|
|
|
207
429
|
Returns:
|
|
208
|
-
Lista de
|
|
209
|
-
|
|
210
|
-
Ejemplo:
|
|
211
|
-
>>> metropolitanas = CodigosLADACatalog.get_por_tipo("metropolitana")
|
|
212
|
-
>>> for codigo in metropolitanas:
|
|
213
|
-
... print(f"{codigo['lada']} - {codigo['ciudad']}")
|
|
430
|
+
Lista de tuplas (cve_entidad, cve_municipio) con códigos INEGI
|
|
214
431
|
"""
|
|
215
432
|
cls._load_data()
|
|
216
|
-
return cls._by_tipo.get(tipo, []).copy() # type: ignore
|
|
217
433
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
434
|
+
# Check if this LADA belongs to a metropolitan area
|
|
435
|
+
for main_lada, associated_ladas in cls.LADAS_METROPOLITANAS.items():
|
|
436
|
+
if lada in associated_ladas:
|
|
437
|
+
# Return all municipalities from this metropolitan zone
|
|
438
|
+
zm = cls.ZONAS_METROPOLITANAS.get(main_lada)
|
|
439
|
+
if zm:
|
|
440
|
+
return list(zm["municipios"])
|
|
222
441
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
442
|
+
# Check direct metropolitan zones
|
|
443
|
+
if lada in cls.ZONAS_METROPOLITANAS:
|
|
444
|
+
return list(cls.ZONAS_METROPOLITANAS[lada]["municipios"])
|
|
226
445
|
|
|
227
|
-
|
|
228
|
-
|
|
446
|
+
# Regular LADA - return municipality from catalog
|
|
447
|
+
info = cls.buscar_por_lada(lada)
|
|
448
|
+
if info and info.get("cve_entidad") and info.get("cve_municipio"):
|
|
449
|
+
return [(info["cve_entidad"], info["cve_municipio"])]
|
|
229
450
|
|
|
230
|
-
|
|
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
|
|
451
|
+
return []
|
|
237
452
|
|
|
238
453
|
@classmethod
|
|
239
|
-
def
|
|
454
|
+
def get_zona_metropolitana(cls, lada: str) -> dict | None:
|
|
240
455
|
"""
|
|
241
|
-
Obtiene
|
|
456
|
+
Obtiene información de la zona metropolitana para una LADA.
|
|
457
|
+
|
|
458
|
+
Args:
|
|
459
|
+
lada: Código LADA (ej: "55", "222", "561")
|
|
242
460
|
|
|
243
461
|
Returns:
|
|
244
|
-
|
|
462
|
+
Dict con nombre, municipios y LADAs asociadas, o None
|
|
245
463
|
"""
|
|
246
|
-
|
|
464
|
+
# Check if this LADA belongs to a metropolitan area
|
|
465
|
+
for main_lada, associated_ladas in cls.LADAS_METROPOLITANAS.items():
|
|
466
|
+
if lada in associated_ladas:
|
|
467
|
+
zm = cls.ZONAS_METROPOLITANAS.get(main_lada)
|
|
468
|
+
if zm:
|
|
469
|
+
return {
|
|
470
|
+
"lada_principal": main_lada,
|
|
471
|
+
"nombre": zm["nombre"],
|
|
472
|
+
"municipios": zm["municipios"],
|
|
473
|
+
"ladas_asociadas": associated_ladas,
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
# Check direct metropolitan zones
|
|
477
|
+
if lada in cls.ZONAS_METROPOLITANAS:
|
|
478
|
+
zm = cls.ZONAS_METROPOLITANAS[lada]
|
|
479
|
+
return {
|
|
480
|
+
"lada_principal": lada,
|
|
481
|
+
"nombre": zm["nombre"],
|
|
482
|
+
"municipios": zm["municipios"],
|
|
483
|
+
"ladas_asociadas": cls.LADAS_METROPOLITANAS.get(lada, [lada]),
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return None
|
|
247
487
|
|
|
248
488
|
@classmethod
|
|
249
|
-
def
|
|
489
|
+
def generar_telefono(
|
|
490
|
+
cls,
|
|
491
|
+
cve_entidad: str,
|
|
492
|
+
cve_municipio: str | None = None,
|
|
493
|
+
_tipo: str = "fijo", # Reserved for future mobile vs fixed differentiation
|
|
494
|
+
) -> str | None:
|
|
250
495
|
"""
|
|
251
|
-
|
|
496
|
+
Genera un número de teléfono válido para una ubicación.
|
|
497
|
+
|
|
498
|
+
Args:
|
|
499
|
+
cve_entidad: Clave de entidad INEGI
|
|
500
|
+
cve_municipio: Clave de municipio INEGI (opcional)
|
|
501
|
+
tipo: 'fijo' o 'movil'
|
|
252
502
|
|
|
253
503
|
Returns:
|
|
254
|
-
|
|
504
|
+
Número de teléfono de 10 dígitos o None
|
|
255
505
|
"""
|
|
256
|
-
|
|
506
|
+
lada_info = cls.get_lada_for_location(cve_entidad, cve_municipio)
|
|
507
|
+
if not lada_info:
|
|
508
|
+
return None
|
|
257
509
|
|
|
258
|
-
|
|
259
|
-
def get_turisticas(cls) -> list[CodigoLADA]:
|
|
260
|
-
"""
|
|
261
|
-
Obtiene códigos LADA de destinos turísticos.
|
|
510
|
+
lada = lada_info["lada"]
|
|
262
511
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
512
|
+
# Generate remaining digits
|
|
513
|
+
# LADA can be 2 or 3 digits, total number is always 10
|
|
514
|
+
remaining = 10 - len(lada)
|
|
515
|
+
|
|
516
|
+
# Generate random digits (avoid starting with 0)
|
|
517
|
+
first_digit = random.randint(1, 9)
|
|
518
|
+
other_digits = "".join(str(random.randint(0, 9)) for _ in range(remaining - 1))
|
|
519
|
+
|
|
520
|
+
return f"{lada}{first_digit}{other_digits}"
|
|
267
521
|
|
|
268
522
|
@classmethod
|
|
269
|
-
def validar_numero(cls, numero: str) ->
|
|
523
|
+
def validar_numero(cls, numero: str) -> dict:
|
|
270
524
|
"""
|
|
271
|
-
Valida
|
|
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.
|
|
525
|
+
Valida un número de teléfono mexicano.
|
|
275
526
|
|
|
276
527
|
Args:
|
|
277
|
-
numero: Número
|
|
528
|
+
numero: Número de teléfono (10 dígitos)
|
|
278
529
|
|
|
279
530
|
Returns:
|
|
280
|
-
|
|
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']}")
|
|
531
|
+
Dict con información de validación
|
|
288
532
|
"""
|
|
289
533
|
cls._load_data()
|
|
290
534
|
|
|
291
|
-
#
|
|
292
|
-
|
|
535
|
+
# Clean number
|
|
536
|
+
numero_clean = "".join(c for c in numero if c.isdigit())
|
|
293
537
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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,
|
|
538
|
+
result = {
|
|
539
|
+
"numero": numero_clean,
|
|
540
|
+
"valido": False,
|
|
541
|
+
"lada": None,
|
|
330
542
|
"ciudad": None,
|
|
331
543
|
"estado": None,
|
|
332
|
-
"
|
|
544
|
+
"tipo": None,
|
|
545
|
+
"error": None,
|
|
333
546
|
}
|
|
334
547
|
|
|
548
|
+
if len(numero_clean) != 10:
|
|
549
|
+
result["error"] = "Debe tener 10 dígitos"
|
|
550
|
+
return result
|
|
551
|
+
|
|
552
|
+
# Try 2-digit LADA first (55, 33, 81)
|
|
553
|
+
lada_2 = numero_clean[:2]
|
|
554
|
+
if lada_2 in ("55", "33", "81") and cls._by_lada:
|
|
555
|
+
info = cls._by_lada.get(lada_2)
|
|
556
|
+
if info:
|
|
557
|
+
result["valido"] = True
|
|
558
|
+
result["lada"] = lada_2
|
|
559
|
+
result["ciudad"] = info.get("ciudad")
|
|
560
|
+
result["estado"] = info.get("estado")
|
|
561
|
+
result["tipo"] = info.get("tipo")
|
|
562
|
+
return result
|
|
563
|
+
|
|
564
|
+
# Try 3-digit LADA
|
|
565
|
+
lada_3 = numero_clean[:3]
|
|
566
|
+
if cls._by_lada:
|
|
567
|
+
info = cls._by_lada.get(lada_3)
|
|
568
|
+
if info:
|
|
569
|
+
result["valido"] = True
|
|
570
|
+
result["lada"] = lada_3
|
|
571
|
+
result["ciudad"] = info.get("ciudad")
|
|
572
|
+
result["estado"] = info.get("estado")
|
|
573
|
+
result["tipo"] = info.get("tipo")
|
|
574
|
+
return result
|
|
575
|
+
|
|
576
|
+
result["error"] = "LADA no reconocida"
|
|
577
|
+
return result
|
|
578
|
+
|
|
335
579
|
@classmethod
|
|
336
580
|
def formatear_numero(cls, numero: str) -> str:
|
|
337
581
|
"""
|
|
338
|
-
Formatea un número
|
|
582
|
+
Formatea un número de teléfono mexicano.
|
|
339
583
|
|
|
340
584
|
Args:
|
|
341
|
-
numero: Número
|
|
585
|
+
numero: Número de teléfono (10 dígitos)
|
|
342
586
|
|
|
343
587
|
Returns:
|
|
344
|
-
Número formateado (
|
|
345
|
-
|
|
346
|
-
Ejemplo:
|
|
347
|
-
>>> formateado = CodigosLADACatalog.formatear_numero("3312345678")
|
|
348
|
-
>>> print(formateado) # "33 1234 5678"
|
|
588
|
+
Número formateado (ej: "55 1234 5678" o "333 123 4567")
|
|
349
589
|
"""
|
|
350
|
-
|
|
590
|
+
numero_clean = "".join(c for c in numero if c.isdigit())
|
|
351
591
|
|
|
352
|
-
if
|
|
592
|
+
if len(numero_clean) != 10:
|
|
353
593
|
return numero
|
|
354
594
|
|
|
355
|
-
|
|
356
|
-
|
|
595
|
+
# Check if 2-digit LADA
|
|
596
|
+
if numero_clean[:2] in ("55", "33", "81"):
|
|
597
|
+
return f"{numero_clean[:2]} {numero_clean[2:6]} {numero_clean[6:]}"
|
|
357
598
|
|
|
358
|
-
#
|
|
359
|
-
|
|
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}"
|
|
599
|
+
# 3-digit LADA
|
|
600
|
+
return f"{numero_clean[:3]} {numero_clean[3:6]} {numero_clean[6:]}"
|
|
365
601
|
|
|
366
602
|
@classmethod
|
|
367
|
-
def
|
|
368
|
-
"""
|
|
369
|
-
|
|
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
|
|
603
|
+
def get_estadisticas(cls) -> dict:
|
|
604
|
+
"""Obtiene estadísticas del catálogo"""
|
|
605
|
+
cls._load_data()
|
|
389
606
|
|
|
390
|
-
|
|
607
|
+
if not cls._data:
|
|
608
|
+
return {}
|
|
391
609
|
|
|
392
|
-
|
|
393
|
-
|
|
610
|
+
tipos = {}
|
|
611
|
+
regiones = {}
|
|
612
|
+
estados = {}
|
|
394
613
|
|
|
395
|
-
|
|
396
|
-
"
|
|
397
|
-
|
|
398
|
-
"estado": codigo["estado"],
|
|
399
|
-
"tipo": codigo["tipo"],
|
|
400
|
-
"region": codigo["region"],
|
|
401
|
-
}
|
|
614
|
+
for c in cls._data:
|
|
615
|
+
t = c.get("tipo", "unknown")
|
|
616
|
+
tipos[t] = tipos.get(t, 0) + 1
|
|
402
617
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
"""
|
|
406
|
-
Obtiene estadísticas del catálogo.
|
|
407
|
-
|
|
408
|
-
Returns:
|
|
409
|
-
Diccionario con estadísticas del catálogo
|
|
618
|
+
r = c.get("region", "unknown")
|
|
619
|
+
regiones[r] = regiones.get(r, 0) + 1
|
|
410
620
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
>>> print(f"Total códigos: {stats['total_codigos']}")
|
|
414
|
-
>>> print(f"Estados: {stats['estados_cubiertos']}")
|
|
415
|
-
"""
|
|
416
|
-
cls._load_data()
|
|
621
|
+
e = c.get("estado", "unknown")
|
|
622
|
+
estados[e] = estados.get(e, 0) + 1
|
|
417
623
|
|
|
418
624
|
return {
|
|
419
|
-
"total_codigos": len(cls._data),
|
|
420
|
-
"
|
|
421
|
-
"
|
|
422
|
-
"
|
|
423
|
-
"
|
|
424
|
-
"regiones": list(cls._by_region.keys()), # type: ignore
|
|
425
|
-
"tipos": list(cls._by_tipo.keys()), # type: ignore
|
|
625
|
+
"total_codigos": len(cls._data),
|
|
626
|
+
"por_tipo": tipos,
|
|
627
|
+
"por_region": regiones,
|
|
628
|
+
"estados_cubiertos": len(estados),
|
|
629
|
+
"zonas_metropolitanas": len(cls.ZONAS_METROPOLITANAS),
|
|
426
630
|
}
|