"""
@RAG_CONTEXT
{
  "archivo": "src/ocr_indexer/cli.py",
  "lineas": 768,
  "tokens_estimados": 8296,
  "hash_contenido": "1852219f0ed07556352ab9776fb2061f622f781de6de5d47bcbde67de4e32236",
  "creacion": "2026-03-13",
  "modificacion": "2026-03-17",
  "tags": [
    "ai_disponible",
    "version",
    "subprocess",
    "logging",
    "get_ai_config",
    "sys",
    "json",
    "os",
    "time",
    ".ai_refiner",
    "importlib.metadata"
  ],
  "descripcion_corta": "CLI para motor OCR local con búsqueda y monitoreo",
  "descripcion_larga": "Interfaz de línea de comandos (CLI) para un motor de indexación OCR local en servidores Linux. Proporciona gestión completa del motor, incluyendo inicio/parada del indexador, búsqueda booleana O(1) con índice invertido, monitoreo en tiempo real, configuración de tolerancia OCR, refinado con IA, y control de sesiones tmux persistentes. Incluye soporte para color en terminal y funciones de ayuda detalladas."
}
"""
#!/usr/bin/env python3
import os
import sys
import json
import subprocess
import time

GREEN = '\033[0;32m'
BLUE = '\033[0;34m'
YELLOW = '\033[1;33m'
RED = '\033[0;31m'
NC = '\033[0m'

def format_time(segundos):
    m, s = divmod(int(segundos), 60)
    h, m = divmod(m, 60)
    return f"{h:02d}h {m:02d}m {s:02d}s"

