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,154 @@
1
+ """
2
+ RESICO (Régimen Simplificado de Confianza) Calculator for Mexico
3
+ Supports years: 2024, 2025, 2026
4
+
5
+ RESICO uses a DIRECT TAX RATE system (simpler than ISR):
6
+ - No deductions allowed
7
+ - No fixed fee (cuota fija)
8
+ - Tax = Total Income × Bracket Rate
9
+
10
+ Official source: Artículo 113-E LISR (Ley del Impuesto Sobre la Renta)
11
+ """
12
+
13
+ import json
14
+ from pathlib import Path
15
+ from typing import Literal, TypedDict
16
+
17
+ RESICOYear = Literal[2024, 2025, 2026]
18
+ RESICOPeriod = Literal["mensual", "anual"]
19
+
20
+
21
+ class RESICOBracket(TypedDict):
22
+ """RESICO tax bracket structure (simplified - no cuota fija)"""
23
+
24
+ limiteInferior: float
25
+ limiteSuperior: float
26
+ tasa: float
27
+
28
+
29
+ class RESICOCalculationResult(TypedDict):
30
+ """Complete RESICO calculation result"""
31
+
32
+ ingreso: float
33
+ periodo: str
34
+ year: int
35
+ limiteMaximo: float
36
+ dentroDeLimite: bool
37
+ bracket: dict
38
+ resicoCalculado: float
39
+ tasaEfectiva: float
40
+
41
+
42
+ # Load RESICO tables from centralized JSON
43
+ _RESICO_TABLES: dict | None = None
44
+
45
+
46
+ def _load_resico_tables() -> dict:
47
+ """Load RESICO tables from shared JSON file"""
48
+ global _RESICO_TABLES
49
+ if _RESICO_TABLES is None:
50
+ json_path = (
51
+ Path(__file__).parent.parent.parent.parent.parent
52
+ / "packages"
53
+ / "shared-data"
54
+ / "resico-tables.json"
55
+ )
56
+ with open(json_path, encoding="utf-8") as f:
57
+ _RESICO_TABLES = json.load(f)
58
+ return _RESICO_TABLES
59
+
60
+
61
+ def get_resico_brackets(year: RESICOYear, periodo: RESICOPeriod) -> list[RESICOBracket]:
62
+ """
63
+ Get RESICO tax brackets for a specific year and period
64
+
65
+ Args:
66
+ year: Tax year (2024, 2025, or 2026)
67
+ periodo: Period (mensual or anual)
68
+
69
+ Returns:
70
+ List of RESICO tax brackets
71
+ """
72
+ tables = _load_resico_tables()
73
+ year_str = str(year)
74
+
75
+ brackets_data = tables["brackets"][year_str][periodo]
76
+
77
+ brackets = []
78
+ for b in brackets_data:
79
+ bracket = RESICOBracket(
80
+ limiteInferior=b["limiteInferior"], limiteSuperior=b["limiteSuperior"], tasa=b["tasa"]
81
+ )
82
+ brackets.append(bracket)
83
+
84
+ return brackets
85
+
86
+
87
+ def calculate_resico(
88
+ ingreso: float, periodo: RESICOPeriod = "mensual", year: RESICOYear = 2026
89
+ ) -> RESICOCalculationResult:
90
+ """
91
+ Calculate RESICO (Régimen Simplificado de Confianza) tax
92
+
93
+ RESICO uses a DIRECT TAX RATE system (not marginal like regular ISR):
94
+ - No deductions allowed
95
+ - No fixed fee (cuota fija)
96
+ - Tax = Total Income × Bracket Rate
97
+
98
+ Args:
99
+ ingreso: Taxable income for the period
100
+ periodo: Period (mensual or anual, default: mensual)
101
+ year: Tax year (default: 2026)
102
+
103
+ Returns:
104
+ Complete RESICO calculation result
105
+
106
+ Examples:
107
+ >>> # Monthly income $50,000 → bracket 1.5% → ISR = $50,000 × 1.5% = $750
108
+ >>> result = calculate_resico(50000, "mensual", 2026)
109
+ >>> print(f"RESICO: ${result['resicoCalculado']:.2f}")
110
+ RESICO: $750.00
111
+
112
+ >>> # Annual income $500,000 → bracket 1.1% → ISR = $500,000 × 1.1% = $5,500
113
+ >>> result = calculate_resico(500000, "anual", 2026)
114
+ >>> print(f"RESICO anual: ${result['resicoCalculado']:.2f}")
115
+ RESICO anual: $5500.00
116
+ """
117
+ tables = _load_resico_tables()
118
+
119
+ # Get income limits
120
+ limits = tables["limits"]["personaFisica"]
121
+ limite_maximo = (
122
+ limits["ingresoMensualMaximo"] if periodo == "mensual" else limits["ingresoAnualMaximo"]
123
+ )
124
+ dentro_de_limite = ingreso <= limite_maximo
125
+
126
+ # Get brackets for year and period
127
+ brackets = get_resico_brackets(year, periodo)
128
+
129
+ # Find applicable bracket
130
+ bracket = None
131
+ for b in brackets:
132
+ if b["limiteInferior"] <= ingreso <= b["limiteSuperior"]:
133
+ bracket = b
134
+ break
135
+
136
+ if bracket is None:
137
+ bracket = brackets[-1] # Last bracket if not found
138
+
139
+ # RESICO calculation: Direct rate on total income (NO cuota fija, NO excedente)
140
+ resico_calculado = ingreso * (bracket["tasa"] / 100)
141
+
142
+ # Effective rate (should equal bracket rate for RESICO)
143
+ tasa_efectiva = (resico_calculado / ingreso * 100) if ingreso > 0 else 0
144
+
145
+ return RESICOCalculationResult(
146
+ ingreso=ingreso,
147
+ periodo=periodo,
148
+ year=year,
149
+ limiteMaximo=limite_maximo,
150
+ dentroDeLimite=dentro_de_limite,
151
+ bracket=dict(bracket),
152
+ resicoCalculado=resico_calculado,
153
+ tasaEfectiva=tasa_efectiva,
154
+ )
@@ -4,21 +4,47 @@ catalogmx.catalogs.banxico - Catálogos de Banxico
4
4
  Catálogos incluidos:
5
5
  - BankCatalog: Bancos autorizados por Banxico
6
6
  - UDICatalog: UDIs (Unidades de Inversión)
7
+ - TipoCambioUSDCatalog: Tipo de cambio USD/MXN FIX
8
+ - TIIE28Catalog: TIIE 28 días
9
+ - CETES28Catalog: CETES 28 días
10
+ - InflacionAnualCatalog: Inflación anual (INPC)
11
+ - SalariosMinimosCatalog: Salarios mínimos por zona
7
12
  - InstitucionesFinancieras: Tipos de instituciones del sistema financiero
8
13
  - MonedasDivisas: Monedas y divisas internacionales
9
14
  - CodigosPlazaCatalog: Códigos de plaza para CLABE
10
15
  """
11
16
 
17
+ # Static catalogs (JSON-based, don't change frequently)
12
18
  from .banks import BankCatalog
19
+ from .cetes_sqlite import CETESCatalog as CETES28Catalog
13
20
  from .codigos_plaza import CodigosPlazaCatalog
21
+ from .inflacion_sqlite import InflacionCatalog as InflacionAnualCatalog
14
22
  from .instituciones_financieras import InstitucionesFinancieras
15
23
  from .monedas_divisas import MonedasDivisas
16
- from .udis import UDICatalog
24
+ from .salarios_minimos_sqlite import SalariosMinimosCatalog
25
+ from .tiie_sqlite import TIIECatalog as TIIE28Catalog
26
+ from .tipo_cambio_usd_sqlite import TipoCambioUSDCatalog
27
+
28
+ # Dynamic catalogs (SQLite-based, auto-updating)
29
+ from .udis_sqlite import UDICatalog
30
+
31
+ # Aliases for convenience
32
+ Banks = BankCatalog
33
+ Bancos = BankCatalog
17
34
 
18
35
  __all__ = [
36
+ # Main classes
19
37
  "BankCatalog",
20
- "UDICatalog",
38
+ "CETES28Catalog",
39
+ "CodigosPlazaCatalog",
40
+ "InflacionAnualCatalog",
21
41
  "InstitucionesFinancieras",
22
42
  "MonedasDivisas",
23
- "CodigosPlazaCatalog",
43
+ "SalariosMinimosCatalog",
44
+ "TIIE28Catalog",
45
+ "TipoCambioUSDCatalog",
46
+ "UDICatalog",
47
+ # Aliases
48
+ "Banks",
49
+ "Bancos",
24
50
  ]
@@ -0,0 +1,279 @@
1
+ """
2
+ CETES (Certificados de la Tesorería) Catalog - SQLite Backend
3
+
4
+ This module provides access to CETES 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 CETESCatalog:
15
+ """
16
+ Catalog of CETES (Certificados de la Tesorería) rates
17
+
18
+ CETES are short-term Mexican treasury bills and serve as the
19
+ risk-free rate benchmark for the Mexican market.
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
+ "instrumento": "CETES", # Add instrumento field for compatibility
43
+ }
44
+
45
+ @classmethod
46
+ def get_data(cls, plazo: int = 28) -> list[dict]:
47
+ """
48
+ Get all CETES data for a specific term
49
+
50
+ :param plazo: Term in days (28, 91, 182, or 364)
51
+ :return: List of all CETES records
52
+ """
53
+ db = sqlite3.connect(cls._get_db_path())
54
+ db.row_factory = sqlite3.Row
55
+ cursor = db.execute(
56
+ """
57
+ SELECT fecha, plazo, tasa, anio, mes
58
+ FROM cetes
59
+ WHERE plazo = ?
60
+ ORDER BY fecha
61
+ """,
62
+ (plazo,),
63
+ )
64
+ results = [cls._row_to_dict(row) for row in cursor.fetchall()]
65
+ db.close()
66
+ return results
67
+
68
+ @classmethod
69
+ def get_por_fecha(cls, fecha: str, plazo: int = 28) -> dict | None:
70
+ """
71
+ Get CETES rate for a specific date and term
72
+
73
+ :param fecha: Date string in YYYY-MM-DD format
74
+ :param plazo: Term in days (28, 91, 182, or 364)
75
+ :return: CETES record or None if not found
76
+ """
77
+ db = sqlite3.connect(cls._get_db_path())
78
+ db.row_factory = sqlite3.Row
79
+
80
+ cursor = db.execute(
81
+ """
82
+ SELECT fecha, plazo, tasa, anio, mes
83
+ FROM cetes
84
+ WHERE fecha = ? AND plazo = ?
85
+ LIMIT 1
86
+ """,
87
+ (fecha, plazo),
88
+ )
89
+ row = cursor.fetchone()
90
+ db.close()
91
+
92
+ if row:
93
+ return cls._row_to_dict(row)
94
+ return None
95
+
96
+ @classmethod
97
+ def get_actual(cls, plazo: int = 28) -> dict | None:
98
+ """
99
+ Get most recent CETES rate
100
+
101
+ :param plazo: Term in days (28, 91, 182, or 364)
102
+ :return: Latest CETES record
103
+ """
104
+ db = sqlite3.connect(cls._get_db_path())
105
+ db.row_factory = sqlite3.Row
106
+
107
+ cursor = db.execute(
108
+ """
109
+ SELECT fecha, plazo, tasa, anio, mes
110
+ FROM cetes
111
+ WHERE plazo = ?
112
+ ORDER BY fecha DESC
113
+ LIMIT 1
114
+ """,
115
+ (plazo,),
116
+ )
117
+ row = cursor.fetchone()
118
+ db.close()
119
+
120
+ if row:
121
+ return cls._row_to_dict(row)
122
+ return None
123
+
124
+ @classmethod
125
+ def get_valor_actual(cls, plazo: int = 28) -> float | None:
126
+ """
127
+ Get current CETES rate value
128
+
129
+ :param plazo: Term in days (28, 91, 182, or 364)
130
+ :return: Current CETES rate or None
131
+ """
132
+ record = cls.get_actual(plazo)
133
+ return record.get("tasa") if record else None
134
+
135
+ @classmethod
136
+ def get_por_anio(cls, anio: int, plazo: int = 28) -> list[dict]:
137
+ """
138
+ Get all CETES rates for a specific year and term
139
+
140
+ :param anio: Year (e.g., 2024)
141
+ :param plazo: Term in days (28, 91, 182, or 364)
142
+ :return: List of CETES records for the year
143
+ """
144
+ db = sqlite3.connect(cls._get_db_path())
145
+ db.row_factory = sqlite3.Row
146
+
147
+ cursor = db.execute(
148
+ """
149
+ SELECT fecha, plazo, tasa, anio, mes
150
+ FROM cetes
151
+ WHERE anio = ? AND plazo = ?
152
+ ORDER BY fecha
153
+ """,
154
+ (anio, plazo),
155
+ )
156
+ results = [cls._row_to_dict(row) for row in cursor.fetchall()]
157
+ db.close()
158
+ return results
159
+
160
+ @classmethod
161
+ def calcular_variacion(cls, fecha_inicio: str, fecha_fin: str, plazo: int = 28) -> float | None:
162
+ """
163
+ Calculate variation between two dates
164
+
165
+ :param fecha_inicio: Start date (YYYY-MM-DD)
166
+ :param fecha_fin: End date (YYYY-MM-DD)
167
+ :param plazo: Term in days (28, 91, 182, or 364)
168
+ :return: Variation in percentage points or None
169
+ """
170
+ record_inicio = cls.get_por_fecha(fecha_inicio, plazo)
171
+ record_fin = cls.get_por_fecha(fecha_fin, plazo)
172
+
173
+ if not record_inicio or not record_fin:
174
+ return None
175
+
176
+ tasa_inicio = record_inicio.get("tasa")
177
+ tasa_fin = record_fin.get("tasa")
178
+
179
+ if tasa_inicio is None or tasa_fin is None:
180
+ return None
181
+
182
+ # Return difference in percentage points (not percentage change)
183
+ return tasa_fin - tasa_inicio
184
+
185
+ @classmethod
186
+ def get_promedio_anual(cls, anio: int, plazo: int = 28) -> float | None:
187
+ """
188
+ Calculate annual average CETES rate
189
+
190
+ :param anio: Year (e.g., 2024)
191
+ :param plazo: Term in days (28, 91, 182, or 364)
192
+ :return: Annual average rate or None if no data
193
+ """
194
+ records = cls.get_por_anio(anio, plazo)
195
+ if not records:
196
+ return None
197
+
198
+ tasas = [r.get("tasa") for r in records if r.get("tasa") is not None]
199
+ return sum(tasas) / len(tasas) if tasas else None
200
+
201
+ @classmethod
202
+ def get_tasa_actual(cls, plazo: int = 28) -> float | None:
203
+ """
204
+ Get current CETES rate value (alias for get_valor_actual)
205
+
206
+ :param plazo: Term in days (28, 91, 182, or 364)
207
+ :return: Current CETES rate or None
208
+ """
209
+ return cls.get_valor_actual(plazo)
210
+
211
+ @classmethod
212
+ def calcular_rendimiento(
213
+ cls, monto: float, fecha_inicio: str, fecha_fin: str, plazo: int = 28
214
+ ) -> float | None:
215
+ """
216
+ Calculate CETES return (principal + interest)
217
+
218
+ :param monto: Initial investment amount
219
+ :param fecha_inicio: Start date (YYYY-MM-DD)
220
+ :param fecha_fin: End date (YYYY-MM-DD)
221
+ :param plazo: Term in days (28, 91, 182, or 364)
222
+ :return: Total return (principal + interest) or None if data not available
223
+ """
224
+ from datetime import datetime
225
+
226
+ record_inicio = cls.get_por_fecha(fecha_inicio, plazo)
227
+ record_fin = cls.get_por_fecha(fecha_fin, plazo)
228
+
229
+ if not record_inicio and not record_fin:
230
+ return None
231
+
232
+ # Use available rate (start or end, or average if both)
233
+ tasa = None
234
+ if record_inicio and record_fin:
235
+ tasa = (record_inicio.get("tasa", 0) + record_fin.get("tasa", 0)) / 2
236
+ elif record_inicio:
237
+ tasa = record_inicio.get("tasa")
238
+ elif record_fin:
239
+ tasa = record_fin.get("tasa")
240
+
241
+ if tasa is None:
242
+ return None
243
+
244
+ # Calculate days between dates
245
+ date_inicio = datetime.strptime(fecha_inicio, "%Y-%m-%d")
246
+ date_fin = datetime.strptime(fecha_fin, "%Y-%m-%d")
247
+ dias = (date_fin - date_inicio).days
248
+
249
+ if dias <= 0:
250
+ return monto
251
+
252
+ # Calculate simple interest return
253
+ interes = monto * (tasa / 100) * (dias / 365)
254
+ return round(monto + interes, 2)
255
+
256
+
257
+ # Convenience functions for CETES 28 (most commonly used)
258
+ def get_cetes_actual(plazo: int = 28) -> dict | None:
259
+ """Get most recent CETES rate"""
260
+ return CETESCatalog.get_actual(plazo)
261
+
262
+
263
+ def get_cetes_por_fecha(fecha: str, plazo: int = 28) -> dict | None:
264
+ """Get CETES rate for a specific date"""
265
+ return CETESCatalog.get_por_fecha(fecha, plazo)
266
+
267
+
268
+ def get_cetes_valor_actual(plazo: int = 28) -> float | None:
269
+ """Get current CETES rate value"""
270
+ return CETESCatalog.get_valor_actual(plazo)
271
+
272
+
273
+ # Export commonly used functions and classes
274
+ __all__ = [
275
+ "CETESCatalog",
276
+ "get_cetes_actual",
277
+ "get_cetes_por_fecha",
278
+ "get_cetes_valor_actual",
279
+ ]