diff --git a/vigilancia/asus_api_control.py b/vigilancia/asus_api_control.py
index ee4eddd..e9ef282 100644
--- a/vigilancia/asus_api_control.py
+++ b/vigilancia/asus_api_control.py
@@ -35,8 +35,20 @@ DEVICE_ALIASES = {
"carolina": {"name": "Caroline-tablet", "mac": "3A:99:2B:ED:D3:5E"},
"caroline": {"name": "Caroline-tablet", "mac": "3A:99:2B:ED:D3:5E"},
"caroline tablet": {"name": "Caroline-tablet", "mac": "3A:99:2B:ED:D3:5E"},
+ "tele": {"name": "Tele", "mac": "14:EA:63:1D:1B:25"},
+ "television": {"name": "Tele", "mac": "14:EA:63:1D:1B:25"},
+ "tv salon": {"name": "Tele", "mac": "14:EA:63:1D:1B:25"},
+ "xbox": {"name": "XBOX", "mac": "A8:8C:3E:7C:F3:C7"},
}
+TIPOS = (
+ ("📺", ("tele", "tv", "samsung", "lg", "androidtv", "bravia", "philips")),
+ ("🎮", ("xbox", "playstation", "ps4", "ps5", "nintendo", "switch")),
+ ("📱", ("iphone", "android", "galaxy", "xiaomi", "redmi", "pixel")),
+ ("💻", ("pc", "laptop", "minguez", "juanjer", "nahid")),
+ ("🏠", ("tapo", "dreame", "vacuum", "echo", "alexa", "google", "nest")),
+)
+
def _env(name, default=""):
return os.getenv(name, FILE_ENV.get(name, default)).strip()
@@ -186,6 +198,79 @@ def fijar_bloqueos(cambios):
return _leer_reglas()
+def _icono(nombre):
+ n = normalizar_nombre(nombre)
+ for icono, claves in TIPOS:
+ if any(c in n for c in claves):
+ return icono
+ return "🔌"
+
+
+def listar_dispositivos(limite=14):
+ """Inventario vivo: leases DHCP + alias conocidos + estado de bloqueo.
+ Devuelve lista de dicts (nombre, mac, ip, tipo, bloqueado)."""
+ try:
+ from router_control import _inventario, _nombre_por_mac
+ except Exception:
+ return []
+ try:
+ devices, _by_mac = _inventario()
+ except Exception:
+ devices = []
+ try:
+ reglas = _leer_reglas()
+ except Exception:
+ reglas = {}
+ vistos, salida = set(), []
+ for ip, mac, nombre in devices:
+ if mac in vistos:
+ continue
+ vistos.add(mac)
+ bonito = nombre
+ try:
+ bonito = _nombre_por_mac(mac)
+ except Exception:
+ pass
+ if bonito in ("pendiente-identificar", "*"):
+ bonito = nombre
+ salida.append({
+ "nombre": bonito, "mac": mac, "ip": ip,
+ "tipo": _icono(f"{bonito} {nombre}"),
+ "bloqueado": mac in reglas,
+ })
+ for alias, info in DEVICE_ALIASES.items():
+ if info["mac"] in vistos:
+ continue
+ vistos.add(info["mac"])
+ salida.append({
+ "nombre": f"{info['name']} (alias)", "mac": info["mac"],
+ "ip": "", "tipo": _icono(info["name"]),
+ "bloqueado": info["mac"] in reglas,
+ })
+ orden = {"📺": 0, "🎮": 1, "📱": 2, "💻": 3, "🏠": 4, "🔌": 5}
+ salida.sort(key=lambda d: (orden.get(d["tipo"], 9),
+ d["bloqueado"], d["nombre"].lower()))
+ return salida[:limite]
+
+
+def bloquear_mac(mac, blocked, nombre=""):
+ """Bloquea/desbloquea por MAC directa (regla 24/7 + iptables + verif)."""
+ from router_control import bloqueo_red
+ mac = (mac or "").upper()
+ if not mac:
+ raise ValueError("MAC vacia")
+ fijar_bloqueos([(mac, nombre or mac, blocked)])
+ reglas = _leer_reglas()
+ dentro = mac in reglas if blocked else mac not in reglas
+ marca = "✅" if dentro else "❌"
+ base = (f"{'Bloqueado' if blocked else 'Activado'}: "
+ f"{nombre or mac} ({mac}) {marca}")
+ try:
+ return f"{base}\n{bloqueo_red(nombre or mac, mac, blocked)}"
+ except Exception as e:
+ return f"{base}\n⚠️ Sin verificacion de red: {e}"
+
+
def cambiar_bloqueo(nombre, blocked):
device = resolver_dispositivo(nombre)
if not device:
diff --git a/vigilancia/menu_idd.py b/vigilancia/menu_idd.py
index 23ce37d..d1147a4 100644
--- a/vigilancia/menu_idd.py
+++ b/vigilancia/menu_idd.py
@@ -185,6 +185,7 @@ def teclado_parental():
_btn("🔓 Desbloquear tablets", PREFIJO + "acc:tablets:desbloquear"),
],
[_btn("📋 Listar estado", PREFIJO + "acc:parental_listar")],
+ [_btn("📺 Bloquear dispositivo…", PREFIJO + "acc:dispositivos")],
[
_btn("🔒 Desactivar…", PREFIJO + "sub:desactivar"),
_btn("🔓 Activar…", PREFIJO + "sub:activar"),
@@ -258,6 +259,39 @@ def teclado_minutos_pausa(nombre):
])
+# Snapshot de la lista de dispositivos por chat (para toggle por numero).
+menu_dispositivos = {}
+
+
+def teclado_dispositivos(lista):
+ filas = []
+ for i, d in enumerate(lista, start=1):
+ marca = "🔒" if d["bloqueado"] else "🔓"
+ extra = f" {d['ip']}" if d.get("ip") else ""
+ filas.append([_btn(f"{i}. {marca} {d['tipo']} {d['nombre'][:28]}{extra}",
+ PREFIJO + f"acc:blqdev:{i}")])
+ filas.append([_btn("⬅️ Control parental", PREFIJO + "cat:parental")])
+ return _mk(filas)
+
+
+def _mostrar_dispositivos(chat_id, message_id):
+ import pi_bot
+ try:
+ from asus_api_control import listar_dispositivos
+ lista = listar_dispositivos()
+ except Exception as e:
+ _enviar_nuevo(chat_id, f"❌ Error listando dispositivos: {e}", None)
+ return
+ if not lista:
+ _enviar_nuevo(chat_id, "No hay dispositivos en red.", None)
+ return
+ menu_dispositivos[chat_id] = lista
+ _enviar_edit(chat_id, message_id,
+ "📺 Bloquear dispositivo\nPulsa el número para "
+ "bloquear/desbloquear:",
+ teclado_dispositivos(lista))
+
+
def teclado_resultados(lista, prefijo):
filas = []
for i, item in enumerate(lista[:12], start=1):
@@ -612,6 +646,29 @@ def _ejecutar_accion(chat_id, message_id, partes):
_cerrar_menu(chat_id, message_id)
_fin_accion(chat_id, "aspi")
return
+ if accion == "dispositivos":
+ _record_cat(chat_id, "parental")
+ _mostrar_dispositivos(chat_id, message_id)
+ return
+ if accion == "blqdev":
+ _record_cat(chat_id, "parental")
+ lista = menu_dispositivos.get(chat_id) or []
+ numero = int(sub) if sub.isdigit() else 0
+ if 1 <= numero <= len(lista):
+ d = lista[numero - 1]
+ try:
+ from asus_api_control import bloquear_mac, listar_dispositivos
+ bloquear_mac(d["mac"], not d["bloqueado"], d["nombre"])
+ lista = listar_dispositivos()
+ menu_dispositivos[chat_id] = lista
+ except Exception as e:
+ _enviar_nuevo(chat_id, f"❌ Error: {e}", None)
+ return
+ _enviar_edit(chat_id, message_id,
+ "📺 Bloquear dispositivo\nPulsa el número para "
+ "bloquear/desbloquear:",
+ teclado_dispositivos(lista))
+ return
if accion == "tablets":
if sub in ("bloquear", "desbloquear"):
verbo = "desactivar" if sub == "bloquear" else "activar"