caudal/programa/pruebas/test_vpo.py
2026-10-20 17:45:13 -05:00

194 lines
9.5 KiB
Python

"""
Pruebas de la vivienda protegida por metros (cartera/vpo.py), de los
intereses de una hipoteca ya cancelada y de borrar una inversión.
python3 -m unittest pruebas.test_vpo -v
"""
from __future__ import annotations
import datetime as dt
import sys
import tempfile
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from cartera import inmuebles, vpo # noqa: E402
from cartera.datos import Datos, escribir # noqa: E402
from cartera.operaciones import Cartera, ErrorOperacion # noqa: E402
from cartera.vista import resumen # noqa: E402
S = vpo.serie()
PIEZAS = [{"nombre": "Vivienda", "m2": 75, "clase": "vivienda", "computa": 100},
{"nombre": "Terraza", "m2": 8, "clase": "vivienda", "computa": 50},
{"nombre": "Garaje", "m2": 12, "clase": "vivienda", "computa": 100},
{"nombre": "Trastero", "m2": 6, "clase": "anejo", "computa": 100}]
def ficha(**extra):
return {"clase": "vivienda", "uso": "propio", "comunidad": "navarra", "auto": True, "protegida": True,
"tope": "metros", "regimen": "vpo", "piezas": PIEZAS, **extra}
def diciembre(año):
return next(v for m, v in S["ipc"]["datos"] if m == f"{año}-12")
class TestModulo(unittest.TestCase):
def test_el_oficial_de_navarra(self):
self.assertEqual(vpo.modulo(ficha(), 2026, S), (1582.65, "oficial"))
self.assertEqual(vpo.modulo(ficha(), 2013, S), (1192.85, "oficial"))
def test_el_que_apunta_el_usuario_manda(self):
self.assertEqual(vpo.modulo(ficha(modulos={"2026": 1600}), 2026, S), (1600.0, "tuyo"))
def test_un_año_sin_dato_se_estima_con_el_ipc_de_diciembre_a_diciembre(self):
valor, origen = vpo.modulo(ficha(), 2018, S)
self.assertEqual(origen, "estimado")
self.assertAlmostEqual(valor, 1118.87 * diciembre(2017) / diciembre(2016), places=1)
# Dos años seguidos sin dato: se encadenan
v19 = vpo.modulo(ficha(), 2019, S)[0]
self.assertAlmostEqual(v19, 1118.87 * diciembre(2018) / diciembre(2016), places=1)
def test_antes_del_primero_el_primero_y_fuera_de_navarra_nada(self):
self.assertEqual(vpo.modulo(ficha(), 2005, S), (1192.85, "estimado"))
self.assertIsNone(vpo.modulo(ficha(comunidad="madrid"), 2026, S))
self.assertEqual(vpo.modulo(ficha(comunidad="madrid", modulos={"2024": 2000}), 2024, S), (2000.0, "tuyo"))
class TestPrecioMaximo(unittest.TestCase):
def test_por_metros_con_la_terraza_a_la_mitad_y_el_trastero_como_anejo(self):
d = vpo.desglose(ficha(), 2026, S)
self.assertEqual((d["m2_vivienda"], d["m2_anejo"]), (91.0, 6.0)) # 75 + 4 de terraza + 12
self.assertEqual(d["total"], round((91 * 1.30 + 6 * 0.52) * 1582.65, 2))
self.assertEqual([p["importe"] for p in d["piezas"]][1], round(4 * 1.30 * 1582.65, 2))
def test_otro_regimen_con_sus_coeficientes(self):
f = ficha(regimen="otro", coef_vivienda=1.5, coef_anejo=0.6)
self.assertEqual(vpo.tope(f, "2026-05-01", S), round((91 * 1.5 + 6 * 0.6) * 1582.65, 2))
self.assertEqual(vpo.tope(ficha(regimen="vpt"), "2026-05-01", S), round((91 * 1.5 + 6 * 0.6) * 1582.65, 2))
def test_la_cifra_fija_y_la_vivienda_libre(self):
self.assertEqual(vpo.tope({"protegida": True, "precio_maximo": 150000}, "2026-01-01", S), 150000.0)
self.assertIsNone(vpo.tope({"protegida": False, "precio_maximo": 150000}, "2026-01-01", S))
def test_vale_su_tope_cada_1_de_enero_desde_el_año_siguiente_a_la_compra(self):
a = {"tipo": "Inmueble", "inmueble": ficha()}
compra = {"tipo": "Compra", "fecha": "2013-06-01", "importe": 150000}
e = inmuebles.estimaciones(a, [compra], [], inmuebles.serie(), dt.date(2026, 10, 20), S)
self.assertEqual((e[0]["fecha"], e[-1]["fecha"], len(e)), ("2014-01-01", "2026-01-01", 13))
self.assertEqual({x["fuente"] for x in e}, {"VPO"})
self.assertEqual(e[-1]["valor_total"], vpo.tope(ficha(), "2026-01-01", S))
venta = {"tipo": "Venta", "fecha": "2020-03-01", "importe": 170000}
e = inmuebles.estimaciones(a, [compra, venta], [], inmuebles.serie(), dt.date(2026, 10, 20), S)
self.assertEqual(e[-1]["fecha"], "2020-01-01")
def test_el_ipc_descargado(self):
class Respuesta:
def raise_for_status(self):
pass
def json(self):
return {"Data": [{"Anyo": 2026 - i // 12, "FK_Periodo": 12 - i % 12, "Valor": 100 + i} for i in range(30)]}
class Sesion:
def get(self, url, timeout):
return Respuesta()
s = vpo.descargar_ipc(Sesion())
self.assertEqual((s["datos"][-1], len(s["datos"])), (["2026-12", 100.0], 30))
class ConCartera(unittest.TestCase):
def setUp(self):
t = tempfile.TemporaryDirectory()
self.addCleanup(t.cleanup)
carpeta = Path(t.name) / "datos"
escribir(carpeta, Datos([], [], [], []))
self.c = Cartera(carpeta)
self.addCleanup(self.c.cerrar)
def alta(self, **extra):
return self.c.guardar_activo({"tipo": "Inmueble", "nombre": extra.pop("nombre", "Mi casa"), "inmueble": ficha(**extra)})
def compra(self, aid, fecha="2013-06-01", importe=150000):
return self.c.guardar_movimiento({"activo": aid, "tipo": "Compra", "fecha": fecha, "importe": importe})
class TestEnLaCartera(ConCartera):
def test_la_casa_vale_su_precio_maximo_y_la_ficha_lo_desglosa(self):
a = self.alta()
self.compra(a["id"])
hoy = dt.date.today()
tope = vpo.tope(a["inmueble"], hoy)
r = resumen(self.c.datos, hoy)
casa = r["bienes"][0]
self.assertEqual((casa["valor"], casa["fuente_valor"], casa["tope"]), (tope, "VPO", tope))
self.assertEqual(casa["tope_desglose"]["año"], hoy.year)
self.assertEqual(r["global"]["patrimonio"], tope)
def test_se_avisa_si_el_modulo_del_año_es_estimado(self):
a = self.alta()
self.compra(a["id"])
avisos = resumen(self.c.datos, dt.date(2099, 2, 1))["avisos"]
self.assertTrue(any(x["tipo"] == "modulo" and "2099" in x["texto"] for x in avisos))
self.assertFalse(any(x["tipo"] == "modulo" for x in resumen(self.c.datos, dt.date(2026, 2, 1))["avisos"]))
def test_la_ficha_se_guarda_limpia(self):
a = self.alta(piezas=PIEZAS + [{"nombre": "Vacía", "m2": None, "clase": "vivienda"}], precio_maximo=1,
modulos={"2026": 1600, "2025": None})
f = a["inmueble"]
self.assertEqual((len(f["piezas"]), f["modulos"], "precio_maximo" in f), (4, {"2026": 1600.0}, False))
def test_lo_que_no_se_acepta(self):
with self.assertRaises(ErrorOperacion):
self.alta(piezas=[{"nombre": "Trastero", "m2": 6, "clase": "anejo"}]) # sin la vivienda
with self.assertRaises(ErrorOperacion):
self.alta(comunidad="madrid") # sin módulo
with self.assertRaises(ErrorOperacion):
self.alta(piezas=[{"nombre": "Vivienda", "m2": 75, "clase": "vivienda", "computa": 150}])
with self.assertRaises(ErrorOperacion):
self.alta(regimen="otro") # sin coeficientes
self.alta(comunidad="madrid", modulos={str(dt.date.today().year): 1900}, nombre="Otra")
def test_la_cifra_fija_sigue_valiendo(self):
a = self.c.guardar_activo({"tipo": "Inmueble", "nombre": "Piso", "inmueble": {
"clase": "vivienda", "uso": "propio", "comunidad": "navarra", "auto": True, "protegida": True, "precio_maximo": 140000}})
self.assertEqual(a["inmueble"]["tope"], "fijo")
self.compra(a["id"], importe=120000)
self.assertEqual(resumen(self.c.datos, dt.date.today())["bienes"][0]["valor"], 140000.0)
class TestIntereses(ConCartera):
def test_el_coste_con_intereses_va_aparte(self):
a = self.alta(intereses_hipoteca=38500)
self.compra(a["id"])
casa = resumen(self.c.datos, dt.date.today())["bienes"][0]
self.assertEqual((casa["compra"], casa["intereses_hipoteca"], casa["coste_con_intereses"]), (150000.0, 38500.0, 188500.0))
self.assertEqual(casa["revalorizacion_con_intereses"], round(casa["valor"] - 188500, 2))
self.assertAlmostEqual(casa["revalorizacion"], casa["valor"] - 150000, places=2) # la de siempre, sin tocar
class TestBorrarInversion(ConCartera):
def test_solo_sin_operaciones_y_se_lleva_sus_valores(self):
t = self.c.guardar_activo({"tipo": "Tierra", "nombre": "Finca vieja", "unidad": "ha"})
m = self.c.guardar_movimiento({"activo": t["id"], "tipo": "Compra", "fecha": "2010-01-01", "cantidad": 2, "precio": 9000, "importe": 18000})
self.c.precio_manual(t["id"], "2024-01-01", 11000)
with self.assertRaises(ErrorOperacion):
self.c.borrar_activo(t["id"])
self.c.borrar_movimiento(m["id"])
self.c.borrar_activo(t["id"])
self.assertEqual([x for x in self.c.datos.vivos("activos") if x["id"] == t["id"]], [])
self.assertEqual([x for x in self.c.datos.vivos("precios") if x["activo"] == t["id"]], [])
def test_no_la_hipoteca_de_un_inmueble(self):
d = self.c.guardar_activo({"tipo": "Deuda", "nombre": "Hipoteca", "fecha_inicio": "2025-01-15",
"capital": 100000, "tin": 3, "cuotas": 240, "primera_cuota": "2025-02-01"})
self.alta(hipoteca=d["id"])
with self.assertRaises(ErrorOperacion):
self.c.borrar_activo(d["id"])
if __name__ == "__main__":
unittest.main()