#!/usr/bin/env python3
"""
editor_sipi_kids.py - SiPi Kids: version simplificada del editor de SiPi
pensada para chicos y para quien recien empieza a programar.

Interfaz construida con CustomTkinter (widgets modernos, esquinas
redondeadas, tema oscuro consistente) en vez del Tkinter clasico, para
que se vea a la altura de un producto terminado y no de un prototipo.
CustomTkinter esta construido ARRIBA de Tkinter (no lo reemplaza), asi
que sigue siendo Python puro sin dependencias pesadas.

Diferencias grandes respecto al editor normal (editor_sipi.py):

  - No hay que escribir la sintaxis a mano: se arma el programa
    clickeando "bloques" grandes y coloridos de una paleta (Hablar,
    Preguntar, Repetir, Decision, Numero al azar, Lista) que insertan
    el fragmento de codigo SiPi correcto en el lugar del cursor. El
    area de texto sigue siendo texto real y editable -- no es un
    editor visual de arrastrar piezas -- pero el chico nunca tiene que
    memorizar la sintaxis exacta para arrancar.
  - Nivel de dificultad fijo en "Principiante" (usa la directiva real
    '#nivel principiante' que ya entiende el motor de SiPi), asi el
    propio interprete avisa con un mensaje claro y amigable si se usa
    algo mas avanzado, en vez de un error tecnico.
  - Tipografia grande, botones grandes, colores vivos, sin menus
    escondidos ni atajos de teclado que aprender.
  - Un boton "Ayuda de la IA" con lenguaje simplificado: el chico
    escribe que quiere hacer con sus propias palabras y SiPi IA arma
    el programa completo listo para insertar.
  - Panel de "Desafios del dia" (nuevo): una mini-consigna corta y
    concreta que cambia cada vez que se pide, pensada para quien no
    sabe que construir y quiere una idea chica para practicar.
  - Reutiliza el mismo motor real (sipi.py) que el editor grande -- el
    codigo que se arma y se ejecuta es SiPi de verdad, no una
    simulacion educativa aparte.

Se instala y se abre igual que el editor normal, como un programa
aparte (pensado para convivir con 'sipi-editor' en el mismo sistema,
para uso familiar/escolar sin mezclar la experiencia "grande" con la
de chicos).
"""
import os
import sys
import subprocess
import tempfile
import atexit
import random

# Mismo bug del .exe que en el editor normal (ver 'editor_sipi.py' ->
# '_intentar_correr_como_motor_interno' para el detalle completo):
# "Ejecutar" armaba un comando 'python "sipi.py" archivo.sipi' a mano,
# que asume un Python externo instalable en el PATH y un 'sipi.py'
# suelto -- ninguna de las dos cosas existe una vez empaquetado en un
# .exe. SiPi Kids reusa el mismo helper central en vez de duplicar el
# arreglo por separado.
try:
    from editor_sipi import _es_exe_empaquetado, _bandera_reentrada_motor, _intentar_correr_como_motor_interno
except ImportError:
    def _es_exe_empaquetado():
        return getattr(sys, "frozen", False)

    def _bandera_reentrada_motor():
        return "--sipi-motor-interno"

    def _intentar_correr_como_motor_interno():
        return False


def _comando_motor_kids(sipi_py, ruta_archivo):
    if _es_exe_empaquetado():
        return [sys.executable, _bandera_reentrada_motor(), ruta_archivo]
    return [sys.executable, sipi_py, ruta_archivo]

try:
    import tkinter as tk
    from tkinter import filedialog, messagebox
except ImportError:
    print("[SiPi Kids] Este programa necesita tkinter. En Linux: sudo apt install python3-tk")
    sys.exit(1)

try:
    import customtkinter as ctk
except ImportError:
    print("[SiPi Kids] Este programa necesita el paquete 'customtkinter'.")
    print("             Instalalo con: pip install customtkinter")
    sys.exit(1)

AQUI = os.path.dirname(os.path.abspath(__file__))

ctk.set_appearance_mode("dark")
ctk.set_default_color_theme("blue")

