""" Pruebas del asistente (frases normales → operaciones propuestas). Sin red. python3 -m unittest discover -s pruebas -v """ from __future__ import annotations import datetime as dt import sys import unittest from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) from cartera.datos import Datos # noqa: E402 from cartera.interprete import _numero, interpretar, trocear # noqa: E402 HOY = dt.date(2026, 9, 17) # jueves def _a(i, nombre, tipo, codigo="", unidad="participaciones", **extra): return {"id": i, "nombre": nombre, "tipo": tipo, "codigo": codigo, "unidad": unidad, "estado": "abierta", **extra} DATOS = Datos( [_a("F1", "Fondo Valor FI Clase C", "Fondo", "ES0000000011", estado="cerrada", fecha_cierre="2024-11-20"), _a("F2", "Fondo Valor FI Clase B", "Fondo", "ES0000000029"), _a("P1", "Ejemplo Pensiones PP", "PlanPensiones", "N0001"), _a("C1", "Bitcoin", "Cripto", "bitcoin", "BTC"), _a("M1", "Oro físico (caja fuerte)", "Metal", "XAU", "oz"), _a("E1", "ETF Mundial", "ETF", "IWDA.AS"), _a("PR2", "Préstamo P2P Ejemplo", "Prestamo", "", "€", fecha_inicio="2026-01-01", prestamo={"importe_final": 5300.0, "vencimiento": "2026-12-01"})], [{"id": "a", "fecha": "2025-01-01", "activo": "F2", "tipo": "Compra", "cantidad": 100, "precio": 200}, {"id": "b", "fecha": "2025-01-01", "activo": "C1", "tipo": "Compra", "cantidad": 0.05, "precio": 60000}, {"id": "c", "fecha": "2025-03-01", "activo": "C1", "tipo": "Gasto", "importe": 70}, {"id": "d", "fecha": "2026-01-01", "activo": "PR2", "tipo": "Compra", "cantidad": 1, "precio": 5000, "importe": 5000}, {"id": "e", "fecha": "2025-01-01", "activo": "E1", "tipo": "Compra", "cantidad": 40, "precio": 100}], [{"fecha": "2026-09-14", "activo": "P1", "precio": 12.5}, {"fecha": "2026-09-15", "activo": "C1", "precio": 98000.0}], []) def una(texto, cambio=lambda f, m: 0.85): r = interpretar(texto, DATOS, HOY, cambio) assert len(r) == 1, [x["frase"] for x in r] return r[0] class TestNumerosYFechas(unittest.TestCase): def test_numeros(self): for texto, valor in (("1.234,56", 1234.56), ("70.500", 70500), ("0.0012", 0.0012), ("0,0012", 0.0012), ("12.345", 12345), ("1.5", 1.5), ("125", 125)): self.assertAlmostEqual(_numero(texto), valor, msg=texto) def test_fechas(self): for texto, fecha in (("ayer aporté 100 € al plan", "2026-09-16"), ("el 14 aporté 100 € al plan", "2026-09-14"), ("el 20 aporté 100 € al plan", "2026-08-20"), ("el lunes aporté 100 € al plan", "2026-09-14"), ("el 3 de octubre compré 0,001 btc por 80 €", "2025-10-03"), ("compré 0,001 btc por 80 € el 3/9", "2026-09-03"), ("compré 0,001 btc por 80 € el 21/03/2025", "2025-03-21")): self.assertEqual(una(texto)["movimiento"]["fecha"], fecha, texto) self.assertIn("No dices la fecha: pongo hoy.", una("compré 0,001 btc por 80 €")["avisos"]) def test_varias_operaciones(self): self.assertEqual(trocear("aporté 100 € al plan y compré 0,001 btc por 90 €. he minado 100 sats"), ["aporté 100 € al plan", "compré 0,001 btc por 90 €", "he minado 100 sats"]) self.assertEqual(trocear("compré oro y plata por 100 €"), ["compré oro y plata por 100 €"]) class TestPropuestas(unittest.TestCase): def test_aportacion_al_plan_estimada(self): r = una("ayer aporté 100 € al plan") m = r["movimiento"] self.assertTrue(r["completa"]) self.assertEqual((m["activo"], m["tipo"], m["importe"], m["precio"]), ("P1", "Compra", 100, 12.5)) self.assertAlmostEqual(m["cantidad"], 8) self.assertTrue(m["estimado"]) def test_mineria_en_sats(self): m = una("he minado 12.345 sats")["movimiento"] self.assertEqual((m["activo"], m["tipo"]), ("C1", "Mineria")) self.assertAlmostEqual(m["cantidad"], 0.00012345) self.assertIsNone(m["importe"]) def test_hosting_en_dolares(self): r = una("pagué 50 $ del hosting el día 1") m = r["movimiento"] self.assertEqual((m["activo"], m["tipo"], m["fecha"], m["moneda"], m["importe_original"]), ("C1", "Gasto", "2026-09-01", "USD", 50)) self.assertAlmostEqual(m["importe"], 42.5) sin_red = una("pagué 50 $ del hosting", cambio=lambda f, mon: None) self.assertFalse(sin_red["completa"]) def test_compra_con_precio_y_miles(self): m = una("compré 0.0012 BTC a 70.500 € el 3/9")["movimiento"] self.assertAlmostEqual(m["cantidad"], 0.0012) self.assertEqual(m["precio"], 70500) self.assertAlmostEqual(m["importe"], 84.6) def test_venta_con_comision(self): m = una("vendí 0,01 btc por 1000€ con 2 € de comisión")["movimiento"] self.assertEqual((m["tipo"], m["importe"], m["comisiones"]), ("Venta", 1000, 2)) def test_vender_todo_propone_cerrar(self): r = una("vendí todo el etf por 5000 €") self.assertEqual(r["accion"], "cierre") self.assertEqual(r["cierre"], {"id": "E1", "fecha": "2026-09-17", "importe": 5000}) def test_cobro_de_prestamo(self): r = una("cobré 5.300 € de p2p ejemplo el 20/06/2026") self.assertEqual(r["accion"], "cierre") self.assertEqual(r["cierre"]["importe"], 5300) def test_el_fondo_va_a_la_clase_abierta(self): self.assertEqual(una("vendí 10 participaciones del valor a 250 €")["movimiento"]["activo"], "F2") self.assertEqual(una("vendí 10 participaciones del valor clase b a 250 €")["movimiento"]["activo"], "F2") def test_no_vende_lo_que_no_tiene(self): r = una("vendí 1 btc a 100000 €") self.assertFalse(r["completa"]) def test_pide_lo_que_falta(self): r = una("compré 100 € de algo raro") self.assertFalse(r["completa"]) self.assertTrue(r["opciones"]) self.assertFalse(una("compré bitcoin")["completa"]) self.assertFalse(una("traspasé el valorium al plan")["completa"]) def test_activo_nuevo_busqueda_y_alta(self): from cartera.interprete import termino_busqueda for frase, termino in (("compré 10 acciones de Iberdrola a 12,50 €", "iberdrola"), ("ayer compré 20 ACME por 1.800 $", "ACME"), ("compré 1.000 € del IE00B4L5Y983", "IE00B4L5Y983"), ("he comprado 5 acciones de banco santander", "banco santander")): self.assertEqual(termino_busqueda(frase), termino, frase) r = una("compré 10 acciones de Iberdrola a 12,50 €") self.assertEqual(r["busqueda"], "iberdrola") self.assertFalse(r["completa"]) nuevo = {"nombre": "Iberdrola, S.A.", "codigo": "IBE.MC", "tipo": "Accion", "unidad": "acciones", "precio": 13.0, "fecha_precio": "2026-09-16"} r = interpretar("compré 10 acciones de Iberdrola a 12,50 €", DATOS, HOY, None, activo_nuevo=nuevo)[0] self.assertTrue(r["completa"]) self.assertEqual((r["accion"], r["movimiento"]["cantidad"], r["movimiento"]["importe"]), ("alta", 10, 125)) r = interpretar("ayer compré 260 € de iberdrola", DATOS, HOY, None, activo_nuevo=nuevo)[0] self.assertTrue(r["completa"]) self.assertAlmostEqual(r["movimiento"]["cantidad"], 20) # estimada con su último precio self.assertTrue(r["movimiento"]["estimado"]) def test_dividendo(self): datos = Datos(DATOS.activos + [_a("A1", "ACCION EJEMPLO SA", "Accion", "ES0000000037", "acciones", alias="ejemplo, ejm")], DATOS.movimientos, DATOS.precios, []) r = interpretar("me han ingresado 12,40 € de dividendo de ejemplo con 3,10 € de retención", datos, HOY)[0] m = r["movimiento"] self.assertTrue(r["completa"]) self.assertEqual((m["activo"], m["tipo"], m["importe"], m["retencion"], m["cantidad"]), ("A1", "Dividendo", 12.4, 3.1, None)) self.assertEqual(interpretar("cobré 15 de dividendos del ejm el día 1", datos, HOY)[0]["movimiento"]["importe"], 15) def test_recompensa_y_redondeo(self): datos = Datos(DATOS.activos + [_a("A1", "ACCION EJEMPLO SA", "Accion", "ES0000000037", "acciones", alias="ejm")], DATOS.movimientos, DATOS.precios + [{"fecha": "2026-09-15", "activo": "A1", "precio": 80.0}], []) r = interpretar("save back de ejm: 0,1234 acciones por 10,20 €", datos, HOY)[0] m = r["movimiento"] self.assertTrue(r["completa"]) self.assertEqual((m["tipo"], m["cantidad"], m["valor_recibido"], m["importe"]), ("Recompensa", 0.1234, 10.2, None)) r = interpretar("ayer el save back me dio 8 € de ejm", datos, HOY)[0] self.assertAlmostEqual(r["movimiento"]["cantidad"], 0.1) # estimada con el precio del día m = interpretar("roundup de ejm 0,35 acciones por 28 €", datos, HOY)[0]["movimiento"] self.assertEqual((m["tipo"], m["cantidad"], m["importe"]), ("Compra", 0.35, 28)) def test_saldo_de_una_cuenta(self): datos = Datos(DATOS.activos + [_a("CC1", "Bankinter", "Cuenta", "", "€"), _a("CC2", "Revolut", "Cuenta", "", "€")], DATOS.movimientos, DATOS.precios, []) r = interpretar("saldo de bankinter 1.234,56 €", datos, HOY)[0] self.assertEqual((r["accion"], r["completa"]), ("saldo", True)) self.assertEqual(r["saldo"], {"activo": "CC1", "fecha": "2026-09-17", "valor": 1234.56}) r = interpretar("ayer en revolut tenía 300 €", datos, HOY)[0] self.assertEqual((r["accion"], r["saldo"]["activo"], r["saldo"]["fecha"]), ("saldo", "CC2", "2026-09-16")) self.assertFalse(interpretar("saldo de bankinter", datos, HOY)[0]["completa"]) def test_activo_elegido_por_el_usuario(self): r = interpretar("compré 2 participaciones por 400 €", DATOS, HOY, None, activo_forzado="F2")[0] self.assertTrue(r["completa"]) self.assertEqual(r["movimiento"]["precio"], 200) if __name__ == "__main__": unittest.main()