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
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Inflación (Inflation) Catalog - SQLite Backend
|
|
3
|
+
|
|
4
|
+
This module provides access to inflation data from Banco de México using a SQLite
|
|
5
|
+
database that can be automatically updated without requiring library releases.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sqlite3
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from catalogmx.data.updater import get_database_path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class InflacionCatalog:
|
|
15
|
+
"""
|
|
16
|
+
Catalog of Mexican inflation data
|
|
17
|
+
|
|
18
|
+
Provides access to INPC (National Consumer Price Index) and
|
|
19
|
+
inflation rates (monthly and annual) from Banco de México.
|
|
20
|
+
|
|
21
|
+
This catalog uses an auto-updating SQLite database backend.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
_db_path: Path | None = None
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def _get_db_path(cls) -> Path:
|
|
28
|
+
"""Get path to database with auto-update"""
|
|
29
|
+
if cls._db_path is None:
|
|
30
|
+
cls._db_path = get_database_path(auto_update=True, max_age_hours=24)
|
|
31
|
+
return cls._db_path
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def _row_to_dict(cls, row: sqlite3.Row) -> dict:
|
|
35
|
+
"""Convert SQLite row to dictionary"""
|
|
36
|
+
return {
|
|
37
|
+
"fecha": row["fecha"],
|
|
38
|
+
"año": row["anio"],
|
|
39
|
+
"mes": row["mes"],
|
|
40
|
+
"inpc": row["inpc"] if row["inpc"] is not None else None,
|
|
41
|
+
"inflacion_mensual": (
|
|
42
|
+
row["inflacion_mensual"] if row["inflacion_mensual"] is not None else None
|
|
43
|
+
),
|
|
44
|
+
"inflacion_anual": (
|
|
45
|
+
row["inflacion_anual"] if row["inflacion_anual"] is not None else None
|
|
46
|
+
),
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def get_data(cls) -> list[dict]:
|
|
51
|
+
"""
|
|
52
|
+
Get all inflation data
|
|
53
|
+
|
|
54
|
+
:return: List of all inflation records
|
|
55
|
+
"""
|
|
56
|
+
db = sqlite3.connect(cls._get_db_path())
|
|
57
|
+
db.row_factory = sqlite3.Row
|
|
58
|
+
cursor = db.execute("""
|
|
59
|
+
SELECT fecha, anio, mes, inpc, inflacion_mensual, inflacion_anual
|
|
60
|
+
FROM inflacion
|
|
61
|
+
ORDER BY fecha
|
|
62
|
+
""")
|
|
63
|
+
results = [cls._row_to_dict(row) for row in cursor.fetchall()]
|
|
64
|
+
db.close()
|
|
65
|
+
return results
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def get_por_fecha(cls, fecha: str) -> dict | None:
|
|
69
|
+
"""
|
|
70
|
+
Get inflation data for a specific date
|
|
71
|
+
|
|
72
|
+
:param fecha: Date string in YYYY-MM-DD format
|
|
73
|
+
:return: Inflation record or None if not found
|
|
74
|
+
"""
|
|
75
|
+
db = sqlite3.connect(cls._get_db_path())
|
|
76
|
+
db.row_factory = sqlite3.Row
|
|
77
|
+
|
|
78
|
+
cursor = db.execute(
|
|
79
|
+
"""
|
|
80
|
+
SELECT fecha, anio, mes, inpc, inflacion_mensual, inflacion_anual
|
|
81
|
+
FROM inflacion
|
|
82
|
+
WHERE fecha = ?
|
|
83
|
+
LIMIT 1
|
|
84
|
+
""",
|
|
85
|
+
(fecha,),
|
|
86
|
+
)
|
|
87
|
+
row = cursor.fetchone()
|
|
88
|
+
db.close()
|
|
89
|
+
|
|
90
|
+
if row:
|
|
91
|
+
return cls._row_to_dict(row)
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
@classmethod
|
|
95
|
+
def get_por_mes(cls, anio: int, mes: int) -> dict | None:
|
|
96
|
+
"""
|
|
97
|
+
Get inflation data for a specific month
|
|
98
|
+
|
|
99
|
+
:param anio: Year (e.g., 2024)
|
|
100
|
+
:param mes: Month (1-12)
|
|
101
|
+
:return: Inflation record or None if not found
|
|
102
|
+
"""
|
|
103
|
+
db = sqlite3.connect(cls._get_db_path())
|
|
104
|
+
db.row_factory = sqlite3.Row
|
|
105
|
+
|
|
106
|
+
cursor = db.execute(
|
|
107
|
+
"""
|
|
108
|
+
SELECT fecha, anio, mes, inpc, inflacion_mensual, inflacion_anual
|
|
109
|
+
FROM inflacion
|
|
110
|
+
WHERE anio = ? AND mes = ?
|
|
111
|
+
LIMIT 1
|
|
112
|
+
""",
|
|
113
|
+
(anio, mes),
|
|
114
|
+
)
|
|
115
|
+
row = cursor.fetchone()
|
|
116
|
+
db.close()
|
|
117
|
+
|
|
118
|
+
if row:
|
|
119
|
+
return cls._row_to_dict(row)
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
@classmethod
|
|
123
|
+
def get_actual(cls) -> dict | None:
|
|
124
|
+
"""
|
|
125
|
+
Get most recent inflation data
|
|
126
|
+
|
|
127
|
+
:return: Latest inflation record
|
|
128
|
+
"""
|
|
129
|
+
db = sqlite3.connect(cls._get_db_path())
|
|
130
|
+
db.row_factory = sqlite3.Row
|
|
131
|
+
|
|
132
|
+
cursor = db.execute("""
|
|
133
|
+
SELECT fecha, anio, mes, inpc, inflacion_mensual, inflacion_anual
|
|
134
|
+
FROM inflacion
|
|
135
|
+
ORDER BY fecha DESC
|
|
136
|
+
LIMIT 1
|
|
137
|
+
""")
|
|
138
|
+
row = cursor.fetchone()
|
|
139
|
+
db.close()
|
|
140
|
+
|
|
141
|
+
if row:
|
|
142
|
+
return cls._row_to_dict(row)
|
|
143
|
+
return None
|
|
144
|
+
|
|
145
|
+
@classmethod
|
|
146
|
+
def get_inflacion_anual_actual(cls) -> float | None:
|
|
147
|
+
"""
|
|
148
|
+
Get current annual inflation rate
|
|
149
|
+
|
|
150
|
+
:return: Current annual inflation rate or None
|
|
151
|
+
"""
|
|
152
|
+
record = cls.get_actual()
|
|
153
|
+
return record.get("inflacion_anual") if record else None
|
|
154
|
+
|
|
155
|
+
@classmethod
|
|
156
|
+
def get_inflacion_mensual_actual(cls) -> float | None:
|
|
157
|
+
"""
|
|
158
|
+
Get current monthly inflation rate
|
|
159
|
+
|
|
160
|
+
:return: Current monthly inflation rate or None
|
|
161
|
+
"""
|
|
162
|
+
record = cls.get_actual()
|
|
163
|
+
return record.get("inflacion_mensual") if record else None
|
|
164
|
+
|
|
165
|
+
@classmethod
|
|
166
|
+
def get_inpc_actual(cls) -> float | None:
|
|
167
|
+
"""
|
|
168
|
+
Get current INPC value
|
|
169
|
+
|
|
170
|
+
:return: Current INPC value or None
|
|
171
|
+
"""
|
|
172
|
+
record = cls.get_actual()
|
|
173
|
+
return record.get("inpc") if record else None
|
|
174
|
+
|
|
175
|
+
@classmethod
|
|
176
|
+
def get_por_anio(cls, anio: int) -> list[dict]:
|
|
177
|
+
"""
|
|
178
|
+
Get all inflation data for a specific year
|
|
179
|
+
|
|
180
|
+
:param anio: Year (e.g., 2024)
|
|
181
|
+
:return: List of inflation records for the year
|
|
182
|
+
"""
|
|
183
|
+
db = sqlite3.connect(cls._get_db_path())
|
|
184
|
+
db.row_factory = sqlite3.Row
|
|
185
|
+
|
|
186
|
+
cursor = db.execute(
|
|
187
|
+
"""
|
|
188
|
+
SELECT fecha, anio, mes, inpc, inflacion_mensual, inflacion_anual
|
|
189
|
+
FROM inflacion
|
|
190
|
+
WHERE anio = ?
|
|
191
|
+
ORDER BY fecha
|
|
192
|
+
""",
|
|
193
|
+
(anio,),
|
|
194
|
+
)
|
|
195
|
+
results = [cls._row_to_dict(row) for row in cursor.fetchall()]
|
|
196
|
+
db.close()
|
|
197
|
+
return results
|
|
198
|
+
|
|
199
|
+
@classmethod
|
|
200
|
+
def calcular_inflacion_acumulada(cls, fecha_inicio: str, fecha_fin: str) -> float | None:
|
|
201
|
+
"""
|
|
202
|
+
Calculate accumulated inflation between two dates
|
|
203
|
+
|
|
204
|
+
:param fecha_inicio: Start date (YYYY-MM-DD)
|
|
205
|
+
:param fecha_fin: End date (YYYY-MM-DD)
|
|
206
|
+
:return: Accumulated inflation percentage or None
|
|
207
|
+
"""
|
|
208
|
+
record_inicio = cls.get_por_fecha(fecha_inicio)
|
|
209
|
+
record_fin = cls.get_por_fecha(fecha_fin)
|
|
210
|
+
|
|
211
|
+
if not record_inicio or not record_fin:
|
|
212
|
+
return None
|
|
213
|
+
|
|
214
|
+
inpc_inicio = record_inicio.get("inpc")
|
|
215
|
+
inpc_fin = record_fin.get("inpc")
|
|
216
|
+
|
|
217
|
+
if inpc_inicio is None or inpc_fin is None:
|
|
218
|
+
return None
|
|
219
|
+
|
|
220
|
+
return ((inpc_fin - inpc_inicio) / inpc_inicio) * 100
|
|
221
|
+
|
|
222
|
+
@classmethod
|
|
223
|
+
def get_promedio_anual(cls, anio: int) -> float | None:
|
|
224
|
+
"""
|
|
225
|
+
Calculate average annual inflation for a year
|
|
226
|
+
|
|
227
|
+
:param anio: Year (e.g., 2024)
|
|
228
|
+
:return: Average annual inflation or None if no data
|
|
229
|
+
"""
|
|
230
|
+
records = cls.get_por_anio(anio)
|
|
231
|
+
if not records:
|
|
232
|
+
return None
|
|
233
|
+
|
|
234
|
+
inflaciones = [
|
|
235
|
+
r.get("inflacion_anual") for r in records if r.get("inflacion_anual") is not None
|
|
236
|
+
]
|
|
237
|
+
return sum(inflaciones) / len(inflaciones) if inflaciones else None
|
|
238
|
+
|
|
239
|
+
@classmethod
|
|
240
|
+
def get_tasa_actual(cls) -> float | None:
|
|
241
|
+
"""
|
|
242
|
+
Get current inflation rate (alias for get_inflacion_anual_actual)
|
|
243
|
+
|
|
244
|
+
:return: Current annual inflation rate or None
|
|
245
|
+
"""
|
|
246
|
+
return cls.get_inflacion_anual_actual()
|
|
247
|
+
|
|
248
|
+
@classmethod
|
|
249
|
+
def ajustar_por_inflacion(cls, monto: float, fecha_inicio: str, fecha_fin: str) -> float | None:
|
|
250
|
+
"""
|
|
251
|
+
Adjust an amount for inflation between two dates
|
|
252
|
+
|
|
253
|
+
:param monto: Original amount
|
|
254
|
+
:param fecha_inicio: Start date (YYYY-MM-DD)
|
|
255
|
+
:param fecha_fin: End date (YYYY-MM-DD)
|
|
256
|
+
:return: Inflation-adjusted amount or None if data not available
|
|
257
|
+
"""
|
|
258
|
+
record_inicio = cls.get_por_fecha(fecha_inicio)
|
|
259
|
+
record_fin = cls.get_por_fecha(fecha_fin)
|
|
260
|
+
|
|
261
|
+
if not record_inicio or not record_fin:
|
|
262
|
+
return None
|
|
263
|
+
|
|
264
|
+
inpc_inicio = record_inicio.get("inpc")
|
|
265
|
+
inpc_fin = record_fin.get("inpc")
|
|
266
|
+
|
|
267
|
+
if inpc_inicio is None or inpc_fin is None or inpc_inicio == 0:
|
|
268
|
+
return None
|
|
269
|
+
|
|
270
|
+
# Adjust amount using INPC ratio
|
|
271
|
+
return round(monto * (inpc_fin / inpc_inicio), 2)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
# Convenience functions
|
|
275
|
+
def get_inflacion_actual() -> dict | None:
|
|
276
|
+
"""Get most recent inflation data"""
|
|
277
|
+
return InflacionCatalog.get_actual()
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def get_inflacion_anual_actual() -> float | None:
|
|
281
|
+
"""Get current annual inflation rate"""
|
|
282
|
+
return InflacionCatalog.get_inflacion_anual_actual()
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def get_inflacion_mensual_actual() -> float | None:
|
|
286
|
+
"""Get current monthly inflation rate"""
|
|
287
|
+
return InflacionCatalog.get_inflacion_mensual_actual()
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def get_inpc_actual() -> float | None:
|
|
291
|
+
"""Get current INPC value"""
|
|
292
|
+
return InflacionCatalog.get_inpc_actual()
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
# Export commonly used functions and classes
|
|
296
|
+
__all__ = [
|
|
297
|
+
"InflacionCatalog",
|
|
298
|
+
"get_inflacion_actual",
|
|
299
|
+
"get_inflacion_anual_actual",
|
|
300
|
+
"get_inflacion_mensual_actual",
|
|
301
|
+
"get_inpc_actual",
|
|
302
|
+
]
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Salarios Mínimos Catalog - SQLite Backend
|
|
3
|
+
|
|
4
|
+
This module provides access to minimum wage values from Banco de México using a SQLite
|
|
5
|
+
database that can be automatically updated without requiring library releases.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sqlite3
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from catalogmx.data.updater import get_database_path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SalariosMinimosCatalog:
|
|
15
|
+
"""
|
|
16
|
+
Catalog of minimum wage values in Mexico
|
|
17
|
+
|
|
18
|
+
Provides access to historical minimum wage data for both general zone and
|
|
19
|
+
northern border free zone (zona libre de la frontera norte).
|
|
20
|
+
|
|
21
|
+
This catalog uses an auto-updating SQLite database backend.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
_db_path: Path | None = None
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def _get_db_path(cls) -> Path:
|
|
28
|
+
"""Get path to database with auto-update"""
|
|
29
|
+
if cls._db_path is None:
|
|
30
|
+
cls._db_path = get_database_path(auto_update=True, max_age_hours=24)
|
|
31
|
+
return cls._db_path
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def _row_to_dict(cls, row: sqlite3.Row) -> dict:
|
|
35
|
+
"""Convert SQLite row to dictionary"""
|
|
36
|
+
return {
|
|
37
|
+
"fecha": row["fecha"],
|
|
38
|
+
"zona": row["zona"],
|
|
39
|
+
"salario_diario": row["salario_diario"],
|
|
40
|
+
"salario_minimo": row["salario_diario"], # Alias for compatibility
|
|
41
|
+
"año": row["anio"],
|
|
42
|
+
"mes": row["mes"],
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def get_data(cls) -> list[dict]:
|
|
47
|
+
"""
|
|
48
|
+
Get all minimum wage data
|
|
49
|
+
|
|
50
|
+
:return: List of all minimum wage records
|
|
51
|
+
"""
|
|
52
|
+
db = sqlite3.connect(cls._get_db_path())
|
|
53
|
+
db.row_factory = sqlite3.Row
|
|
54
|
+
cursor = db.execute("""
|
|
55
|
+
SELECT fecha, zona, salario_diario, anio, mes
|
|
56
|
+
FROM salarios_minimos
|
|
57
|
+
ORDER BY fecha, zona
|
|
58
|
+
""")
|
|
59
|
+
results = [cls._row_to_dict(row) for row in cursor.fetchall()]
|
|
60
|
+
db.close()
|
|
61
|
+
return results
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def get_por_fecha(cls, fecha: str, zona: str = "general") -> dict | None:
|
|
65
|
+
"""
|
|
66
|
+
Get minimum wage for a specific date and zone
|
|
67
|
+
|
|
68
|
+
:param fecha: Date string in YYYY-MM-DD format
|
|
69
|
+
:param zona: Zone ('general' or 'frontera_norte')
|
|
70
|
+
:return: Minimum wage record or None if not found
|
|
71
|
+
"""
|
|
72
|
+
db = sqlite3.connect(cls._get_db_path())
|
|
73
|
+
db.row_factory = sqlite3.Row
|
|
74
|
+
|
|
75
|
+
cursor = db.execute(
|
|
76
|
+
"""
|
|
77
|
+
SELECT fecha, zona, salario_diario, anio, mes
|
|
78
|
+
FROM salarios_minimos
|
|
79
|
+
WHERE fecha <= ? AND zona = ?
|
|
80
|
+
ORDER BY fecha DESC
|
|
81
|
+
LIMIT 1
|
|
82
|
+
""",
|
|
83
|
+
(fecha, zona),
|
|
84
|
+
)
|
|
85
|
+
row = cursor.fetchone()
|
|
86
|
+
db.close()
|
|
87
|
+
|
|
88
|
+
if row:
|
|
89
|
+
return cls._row_to_dict(row)
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
@classmethod
|
|
93
|
+
def get_actual(cls, zona: str = "general") -> dict | None:
|
|
94
|
+
"""
|
|
95
|
+
Get current minimum wage
|
|
96
|
+
|
|
97
|
+
:param zona: Zone ('general' or 'frontera_norte')
|
|
98
|
+
:return: Latest minimum wage record
|
|
99
|
+
"""
|
|
100
|
+
db = sqlite3.connect(cls._get_db_path())
|
|
101
|
+
db.row_factory = sqlite3.Row
|
|
102
|
+
|
|
103
|
+
cursor = db.execute(
|
|
104
|
+
"""
|
|
105
|
+
SELECT fecha, zona, salario_diario, anio, mes
|
|
106
|
+
FROM salarios_minimos
|
|
107
|
+
WHERE zona = ?
|
|
108
|
+
ORDER BY fecha DESC
|
|
109
|
+
LIMIT 1
|
|
110
|
+
""",
|
|
111
|
+
(zona,),
|
|
112
|
+
)
|
|
113
|
+
row = cursor.fetchone()
|
|
114
|
+
db.close()
|
|
115
|
+
|
|
116
|
+
if row:
|
|
117
|
+
return cls._row_to_dict(row)
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
@classmethod
|
|
121
|
+
def get_valor_actual(cls, zona: str = "general") -> float | None:
|
|
122
|
+
"""
|
|
123
|
+
Get current minimum wage value
|
|
124
|
+
|
|
125
|
+
:param zona: Zone ('general' or 'frontera_norte')
|
|
126
|
+
:return: Current daily minimum wage or None
|
|
127
|
+
"""
|
|
128
|
+
record = cls.get_actual(zona)
|
|
129
|
+
return record.get("salario_diario") if record else None
|
|
130
|
+
|
|
131
|
+
@classmethod
|
|
132
|
+
def get_por_anio(cls, anio: int) -> list[dict]:
|
|
133
|
+
"""
|
|
134
|
+
Get all minimum wage records for a specific year
|
|
135
|
+
|
|
136
|
+
:param anio: Year (e.g., 2024)
|
|
137
|
+
:return: List of minimum wage records for the year
|
|
138
|
+
"""
|
|
139
|
+
db = sqlite3.connect(cls._get_db_path())
|
|
140
|
+
db.row_factory = sqlite3.Row
|
|
141
|
+
|
|
142
|
+
cursor = db.execute(
|
|
143
|
+
"""
|
|
144
|
+
SELECT fecha, zona, salario_diario, anio, mes
|
|
145
|
+
FROM salarios_minimos
|
|
146
|
+
WHERE anio = ?
|
|
147
|
+
ORDER BY fecha, zona
|
|
148
|
+
""",
|
|
149
|
+
(anio,),
|
|
150
|
+
)
|
|
151
|
+
results = [cls._row_to_dict(row) for row in cursor.fetchall()]
|
|
152
|
+
db.close()
|
|
153
|
+
return results
|
|
154
|
+
|
|
155
|
+
@classmethod
|
|
156
|
+
def calcular_variacion(
|
|
157
|
+
cls, fecha_inicio: str, fecha_fin: str, zona: str = "general"
|
|
158
|
+
) -> float | None:
|
|
159
|
+
"""
|
|
160
|
+
Calculate percentage variation between two dates
|
|
161
|
+
|
|
162
|
+
:param fecha_inicio: Start date (YYYY-MM-DD)
|
|
163
|
+
:param fecha_fin: End date (YYYY-MM-DD)
|
|
164
|
+
:param zona: Zone ('general' or 'frontera_norte')
|
|
165
|
+
:return: Percentage variation or None if values not found
|
|
166
|
+
"""
|
|
167
|
+
record_inicio = cls.get_por_fecha(fecha_inicio, zona)
|
|
168
|
+
record_fin = cls.get_por_fecha(fecha_fin, zona)
|
|
169
|
+
|
|
170
|
+
if not record_inicio or not record_fin:
|
|
171
|
+
return None
|
|
172
|
+
|
|
173
|
+
salario_inicio = record_inicio.get("salario_diario")
|
|
174
|
+
salario_fin = record_fin.get("salario_diario")
|
|
175
|
+
|
|
176
|
+
if not salario_inicio or not salario_fin:
|
|
177
|
+
return None
|
|
178
|
+
|
|
179
|
+
return ((salario_fin - salario_inicio) / salario_inicio) * 100
|
|
180
|
+
|
|
181
|
+
@classmethod
|
|
182
|
+
def salario_mensual(cls, zona: str = "general", fecha: str | None = None) -> float | None:
|
|
183
|
+
"""
|
|
184
|
+
Calculate monthly minimum wage (daily * 30.4)
|
|
185
|
+
|
|
186
|
+
:param zona: Zone ('general' or 'frontera_norte')
|
|
187
|
+
:param fecha: Date string or None for current
|
|
188
|
+
:return: Monthly minimum wage or None
|
|
189
|
+
"""
|
|
190
|
+
if fecha:
|
|
191
|
+
record = cls.get_por_fecha(fecha, zona)
|
|
192
|
+
else:
|
|
193
|
+
record = cls.get_actual(zona)
|
|
194
|
+
|
|
195
|
+
if not record:
|
|
196
|
+
return None
|
|
197
|
+
|
|
198
|
+
salario_diario = record.get("salario_diario")
|
|
199
|
+
return salario_diario * 30.4 if salario_diario else None
|
|
200
|
+
|
|
201
|
+
@classmethod
|
|
202
|
+
def salario_anual(cls, zona: str = "general", fecha: str | None = None) -> float | None:
|
|
203
|
+
"""
|
|
204
|
+
Calculate annual minimum wage (daily * 365)
|
|
205
|
+
|
|
206
|
+
:param zona: Zone ('general' or 'frontera_norte')
|
|
207
|
+
:param fecha: Date string or None for current
|
|
208
|
+
:return: Annual minimum wage or None
|
|
209
|
+
"""
|
|
210
|
+
if fecha:
|
|
211
|
+
record = cls.get_por_fecha(fecha, zona)
|
|
212
|
+
else:
|
|
213
|
+
record = cls.get_actual(zona)
|
|
214
|
+
|
|
215
|
+
if not record:
|
|
216
|
+
return None
|
|
217
|
+
|
|
218
|
+
salario_diario = record.get("salario_diario")
|
|
219
|
+
return salario_diario * 365 if salario_diario else None
|
|
220
|
+
|
|
221
|
+
@classmethod
|
|
222
|
+
def get_actual_general(cls) -> dict | None:
|
|
223
|
+
"""
|
|
224
|
+
Get current minimum wage for general zone
|
|
225
|
+
|
|
226
|
+
:return: Latest minimum wage record for general zone
|
|
227
|
+
"""
|
|
228
|
+
return cls.get_actual(zona="general")
|
|
229
|
+
|
|
230
|
+
@classmethod
|
|
231
|
+
def get_actual_frontera(cls) -> dict | None:
|
|
232
|
+
"""
|
|
233
|
+
Get current minimum wage for northern border zone
|
|
234
|
+
|
|
235
|
+
:return: Latest minimum wage record for frontera_norte zone
|
|
236
|
+
"""
|
|
237
|
+
return cls.get_actual(zona="frontera_norte")
|
|
238
|
+
|
|
239
|
+
@classmethod
|
|
240
|
+
def get_salario_actual_zona(cls, zona: str) -> float | None:
|
|
241
|
+
"""
|
|
242
|
+
Get current salary value for a specific zone
|
|
243
|
+
|
|
244
|
+
:param zona: Zone ('general' or 'frontera_norte')
|
|
245
|
+
:return: Current daily minimum wage value or None
|
|
246
|
+
"""
|
|
247
|
+
return cls.get_valor_actual(zona)
|
|
248
|
+
|
|
249
|
+
@classmethod
|
|
250
|
+
def get_por_fecha_zona(cls, fecha: str, zona: str) -> dict | None:
|
|
251
|
+
"""
|
|
252
|
+
Get minimum wage by date and zone
|
|
253
|
+
|
|
254
|
+
:param fecha: Date string in YYYY-MM-DD format
|
|
255
|
+
:param zona: Zone ('general' or 'frontera_norte')
|
|
256
|
+
:return: Minimum wage record or None if not found
|
|
257
|
+
"""
|
|
258
|
+
return cls.get_por_fecha(fecha, zona)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
# Convenience functions
|
|
262
|
+
def get_salario_minimo_actual(zona: str = "general") -> dict | None:
|
|
263
|
+
"""Get current minimum wage"""
|
|
264
|
+
return SalariosMinimosCatalog.get_actual(zona)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def get_salario_minimo_por_fecha(fecha: str, zona: str = "general") -> dict | None:
|
|
268
|
+
"""Get minimum wage for a specific date"""
|
|
269
|
+
return SalariosMinimosCatalog.get_por_fecha(fecha, zona)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def get_valor_actual(zona: str = "general") -> float | None:
|
|
273
|
+
"""Get current minimum wage value"""
|
|
274
|
+
return SalariosMinimosCatalog.get_valor_actual(zona)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def salario_mensual(zona: str = "general") -> float | None:
|
|
278
|
+
"""Get current monthly minimum wage"""
|
|
279
|
+
return SalariosMinimosCatalog.salario_mensual(zona)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def salario_anual(zona: str = "general") -> float | None:
|
|
283
|
+
"""Get current annual minimum wage"""
|
|
284
|
+
return SalariosMinimosCatalog.salario_anual(zona)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
# Export commonly used functions and classes
|
|
288
|
+
__all__ = [
|
|
289
|
+
"SalariosMinimosCatalog",
|
|
290
|
+
"get_salario_minimo_actual",
|
|
291
|
+
"get_salario_minimo_por_fecha",
|
|
292
|
+
"get_valor_actual",
|
|
293
|
+
"salario_mensual",
|
|
294
|
+
"salario_anual",
|
|
295
|
+
]
|