# ------- Paleta de bloques: cada uno es (icono, titulo, color, fragmento_de_codigo) -------
# El '{{CURSOR}}' marca donde queda el cursor de texto despues de insertar,
# para que el chico pueda completar el dato (el texto a decir, la
# condicion, etc.) sin tener que buscarlo en medio del bloque.

BLOQUES = [
    {
        "icono": "inicio", "titulo": "Inicio del\nprograma", "color": "#40a02b",
        "codigo": '#nivel principiante\nprograma "Mi programa"\n\n{{CURSOR}}\n',
        "ayuda": "Todo programa tiene que empezar con esto. Va primero, una sola vez.",
    },
    {
        "icono": "hablar", "titulo": "Decir algo", "color": "#89b4fa",
        "codigo": 'decir "{{CURSOR}}"\n',
        "ayuda": "Muestra un mensaje en pantalla. Escribi lo que quieras adentro de las comillas.",
    },
    {
        "icono": "preguntar", "titulo": "Preguntar y\nguardar", "color": "#f9c74f",
        "codigo": 'variable respuesta = preguntar "{{CURSOR}}"\n',
        "ayuda": "Le pregunta algo a quien juega y guarda lo que responda en 'respuesta'.",
    },
    {
        "icono": "numero", "titulo": "Numero al\nazar", "color": "#94e2d5",
        "codigo": 'azar_entre 1 10 -> numero_azar\n{{CURSOR}}',
        "ayuda": "Guarda un numero al azar entre 1 y 10 en 'numero_azar'. Cambia el 1 y el 10 si queres otro rango.",
    },
    {
        "icono": "decision", "titulo": "Si pasa\nesto...", "color": "#cba6f7",
        "codigo": 'si {{CURSOR}}\n    decir "Se cumplio!"\nsino\n    decir "No se cumplio."\nfin\n',
        "ayuda": "Hace una cosa u otra segun se cumpla una condicion. Ejemplo: respuesta == \"si\"",
    },
    {
        "icono": "repetir", "titulo": "Repetir\nvarias veces", "color": "#f38ba8",
        "codigo": 'repetir {{CURSOR}}5 veces\n    decir "Hola!"\nfin\n',
        "ayuda": "Repite lo de adentro la cantidad de veces que pongas.",
    },
    {
        "icono": "lista", "titulo": "Lista de\ncosas", "color": "#fab387",
        "codigo": 'variable cosas = ["{{CURSOR}}", "otra cosa"]\npara_cada elemento en cosas\n    decir elemento\nfin\n',
        "ayuda": "Guarda varias cosas juntas y las recorre una por una.",
    },
]

# ------- Desafios del dia: consignas cortas para quien no sabe que hacer -------
# Sistema nuevo: no ejecuta nada especial, es una idea concreta para que
# el chico tenga un punto de partida en vez de la pagina en blanco.
DESAFIOS = [
    "Hace un programa que te salude por tu nombre.",
    "Pedile a la compu un numero y decile si es mayor o menor a 10.",
    "Hace una lista con tus 3 juegos favoritos y mostralos uno por uno.",
    "Inventa una adivinanza: la compu piensa un numero del 1 al 10 y vos "
    "tenes que adivinarlo (con 'preguntar' y 'si').",
    "Hace un programa que repita 'Feliz cumpleanios' 5 veces.",
    "Pregunta que animal le gusta a alguien y responde algo distinto para cada animal.",
    "Hace una lista de compras y despues decile 'Comprado!' a cada cosa.",
    "Inventa un programa que tire una moneda al azar (cara o ceca) usando 'numero al azar'.",
]

TEMA = {
    "fondo": "#1e1e2e", "panel": "#181825", "texto": "#cdd6f4", "texto_suave": "#a6adc8",
    "codigo_fondo": "#11111b", "codigo_texto": "#a6e3a1",
}

FUENTE_TITULO = ("Comic Sans MS", 20, "bold")
FUENTE_BOTON = ("Comic Sans MS", 12, "bold")
FUENTE_TEXTO = ("Comic Sans MS", 11)


def _cargar_motor_sipi():
    for nombre in ("sipi.py", "sipi_protegido.py"):
        ruta = os.path.join(AQUI, nombre)
        if os.path.exists(ruta):
            return ruta
    raise ImportError("No se encontro sipi.py junto a editor_sipi_kids.py")


