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