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,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"])]