JARVIS/nucleo/pruebas/prueba_candado.py
sito 8e4bc8ad94 JARVIS: asistente de voz local para Linux
Nucleo propio: oye con whisper.cpp, piensa con un modelo de Ollama, habla
con Piper, y hace RAG sobre los apuntes del usuario. 100% local, sin
cuentas ni claves.

Escrito bajo una restriccion dura, 4 GB de VRAM: el cerebro y whisper
comparten tarjeta y solo caben porque estan dimensionados para ello. El
RAG usa embeddings estaticos con busqueda hibrida; la voz clonada se sirve
de una cache de frases.

Incluye instalador (install.sh), requisitos, y documentacion del stack,
del manejo de root y de las acciones. Los apuntes indexados y el diario NO
se incluyen: son privados y el .gitignore los bloquea.
2026-08-16 15:34:37 +02:00

133 lines
4.6 KiB
Python
Executable file

#!/usr/bin/env python3
"""El candado: que tape, que rechace lo que no es, y que NO obedezca.
./venv/bin/python pruebas/prueba_candado.py
Lo que de verdad importa aqui no es que el panel se ponga negro —eso es
cosmetica— sino que con el candado puesto **no se ejecute nada**. El panel tapado
pero obedeciendo seria peor que no tener candado, porque da sensacion de
seguridad sin darla.
No se prueba con la contraseña de verdad: no la tenemos ni hace falta. Se
comprueba que una mala se rechaza y que mientras tanto no pasa ni una orden.
"""
import os
import sys
import threading
import time
AQUI = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, AQUI)
from boca.voz import Boca # noqa: E402
from cara.panel import Panel # noqa: E402
from manos import Manos # noqa: E402
fallos = []
def afirma(condicion, queja):
print(f" {'OK ' if condicion else 'MAL'} {queja}")
if not condicion:
fallos.append(queja)
class CaraFalsa:
"""Se queda con lo que el panel manda, sin levantar ningun servidor."""
def __init__(self):
self.mensajes = []
self.conectados = 1
def manda(self, tipo, datos):
self.mensajes.append((tipo, datos))
def de(self, tipo):
return [d for t, d in self.mensajes if t == tipo]
class BocaMuda(Boca):
"""Como la de verdad pero sin sonar: la prueba no tiene que hablar."""
def __init__(self):
super().__init__("davefx")
self.dicho = []
def di(self, texto):
self.dicho.append(texto); return True
def di_si_libre(self, texto):
self.dicho.append(texto); return True
def main() -> int:
manos = Manos()
cara = CaraFalsa()
boca = BocaMuda()
p = Panel(cara, boca, manos, candado=True)
print("\n al abrir")
p.arranca()
time.sleep(0.2)
afirma(not p.desbloqueado, "nace bloqueado")
afirma(bool(cara.de("bloqueo")), "pide la contraseña nada mas abrir")
print("\n con el candado puesto NO obedece")
antes = len(cara.mensajes)
for orden in ("prueba", "voz:siguiente", "parar"):
p.pulsado("accion", orden)
time.sleep(1.0)
afirma(not boca.dicho, f"no ha hablado ({boca.dicho})")
# Desde que la tanda va por tareas, lo que se ve al ejecutar son mensajes
# "tarea" (una tarjeta por orden), no "respuesta".
hechas = [d for t, d in cara.mensajes[antes:] if t in ("tarea", "respuesta")]
afirma(not hechas, f"no ha ejecutado nada ({len(hechas)} tareas o respuestas)")
# Solo se miran las respuestas a lo pulsado: la telemetria sigue latiendo
# en su hilo cada segundo y se cuela entre medias.
contestaciones = [t for t, _ in cara.mensajes[antes:] if t != "telemetria"]
afirma(contestaciones and all(t == "bloqueo" for t in contestaciones),
f"a cada intento contesta pidiendo la contraseña ({contestaciones})")
print("\n una contraseña mala se rechaza")
antes = len(cara.mensajes)
p.pulsado("accion", "desbloquear:no-es-esta-contrasena-12345")
time.sleep(0.5)
afirma(not p.desbloqueado, "sigue bloqueado")
ultimos = cara.de("bloqueo")[-1:] or [{}]
afirma(ultimos[0].get("error") is True, "y lo dice como error")
afirma(not cara.de("desbloqueado"), "no manda 'desbloqueado'")
print("\n una vacia ni se intenta")
antes = len(cara.mensajes)
p.pulsado("accion", "desbloquear:")
afirma(len(cara.mensajes) == antes, "no molesta a sudo con una clave vacia")
print("\n sin candado, obedece")
cara2, boca2 = CaraFalsa(), BocaMuda()
p2 = Panel(cara2, boca2, manos, candado=False)
p2.arranca()
afirma(p2.desbloqueado, "nace desbloqueado")
afirma(not cara2.de("bloqueo"), "no pide contraseña")
p2.pulsado("accion", "prueba")
time.sleep(3.0)
tarjetas = {d["id"] for d in cara2.de("tarea")}
afirma(bool(tarjetas), f"ejecuta de verdad ({len(tarjetas)} tarjetas)")
afirma(any(d["estado"] == "hecha" for d in cara2.de("tarea")),
"y las tareas llegan a terminar")
print("\n y se puede volver a echar")
p2.bloquea()
afirma(not p2.desbloqueado, "bloquea() cierra aunque arrancara sin candado")
antes = len(cara2.mensajes)
p2.pulsado("accion", "prueba")
time.sleep(0.6)
afirma(not [d for t, d in cara2.mensajes[antes:] if t in ("tarea", "respuesta")],
"y despues de cerrar ya no obedece")
p.para(); p2.para()
print(f"\n {'todo en orden' if not fallos else str(len(fallos)) + ' fallan'}\n")
return 1 if fallos else 0
if __name__ == "__main__":
sys.exit(main())