""" 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