Taxes for the whole of Spain (common territory, Navarre, Álava, Bizkaia and Gipuzkoa); flats, garages and land valued on their own; Treasury bills, bonds and deposits priced on BME; broker accounts; a guided start; a page per investment with its chart and the tax of selling today; net worth at the top; undo when deleting; Woodpecker CI for the tests and the Linux build.
124 lines
7.7 KiB
Python
124 lines
7.7 KiB
Python
"""
|
|
fiscal — Las cifras que hay que declarar, aparte de las de la cartera.
|
|
|
|
La cartera (calculo.py) contesta «¿cómo va mi inversión?» y usa el coste
|
|
medio. Hacienda pregunta otra cosa: «¿qué ganaste o perdiste en cada venta?»,
|
|
y para contestarla manda el FIFO: lo que se vende es lo que se compró primero.
|
|
Son dos cálculos con propósitos distintos y viven separados a propósito: este
|
|
paquete LEE las mismas operaciones y no cambia ni una cifra de la cartera.
|
|
|
|
fifo.py lotes de compra, ganancia o pérdida de cada transmisión,
|
|
rendimientos cobrados y existencias a 31 de diciembre.
|
|
cuota.py lo que saldría a pagar por la parte del ahorro.
|
|
actividad.py el rendimiento de una actividad económica (la minería), con
|
|
sus gastos y la amortización de los aparatos: parte general.
|
|
regularizar.py lo que costaría declarar ahora lo no declarado: recargos e intereses.
|
|
exportar.py el informe de un ejercicio en una hoja de cálculo.
|
|
reglas/ las reglas de cada territorio, por ejercicios, con su cita.
|
|
|
|
Es una ayuda para preparar la declaración, no asesoría fiscal. Cada criterio
|
|
que se aplica está escrito donde se aplica, con su fuente cuando la tiene.
|
|
"""
|
|
|
|
import datetime as dt
|
|
|
|
from . import alquiler, cuota, regularizar
|
|
from .fifo import CRITERIOS, OPCIONES, ejercicios, informe, opciones_validas
|
|
|
|
__all__ = ["CRITERIOS", "OPCIONES", "completo", "cuentas_bróker", "cuota", "ejercicios", "informe", "opciones_validas",
|
|
"regularizacion", "regularizar"]
|
|
|
|
|
|
def cuentas_bróker(activos: list[dict], movimientos: list[dict], ejercicio: int, trading: dict | None) -> list[dict]:
|
|
"""Las cuentas de bróker o trading con algo apuntado hasta ese año, con el
|
|
resultado que el usuario ha copiado del informe fiscal del bróker (o None).
|
|
«trading» es {"2025": {id del activo: euros}}."""
|
|
del_año = (trading or {}).get(str(ejercicio)) or {}
|
|
hasta = f"{ejercicio}-12-31"
|
|
con_algo = {m["activo"] for m in movimientos if m["fecha"] <= hasta}
|
|
return [{"activo": a["id"], "nombre": a["nombre"], "resultado": del_año.get(a["id"])}
|
|
for a in activos if a["tipo"] == "CuentaTrading" and a["id"] in con_algo]
|
|
|
|
|
|
def _resultado_bróker(activos, movimientos, ejercicio, trading) -> float:
|
|
return round(sum(c["resultado"] or 0.0 for c in cuentas_bróker(activos, movimientos, ejercicio, trading)), 2)
|
|
|
|
|
|
def completo(activos: list[dict], movimientos: list[dict], precios: list[dict], ejercicio: int,
|
|
opciones: dict | None = None, territorio: str | None = None, trading: dict | None = None) -> dict:
|
|
"""El informe del ejercicio con la liquidación de la parte del ahorro.
|
|
|
|
Para arrastrar bien las pérdidas pendientes hay que calcular todos los
|
|
años desde el primero con operaciones hasta ese. El territorio es el
|
|
elegido en las opciones, salvo que se pida otro."""
|
|
territorio = territorio or opciones_validas(opciones)["territorio"]
|
|
citas = cuota.reglas(territorio)["citas"]
|
|
fuera = set(cuota.reglas(territorio)["fuera_del_ahorro"])
|
|
# Los inmuebles: lo que su alquiler lleva cada año a la base del ahorro (en el País Vasco) y a la general
|
|
inm = alquiler.calcular(activos, movimientos, precios, ejercicio, cuota.reglas(territorio))
|
|
años, pedido = [], None
|
|
primero = min([int(m["fecha"][:4]) for m in movimientos] + [ejercicio])
|
|
for y in range(primero, ejercicio + 1):
|
|
r = informe(activos, movimientos, precios, y, opciones, citas,
|
|
cuota.reglas(territorio)["actividad"] if y == ejercicio else None,
|
|
cuota.reglas(territorio).get("recompra_cripto"), cuota.reglas(territorio).get("coeficientes"))
|
|
al_ahorro = [c for c in r["por_concepto"] if c["concepto"] not in fuera]
|
|
años.append({
|
|
"ejercicio": y,
|
|
"rendimientos": sum(c["bruto"] for c in al_ahorro) + inm["ahorro"].get(y, 0.0),
|
|
"retenciones": sum(c["retencion"] for c in al_ahorro),
|
|
# Lo que da el bróker de cada cuenta de trading, y la venta de un inmueble, también son ganancias y pérdidas
|
|
"ganancias": r["total"]["computable"] + _resultado_bróker(activos, movimientos, y, trading)
|
|
+ sum(v["ganancia"] for v in inm["ventas"].get(y, [])),
|
|
"incrementos": sum(t["ganancia"] for t in r["transmisiones"] if t["ganancia"] > 0),
|
|
"transmitido": sum(t["valor_transmision"] for t in r["transmisiones"]),
|
|
# Sin saber cuánto se vendió dentro de la cuenta, la exención por vender poco no se aplica
|
|
"sin_exencion": bool(_resultado_bróker(activos, movimientos, y, trading)),
|
|
})
|
|
pedido = r
|
|
pedido["cuota"] = cuota.ahorro(territorio, años)
|
|
pedido["cuota"]["fuera_del_ahorro"] = [c for c in pedido["por_concepto"] if c["concepto"] in fuera]
|
|
pedido["bróker"] = cuentas_bróker(activos, movimientos, ejercicio, trading)
|
|
con_dato = {c["activo"] for c in pedido["bróker"] if c["resultado"] is not None}
|
|
pedido["fuera"] = [f for f in pedido["fuera"] if f["activo"] not in con_dato]
|
|
pedido["alquileres"], pedido["imputaciones"] = inm["fichas"], inm["imputaciones"]
|
|
pedido["ventas_inmuebles"] = inm["ventas"].get(ejercicio, [])
|
|
pedido["cuota"]["alquiler_ahorro"] = inm["ahorro"].get(ejercicio, 0.0)
|
|
return pedido
|
|
|
|
|
|
|
|
def regularizacion(activos: list[dict], movimientos: list[dict], precios: list[dict], hoy: dt.date,
|
|
opciones: dict | None = None, declarado: dict | None = None, territorio: str | None = None,
|
|
trading: dict | None = None) -> dict:
|
|
"""Lo que costaría ponerse al día de cada ejercicio ya vencido y no prescrito
|
|
(ver regularizar.py). «declarado» es lo que el usuario dice de cada año."""
|
|
territorio = territorio or opciones_validas(opciones)["territorio"]
|
|
reglas = cuota.reglas(territorio)
|
|
fuera = set(reglas["fuera_del_ahorro"])
|
|
años, filas = [], []
|
|
inm = alquiler.calcular(activos, movimientos, precios, hoy.year, reglas) if movimientos else {"ahorro": {}, "general": {}, "ventas": {}}
|
|
if movimientos:
|
|
for y in range(min(int(m["fecha"][:4]) for m in movimientos), hoy.year + 1):
|
|
r = informe(activos, movimientos, precios, y, opciones, reglas["citas"], reglas["actividad"],
|
|
reglas.get("recompra_cripto"), reglas.get("coeficientes"))
|
|
al_ahorro = [c for c in r["por_concepto"] if c["concepto"] not in fuera]
|
|
años.append({
|
|
"ejercicio": y,
|
|
"rendimientos": sum(c["bruto"] for c in al_ahorro) + inm["ahorro"].get(y, 0.0),
|
|
"retenciones": sum(c["retencion"] for c in al_ahorro),
|
|
"ganancias": r["total"]["computable"] + _resultado_bróker(activos, movimientos, y, trading)
|
|
+ sum(v["ganancia"] for v in inm["ventas"].get(y, [])),
|
|
"incrementos": sum(t["ganancia"] for t in r["transmisiones"] if t["ganancia"] > 0),
|
|
"transmitido": sum(t["valor_transmision"] for t in r["transmisiones"]),
|
|
"sin_exencion": bool(_resultado_bróker(activos, movimientos, y, trading)),
|
|
})
|
|
liquidacion = cuota.ahorro(territorio, años)
|
|
filas.append({"ejercicio": y, "ahorro": liquidacion["resultado"], "verificado": liquidacion["verificado"],
|
|
# A tu tipo marginal: la minería y lo que el alquiler y la renta imputada llevan a la parte general
|
|
"actividades": [{"nombre": a["nombre"], "rendimiento": a["rendimiento"]} for a in r["actividades"]]
|
|
+ inm["general"].get(y, [])})
|
|
salida = regularizar.plan(filas, declarado or {}, hoy, reglas["regularizacion"])
|
|
salida["nombre_territorio"] = cuota.TERRITORIOS[territorio]
|
|
salida["sin_desembolso"] = opciones_validas(opciones)["sin_desembolso"]
|
|
return salida
|