class SiPiKids:
    def __init__(self, root):
        self.root = root
        self.root.title("SiPi Kids - Programar jugando")
        self.root.geometry("1150x740")
        self.root.minsize(860, 580)
        self.root.configure(fg_color=TEMA["fondo"])
        self.archivo_actual = None
        self.carpeta_temporal = tempfile.mkdtemp(prefix="sipi_kids_")
        atexit.register(self._limpiar)

        self._cache_iconos = {}
        self._aplicar_icono_ventana()
        self._crear_encabezado()
        self._crear_cuerpo()
        self._insertar_plantilla_inicial()

    # ------- utilidades -------
    def _aplicar_icono_ventana(self):
        try:
            import iconos_datos
            img = tk.PhotoImage(data=iconos_datos.ICONOS_EDITOR_B64["sipi_logo"])
            self.root.iconphoto(True, img)
            self._icono_ventana_ref = img
        except Exception:
            pass

    def _icono(self, nombre, tamano=32):
        """Igual criterio que en el editor grande: los iconos estan
        incrustados como base64 en iconos_datos_kids.py, para que nunca
        dependan de que la carpeta iconos_kids/ se haya copiado bien al
        instalar. Cae a leer el .png suelto como respaldo si el modulo
        de datos no esta disponible. Devuelve un ctk.CTkImage (envoltorio
        de Pillow) para que CustomTkinter lo pueda escalar bien en
        pantallas HighDPI -- si Pillow no esta instalado, cae a
        tk.PhotoImage crudo (funciona igual, solo pierde el escalado
        fino en pantallas de alta densidad)."""
        clave = (nombre, tamano)
        if clave in self._cache_iconos:
            return self._cache_iconos[clave]

        icono = None
        datos_png = None
        try:
            import iconos_datos_kids
            import base64
            datos_png = base64.b64decode(iconos_datos_kids.ICONOS_KIDS_B64[nombre])
        except Exception:
            ruta = os.path.join(AQUI, "iconos_kids", nombre + ".png")
            try:
                with open(ruta, "rb") as f:
                    datos_png = f.read()
            except Exception:
                datos_png = None

        if datos_png is not None:
            try:
                from PIL import Image
                import io
                imagen_pil = Image.open(io.BytesIO(datos_png)).convert("RGBA")
                icono = ctk.CTkImage(light_image=imagen_pil, dark_image=imagen_pil,
                                      size=(tamano, tamano))
            except Exception:
                # Sin Pillow (o algo raro con la imagen): respaldo con
                # tk.PhotoImage crudo, igual que la version anterior.
                try:
                    crudo = tk.PhotoImage(data=datos_png)
                    factor = max(1, crudo.width() // tamano)
                    if factor > 1:
                        crudo = crudo.subsample(factor, factor)
                    icono = crudo
                except Exception:
                    icono = None

        self._cache_iconos[clave] = icono
        return icono

    def _limpiar(self):
        import shutil
        try:
            shutil.rmtree(self.carpeta_temporal, ignore_errors=True)
        except OSError:
            pass

    # ------- interfaz -------
    def _crear_encabezado(self):
        barra = ctk.CTkFrame(self.root, fg_color=TEMA["panel"], height=68, corner_radius=0)
        barra.pack(side=tk.TOP, fill=tk.X)

        ctk.CTkLabel(barra, text="SiPi Kids", text_color="#f9e2af",
                     font=FUENTE_TITULO).pack(side=tk.LEFT, padx=18, pady=10)

        def boton_grande(icono, texto, comando, color, color_hover):
            img = self._icono(icono, 26)
            b = ctk.CTkButton(barra, image=img, text=texto, compound=tk.LEFT, command=comando,
                               fg_color=color, hover_color=color_hover, text_color="white",
                               font=FUENTE_BOTON, corner_radius=10, height=44, cursor="hand2")
            b.pack(side=tk.LEFT, padx=6, pady=12)
            return b

        boton_grande("abrir_kids", " Abrir", self.abrir, "#6c7086", "#585b70")
        boton_grande("guardar_kids", " Guardar", self.guardar, "#6c7086", "#585b70")
        boton_grande("jugar", " ¡Ejecutar!", self.ejecutar, "#40a02b", "#329323")
        boton_grande("ia_kids", " Ayuda de la IA", self.abrir_ayuda_ia, "#04a5e5", "#0389bd")
        boton_grande("trofeo", " Mis logros", self.abrir_logros, "#f9c74f", "#dba934")

    def _crear_cuerpo(self):
        cuerpo = ctk.CTkFrame(self.root, fg_color=TEMA["fondo"], corner_radius=0)
        cuerpo.pack(fill=tk.BOTH, expand=True)

        # --- Paleta de bloques (izquierda) ---
        paleta = ctk.CTkScrollableFrame(cuerpo, fg_color=TEMA["panel"], width=210,
                                         corner_radius=0, label_text="Bloques",
                                         label_font=("Comic Sans MS", 13, "bold"),
                                         label_text_color=TEMA["texto"])
        paleta.pack(side=tk.LEFT, fill=tk.Y)

        for bloque in BLOQUES:
            self._crear_boton_bloque(paleta, bloque)

        # --- Editor de texto (centro) ---
        centro = ctk.CTkFrame(cuerpo, fg_color=TEMA["fondo"], corner_radius=0)
        centro.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        franja_superior = ctk.CTkFrame(centro, fg_color=TEMA["fondo"], corner_radius=0)
        franja_superior.pack(fill=tk.X, padx=10, pady=(10, 0))
        ctk.CTkLabel(franja_superior, text="Tu programa:", text_color=TEMA["texto"],
                     font=("Comic Sans MS", 12, "bold"), anchor="w").pack(side=tk.LEFT)

        self.texto = ctk.CTkTextbox(centro, wrap="word", fg_color=TEMA["codigo_fondo"],
                                     text_color=TEMA["codigo_texto"], font=("Consolas", 14),
                                     corner_radius=8, border_width=0)
        self.texto.pack(fill=tk.BOTH, expand=True, padx=10, pady=(6, 6))

        self.etiqueta_ayuda_bloque = ctk.CTkLabel(
            centro, text="Toca un bloque de la izquierda para agregarlo.",
            text_color=TEMA["texto_suave"], font=("Comic Sans MS", 10),
            wraplength=650, justify="left", anchor="w")
        self.etiqueta_ayuda_bloque.pack(fill=tk.X, padx=10, pady=(0, 4))

        # --- Desafio del dia (nuevo): idea concreta para quien no sabe
        # que programar. Boton "Otra idea" para pedir otro al azar sin
        # cerrar/reabrir el programa.
        marco_desafio = ctk.CTkFrame(centro, fg_color="#2a2a3d", corner_radius=8)
        marco_desafio.pack(fill=tk.X, padx=10, pady=(0, 10))
        ctk.CTkLabel(marco_desafio, text="Desafio del dia:", text_color="#f9c74f",
                     font=("Comic Sans MS", 10, "bold"), anchor="w").pack(
            side=tk.LEFT, padx=(10, 4), pady=8)
        self.etiqueta_desafio = ctk.CTkLabel(marco_desafio, text="", text_color=TEMA["texto"],
                                              font=("Comic Sans MS", 10), anchor="w",
                                              wraplength=520, justify="left")
        self.etiqueta_desafio.pack(side=tk.LEFT, fill=tk.X, expand=True, pady=8)
        ctk.CTkButton(marco_desafio, text="Otra idea", command=self._nuevo_desafio,
                      fg_color="#585b70", hover_color="#45475a", width=90,
                      font=("Comic Sans MS", 9, "bold")).pack(side=tk.RIGHT, padx=10, pady=8)
        self._nuevo_desafio()

        # --- Resultado (derecha) ---
        derecha = ctk.CTkFrame(cuerpo, fg_color=TEMA["panel"], width=340, corner_radius=0)
        derecha.pack(side=tk.RIGHT, fill=tk.Y)
        derecha.pack_propagate(False)

        ctk.CTkLabel(derecha, text="Resultado", text_color=TEMA["texto"],
                     font=("Comic Sans MS", 13, "bold")).pack(pady=10)

        self.consola = ctk.CTkTextbox(derecha, wrap="word", fg_color="#0a0a12",
                                       text_color="#f9e2af", font=("Consolas", 12),
                                       corner_radius=8, border_width=0)
        self.consola.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10))
        self._escribir_consola("Aca vas a ver lo que pase cuando toques '¡Ejecutar!'.\n")

    def _nuevo_desafio(self):
        self.etiqueta_desafio.configure(text=random.choice(DESAFIOS))
        if not getattr(self, "_cargando_plantilla_inicial", False):
            self._registrar_logro("desafio_visto")

    def _crear_boton_bloque(self, contenedor, bloque):
        marco = ctk.CTkFrame(contenedor, fg_color=TEMA["panel"])
        marco.pack(fill=tk.X, pady=6)
        img = self._icono(bloque["icono"], 26)
        b = ctk.CTkButton(marco, image=img, text="  " + bloque["titulo"], compound=tk.LEFT,
                           command=lambda bl=bloque: self._insertar_bloque(bl),
                           fg_color=bloque["color"], hover_color=bloque["color"],
                           text_color="white", font=("Comic Sans MS", 10, "bold"),
                           anchor="w", corner_radius=10, height=52, cursor="hand2")
        b.pack(fill=tk.X)
        b.bind("<Enter>", lambda e, bl=bloque: self.etiqueta_ayuda_bloque.configure(text=bl["ayuda"]))

    def _insertar_bloque(self, bloque):
        codigo = bloque["codigo"]
        pos_cursor = codigo.find("{{CURSOR}}")
        codigo_limpio = codigo.replace("{{CURSOR}}", "")
        self.texto.insert(tk.INSERT, codigo_limpio)
        if pos_cursor != -1:
            # Reposiciona el cursor justo donde estaba el marcador, para
            # que el chico pueda escribir el texto/condicion sin buscarlo.
            indice_actual = self.texto.index(tk.INSERT)
            offset_atras = len(codigo_limpio) - pos_cursor
            self.texto.mark_set(tk.INSERT, f"{indice_actual} - {offset_atras}c")
        self.texto.focus_set()
        if not getattr(self, "_cargando_plantilla_inicial", False):
            self._registrar_logro("bloque_usado", bloque=bloque["icono"])

    def _insertar_plantilla_inicial(self):
        self._cargando_plantilla_inicial = True
        self._insertar_bloque(BLOQUES[0])
        self._insertar_bloque(BLOQUES[1])
        self._cargando_plantilla_inicial = False
        self.texto.mark_set(tk.INSERT, "1.0")
        # posiciona el cursor dentro de las comillas del "decir" de ejemplo
        contenido = self.texto.get("1.0", "end-1c")
        idx = contenido.find('decir "')
        if idx != -1:
            self.texto.mark_set(tk.INSERT, f"1.0 + {idx + 7}c")
        self.texto.focus_set()

    def _escribir_consola(self, texto):
        self.consola.insert(tk.END, texto)
        self.consola.see(tk.END)

    def _registrar_logro(self, tipo, **datos):
        """Envuelve logros_kids.registrar_evento y muestra una
        notificacion emergente por cada logro nuevo desbloqueado."""
        try:
            import logros_kids
            nuevos = logros_kids.registrar_evento(tipo, **datos)
        except Exception:
            nuevos = []
        for logro in nuevos:
            self._mostrar_notificacion_logro(logro)

    def _mostrar_notificacion_logro(self, logro):
        """Ventanita sin bordes, arriba a la derecha, que aparece unos
        segundos y se cierra sola -- para no interrumpir al chico en
        medio de escribir el programa."""
        aviso = ctk.CTkToplevel(self.root)
        aviso.overrideredirect(True)
        aviso.configure(fg_color="#f9c74f")
        self.root.update_idletasks()
        x = self.root.winfo_x() + self.root.winfo_width() - 320
        y = self.root.winfo_y() + 80
        aviso.geometry(f"300x90+{x}+{y}")
        aviso.attributes("-topmost", True)

        marco = ctk.CTkFrame(aviso, fg_color="#f9c74f")
        marco.pack(fill=tk.BOTH, expand=True, padx=12, pady=10)
        img = self._icono(logro["icono"], 40)
        etiqueta_img = ctk.CTkLabel(marco, image=img, text="", fg_color="#f9c74f")
        etiqueta_img.pack(side=tk.LEFT, padx=(0, 10))

        marco_texto = ctk.CTkFrame(marco, fg_color="#f9c74f")
        marco_texto.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        ctk.CTkLabel(marco_texto, text="¡Logro desbloqueado!", text_color="#4a3600",
                     font=("Comic Sans MS", 10, "bold"), anchor="w").pack(fill=tk.X)
        ctk.CTkLabel(marco_texto, text=logro["titulo"], text_color="#4a3600",
                     font=("Comic Sans MS", 11, "bold"), anchor="w",
                     wraplength=200).pack(fill=tk.X)

        aviso.after(4000, aviso.destroy)

    # ------- acciones -------
    def abrir(self):
        ruta = filedialog.askopenfilename(filetypes=[("Programas SiPi", "*.sipi"), ("Todos", "*.*")])
        if not ruta:
            return
        with open(ruta, "r", encoding="utf-8") as f:
            contenido = f.read()
        self.texto.delete("1.0", tk.END)
        self.texto.insert("1.0", contenido)
        self.archivo_actual = ruta

    def guardar(self):
        if not self.archivo_actual:
            ruta = filedialog.asksaveasfilename(defaultextension=".sipi",
                                                 filetypes=[("Programas SiPi", "*.sipi")])
            if not ruta:
                return
            self.archivo_actual = ruta
        with open(self.archivo_actual, "w", encoding="utf-8") as f:
            f.write(self.texto.get("1.0", "end-1c"))
        self._escribir_consola(f"\n[Guardado en {self.archivo_actual}]\n")
        self._registrar_logro("programa_guardado")

    def ejecutar(self):
        codigo = self.texto.get("1.0", "end-1c")
        try:
            sipi_py = _cargar_motor_sipi()
        except ImportError as e:
            messagebox.showerror("SiPi Kids", f"No se encontro el motor de SiPi.\n{e}")
            return
        ruta_temp = os.path.join(self.carpeta_temporal, "programa.sipi")
        with open(ruta_temp, "w", encoding="utf-8") as f:
            f.write(codigo)
        self.consola.delete("1.0", tk.END)
        self._escribir_consola("Ejecutando tu programa...\n\n")
        comando = _comando_motor_kids(sipi_py, ruta_temp)
        if os.name == "nt":
            comando_texto = " ".join(f'"{parte}"' for parte in comando)
            subprocess.Popen(f'start cmd /k chcp 65001 >nul && {comando_texto}', shell=True)
            self._escribir_consola("(Se abrio una ventana nueva con el resultado.)\n")
        else:
            # En Linux/Mac se corre capturando salida para mostrarla directo
            # en el panel de "Resultado", que es mas simple para un chico
            # que andar buscando una terminal aparte.
            try:
                resultado = subprocess.run(comando,
                                            capture_output=True, text=True, timeout=15,
                                            encoding="utf-8", errors="replace",
                                            env={**os.environ, "PYTHONIOENCODING": "utf-8"})
                self._escribir_consola(resultado.stdout or "")
                ok = resultado.returncode == 0 and not resultado.stderr.strip()
                if resultado.stderr.strip():
                    self._escribir_consola("\n--- Uy, algo no salio bien ---\n")
                    self._escribir_consola(resultado.stderr.strip() + "\n")
                    self._escribir_consola("\nProba tocar '¡Ejecutar!' de nuevo despues de arreglarlo,\n"
                                            "o usa 'Ayuda de la IA' para que te ayude.\n")
                self._registrar_logro("programa_ejecutado", ok=ok)
            except subprocess.TimeoutExpired:
                self._escribir_consola("\n(El programa tardo demasiado o esta esperando que respondas algo.)\n")

    def abrir_ayuda_ia(self):
        """Version simplificada del panel de SiPi IA, pensada para chicos:
        una sola caja de texto ('que queres que haga tu programa?') y un
        boton gigante. Reutiliza el mismo modulo sipi_ia.py del editor
        grande, con un prompt adaptado a un publico infantil."""
        sys.path.insert(0, AQUI)
        import sipi_ia

        ventana = ctk.CTkToplevel(self.root)
        ventana.title("Ayuda de la IA")
        ventana.geometry("520x460")
        ventana.configure(fg_color=TEMA["fondo"])

        if not sipi_ia.hay_api_key_configurada():
            ctk.CTkLabel(ventana, text="Pedile a un adulto que configure la IA la primera vez\n"
                                        "(boton 'Guardar clave' abajo).",
                         text_color=TEMA["texto"], font=FUENTE_TEXTO,
                         wraplength=460, justify="left").pack(pady=20)
            campo_clave = ctk.CTkEntry(ventana, show="*", font=("Segoe UI", 10))
            campo_clave.pack(padx=20, pady=6, fill=tk.X)

            def _guardar_clave():
                clave = campo_clave.get().strip()
                if not clave:
                    return
                try:
                    sipi_ia.guardar_api_key(clave)
                except sipi_ia.ErrorSiPiIA as e:
                    messagebox.showerror("SiPi Kids", str(e))
                    return
                ventana.destroy()
                self.abrir_ayuda_ia()

            ctk.CTkButton(ventana, text="Guardar clave", command=_guardar_clave,
                          fg_color="#40a02b", hover_color="#329323", text_color="white",
                          font=("Comic Sans MS", 10, "bold")).pack(pady=10)
            return

        ctk.CTkLabel(ventana, text="Conta que queres que haga tu programa:",
                     text_color=TEMA["texto"], font=("Comic Sans MS", 12, "bold"),
                     wraplength=480).pack(pady=(14, 6))

        entrada = ctk.CTkTextbox(ventana, height=90, wrap="word", fg_color=TEMA["codigo_fondo"],
                                  text_color=TEMA["texto"], font=("Comic Sans MS", 11), corner_radius=8)
        entrada.pack(fill=tk.X, padx=20)
        entrada.insert("1.0", "Por ejemplo: un juego que adivine un numero secreto")

        salida = ctk.CTkTextbox(ventana, wrap="word", fg_color=TEMA["codigo_fondo"],
                                 text_color=TEMA["codigo_texto"], font=("Consolas", 10), corner_radius=8)
        salida.pack(fill=tk.BOTH, expand=True, padx=20, pady=10)

        def _pedir():
            pedido = entrada.get("1.0", "end-1c").strip()
            salida.delete("1.0", tk.END)
            salida.insert("1.0", "Pensando... espera un toque.")
            boton.configure(state="disabled")
            self._registrar_logro("pedido_ia")

            def _trabajo():
                try:
                    codigo = sipi_ia.generar_codigo(
                        f"(para un chico que recien empieza, con codigo simple y comentado): {pedido}"
                    )
                except sipi_ia.ErrorSiPiIA as e:
                    codigo = f"No se pudo pedir ayuda ahora: {e}"
                self.root.after(0, lambda: (salida.delete("1.0", tk.END), salida.insert("1.0", codigo)))
                self.root.after(0, lambda: boton.configure(state="normal"))

            import threading
            threading.Thread(target=_trabajo, daemon=True).start()

        def _usar():
            codigo = sipi_ia._extraer_bloque_codigo(salida.get("1.0", "end-1c"))
            self.texto.delete("1.0", tk.END)
            self.texto.insert("1.0", codigo)
            ventana.destroy()

        marco_botones = ctk.CTkFrame(ventana, fg_color=TEMA["fondo"])
        marco_botones.pack(pady=(0, 14))
        boton = ctk.CTkButton(marco_botones, text="Pedirle a la IA", command=_pedir,
                              fg_color="#04a5e5", hover_color="#0389bd", text_color="white",
                              font=("Comic Sans MS", 11, "bold"))
        boton.pack(side=tk.LEFT, padx=6)
        ctk.CTkButton(marco_botones, text="Usar este programa", command=_usar,
                      fg_color="#40a02b", hover_color="#329323", text_color="white",
                      font=("Comic Sans MS", 11, "bold")).pack(side=tk.LEFT, padx=6)

    def abrir_logros(self):
        """Vitrina de logros: muestra el catalogo completo, con los ya
        desbloqueados destacados en color y el resto en gris (para que
        el chico vea que hay mas por descubrir, sin arruinarle la
        sorpresa mostrando la condicion exacta de cada uno bloqueado)."""
        import logros_kids
        desbloqueados, todos, stats = logros_kids.obtener_progreso()
        ids_desbloqueados = {l["id"] for l in desbloqueados}

        ventana = ctk.CTkToplevel(self.root)
        ventana.title("Mis logros")
        ventana.geometry("580x580")
        ventana.configure(fg_color=TEMA["fondo"])

        ctk.CTkLabel(ventana, text=f"Lograste {len(desbloqueados)} de {len(todos)}",
                     text_color="#f9e2af", font=("Comic Sans MS", 15, "bold")).pack(pady=(14, 4))

        # Sistema de niveles (XP): cada logro suma XP, cada tanto de XP
        # sube un nivel. Barra simple, sin numeros abrumadores.
        nivel, xp_total, xp_en_nivel, xp_para_nivel = logros_kids.calcular_nivel(stats)
        marco_nivel = ctk.CTkFrame(ventana, fg_color="transparent")
        marco_nivel.pack(fill=tk.X, padx=30, pady=(0, 14))
        ctk.CTkLabel(marco_nivel, text=f"Nivel {nivel}", text_color=TEMA["texto"],
                     font=("Comic Sans MS", 11, "bold")).pack(anchor="w")
        barra_nivel = ctk.CTkProgressBar(marco_nivel, progress_color="#40a02b",
                                          fg_color="#313244", height=14)
        barra_nivel.pack(fill=tk.X, pady=(4, 0))
        barra_nivel.set(xp_en_nivel / xp_para_nivel if xp_para_nivel else 0)
        ctk.CTkLabel(marco_nivel, text=f"{xp_en_nivel} / {xp_para_nivel} XP para el proximo nivel",
                     text_color=TEMA["texto_suave"], font=("Comic Sans MS", 9)).pack(anchor="w", pady=(2, 0))

        contenedor = ctk.CTkScrollableFrame(ventana, fg_color=TEMA["fondo"])
        contenedor.pack(fill=tk.BOTH, expand=True, padx=16)

        for logro in todos:
            logrado = logro["id"] in ids_desbloqueados
            color_fondo = "#313244" if logrado else "#181825"
            fila = ctk.CTkFrame(contenedor, fg_color=color_fondo, corner_radius=8)
            fila.pack(fill=tk.X, pady=5, padx=2)

            icono_usar = logro["icono"] if logrado else "logro_bloqueado"
            img = self._icono(icono_usar, 34)
            etq_img = ctk.CTkLabel(fila, image=img, text="", fg_color=color_fondo)
            etq_img.pack(side=tk.LEFT, padx=10, pady=8)

            marco_texto = ctk.CTkFrame(fila, fg_color=color_fondo)
            marco_texto.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, pady=8)
            titulo_mostrado = logro["titulo"] if logrado else "???"
            desc_mostrada = logro["descripcion"] if logrado else "Todavia no lo descubriste."
            color_titulo = "#f9e2af" if logrado else "#6c7086"
            ctk.CTkLabel(marco_texto, text=titulo_mostrado, text_color=color_titulo,
                         font=("Comic Sans MS", 11, "bold"), anchor="w").pack(fill=tk.X)
            ctk.CTkLabel(marco_texto, text=desc_mostrada, text_color=TEMA["texto_suave"],
                         font=("Comic Sans MS", 9), anchor="w", wraplength=380,
                         justify="left").pack(fill=tk.X)

        ctk.CTkLabel(ventana, text=(
            f"Programas ejecutados: {stats['programas_ejecutados']}   |   "
            f"Racha sin errores: {stats['racha_sin_error']}"
        ), text_color=TEMA["texto_suave"], font=("Comic Sans MS", 9)).pack(pady=10)


def main():
    root = ctk.CTk()
    SiPiKids(root)
    root.mainloop()


if __name__ == "__main__":
    if not _intentar_correr_como_motor_interno():
        main()
