caudal/programa/cartera/fiscal/exportar.py
esetheyers 7e18576f86 Caudal 0.2.0
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.
2026-10-15 16:49:15 -05:00

171 lines
11 KiB
Python

"""
exportar.py — El informe fiscal de un ejercicio en una hoja de cálculo.
Lo mismo que enseña la pestaña «Fiscal», con todo el detalle, para repasarlo
fuera del programa, guardarlo con la declaración o dárselo a quien te lleve
los impuestos. Valores, sin fórmulas ni macros: es una foto, no se edita.
Resumen las cifras del año y la liquidación de la parte del ahorro
Ventas cada transmisión: lo cobrado, lo que costó y la ganancia
Lotes de qué compras sale cada venta (el FIFO, lote a lote)
Rendimientos dividendos, intereses y rentas, con su retención
Actividad la minería: ingresos, gastos y amortización de los aparatos
Recibido cada entrada minada o recibida sin pagar, a su valor del día
Existencias lo que quedaba a 31 de diciembre
Criterios con qué criterios está hecho y qué queda por resolver
Recibe el informe ya calculado (fiscal.completo): aquí no se calcula nada.
Necesita openpyxl, como la exportación de la cartera.
"""
from __future__ import annotations
from pathlib import Path
from ..exportar_xlsx import EUROS, UNIDADES, _fecha, _Hoja, _openpyxl
FECHA = "DD/MM/YYYY"
CANTIDAD = "#,##0.00000000"
def exportar(r: dict, destino: Path) -> None:
"""Escribe el libro del informe «r» (lo que devuelve fiscal.completo)."""
_openpyxl()
from openpyxl import Workbook
wb = Workbook()
wb.remove(wb.active)
c, t = r["cuota"], r["total"]
h = _Hoja(wb, "Resumen", [("Concepto", 62, None), ("Importe", 16, EUROS), ("Nota", 90, None)])
h.datos([f"Ejercicio {r['ejercicio']} · {c['nombre_territorio']}", None,
"Ayuda para preparar la declaración, no asesoría fiscal. Hecho solo con lo que hay en la cartera."], True)
h.titulo("Ganancias y pérdidas por ventas")
h.datos(["Ganancias", t["ganancias"]])
h.datos(["Pérdidas", t["perdidas"]])
h.datos(["Pérdidas aplazadas por recompra", t["aplazadas"]])
h.datos(["Pérdidas de años anteriores que se integran", t["integradas"]])
h.datos(["Lo que cuenta este año", t["computable"]], True)
for x in r["por_clase"]:
h.datos([f" {x['clase']} ({x['transmisiones']} operaciones)", x["neto"],
f"Transmisión {x['valor_transmision']:.2f} · adquisición {x['valor_adquisicion']:.2f}"])
if r.get("bróker"):
h.titulo("Cuentas de bróker o trading (del informe fiscal del bróker)")
for x in r["bróker"]:
h.datos([x["nombre"], x["resultado"], None if x["resultado"] is not None else "Sin apuntar: no entra en la cuota"])
if r.get("alquileres") or r.get("imputaciones") or r.get("ventas_inmuebles"):
h.titulo("Inmuebles")
for x in r.get("alquileres", []):
h.datos([f"{x['nombre']}: alquiler ({'base del ahorro' if x['base'] == 'ahorro' else 'parte general'})", x["rendimiento"],
f"Cobrado {x['ingresos']:.2f} · gastos {x['deducido']:.2f} · bonificación {x['bonificacion']:.2f} · reducción {x['reduccion']:.2f}"
+ ("" if x["verificado"] else " · REGLAS SIN COMPROBAR")])
for x in r.get("ventas_inmuebles", []):
h.datos([f"{x['nombre']}: venta (base del ahorro)", x["ganancia"],
f"Cobrado {x['valor_transmision']:.2f} · coste {x['valor_adquisicion']:.2f} (pagado {x['pagado']:.2f}, "
f"actualizado {x['actualizado']:.2f}, amortizado {x['amortizacion']:.2f})"])
for x in r.get("imputaciones", []):
h.datos([f"{x['nombre']}: renta imputada (parte general)", x["renta"], x["aviso"] or x["cita"]])
h.titulo("Dividendos, intereses y rentas")
for x in r["por_concepto"]:
h.datos([x["concepto"], x["bruto"], f"Retención {x['retencion']:.2f} · cobrado {x['neto']:.2f}"])
h.titulo("Actividades económicas (parte general)")
for a in r["actividades"]:
h.datos([f"{a['nombre']}: rendimiento neto", a["rendimiento"], "Detalle en la hoja «Actividad»"], True)
h.datos(["Minado o recibido sin pagar, a su valor del día", r["total_recibido"]])
h.titulo(f"Parte del ahorro · {c['nombre_territorio']}")
if not c["verificado"]:
h.datos(["REGLAS SIN COMPROBAR PARA ESTE AÑO", None, c["nota"]], True)
h.datos(["Dividendos e intereses (bruto)", c["rendimientos"]])
h.datos(["Ganancias y pérdidas (ventas y bróker)" if any(x["resultado"] is not None for x in r.get("bróker", [])) else
"Ganancias y pérdidas por ventas", c["ganancias"]])
for p in c["pasos"]:
h.datos([f" ↳ {p['texto']}", None, p["cita"]])
h.datos(["Base del ahorro", c["base"]], True)
for x in c["tramos"]:
h.datos([f" {x['base']:.2f} al {x['tipo']} %", x["cuota"]])
h.datos(["Cuota del ahorro", c["cuota"], c["citas"]["escala"]], True)
h.datos(["Retenciones", -c["retenciones"]])
h.datos(["Resultado (a pagar si es positivo)", c["resultado"]], True)
for x in c["pendientes"]:
h.datos([f"Queda para restar: {x['clase']} de {x['ejercicio']}", x["importe"], f"Hasta {x['hasta']}"])
h.datos([None, None, f"Reglas: {c['norma']} ({c['fuente']}), consultado el {c['consultado']}."])
h = _Hoja(wb, "Ventas", [("Fecha", 12, FECHA), ("Activo", 28, None), ("Clase", 20, None), ("Qué", 32, None),
("Cantidad", 16, CANTIDAD), ("Valor de transmisión", 18, EUROS),
("Valor de adquisición", 18, EUROS), ("Ganancia", 14, EUROS),
("Aplazada por recompra", 18, EUROS), ("Cuenta este año", 16, EUROS), ("Avisos", 30, None)])
lotes = _Hoja(wb, "Lotes", [("Fecha de la venta", 16, FECHA), ("Activo", 28, None), ("Qué", 32, None),
("Fecha de la compra", 16, FECHA), ("Cantidad", 16, CANTIDAD), ("Coste", 14, EUROS)])
avisos = {"sin_cubrir": "falta historial", "estimado": "cifras estimadas", "sin_fecha": "compra sin fecha (traspaso de fuera)"}
for x in r["transmisiones"]:
h.datos([_fecha(x["fecha"]), x["nombre"], x["clase"], x["concepto"], x["cantidad"], x["valor_transmision"],
x["valor_adquisicion"], x["ganancia"], x["aplazada"], x["computable"],
", ".join(avisos.get(a, a) for a in x["avisos"])])
for lote in x["lotes"]:
lotes.datos([_fecha(x["fecha"]), x["nombre"], x["concepto"], _fecha(lote["fecha"]), lote["cantidad"], lote["coste"]])
for x in r["integradas"]:
h.datos([_fecha(x["fecha"]), x["nombre"], None, f"Pérdida aplazada de la venta del {x['fecha_venta']} que se integra",
None, None, None, None, None, x["importe"]])
h = _Hoja(wb, "Rendimientos", [("Fecha", 12, FECHA), ("Activo", 28, None), ("Concepto", 26, None),
("Bruto", 14, EUROS), ("Retención", 14, EUROS), ("Cobrado", 14, EUROS)])
for x in r["rendimientos"]:
h.datos([_fecha(x["fecha"]), x["nombre"], x["concepto"], x["bruto"], x["retencion"], x["neto"]])
h = _Hoja(wb, "Actividad", [("Concepto", 46, None), ("Fecha", 12, FECHA), ("Importe", 16, EUROS),
("Amortizado antes", 16, EUROS), ("Este año", 14, EUROS), ("Queda", 14, EUROS), ("Nota", 70, None)])
for a in r["actividades"]:
h.datos([f"{a['nombre']} · {a['ejercicio']}", None, None, None, None, None, a["citas"]["actividad"]], True)
h.datos(["Ingresos: lo recibido, a su valor de ese día", None, a["ingresos"], None, None, None, f"{a['entradas']} entradas"])
h.datos(["Gastos del año", None, -a["gastos"]])
if a["amortiza"]:
h.datos(["Amortización de los aparatos", None, -a["amortizacion"], None, None, None,
f"{a['tabla']['nombre']}: {a['tabla']['coeficiente']} % al año como mucho ({a['citas']['amortizacion']})"])
else:
h.datos(["Amortización de los aparatos", None, None, None, None, None,
f"No se restan aparte: los cubre la reducción ({a['citas']['amortizacion']})"])
h.datos([a["texto_reduccion"] or "Reducción", None, -a["reduccion"], None, None, None, a["citas"]["reduccion"]])
h.datos(["Rendimiento neto de la actividad", None, a["rendimiento"], None, None, None,
a["texto_negativo"] if a["rendimiento"] < 0 and a["texto_negativo"] else a["citas"]["general"]], True)
if not a["verificado"]:
h.datos(["REGLAS SIN COMPROBAR", None, None, None, None, None, a["nota"]], True)
h.titulo("Aparatos")
for x in a["aparatos"]:
h.datos([x["notas"] or "Aparato", _fecha(x["fecha"]), x["coste"], x["antes"], x["amortizacion"], x["pendiente"],
"cifras estimadas" if x["estimado"] else None])
h.titulo("Gastos del año, uno a uno")
for x in a["corrientes"]:
h.datos([x["notas"] or ("Devolución" if x["importe"] < 0 else "Gasto"), _fecha(x["fecha"]), x["importe"],
None, None, None, "cifras estimadas" if x["estimado"] else None])
h.fila += 1
h = _Hoja(wb, "Recibido", [("Fecha", 12, FECHA), ("Activo", 28, None), ("Qué", 14, None), ("Actividad", 18, None),
("Cantidad", 16, CANTIDAD), ("Valor al recibirlo", 16, EUROS)])
for x in r["recibido"]:
h.datos([_fecha(x["fecha"]), x["nombre"], "Minado" if x["tipo"] == "Mineria" else "Recibido", x["actividad"],
x["cantidad"], x["valor"]])
h = _Hoja(wb, "Existencias", [("Qué", 28, None), ("Clase", 20, None), ("Cantidad", 16, CANTIDAD), ("Coste (FIFO)", 16, EUROS),
(f"Valor a 31-12-{r['ejercicio']}", 18, EUROS), ("Compras vivas", 14, None),
("Compra más antigua", 18, FECHA)])
for x in r["existencias"]:
h.datos([x["nombre"], x["clase"], x["cantidad"], x["coste"], x["valor"], x["lotes"], _fecha(x["primer_lote"])])
h = _Hoja(wb, "Criterios", [("Criterio", 50, None), ("Cómo se aplica", 120, None), ("Fuente", 60, None)])
for x in r["criterios"]:
h.datos([x["titulo"], x["texto"], x["fuente"]])
for clave, o in r["opciones_posibles"].items():
h.datos([f"Elegido · {o['titulo']}", dict(o["valores"]).get(r["opciones"][clave], r["opciones"][clave])])
if r["tareas"]:
h.titulo("Por resolver antes de fiarse de estas cifras")
for x in r["tareas"]:
h.datos([f"{x['fecha']} · {x['activo']}", x["texto"]])
fuera = [x for x in r["fuera"] if x["cobrado"]]
if fuera:
h.titulo("No entran en este cálculo")
for x in fuera:
h.datos([x["nombre"], f"Cobraste {x['cobrado']:.2f} €; {x['motivo']}"])
destino = Path(destino)
destino.parent.mkdir(parents=True, exist_ok=True)
wb.save(destino)