def draw_progress(porcentaje):
    bar_length = 20
    filled = int(bar_length * porcentaje // 100)
    bar = '█' * filled + '-' * (bar_length - filled)
    return f"[{bar}] {porcentaje:.1f}%"

def find_engine():
    curr_dir = os.getcwd()
    while curr_dir != '/':
        engine_path = os.path.join(curr_dir, '.ocr-indexer')
        if os.path.isdir(engine_path):
            return engine_path
        curr_dir = os.path.dirname(curr_dir)
    return None

def _supports_color():
    return sys.stdout.isatty() and not os.environ.get("NO_COLOR")


def cmd_help():
    try:
        from importlib.metadata import version as _pkg_version
        ver = _pkg_version("ocr-indexer-pro")
    except Exception:
        ver = "?"
    c = _supports_color()
    R  = "\033[0m"  if c else ""
    B  = "\033[1m"  if c else ""
    C  = "\033[36m" if c else ""
    Y  = "\033[33m" if c else ""
    G  = "\033[32m" if c else ""
    DM = "\033[2m"  if c else ""
    W  = 60
    print(f"\n{B}{C}╔{'═'*W}╗{R}")
    print(f"{B}{C}║{'OCR INDEXER PRO':^{W}}║{R}")
    print(f"{B}{C}║{f'v{ver}  —  Motor OCR local para servidores Linux':^{W}}║{R}")
    print(f"{B}{C}╚{'═'*W}╝{R}\n")
    grupos = [
        ("🔍 BÚSQUEDA Y MONITOREO", [
            ("search",              "Búsqueda booleana (&&, ||) con índice invertido O(1)"),
            ("last",                "Últimos 10 archivos indexados con preview de texto OCR"),
            ("status",              "Estado del motor, fase actual y progreso en tiempo real"),
            ("stats",               "Estadísticas de extensiones indexadas"),
            ("logs",                "Ver últimas 50 líneas del log del indexador"),
        ]),
        ("⚙️  CONTROL DEL MOTOR", [
            ("dir [ruta]",          "Crear motor local en la ruta indicada (o cwd)"),
            ("start",               "Iniciar indexador completo (Fase 1 + Fase 2) en background"),
            ("watch",               "Iniciar vigía de archivos nuevos en background"),
            ("watch-tmux",          "Iniciar vigía en sesión tmux persistente (sobrevive SSH)"),
            ("watch-tmux-stop",     "Detener y eliminar la sesión tmux del vigía"),
            ("watch-tmux-status",   "Ver estado de la sesión tmux del vigía"),
            ("stop",                "Detener indexador y vigía"),
            ("reindex <ruta>",      "Re-procesar un archivo específico con OCR"),
            ("refine",              "Refinar texto OCR con IA para archivos sin refinar"),
            ("refine --force",      "Re-refinar TODOS los archivos con IA"),
            ("ai-config",           "Ver/configurar API de IA para refinado OCR"),
            ("reset",               "Borrar el índice completo y archivos auxiliares"),
        ]),
        ("🧠 CONFIGURACIÓN OCR", [
            ("tol-list",            "Ver reglas de tolerancia activas (ej. O ↔ 0)"),
            ("tol-add",             "Añadir reglas en lote (formato: O:0, I:1)"),
            ("tol-rm",              "Eliminar una regla de tolerancia (y su espejo)"),
            ("tol-clear",           "Borrar todas las reglas de tolerancia"),
            ("blacklist-list",      "Ver carpetas excluidas del indexado"),
            ("blacklist-add",       "Añadir carpeta a la lista negra"),
            ("blacklist-rm",        "Quitar carpeta de la lista negra"),
        ]),
        ("🌐 SISTEMA GLOBAL", [
            ("engines",             "Descubrir y navegar a otros motores en el sistema"),
            ("help",                "Mostrar esta ayuda"),
        ]),
    ]
    for titulo, cmds in grupos:
        print(f"{B}{Y}  {titulo}{R}")
        for cmd, desc in cmds:
            print(f"    {G}{cmd:<24}{R}  {DM}{desc}{R}")
        print()
    print(f"{DM}  Uso: ocr-indexer <comando> [argumentos]{R}")
    print(f"{DM}  Sin argumentos: menú interactivo numerado{R}\n")


def get_tmux_session(engine):
    config_path = os.path.join(engine, 'config.json')
    try:
        with open(config_path, 'r') as f:
            config = json.load(f)
        root = config.get('root', os.path.dirname(engine))
    except Exception:
        root = os.path.dirname(engine)
    base = os.path.basename(root.rstrip('/')) or "proyecto"
    return f"{base}___ocr-indexer"


def _tmux_session_existe(name):
    r = subprocess.run(["tmux", "has-session", "-t", name], capture_output=True)
    return r.returncode == 0


def cmd_watch_tmux(engine, script_watcher):
    try:
        subprocess.run(["tmux", "-V"], capture_output=True, check=True)
    except (FileNotFoundError, subprocess.CalledProcessError):
        print(f"{RED}❌ tmux no está instalado. Instala con: apt install tmux{NC}")
        return
    name = get_tmux_session(engine)
    if not _tmux_session_existe(name):
        try:
            subprocess.run(["tmux", "new-session", "-d", "-s", name, "-n", "Vigía"], check=True, timeout=10)
            subprocess.run(["tmux", "send-keys", "-t", f"{name}:Vigía",
                            f"{sys.executable} -u {script_watcher} {engine}", "Enter"], timeout=5)
            subprocess.run(["tmux", "new-window", "-t", f"{name}:", "-n", "Shell"], check=True, timeout=10)
            subprocess.run(["tmux", "select-window", "-t", f"{name}:Vigía"], timeout=5)
            print(f"{GREEN}✅ Sesión '{name}' creada. Vigía activo en ventana [Vigía].{NC}")
        except subprocess.CalledProcessError as e:
            print(f"{RED}❌ Error creando sesión tmux: {e}{NC}")
            return
    else:
        print(f"{YELLOW}⚡ Sesión '{name}' ya existe — reconectando.{NC}")
    if os.environ.get("TMUX"):
        print(f"{YELLOW}⚠️  Ya estás dentro de tmux. Cambiando a sesión '{name}'...{NC}")
        os.execlp("tmux", "tmux", "switch-client", "-t", name)
    else:
        os.execlp("tmux", "tmux", "attach-session", "-t", name)


def cmd_watch_tmux_stop(engine):
    name = get_tmux_session(engine)
    if _tmux_session_existe(name):
        subprocess.run(["tmux", "kill-session", "-t", name])
        print(f"{RED}🛑 Sesión '{name}' eliminada.{NC}")
    else:
        print(f"{YELLOW}No hay sesión activa con nombre '{name}'.{NC}")


def cmd_watch_tmux_status(engine):
    name = get_tmux_session(engine)
    existe = _tmux_session_existe(name)
    print(f"\n{BLUE}--- ESTADO VIGÍA TMUX ---{NC}")
    print(f"Sesión tmux  : {YELLOW}{name}{NC}")
    if existe:
        print(f"Estado       : {GREEN}ACTIVA{NC}")
        r = subprocess.run(["tmux", "list-windows", "-t", name], capture_output=True, text=True)
        if r.stdout.strip():
            for win in r.stdout.strip().split('\n'):
                print(f"  Ventana    : {YELLOW}{win.strip()}{NC}")
    else:
        print(f"Estado       : {RED}INACTIVA{NC}")
        print(f"{YELLOW}Usa 'watch-tmux' para iniciar.{NC}")
    print(f"{BLUE}-------------------------{NC}\n")


def cmd_reindex(engine, index_path, script_indexer, args):
    if len(args) < 2:
        print(f"{RED}Uso: ocr-indexer reindex <ruta/al/archivo>{NC}")
        return
    filepath = os.path.abspath(args[1])
    try:
        with open(index_path, 'r') as f:
            db = json.load(f)
    except Exception as e:
        print(f"{RED}Error leyendo índice: {e}{NC}")
        return
    if filepath not in db:
        print(f"{YELLOW}⚠️  El archivo no está en el índice: {filepath}{NC}")
        print(f"{YELLOW}Usa 'start' para indexar todos los archivos pendientes.{NC}")
        return
    db[filepath]["estado"] = "pendiente"
    db[filepath].pop("refinado_ia", None)
    if isinstance(db[filepath].get("contenido"), dict):
        db[filepath]["contenido"]["texto_ocr"] = ""
    try:
        with open(index_path, 'w') as f:
            json.dump(db, f, indent=4)
    except Exception as e:
        print(f"{RED}Error guardando índice: {e}{NC}")
        return
    log_path = os.path.join(engine, 'indexer.log')
    with open(log_path, 'a') as log_file:
        subprocess.Popen([sys.executable, "-u", script_indexer, engine, "--fase2"],
                         stdout=log_file, stderr=log_file)
    print(f"{GREEN}✅ '{os.path.basename(filepath)}' marcado para re-indexar. Fase 2 iniciada.{NC}")


def cmd_ai_config(engine, config_path):
    from .ai_refiner import get_ai_config, _MODELOS, ai_disponible
    try:
        with open(config_path, 'r') as f:
            config = json.load(f)
    except Exception as e:
        print(f"{RED}Error leyendo config.json: {e}{NC}")
        return

    proveedor, api_key, modelo = get_ai_config(engine, config)

    print(f"\n{BLUE}--- CONFIGURACIÓN IA (Refinado OCR) ---{NC}")
    print(f"Proveedor  : {GREEN}{proveedor or '(no configurado)'}{NC}")
    if api_key:
        preview = api_key[:4] + '*' * max(4, len(api_key) - 8) + api_key[-4:] if len(api_key) > 8 else '****'
        print(f"API Key    : {YELLOW}{preview}{NC}")
    else:
        print(f"API Key    : {RED}(no configurada){NC}")
    print(f"Modelo     : {GREEN}{modelo or '(auto)'}{NC}")
    print(f"Estado IA  : {(GREEN + 'ACTIVA') if ai_disponible(engine, config) else (RED + 'INACTIVA')}{NC}")
    print(f"\nProveedores: openai | anthropic | gemini | deepseek")
    print(f"También puedes usar variables de entorno: RAG_AI_PROVIDER + RAG_API_KEY")
    print(f"{BLUE}----------------------------------------------------{NC}")

    nuevo_proveedor = input(f"{YELLOW}Proveedor [{proveedor or 'deepseek'}]: {NC}").strip().lower() or proveedor or "deepseek"
    nueva_key = input(f"{YELLOW}API Key (Enter para conservar actual): {NC}").strip() or api_key

    if not nueva_key:
        print(f"{YELLOW}Sin API key. Configuración no guardada.{NC}")
        return

    config.setdefault("ia", {})
    config["ia"]["proveedor"] = nuevo_proveedor
    config["ia"]["api_key"] = nueva_key
    config["ia"]["modelo"] = _MODELOS.get(nuevo_proveedor, "")
    try:
        with open(config_path, 'w') as f:
            json.dump(config, f, indent=4)
        print(f"{GREEN}✅ Config IA guardada. Proveedor: {nuevo_proveedor} | Modelo: {config['ia']['modelo']}{NC}")
    except Exception as e:
        print(f"{RED}Error guardando config: {e}{NC}")


def cmd_refine(engine, index_path, config_path, args):
    from .ai_refiner import ai_disponible, refinar_fase3, get_ai_config
    import logging as _logging
    _log = _logging.getLogger("cli")
    force = "--force" in args

    try:
        with open(config_path, 'r') as f:
            config = json.load(f)
    except Exception as e:
        print(f"{RED}Error leyendo config.json: {e}{NC}")
        return

    if not ai_disponible(engine, config):
        print(f"{RED}❌ No hay API de IA configurada.{NC}")
        print(f"{YELLOW}Usa 'ocr-indexer ai-config' para configurarla.{NC}")
        return

    try:
        with open(index_path, 'r') as f:
            db = json.load(f)
    except Exception as e:
        print(f"{RED}Error leyendo índice: {e}{NC}")
        return

    if force:
        pendientes = [k for k, v in db.items() if v.get("estado") == "completado"]
        print(f"{YELLOW}Modo --force: se re-procesarán TODOS los archivos completados.{NC}")
    else:
        pendientes = [k for k, v in db.items()
                      if v.get("estado") == "completado" and not v.get("refinado_ia")]

    if not pendientes:
        print(f"{GREEN}✅ Todos los archivos ya fueron refinados por IA. Usa --force para re-procesar.{NC}")
        return

    proveedor, api_key, modelo = get_ai_config(engine, config)
    print(f"\n{BLUE}--- REFINADO IA ---{NC}")
    print(f"Archivos a refinar : {YELLOW}{len(pendientes)}{NC}")
    print(f"Proveedor          : {GREEN}{proveedor} / {modelo}{NC}")

    if not force:
        res = input(f"{YELLOW}¿Iniciar refinado? [S/n]: {NC}").strip().lower()
        if res == 'n':
            print("Cancelado.")
            return

    status_path = os.path.join(engine, 'status.json')
    log_path = os.path.join(engine, 'indexer.log')

    import logging as _log_module
    _log_module.basicConfig(filename=log_path, level=_log_module.INFO,
                            format='%(asctime)s [%(levelname)s] %(message)s',
                            datefmt='%Y-%m-%d %H:%M:%S')

    def actualizar_status(datos):
        try:
            with open(status_path, 'w') as f:
                json.dump(datos, f, indent=4)
        except Exception:
            pass

    refinar_fase3(pendientes, db, index_path, engine, config, _log, actualizar_status)


def mostrar_help():
    print(f"Uso: {GREEN}ocr-indexer [comando]{NC} (o solo {GREEN}ocr-indexer{NC} para el menú interactivo)")

def mostrar_menu():
    print(f"\n{BLUE}===================================================={NC}")
    print(f"{YELLOW}          SISTEMA OCR - MENÚ INTERACTIVO {NC}")
    print(f"{BLUE}===================================================={NC}")
    
    categorias = [
        ("🔍 BÚSQUEDA Y MONITOREO", [
            ("Buscar texto", ["search"]),
            ("Ver últimos 10 procesados", ["last"]),
            ("Ver estado y progreso en vivo", ["status"]),
            ("Ver estadísticas de extensiones", ["stats"]),
            ("Ver registros de terminal (logs)", ["logs"])
        ]),
        ("⚙️ CONTROL DEL MOTOR LOCAL", [
            ("Iniciar indexador en background", ["start"]),
            ("Iniciar vigía de archivos nuevos", ["watch"]),
            ("Vigía persistente en sesión tmux", ["watch-tmux"]),
            ("Detener sesión tmux del vigía", ["watch-tmux-stop"]),
            ("Estado de sesión tmux del vigía", ["watch-tmux-status"]),
            ("Detener indexador y vigía", ["stop"]),
            ("Re-indexar un archivo específico", ["reindex"]),
            ("Refinar texto OCR con IA", ["refine"]),
            ("Configurar API de IA", ["ai-config"]),
            ("Resetear motor (Borrar índice)", ["reset"]),
            ("Crear motor en esta carpeta", ["dir"])
        ]),
        ("🧠 REGLAS DE TOLERANCIA OCR", [
            ("Ver reglas actuales", ["tol-list"]),
            ("Añadir reglas (Lote: O:0, I:1)", ["tol-add"]),
            ("Eliminar regla", ["tol-rm"]),
            ("Limpiar todas", ["tol-clear"])
        ]),
        ("🚫 LISTA NEGRA (EXCLUSIONES)", [
            ("Ver lista negra actual", ["blacklist-list"]),
            ("Añadir carpeta a lista negra", ["blacklist-add"]),
            ("Quitar carpeta de lista negra", ["blacklist-rm"])
        ]),
        ("🌐 SISTEMA GLOBAL", [
            ("Buscar y viajar a otros motores", ["engines"]),
            ("Ver ayuda de comandos", ["help"]),
            ("Salir", ["salir"])
        ])
    ]

    opciones_planas = []
    contador = 1

    for titulo, opciones in categorias:
        print(f"{YELLOW}--- {titulo} ---{NC}")
        for desc, cmd in opciones:
            comando_texto = f"ocr-indexer {cmd[0]}" if cmd[0] != "salir" else "salir"
            print(f"  {GREEN}[{contador:<2}]{NC} {desc:<35} {YELLOW}({comando_texto}){NC}")
            opciones_planas.append(cmd)
            contador += 1
        print("") 

    print(f"{BLUE}----------------------------------------------------{NC}")
    try:
        sel = input(f"{YELLOW}Elige el número de la acción: {NC}")
        sel = int(sel)
        if 1 <= sel <= len(opciones_planas):
            cmd = opciones_planas[sel-1]
            if cmd[0] == "salir":
                sys.exit(0)
            
            if cmd[0] in ["blacklist-add", "blacklist-rm"]:
                carpeta = input(f"{YELLOW}Escribe el nombre de la carpeta: {NC}")
                if not carpeta.strip():
                    sys.exit(0)
                return [cmd[0], carpeta.strip()]

            if cmd[0] == "reindex":
                ruta = input(f"{YELLOW}Ruta del archivo a re-indexar: {NC}").strip()
                if not ruta:
                    sys.exit(0)
                return [cmd[0], ruta]

            return cmd
        else:
            print(f"{RED}❌ Opción inválida.{NC}")
            sys.exit(1)
    except ValueError:
        print(f"{RED}❌ Ingresa solo el número.{NC}")
        sys.exit(1)
    except KeyboardInterrupt:
        sys.exit(0)

def main():
    initial_args = sys.argv[1:]
    interactive = not initial_args or initial_args[0] == "menu"

    while True:
        if interactive:
            args = mostrar_menu()
        else:
            args = initial_args

        comando = args[0]
        base_dir = os.path.dirname(__file__)
        script_indexer = os.path.join(base_dir, "indexer.py")
        script_searcher = os.path.join(base_dir, "searcher.py")
        script_watcher = os.path.join(base_dir, "watcher.py")
        engine = find_engine()

        if comando == "engines":
            cache_file = os.path.expanduser("~/.ocr_engines_cache.json")
            motores = []
            if os.path.exists(cache_file):
                try:
                    with open(cache_file, 'r') as f: motores = json.load(f)
                except: pass
                if motores:
                    print(f"\n{BLUE}===================================================={NC}")
                    print(f"{YELLOW}        ⚡ MOTORES CONOCIDOS (Carga Rápida)        {NC}")
                    print(f"{BLUE}===================================================={NC}")
                    for i, motor in enumerate(motores, 1): print(f"  {GREEN}[{i:<2}]{NC} {motor}")
                    print(f"{BLUE}----------------------------------------------------{NC}")
                    sel = input(f"{YELLOW}Elige un número para viajar YA, o [Enter] para escanear: {NC}").strip()
                    if sel.isdigit() and 1 <= int(sel) <= len(motores):
                        os.chdir(motores[int(sel)-1])
                        print(f"{GREEN}🚀 Viajando a: {motores[int(sel)-1]}...{NC}")
                        continue
                    elif sel.lower() == 'salir':
                        continue

            print(f"\n{BLUE}🔍 Escaneando todo el sistema en busca de motores...{NC}")
            proc = subprocess.run("find / -type d -name '.ocr-indexer' 2>/dev/null", shell=True, capture_output=True, text=True)
            rutas = proc.stdout.strip().split('\n')
            nuevos_motores = [r.replace('/.ocr-indexer', '') for r in rutas if r]
            if not nuevos_motores:
                continue
            try:
                with open(cache_file, 'w') as f: json.dump(nuevos_motores, f, indent=4)
            except: pass
            print(f"\n{BLUE}===================================================={NC}")
            for i, motor in enumerate(nuevos_motores, 1): print(f"  {GREEN}[{i:<2}]{NC} {motor}")
            print(f"{BLUE}----------------------------------------------------{NC}")
            try:
                sel = input(f"{YELLOW}Elige el número para viajar a esa carpeta: {NC}").strip()
                if sel.isdigit() and 1 <= int(sel) <= len(nuevos_motores):
                    os.chdir(nuevos_motores[int(sel)-1])
                    print(f"{GREEN}🚀 Viajando a: {nuevos_motores[int(sel)-1]}...{NC}")
            except: pass
            continue

        if comando == "dir":
            target = args[1] if len(args) > 1 else os.getcwd()
            engine_path = os.path.join(target, '.ocr-indexer')
            if not os.path.exists(engine_path):
                os.makedirs(engine_path)
                with open(os.path.join(engine_path, 'config.json'), 'w') as f:
                    json.dump({"root": target, "blacklist": ["Talk", "trashbin"], "tolerancias": {"O": "0", "0": "O"}}, f, indent=4)
                with open(os.path.join(engine_path, 'index.json'), 'w') as f: json.dump({}, f)
                print(f"{GREEN}✅ Motor local creado en: {target}{NC}")
            if not interactive: break
            continue

        if comando == "help":
            cmd_help()
            if not interactive: break
            continue

        if not engine:
            print(f"{RED}❌ No se detectó ningún motor aquí.{NC}")
            if not interactive: break
            continue

        config_path = os.path.join(engine, 'config.json')
        index_path = os.path.join(engine, 'index.json')
        status_path = os.path.join(engine, 'status.json')
        log_path = os.path.join(engine, 'indexer.log')

        def cargar_config():
            with open(config_path, 'r') as f: return json.load(f)
        def guardar_config(data):
            with open(config_path, 'w') as f: json.dump(data, f, indent=4)

        if comando == "start":
            print(f"{GREEN}🚀 Iniciando indexador...{NC}")
            with open(log_path, 'a') as log_file:
                subprocess.Popen([sys.executable, "-u", script_indexer, engine], stdout=log_file, stderr=log_file)

        elif comando == "watch":
            print(f"{GREEN}👁️  Iniciando vigía de archivos nuevos...{NC}")
            with open(log_path, 'a') as log_file:
                subprocess.Popen([sys.executable, "-u", script_watcher, engine], stdout=log_file, stderr=log_file)
            print(f"{YELLOW}El vigía corre en background. Los archivos nuevos se indexarán automáticamente.{NC}")

        elif comando == "watch-tmux":
            cmd_watch_tmux(engine, script_watcher)

        elif comando == "watch-tmux-stop":
            cmd_watch_tmux_stop(engine)

        elif comando == "watch-tmux-status":
            cmd_watch_tmux_status(engine)

        elif comando == "reindex":
            cmd_reindex(engine, index_path, script_indexer, args)

        elif comando == "refine":
            cmd_refine(engine, index_path, config_path, args)

        elif comando == "ai-config":
            cmd_ai_config(engine, config_path)

        elif comando == "logs":
            try:
                subprocess.run(["tail", "-n", "50", log_path])
            except Exception as e:
                print(f"{RED}Error leyendo logs: {e}{NC}")

        elif comando == "stop":
            subprocess.run(["pkill", "-f", engine])
            print(f"{RED}🛑 Indexador y vigía detenidos.{NC}")

        elif comando in ["status", "stats"]:
            print(f"{BLUE}================ ESTADO DEL MOTOR ================{NC}")
            proc = subprocess.run(["pgrep", "-f", engine], capture_output=True, text=True)
            estado_proceso = f"{GREEN}CORRIENDO{NC}" if proc.stdout.strip() else f"{RED}DETENIDO{NC}"

            try:
                with open(status_path, 'r') as f:
                    st = json.load(f)
            except:
                st = {"fase": "Iniciando o Inactivo"}

            fase = st.get("fase", "Desconocida")
            print(f"Estado General : {estado_proceso}")
            print(f"Fase Actual    : {YELLOW}{fase}{NC}")

            if fase == "Procesando OCR":
                total = st.get("total_ocr", 0)
                procesados = st.get("procesados", 0)
                faltantes = total - procesados
                porcentaje = (procesados / total * 100) if total > 0 else 100
                inicio = st.get("inicio_ocr", time.time())
                transcurrido = time.time() - inicio
                velocidad = procesados / transcurrido if transcurrido > 0 and procesados > 0 else 0
                eta = faltantes / velocidad if velocidad > 0 else 0

                print(f"\n{BLUE}--- RENDIMIENTO OCR ---{NC}")
                print(f"Progreso       : {GREEN}{draw_progress(porcentaje)}{NC}")
                print(f"Completados    : {procesados} archivos")
                print(f"Faltantes      : {YELLOW}{faltantes} archivos{NC}")
                print(f"T. Transcurrido: {format_time(transcurrido)}")
                print(f"ETA (Restante) : {YELLOW}{format_time(eta)}{NC}")

            if comando == "stats":
                print(f"\n{BLUE}--- ESTADÍSTICAS DE ARCHIVOS ---{NC}")
                try:
                    with open(index_path, 'r') as f:
                        data = json.load(f)
                    stats = {}
                    for item in data.values():
                        ext = item.get("archivo", {}).get("extension", "unknown")
                        stats[ext] = stats.get(ext, 0) + 1
                    for k, v in stats.items():
                        print(f"  {k.upper()}: {v}")
                except:
                    print(f"{YELLOW}Aún no hay datos analizados.{NC}")
            print(f"{BLUE}===================================================={NC}")

        elif comando == "last":
            try:
                with open(index_path, 'r') as f:
                    db = json.load(f)

                items = list(db.items())
                ultimos = items[-10:] if len(items) >= 10 else items

                print(f"\n{BLUE}===================================================={NC}")
                print(f"{YELLOW}       ÚLTIMOS {len(ultimos)} ARCHIVOS EN EL ÍNDICE       {NC}")
                print(f"{BLUE}===================================================={NC}")

                if not ultimos:
                    print(f"{YELLOW}El índice está vacío.{NC}")
                else:
                    for filepath, data in reversed(ultimos):
                        if isinstance(data, dict):
                            nombre = data.get("archivo", {}).get("nombre", "Desconocido")
                            ext = data.get("archivo", {}).get("extension", "")
                            estado = data.get("estado", "Desconocido")

                            texto = data.get("contenido", {}).get("texto_ocr", "")
                            if texto:
                                texto = texto[:60].replace('\n', ' ') + "..."
                            else:
                                texto = "(Sin texto extraído aún)"

                            color_estado = GREEN if estado == "completado" else YELLOW

                            print(f"{BLUE}📄 {nombre}.{ext}{NC}")
                            print(f"   Estado: {color_estado}{estado.upper()}{NC}")
                            print(f"   Texto : {texto}\n")

                print(f"{BLUE}===================================================={NC}")
                input(f"{YELLOW}Presiona [Enter] para regresar al menú...{NC}")

            except Exception as e:
                print(f"{RED}Error al leer el índice: {e}{NC}")

        elif comando == "search":
            subprocess.run([sys.executable, script_searcher, index_path])

        elif comando == "tol-list":
            data = cargar_config()
            tols = data.get("tolerancias", {})
            print(f"\n{BLUE}--- REGLAS DE TOLERANCIA OCR ---{NC}")
            if not tols: print(f"{YELLOW}No hay reglas activas.{NC}")

            impresos = set()
            for orig, reemp in tols.items():
                if orig not in impresos:
                    if tols.get(reemp) == orig:
                        print(f"  {GREEN}{orig} <=> {reemp}{NC} (Bidireccional)")
                        impresos.add(orig)
                        impresos.add(reemp)
                    else:
                        print(f"  {GREEN}{orig}  => {reemp}{NC}")
                        impresos.add(orig)
            print("")

        elif comando == "tol-add":
            while True:
                data = cargar_config()
                tols = data.get("tolerancias", {})

                print(f"\n{BLUE}--- AÑADIR NUEVAS REGLAS ---{NC}")
                if tols:
                    reglas_str = ", ".join([f"{k}:{v}" for k, v in tols.items()])
                    print(f"{GREEN}Reglas actuales: {NC}{reglas_str}")
                else:
                    print(f"{YELLOW}Reglas actuales: Ninguna{NC}")

                print(f"{YELLOW}Formato: original:reemplazo, original:reemplazo{NC}")
                entrada = input(f"{YELLOW}Ejemplo (O:0, I:1): {NC}").strip()

                if not entrada: break

                tols_ref = data.setdefault("tolerancias", {})
                agregadas = 0

                reglas = entrada.split(',')
                for regla in reglas:
                    if ':' in regla:
                        orig, reemp = regla.split(':', 1)
                        orig = orig.strip()
                        reemp = reemp.strip()

                        if orig and reemp:
                            tols_ref[orig] = reemp
                            tols_ref[reemp] = orig
                            agregadas += 2
                            print(f"  {GREEN}✅ Añadida: '{orig}' <=> '{reemp}' (Bidireccional){NC}")
                    else:
                        if regla.strip():
                            print(f"  {RED}❌ Ignorado (falta formato ':'): '{regla.strip()}'{NC}")

                if agregadas > 0:
                    guardar_config(data)

                print(f"{BLUE}----------------------------------------------------{NC}")
                resp = input(f"{YELLOW}¿Qué deseas hacer? [Enter] Añadir más | [M] Menú | [S] Salir: {NC}").strip().lower()
                if resp == 's': sys.exit(0)
                elif resp == 'm': break

        elif comando == "tol-rm":
            if len(args) > 1:
                data = cargar_config()
                tols = data.get("tolerancias", {})
                orig = args[1]
                if orig in tols:
                    inverso = tols[orig]
                    del tols[orig]
                    print(f"{YELLOW}🗑️ Regla '{orig}' eliminada.{NC}")
                    if inverso in tols and tols[inverso] == orig:
                        del tols[inverso]
                        print(f"{YELLOW}🗑️ Espejo '{inverso}' eliminado automáticamente.{NC}")
                    data["tolerancias"] = tols
                    guardar_config(data)
            else:
                while True:
                    data = cargar_config()
                    tols = data.get("tolerancias", {})
                    if not tols:
                        print(f"{YELLOW}No hay reglas para eliminar.{NC}")
                        time.sleep(1)
                        break

                    print(f"\n{BLUE}--- REGLAS ACTUALES ---{NC}")
                    reglas_str = ", ".join([f"{k}:{v}" for k, v in tols.items()])
                    print(f"{GREEN}{reglas_str}{NC}")

                    orig = input(f"\n{YELLOW}Escribe el caracter de la regla a eliminar (Respetando Mayúsculas): {NC}").strip()
                    if not orig: break

                    if orig in tols:
                        inverso = tols[orig]
                        del tols[orig]
                        print(f"{YELLOW}🗑️ Regla '{orig}' eliminada.{NC}")
                        if inverso in tols and tols[inverso] == orig:
                            del tols[inverso]
                            print(f"{YELLOW}🗑️ Espejo '{inverso}' eliminado automáticamente.{NC}")

                        data["tolerancias"] = tols
                        guardar_config(data)
                    else:
                        print(f"{RED}❌ No existe regla para '{orig}'.{NC}")

                    print(f"{BLUE}----------------------------------------------------{NC}")
                    resp = input(f"{YELLOW}¿Qué deseas hacer? [Enter] Eliminar otra | [M] Menú | [S] Salir: {NC}").strip().lower()
                    if resp == 's': sys.exit(0)
                    elif resp == 'm': break

        elif comando == "tol-clear":
            res = input(f"{YELLOW}¿Seguro que deseas eliminar TODAS las reglas de tolerancia? (s/n): {NC}")
            if res.lower() == 's':
                data = cargar_config()
                data["tolerancias"] = {}
                guardar_config(data)
                print(f"{RED}♻️ Tolerancias borradas exitosamente.{NC}")

        elif comando == "blacklist-list":
            print('\n'.join(cargar_config().get('blacklist', [])))
        elif comando == "blacklist-add":
            if len(args) > 1:
                data = cargar_config()
                if args[1] not in data.get('blacklist', []):
                    data.setdefault('blacklist', []).append(args[1])
                    guardar_config(data)
                print(f"{GREEN}✅ '{args[1]}' añadido a la lista negra.{NC}")
        elif comando == "blacklist-rm":
            if len(args) > 1:
                data = cargar_config()
                if args[1] in data.get('blacklist', []):
                    data['blacklist'].remove(args[1])
                    guardar_config(data)
                print(f"{YELLOW}🗑️ '{args[1]}' eliminado de la lista negra.{NC}")

        elif comando == "reset":
            res = input(f"{YELLOW}¿Borrar el índice? Esto no se puede deshacer. (s/n): {NC}")
            if res.lower() == 's':
                with open(index_path, 'w') as f:
                    json.dump({}, f)
                for extra in [status_path, os.path.join(engine, 'inverted.json')]:
                    if os.path.exists(extra):
                        os.remove(extra)
                print(f"{GREEN}✅ Índice reiniciado. Usa 'start' para re-indexar.{NC}")

        if not interactive:
            break

if __name__ == "__main__":
    main()
