PocketAccountant: custom ledger UI + deterministic agent (engine, ledger, retrieval, classifier)
c55ab5e verified | """ISR — Impuesto Sobre la Renta (Mexico), general regime for personas físicas. | |
| Monthly provisional payment under the Régimen de Actividades Empresariales y | |
| Profesionales: tax is computed on a *taxable base* (income minus authorized | |
| deductions) using the progressive Article-96 tariff. | |
| This module also computes the standard withholdings (retenciones) a persona moral | |
| applies when paying a persona física for professional services. | |
| """ | |
| from __future__ import annotations | |
| from decimal import Decimal | |
| from .money import D, money | |
| from .result import CalcResult | |
| from .tax_tables import ( | |
| ISR_MONTHLY_MX_2024, | |
| ISR_RETENTION_PROF_SERVICES_MX, | |
| IVA_RETENTION_PROF_SERVICES_MX, | |
| TaxTable, | |
| ) | |
| def isr_provisional_monthly( | |
| taxable_base, table: TaxTable = ISR_MONTHLY_MX_2024 | |
| ) -> CalcResult: | |
| """ISR for one month given an already-computed taxable base. | |
| taxable_base = monthly income − authorized deductions (computed elsewhere). | |
| """ | |
| base = D(taxable_base) | |
| # Below the first bracket floor (which starts at 0.01) there is no ISR. | |
| if base <= 0: | |
| note = ( | |
| "Negative base → no ISR this month (carry the loss forward)." | |
| if base < 0 | |
| else "Zero base → no ISR this month." | |
| ) | |
| return CalcResult( | |
| amount=money(0), | |
| label="ISR provisional (mensual)", | |
| breakdown=[("Base gravable", money(base))], | |
| source=table.source, | |
| effective_year=table.effective_year, | |
| notes=[note], | |
| ) | |
| bracket = next(b for b in table.brackets if b.contains(base)) | |
| excess = base - bracket.lower | |
| tax_on_excess = excess * bracket.rate | |
| tax = bracket.fixed_fee + tax_on_excess | |
| return CalcResult( | |
| amount=money(tax), | |
| label="ISR provisional (mensual)", | |
| breakdown=[ | |
| ("Base gravable", money(base)), | |
| ("Límite inferior", money(bracket.lower)), | |
| ("Excedente sobre límite inferior", money(excess)), | |
| ("Tasa marginal", bracket.rate), | |
| ("Impuesto sobre excedente", money(tax_on_excess)), | |
| ("Cuota fija", money(bracket.fixed_fee)), | |
| ], | |
| source=table.source, | |
| effective_year=table.effective_year, | |
| ) | |
| def taxable_base(income, deductions) -> Decimal: | |
| """income − deductions, floored at zero.""" | |
| base = D(income) - D(deductions) | |
| return base if base > 0 else D(0) | |
| def retenciones_servicios_profesionales(amount) -> CalcResult: | |
| """Withholdings on professional fees: 10% ISR + 10.6667% IVA (two-thirds of 16%). | |
| Applies when a persona física invoices a persona moral for professional services. | |
| The persona moral retains these and the persona física credits them later. | |
| """ | |
| base = D(amount) | |
| isr_ret = base * ISR_RETENTION_PROF_SERVICES_MX | |
| iva_ret = base * IVA_RETENTION_PROF_SERVICES_MX | |
| total = isr_ret + iva_ret | |
| return CalcResult( | |
| amount=money(total), | |
| label="Retenciones por servicios profesionales", | |
| breakdown=[ | |
| ("Monto del servicio (base)", money(base)), | |
| ("Retención ISR (10%)", money(isr_ret)), | |
| ("Retención IVA (2/3 de 16%)", money(iva_ret)), | |
| ], | |
| source="LISR Art. 106 / LIVA Art. 1-A (verify)", | |
| notes=["These retentions are credited against your own monthly ISR/IVA."], | |
| ) | |