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.
Files changed (53) hide show
  1. catalogmx/__init__.py +133 -19
  2. catalogmx/calculators/__init__.py +113 -0
  3. catalogmx/calculators/costo_trabajador.py +213 -0
  4. catalogmx/calculators/impuestos.py +920 -0
  5. catalogmx/calculators/imss.py +370 -0
  6. catalogmx/calculators/isr.py +290 -0
  7. catalogmx/calculators/resico.py +154 -0
  8. catalogmx/catalogs/banxico/__init__.py +29 -3
  9. catalogmx/catalogs/banxico/cetes_sqlite.py +279 -0
  10. catalogmx/catalogs/banxico/inflacion_sqlite.py +302 -0
  11. catalogmx/catalogs/banxico/salarios_minimos_sqlite.py +295 -0
  12. catalogmx/catalogs/banxico/tiie_sqlite.py +279 -0
  13. catalogmx/catalogs/banxico/tipo_cambio_usd_sqlite.py +255 -0
  14. catalogmx/catalogs/banxico/udis_sqlite.py +332 -0
  15. catalogmx/catalogs/cnbv/__init__.py +9 -0
  16. catalogmx/catalogs/cnbv/sectores.py +173 -0
  17. catalogmx/catalogs/conapo/__init__.py +15 -0
  18. catalogmx/catalogs/conapo/sistema_urbano_nacional.py +50 -0
  19. catalogmx/catalogs/conapo/zonas_metropolitanas.py +230 -0
  20. catalogmx/catalogs/ift/__init__.py +1 -1
  21. catalogmx/catalogs/ift/codigos_lada.py +517 -313
  22. catalogmx/catalogs/inegi/__init__.py +17 -0
  23. catalogmx/catalogs/inegi/scian.py +127 -0
  24. catalogmx/catalogs/mexico/__init__.py +2 -0
  25. catalogmx/catalogs/mexico/giros_mercantiles.py +119 -0
  26. catalogmx/catalogs/sat/carta_porte/material_peligroso.py +5 -1
  27. catalogmx/catalogs/sat/cfdi_4/clave_prod_serv.py +78 -0
  28. catalogmx/catalogs/sat/cfdi_4/tasa_o_cuota.py +2 -1
  29. catalogmx/catalogs/sepomex/__init__.py +2 -1
  30. catalogmx/catalogs/sepomex/codigos_postales.py +30 -2
  31. catalogmx/catalogs/sepomex/codigos_postales_completo.py +261 -0
  32. catalogmx/cli.py +12 -9
  33. catalogmx/data/__init__.py +10 -0
  34. catalogmx/data/mexico_dynamic.sqlite3 +0 -0
  35. catalogmx/data/updater.py +362 -0
  36. catalogmx/generators/__init__.py +20 -0
  37. catalogmx/generators/identity.py +582 -0
  38. catalogmx/helpers.py +177 -3
  39. catalogmx/utils/__init__.py +29 -0
  40. catalogmx/utils/clabe_utils.py +417 -0
  41. catalogmx/utils/text.py +7 -1
  42. catalogmx/validators/clabe.py +52 -2
  43. catalogmx/validators/nss.py +32 -27
  44. catalogmx/validators/rfc.py +185 -52
  45. catalogmx-0.4.0.dist-info/METADATA +905 -0
  46. {catalogmx-0.3.0.dist-info → catalogmx-0.4.0.dist-info}/RECORD +51 -25
  47. {catalogmx-0.3.0.dist-info → catalogmx-0.4.0.dist-info}/WHEEL +1 -1
  48. catalogmx/catalogs/banxico/udis.py +0 -279
  49. catalogmx-0.3.0.dist-info/METADATA +0 -644
  50. {catalogmx-0.3.0.dist-info → catalogmx-0.4.0.dist-info}/entry_points.txt +0 -0
  51. {catalogmx-0.3.0.dist-info → catalogmx-0.4.0.dist-info}/licenses/AUTHORS.rst +0 -0
  52. {catalogmx-0.3.0.dist-info → catalogmx-0.4.0.dist-info}/licenses/LICENSE +0 -0
  53. {catalogmx-0.3.0.dist-info → catalogmx-0.4.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,279 @@
1
+ """
2
+ TIIE (Tasa de Interés Interbancaria de Equilibrio) Catalog - SQLite Backend
3
+
4
+ This module provides access to TIIE rates 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 TIIECatalog:
15
+ """
16
+ Catalog of TIIE (Tasa de Interés Interbancaria de Equilibrio) values
17
+
18
+ TIIE is the equilibrium interbank interest rate used as a reference
19
+ for variable rate loans in Mexico.
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
+ "plazo": row["plazo"],
39
+ "tasa": row["tasa"],
40
+ "año": row["anio"],
41
+ "mes": row["mes"],
42
+ }
43
+
44
+ @classmethod
45
+ def get_data(cls, plazo: int = 28) -> list[dict]:
46
+ """
47
+ Get all TIIE data for a specific term
48
+
49
+ :param plazo: Term in days (28, 91, or 182)
50
+ :return: List of all TIIE records
51
+ """
52
+ db = sqlite3.connect(cls._get_db_path())
53
+ db.row_factory = sqlite3.Row
54
+ cursor = db.execute(
55
+ """
56
+ SELECT fecha, plazo, tasa, anio, mes
57
+ FROM tiie
58
+ WHERE plazo = ?
59
+ ORDER BY fecha
60
+ """,
61
+ (plazo,),
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, plazo: int = 28) -> dict | None:
69
+ """
70
+ Get TIIE rate for a specific date and term
71
+
72
+ :param fecha: Date string in YYYY-MM-DD format
73
+ :param plazo: Term in days (28, 91, or 182)
74
+ :return: TIIE record or None if not found
75
+ """
76
+ db = sqlite3.connect(cls._get_db_path())
77
+ db.row_factory = sqlite3.Row
78
+
79
+ cursor = db.execute(
80
+ """
81
+ SELECT fecha, plazo, tasa, anio, mes
82
+ FROM tiie
83
+ WHERE fecha = ? AND plazo = ?
84
+ LIMIT 1
85
+ """,
86
+ (fecha, plazo),
87
+ )
88
+ row = cursor.fetchone()
89
+ db.close()
90
+
91
+ if row:
92
+ return cls._row_to_dict(row)
93
+ return None
94
+
95
+ @classmethod
96
+ def get_actual(cls, plazo: int = 28) -> dict | None:
97
+ """
98
+ Get most recent TIIE rate
99
+
100
+ :param plazo: Term in days (28, 91, or 182)
101
+ :return: Latest TIIE record
102
+ """
103
+ db = sqlite3.connect(cls._get_db_path())
104
+ db.row_factory = sqlite3.Row
105
+
106
+ cursor = db.execute(
107
+ """
108
+ SELECT fecha, plazo, tasa, anio, mes
109
+ FROM tiie
110
+ WHERE plazo = ?
111
+ ORDER BY fecha DESC
112
+ LIMIT 1
113
+ """,
114
+ (plazo,),
115
+ )
116
+ row = cursor.fetchone()
117
+ db.close()
118
+
119
+ if row:
120
+ return cls._row_to_dict(row)
121
+ return None
122
+
123
+ @classmethod
124
+ def get_valor_actual(cls, plazo: int = 28) -> float | None:
125
+ """
126
+ Get current TIIE rate value
127
+
128
+ :param plazo: Term in days (28, 91, or 182)
129
+ :return: Current TIIE rate or None
130
+ """
131
+ record = cls.get_actual(plazo)
132
+ return record.get("tasa") if record else None
133
+
134
+ @classmethod
135
+ def get_por_anio(cls, anio: int, plazo: int = 28) -> list[dict]:
136
+ """
137
+ Get all TIIE rates for a specific year and term
138
+
139
+ :param anio: Year (e.g., 2024)
140
+ :param plazo: Term in days (28, 91, or 182)
141
+ :return: List of TIIE records for the year
142
+ """
143
+ db = sqlite3.connect(cls._get_db_path())
144
+ db.row_factory = sqlite3.Row
145
+
146
+ cursor = db.execute(
147
+ """
148
+ SELECT fecha, plazo, tasa, anio, mes
149
+ FROM tiie
150
+ WHERE anio = ? AND plazo = ?
151
+ ORDER BY fecha
152
+ """,
153
+ (anio, plazo),
154
+ )
155
+ results = [cls._row_to_dict(row) for row in cursor.fetchall()]
156
+ db.close()
157
+ return results
158
+
159
+ @classmethod
160
+ def calcular_variacion(cls, fecha_inicio: str, fecha_fin: str, plazo: int = 28) -> float | None:
161
+ """
162
+ Calculate variation between two dates
163
+
164
+ :param fecha_inicio: Start date (YYYY-MM-DD)
165
+ :param fecha_fin: End date (YYYY-MM-DD)
166
+ :param plazo: Term in days (28, 91, or 182)
167
+ :return: Variation in percentage points or None
168
+ """
169
+ record_inicio = cls.get_por_fecha(fecha_inicio, plazo)
170
+ record_fin = cls.get_por_fecha(fecha_fin, plazo)
171
+
172
+ if not record_inicio or not record_fin:
173
+ return None
174
+
175
+ tasa_inicio = record_inicio.get("tasa")
176
+ tasa_fin = record_fin.get("tasa")
177
+
178
+ if tasa_inicio is None or tasa_fin is None:
179
+ return None
180
+
181
+ # Return difference in percentage points (not percentage change)
182
+ return tasa_fin - tasa_inicio
183
+
184
+ @classmethod
185
+ def get_promedio_anual(cls, anio: int, plazo: int = 28) -> float | None:
186
+ """
187
+ Calculate annual average TIIE rate
188
+
189
+ :param anio: Year (e.g., 2024)
190
+ :param plazo: Term in days (28, 91, or 182)
191
+ :return: Annual average rate or None if no data
192
+ """
193
+ records = cls.get_por_anio(anio, plazo)
194
+ if not records:
195
+ return None
196
+
197
+ tasas = [r.get("tasa") for r in records if r.get("tasa") is not None]
198
+ return sum(tasas) / len(tasas) if tasas else None
199
+
200
+ @classmethod
201
+ def get_tasa_actual(cls, plazo: int = 28) -> float | None:
202
+ """
203
+ Get current TIIE rate value (alias for get_valor_actual)
204
+
205
+ :param plazo: Term in days (28, 91, or 182)
206
+ :return: Current TIIE rate or None
207
+ """
208
+ return cls.get_valor_actual(plazo)
209
+
210
+ @classmethod
211
+ def calcular_interes(
212
+ cls, monto: float, fecha_inicio: str, fecha_fin: str, plazo: int = 28
213
+ ) -> float | None:
214
+ """
215
+ Calculate interest using TIIE rate
216
+
217
+ :param monto: Principal amount
218
+ :param fecha_inicio: Start date (YYYY-MM-DD)
219
+ :param fecha_fin: End date (YYYY-MM-DD)
220
+ :param plazo: Term in days (28, 91, or 182)
221
+ :return: Interest amount or None if data not available
222
+ """
223
+ from datetime import datetime
224
+
225
+ record_inicio = cls.get_por_fecha(fecha_inicio, plazo)
226
+ record_fin = cls.get_por_fecha(fecha_fin, plazo)
227
+
228
+ if not record_inicio and not record_fin:
229
+ return None
230
+
231
+ # Use available rate (start or end, or average if both)
232
+ tasa = None
233
+ if record_inicio and record_fin:
234
+ tasa = (record_inicio.get("tasa", 0) + record_fin.get("tasa", 0)) / 2
235
+ elif record_inicio:
236
+ tasa = record_inicio.get("tasa")
237
+ elif record_fin:
238
+ tasa = record_fin.get("tasa")
239
+
240
+ if tasa is None:
241
+ return None
242
+
243
+ # Calculate days between dates
244
+ date_inicio = datetime.strptime(fecha_inicio, "%Y-%m-%d")
245
+ date_fin = datetime.strptime(fecha_fin, "%Y-%m-%d")
246
+ dias = (date_fin - date_inicio).days
247
+
248
+ if dias <= 0:
249
+ return 0.0
250
+
251
+ # Calculate simple interest: I = P * r * t / 36500
252
+ # (tasa is annual percentage, divide by 100 to get decimal, then by 365 for daily rate)
253
+ interes = monto * (tasa / 100) * (dias / 365)
254
+ return round(interes, 2)
255
+
256
+
257
+ # Convenience functions for TIIE 28 (most commonly used)
258
+ def get_tiie_actual(plazo: int = 28) -> dict | None:
259
+ """Get most recent TIIE rate"""
260
+ return TIIECatalog.get_actual(plazo)
261
+
262
+
263
+ def get_tiie_por_fecha(fecha: str, plazo: int = 28) -> dict | None:
264
+ """Get TIIE rate for a specific date"""
265
+ return TIIECatalog.get_por_fecha(fecha, plazo)
266
+
267
+
268
+ def get_tiie_valor_actual(plazo: int = 28) -> float | None:
269
+ """Get current TIIE rate value"""
270
+ return TIIECatalog.get_valor_actual(plazo)
271
+
272
+
273
+ # Export commonly used functions and classes
274
+ __all__ = [
275
+ "TIIECatalog",
276
+ "get_tiie_actual",
277
+ "get_tiie_por_fecha",
278
+ "get_tiie_valor_actual",
279
+ ]
@@ -0,0 +1,255 @@
1
+ """
2
+ Tipo de Cambio USD/MXN Catalog - SQLite Backend
3
+
4
+ This module provides access to USD/MXN exchange rate values using a SQLite database
5
+ 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 TipoCambioUSDCatalog:
15
+ """
16
+ Catalog of USD/MXN exchange rate values
17
+
18
+ The FIX exchange rate is the official reference rate published daily by
19
+ Banco de México. This catalog uses an auto-updating SQLite database backend.
20
+ """
21
+
22
+ _db_path: Path | None = None
23
+
24
+ @classmethod
25
+ def _get_db_path(cls) -> Path:
26
+ """Get path to database with auto-update"""
27
+ if cls._db_path is None:
28
+ cls._db_path = get_database_path(auto_update=True, max_age_hours=24)
29
+ return cls._db_path
30
+
31
+ @classmethod
32
+ def _row_to_dict(cls, row: sqlite3.Row) -> dict:
33
+ """Convert SQLite row to dictionary"""
34
+ return {
35
+ "fecha": row["fecha"],
36
+ "tipo_cambio": row["tipo_cambio"],
37
+ "año": row["anio"],
38
+ "mes": row["mes"],
39
+ "fuente": row["fuente"],
40
+ "moneda_origen": row["moneda_origen"],
41
+ "moneda_destino": row["moneda_destino"],
42
+ }
43
+
44
+ @classmethod
45
+ def get_data(cls) -> list[dict]:
46
+ """
47
+ Get all exchange rate data
48
+
49
+ :return: List of all exchange rate records
50
+ """
51
+ db = sqlite3.connect(cls._get_db_path())
52
+ db.row_factory = sqlite3.Row
53
+ cursor = db.execute("""
54
+ SELECT fecha, tipo_cambio, anio, mes, fuente, moneda_origen, moneda_destino
55
+ FROM tipo_cambio
56
+ WHERE fuente = 'FIX'
57
+ ORDER BY fecha
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) -> dict | None:
65
+ """
66
+ Get exchange rate for a specific date
67
+
68
+ :param fecha: Date string in YYYY-MM-DD format
69
+ :return: Exchange rate record or None if not found
70
+ """
71
+ db = sqlite3.connect(cls._get_db_path())
72
+ db.row_factory = sqlite3.Row
73
+
74
+ cursor = db.execute(
75
+ """
76
+ SELECT fecha, tipo_cambio, anio, mes, fuente, moneda_origen, moneda_destino
77
+ FROM tipo_cambio
78
+ WHERE fecha = ? AND fuente = 'FIX'
79
+ LIMIT 1
80
+ """,
81
+ (fecha,),
82
+ )
83
+ row = cursor.fetchone()
84
+ db.close()
85
+
86
+ if row:
87
+ return cls._row_to_dict(row)
88
+ return None
89
+
90
+ @classmethod
91
+ def get_por_anio(cls, anio: int) -> list[dict]:
92
+ """
93
+ Get all exchange rates for a specific year
94
+
95
+ :param anio: Year (e.g., 2024)
96
+ :return: List of exchange rate records for the year
97
+ """
98
+ db = sqlite3.connect(cls._get_db_path())
99
+ db.row_factory = sqlite3.Row
100
+
101
+ cursor = db.execute(
102
+ """
103
+ SELECT fecha, tipo_cambio, anio, mes, fuente, moneda_origen, moneda_destino
104
+ FROM tipo_cambio
105
+ WHERE anio = ? AND fuente = 'FIX'
106
+ ORDER BY fecha
107
+ """,
108
+ (anio,),
109
+ )
110
+ results = [cls._row_to_dict(row) for row in cursor.fetchall()]
111
+ db.close()
112
+ return results
113
+
114
+ @classmethod
115
+ def get_actual(cls) -> dict | None:
116
+ """
117
+ Get most recent exchange rate
118
+
119
+ :return: Latest exchange rate record
120
+ """
121
+ db = sqlite3.connect(cls._get_db_path())
122
+ db.row_factory = sqlite3.Row
123
+
124
+ cursor = db.execute("""
125
+ SELECT fecha, tipo_cambio, anio, mes, fuente, moneda_origen, moneda_destino
126
+ FROM tipo_cambio
127
+ WHERE fuente = 'FIX'
128
+ ORDER BY fecha DESC
129
+ LIMIT 1
130
+ """)
131
+ row = cursor.fetchone()
132
+ db.close()
133
+
134
+ if row:
135
+ return cls._row_to_dict(row)
136
+ return None
137
+
138
+ @classmethod
139
+ def get_valor_actual(cls) -> float | None:
140
+ """
141
+ Get current exchange rate value
142
+
143
+ :return: Current USD/MXN rate or None
144
+ """
145
+ record = cls.get_actual()
146
+ return record.get("tipo_cambio") if record else None
147
+
148
+ @classmethod
149
+ def usd_a_mxn(cls, usd: float, fecha: str | None = None) -> float | None:
150
+ """
151
+ Convert USD to MXN
152
+
153
+ :param usd: Amount in USD
154
+ :param fecha: Date string in YYYY-MM-DD format, or None for latest rate
155
+ :return: Amount in MXN or None if rate not found
156
+ """
157
+ if fecha:
158
+ record = cls.get_por_fecha(fecha)
159
+ else:
160
+ record = cls.get_actual()
161
+
162
+ if not record:
163
+ return None
164
+
165
+ rate = record.get("tipo_cambio")
166
+ return usd * rate if rate else None
167
+
168
+ @classmethod
169
+ def mxn_a_usd(cls, mxn: float, fecha: str | None = None) -> float | None:
170
+ """
171
+ Convert MXN to USD
172
+
173
+ :param mxn: Amount in MXN
174
+ :param fecha: Date string in YYYY-MM-DD format, or None for latest rate
175
+ :return: Amount in USD or None if rate not found
176
+ """
177
+ if fecha:
178
+ record = cls.get_por_fecha(fecha)
179
+ else:
180
+ record = cls.get_actual()
181
+
182
+ if not record:
183
+ return None
184
+
185
+ rate = record.get("tipo_cambio")
186
+ return mxn / rate if rate else None
187
+
188
+ @classmethod
189
+ def calcular_variacion(cls, fecha_inicio: str, fecha_fin: str) -> float | None:
190
+ """
191
+ Calculate percentage variation between two dates
192
+
193
+ :param fecha_inicio: Start date (YYYY-MM-DD)
194
+ :param fecha_fin: End date (YYYY-MM-DD)
195
+ :return: Percentage variation or None if values not found
196
+ """
197
+ record_inicio = cls.get_por_fecha(fecha_inicio)
198
+ record_fin = cls.get_por_fecha(fecha_fin)
199
+
200
+ if not record_inicio or not record_fin:
201
+ return None
202
+
203
+ rate_inicio = record_inicio.get("tipo_cambio")
204
+ rate_fin = record_fin.get("tipo_cambio")
205
+
206
+ if not rate_inicio or not rate_fin:
207
+ return None
208
+
209
+ return ((rate_fin - rate_inicio) / rate_inicio) * 100
210
+
211
+ @classmethod
212
+ def get_promedio_anual(cls, anio: int) -> float | None:
213
+ """
214
+ Calculate annual average exchange rate
215
+
216
+ :param anio: Year (e.g., 2024)
217
+ :return: Annual average rate or None if no data
218
+ """
219
+ records = cls.get_por_anio(anio)
220
+ if not records:
221
+ return None
222
+
223
+ rates = [r.get("tipo_cambio") for r in records if r.get("tipo_cambio")]
224
+ return sum(rates) / len(rates) if rates else None
225
+
226
+
227
+ # Convenience functions
228
+ def get_tipo_cambio_actual() -> dict | None:
229
+ """Get most recent exchange rate"""
230
+ return TipoCambioUSDCatalog.get_actual()
231
+
232
+
233
+ def get_tipo_cambio_por_fecha(fecha: str) -> dict | None:
234
+ """Get exchange rate for a specific date"""
235
+ return TipoCambioUSDCatalog.get_por_fecha(fecha)
236
+
237
+
238
+ def usd_a_mxn(usd: float, fecha: str | None = None) -> float | None:
239
+ """Convert USD to MXN"""
240
+ return TipoCambioUSDCatalog.usd_a_mxn(usd, fecha)
241
+
242
+
243
+ def mxn_a_usd(mxn: float, fecha: str | None = None) -> float | None:
244
+ """Convert MXN to USD"""
245
+ return TipoCambioUSDCatalog.mxn_a_usd(mxn, fecha)
246
+
247
+
248
+ # Export commonly used functions and classes
249
+ __all__ = [
250
+ "TipoCambioUSDCatalog",
251
+ "get_tipo_cambio_actual",
252
+ "get_tipo_cambio_por_fecha",
253
+ "usd_a_mxn",
254
+ "mxn_a_usd",
255
+ ]