eldinosaur's picture
PocketAccountant: custom ledger UI + deterministic agent (engine, ledger, retrieval, classifier)
c55ab5e verified
Raw
History Blame Contribute Delete
3.38 kB
"""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."],
)