Autor: Keivin Wedell Reyes Gutiérrez

  • Generación de archivo .WAV de audio para entrenamiento de voz TTS

    En nuestra entrada anterior (Generación de texto a voz con XTTSv2 sobre WSL2 con Coqui), exploramos cómo montar un estudio local de locución y síntesis mediante inteligencia artificial utilizando el motor XTTSv2 y contenedores en WSL2.

    Uno de los pilares fundamentales para lograr una clonación zero-shot exitosa —y evitar que la IA invente acentos extraños, balbucee o lea incorrectamente la puntuación— reside en la calidad de la muestra de audio que le entregamos al sistema.

    En este artículo, te comparto la guía definitiva para grabar, procesar y exportar tu archivo referencia.wav utilizando Audacity, optimizado matemáticamente para que el motor neuronal capture los graves y matices de tu voz sin distorsiones.

    1. El secreto de una buena muestra: Duración y Fonética

    Cuando nos enfrentamos a la clonación de voz por primera vez, solemos cometer el error de grabar textos demasiado largos (como páginas enteras o artículos técnicos). Irónicamente, menos es más.

    • Duración recomendada: Entre 6 y 8 segundos.
    • Por qué: Una muestra corta evita que cargues la lectura con entonaciones dramáticas innecesarias, pausas excesivas o variaciones de volumen que confunden al codificador de la IA.

    El Párrafo Fonético Sugerido

    Utiliza este texto exacto para tu grabación. Está diseñado con un ritmo conversacional completamente plano y natural, cubriendo las vocales abiertas, consonantes nasales y oclusivas clave de nuestro idioma:

    “Hola, soy Keivin. Hoy analizaremos cómo la tecnología y la educación transforman nuestra realidad a través de procesos simples.”

    2. Paso a Paso: Grabación y Edición en Audacity

    Abre Audacity en tu equipo y asegúrate de tener tu micrófono bien posicionado (a unos 10-15 cm, ligeramente ladeado para evitar ruidos de respiración directa).

    A. La interpretación vocal

    • Imagina que estás platicando tranquilamente con un colega en una charla de café.
    • Mantén un volumen constante y un tono estrictamente plano y neutral (cero dramatismo de locutor comercial, sin subidas de voz exageradas en los signos de puntuación).
    • Lee la frase de corrido y deja una micro-pausa limpia al terminar.

    B. Limpieza de ruido ambiente (Reducción de ruido)

    1. Selecciona con el cursor un pequeño fragmento de silencio al inicio o final de tu pista (donde solo se capture el ruido estático de fondo de tu habitación).
    2. Ve al menú superior Efecto > Reducción de ruido y haz clic en el botón Obtener perfil de ruido.
    3. Selecciona toda tu grabación presionando Ctrl + A.
    4. Vuelve a Efecto > Reducción de ruido, deja los valores por defecto y haz clic en Aceptar. Esto eliminará cualquier zumbido eléctrico sin afectar tu timbre de voz.

    3. La Exportación Quirúrgica (Requisito Indispensable para XTTSv2)

    Los motores de clonación neuronal son extremadamente estrictos con los metadatos y la tasa de muestreo. Si exportas el archivo con un formato incorrecto, la inferencia fallará, se escuchará robótica o el sistema arrojará errores de decodificación.

    Para cumplir exactamente con los requerimientos técnicos de Coqui XTTSv2, exporta tu archivo siguiendo estos pasos:

    1. Ve a Archivo > Exportar > Exportar como WAV.
    2. Nombra el archivo exactamente como referencia.wav y guárdalo dentro de tu directorio de trabajo del proyecto (por ejemplo, en ~/podcast_ia/).
    3. En la ventana de diálogo de exportación, configura los parámetros de formato de esta manera:
      • Formato: WAV (Microsoft)
      • Codificación: PCM de 16 bits (Signed 16-bit PCM)
      • Canales: Cámbialo obligatoriamente a Mono (1 canal). (Nota técnica: Los modelos de IA procesan el audio en un solo canal; si lo exportas en Estéreo, el desajuste de fases arruinará la clonación).

    Conclusión

    Con tu archivo referencia.wav limpio, grabado en mono a 16 bits y con una duración menor a 8 segundos, habrás construido el insumo perfecto para alimentar el pipeline que vimos en nuestra entrada principal de Generación de texto a voz con XTTSv2 sobre WSL2 con Coqui.

    Prueba colocar este archivo en tu entorno de trabajo y corre tu script de síntesis. ¡Notarás de inmediato cómo la fidelidad de tu voz clonada alcanza un nivel hiperrealista!

  • Generación de texto a voz con XTTSv2 sobre WSL2 con Coqui

    En la era de la nube y los servicios de suscripción costosos, la filosofía del software libre y el desarrollo local nos invita a recuperar el control de nuestras herramientas. Bajo esta premisa , me propuse un reto técnico: montar un estudio de locución y síntesis de voz por inteligencia artificial completamente local, privado y automatizado, sin depender de APIs de pago ni de servidores externos.

    Después de experimentar con diversas configuraciones, hoy comparto la guía definitiva que me funcionó de manera infalible en producción.

    1. El Banco de Pruebas (Especificaciones de Hardware y Entorno)

    Para replicar este entorno con éxito, es fundamental conocer la arquitectura sobre la cual testeamos y validamos este pipeline:

    • Sistema Host: Windows 11.
    • Capa de Virtualización: Subsistema de Windows para Linux (WSL2).
    • Entorno Invitado (Guest): Ubuntu 22.04 LTS (que provee nativamente Python 3.10).
    • Aceleración Gráfica (GPU): Tarjeta gráfica NVIDIA GeForce GTX 1060 con 6 GB de VRAM y controladores actualizados con soporte WDDM.

    Ajuste preventivo de memoria en WSL2

    Para evitar que la IA desborde los recursos de tu equipo durante la inferencia en la GPU, es altamente recomendable limitar la memoria de WSL. Crea un archivo llamado .wslconfig en tu ruta de usuario de Windows (%userprofile%) con estos valores:

    [wsl2]
    memory=6GB
    swap=4GB

    Aplica los cambios abriendo tu terminal en Windows y ejecutando: wsl --shutdown

    2. Preparando el Entorno de Trabajo en Ubuntu (WSL2)

    Inicia tu terminal de Ubuntu 22.04 y actualiza los paquetes base del sistema, asegurando las herramientas de compilación y manipulación de audio:

    sudo apt update && sudo apt upgrade -y
    sudo apt install -y python3-pip python3-venv git ffmpeg

    Crea un directorio exclusivo para tu proyecto y aísla las dependencias en un entorno virtual (venv):

    mkdir ~/podcast_ia
    cd ~/podcast_ia
    python3 -m venv podcast_env

    ¿Cómo iniciar sesión en cada nueva sesión de trabajo?

    Cada vez que enciendas tu computadora o cierres la terminal y quieras volver a trabajar en tus pódcasts, recuerda seguir estos pasos para activar tu entorno virtual:

    1. Entra a la carpeta del proyecto:

    cd ~/podcast_ia

    2. Activa el entorno (env):

    source podcast_env/bin/activate

    (Verás que la terminal antepone (podcast_env) en la línea de comandos).

    3. Una vez terminado tu trabajo, puedes salir del entorno simplemente escribiendo: deactivate.

    3. Instalación de Dependencias de IA (PyTorch y Coqui TTS)

    Dentro de tu entorno virtual activo, instala PyTorch configurado específicamente para el soporte de CUDA (versión 11.8, óptima para la arquitectura de la GTX 1060), seguido por el motor de Coqui y utilidades de audio:

    pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
    pip install TTS==0.22.0 transformers==4.33.0 pydub

    Nota técnica previa: Para que el motor neuronal capture con absoluta fidelidad los matices de tu voz y evite distorsiones, artefactos o lecturas erróneas de puntuación, es indispensable alimentar el sistema con una muestra de audio perfectamente calibrada. Si deseas profundizar en el proceso de captura, el tiempo de duración ideal y las especificaciones exactas para exportar tu muestra limpia en Audacity, puedes consultar mi guía detallada en el post sobre la Generación de archivo .WAV de audio para entrenamiento de voz TTS.

    4. El Script Básico: Síntesis Lineal con Archivo de Referencia .wav

    Para nuestra primera versión funcional del script, mantendremos las cosas simples. Este programa básico toma un archivo de texto plano (guion.txt) y un archivo de audio con tu propia voz (referencia.wav), aplica el parche de seguridad indispensable para versiones recientes de PyTorch, procesa el texto eliminando los puntos finales problemáticos y une los fragmentos inyectando silencios limpios mediante Pydub.

    Crea un archivo llamado estudio_basico.py y pega el siguiente código:

    """
    =============================================================================
    ESTUDIO BÁSICO DE PÓDCAST IA - CLONACIÓN ZERO-SHOT CON ARCHIVO WAV
    Descripción: Script lineal y directo para generar locuciones a partir de 
                 un guion en texto plano y una muestra de voz personalizada.
    =============================================================================
    """
    
    import os
    import torch
    import re
    from TTS.api import TTS
    from pydub import AudioSegment
    
    # --- 1. PARCHE DE SEGURIDAD PARA PYTORCH 2.6+ ---
    # Permite cargar de forma segura los pesos locales del modelo de Coqui.
    original_load = torch.load
    def parche_load(*args, **kwargs):
        kwargs['weights_only'] = False
        return original_load(*args, **kwargs)
    torch.load = parche_load
    
    os.environ["COQUI_TOS_AGREED"] = "1"
    
    def cargar_guion_simple(ruta_archivo):
        """
        Lee el guion línea por línea, eliminando puntos finales para evitar 
        que la IA verbalice la palabra 'punto'.
        """
        if not os.path.exists(ruta_archivo):
            return None
    
        with open(ruta_archivo, 'r', encoding='utf-8') as f:
            lineas = f.readlines()
    
        parrafos_limpios = []
        for linea in lineas:
            texto = linea.strip()
            if not texto or texto.startswith(('#', '[')):
                continue
                
            # Purgado de caracteres raros y eliminación del punto final
            texto = texto.replace('\xa0', ' ').replace('\t', ' ')
            texto = texto.rstrip('.')
            texto = re.sub(r'\s+', ' ', texto).strip()
            
            if texto:
                parrafos_limpios.append(texto)
                    
        return parrafos_limpios
    
    def main():
        print("\n=== ESTUDIO BÁSICO DE PÓDCAST (LOCAL) ===")
        
        # Detección de hardware (GPU NVIDIA)
        device = "cuda" if torch.cuda.is_available() else "cpu"
        print(f"Hardware en uso: {device.upper()}")
        
        # 1. Carga del guion
        archivo_entrada = input("Ingresa el nombre de tu archivo de guion (ej. guion.txt): ").strip()
        parrafos = cargar_guion_simple(archivo_entrada)
        
        if parrafos is None:
            print(f"❌ Error: No se encontró el archivo '{archivo_entrada}'.")
            return
    
        print(f"✅ Guion cargado: {len(parrafos)} bloques listos.")
        
        # 2. Verificación del archivo de voz de referencia
        audio_referencia = "referencia.wav"
        if not os.path.exists(audio_referencia):
            print(f"❌ Error crítico: No se encuentra el archivo '{audio_referencia}' en este directorio.")
            print("Coloca tu muestra de voz en formato .wav (Mono, 22050Hz) antes de continuar.")
            return
    
        # 3. Carga del motor XTTSv2 en la GPU
        print("\nCargando motor XTTSv2 en la GPU...")
        tts = TTS("tts_models/multilingual/multi-dataset/xtts_v2").to(device)
    
        # --- PARÁMETROS DE CALIBRACIÓN ACÚSTICA ---
        CONFIG_VELOCIDAD = 0.85
        CONFIG_TEMPERATURA = 0.65
        CONFIG_PENALIZACION = 2.0
    
        print("\n🚀 Iniciando síntesis de audio...")
        audio_maestro = AudioSegment.empty()
        
        for i, p in enumerate(parrafos):
            print(f"[{i+1}/{len(parrafos)}] Procesando fragmento...")
            temp_file = f"temp_chunk_{i}.wav"
            
            # Inferencia utilizando tu archivo de voz personalizado
            tts.tts_to_file(
                text=p, 
                language="es", 
                speaker_wav=audio_referencia, 
                file_path=temp_file, 
                split_sentences=False,
                speed=CONFIG_VELOCIDAD,
                temperature=CONFIG_TEMPERATURA,
                repetition_penalty=CONFIG_PENALIZACION
            )
            
            # Ensamblaje con pausa natural de medio segundo entre frases
            chunk_audio = AudioSegment.from_wav(temp_file)
            silencio = AudioSegment.silent(duration=500) 
            audio_maestro += chunk_audio + silencio
            os.remove(temp_file)
            
        archivo_final = "episodio_maestro.wav"
        audio_maestro.export(archivo_final, format="wav")
        print(f"\n🎉 ¡Proceso finalizado! Audio guardado con éxito como: '{archivo_final}'")
    
    if __name__ == "__main__":
        main()

    5. Evolucionando al Siguiente Nivel: El Script Avanzado (estudio_voces.py)

    Una vez que dominas la síntesis básica con tu archivo de referencia, es probable que quieras un flujo de trabajo más versátil: poder elegir entre distintas voces oficiales integradas en el modelo, calibrar los parámetros de velocidad y temperatura al vuelo, y nombrar tus episodios personalizados sin el riesgo de perder producciones anteriores por sobrescritura accidental.

    Para lograrlo, creamos estudio_voces.py. Este script interactivo incluye un gestor de nombres seguro y un menú de selección múltiple.

    Crea este archivo en tu directorio de trabajo y pega el código completo:

    """
    =============================================================================
    ESTUDIO AVANZADO DE PÓDCAST IA - SELECTOR DE VOCES Y GESTIÓN INTELIGENTE
    Descripción: Script interactivo para la generación automatizada de locuciones 
                 con opciones de voces oficiales, purga de puntuación y protección 
                 de archivos.
    =============================================================================
    """
    
    import os
    import torch
    import re
    from TTS.api import TTS
    from pydub import AudioSegment
    
    # --- 1. PARCHE DE SEGURIDAD PARA PYTORCH 2.6+ ---
    original_load = torch.load
    def parche_load(*args, **kwargs):
        kwargs['weights_only'] = False
        return original_load(*args, **kwargs)
    torch.load = parche_load
    
    os.environ["COQUI_TOS_AGREED"] = "1"
    
    # --- 2. INGESTA Y LIMPIEZA QUIRÚRGICA DEL GUION ---
    def cargar_guion_por_parrafos(ruta_archivo):
        """
        Lee el guion línea por línea, limpiando caracteres especiales y 
        eliminando puntos finales para evitar la lectura fonética de 'punto'.
        """
        if not os.path.exists(ruta_archivo):
            return None
    
        with open(ruta_archivo, 'r', encoding='utf-8') as f:
            lineas = f.readlines()
    
        parrafos_limpios = []
        for linea in lineas:
            texto = linea.strip()
            if not texto or texto.startswith(('#', '[')):
                continue
                
            texto = texto.replace('\xa0', ' ').replace('\t', ' ')
            texto = re.sub(r'\[.*?\]', '', texto)
            texto = texto.rstrip('.') # Truco antipunto final
            texto = re.sub(r'\.{2,}', ',', texto)
            texto = re.sub(r'\s+', ' ', texto).strip()
            
            if texto:
                parrafos_limpios.append(texto)
                    
        return parrafos_limpios
    
    # --- 3. GESTOR INTELIGENTE DE NOMBRES DE ARCHIVO ---
    def obtener_nombre_archivo_seguro(nombre_base):
        """
        Previene la sobrescritura de archivos agregando un sufijo numérico 
        autoincremental si el nombre de salida ya existe.
        """
        if not nombre_base.endswith(".wav"):
            nombre_base += ".wav"
            
        if not os.path.exists(nombre_base):
            return nombre_base
            
        base, ext = os.path.splitext(nombre_base)
        contador = 1
        while True:
            nuevo_nombre = f"{base}_{contador}{ext}"
            if not os.path.exists(nuevo_nombre):
                return nuevo_nombre
            contador += 1
    
    # --- 4. FLUJO PRINCIPAL AVANZADO ---
    def main():
        print("\n==========================================================")
        print("=== ESTUDIO DE PÓDCAST IA - SELECTOR DE VOZ & AVANZADO ===")
        print("==========================================================\n")
        
        device = "cuda" if torch.cuda.is_available() else "cpu"
        print(f"Hardware detectado: {device.upper()}")
        if device == "cuda":
            print(f"Tarjeta gráfica en uso: {torch.cuda.get_device_name(0)}")
        
        # Paso 1: Carga del texto
        archivo_entrada = input("\n[1/5] Ingresa el nombre de tu archivo de guion (ej. guion.txt): ").strip()
        parrafos = cargar_guion_por_parrafos(archivo_entrada)
        
        if parrafos is None:
            print(f"❌ Error: No se encontró el archivo '{archivo_entrada}'.")
            return
    
        print(f"✅ Guion cargado: {len(parrafos)} bloques purgados y listos.")
        
        # Paso 2: Elección de fuente de voz (Oficiales del sistema vs. Archivo WAV)
        print("\n[2/5] Selecciona la fuente de voz para la locución:")
        print("[1] Voces oficiales del sistema (Luis Moray, Marcos Rudaski, Alma María)")
        print("[2] Usar mi propio archivo de audio de referencia (.wav personalizado)")
        tipo_voz = input("Elige una opción (1 o 2) [Por defecto 1]: ").strip()
        if tipo_voz == "":
            tipo_voz = "1"
    
        speaker_id = None
        speaker_wav_path = None
    
        if tipo_voz == '1':
            print("\n--- Voces Oficiales del Sistema ---")
            print("[A] Luis Moray (Masculino - Predeterminado)")
            print("[B] Marcos Rudaski (Masculino alternativo)")
            print("[C] Alma María (Femenino)")
            eleccion = input("Elige tu locutor (A, B o C) [Por defecto A]: ").strip().upper()
            
            if eleccion == 'B':
                speaker_id = "Marcos Rudaski"
            elif eleccion == 'C':
                speaker_id = "Alma María"
            else:
                speaker_id = "Luis Moray"
            print(f"🎙️ Locutor seleccionado: {speaker_id}")
        else:
            nombre_wav = input("Ingresa el nombre de tu archivo de referencia [Por defecto 'referencia.wav']: ").strip()
            if nombre_wav == "":
                nombre_wav = "referencia.wav"
            if not os.path.exists(nombre_wav):
                print(f"❌ Error: No se encuentra el archivo '{nombre_wav}'.")
                return
            speaker_wav_path = nombre_wav
            print(f"🎙️ Archivo de referencia seleccionado: {speaker_wav_path}")
    
        # Paso 3: Calibración acústica interactiva
        print("\n[3/5] Calibración acústica (Presiona Enter para usar los valores por defecto):")
        vel_input = input(" -> Velocidad [Por defecto 0.85]: ").strip()
        CONFIG_VELOCIDAD = float(vel_input) if vel_input else 0.85
    
        temp_input = input(" -> Temperatura [Por defecto 0.65]: ").strip()
        CONFIG_TEMPERATURA = float(temp_input) if temp_input else 0.65
    
        pen_input = input(" -> Penalización de repetición [Por defecto 2.0]: ").strip()
        CONFIG_PENALIZACION = float(pen_input) if pen_input else 2.0
    
        # Paso 4: Modo de exportación
        print("\n[4/5] ¿Cómo deseas exportar el audio final?")
        print("[1] Un solo archivo maestro continuo (con pausas automáticas)")
        print("[2] Múltiples archivos independientes (un .wav por cada párrafo)")
        opcion_exp = input("Elige una opción (1 o 2) [Por defecto 1]: ").strip()
        if opcion_exp == "":
            opcion_exp = "1"
    
        # Paso 5: Nombramiento inteligente del archivo
        nombre_personalizado = input("\n[5/5] ¿Cómo quieres nombrar tu archivo de salida? [Por defecto 'episodio_maestro.wav']: ").strip()
        if not nombre_personalizado:
            nombre_personalizado = "episodio_maestro.wav"
    
        archivo_final = obtener_nombre_archivo_seguro(nombre_personalizado)
        if archivo_final != nombre_personalizado:
            print(f"ℹ️ El archivo '{nombre_personalizado}' ya existía. Se guardará de forma segura como: '{archivo_final}'")
    
        # Carga del motor en la GPU
        print("\nCargando motor XTTSv2 en la GPU (esto tomará unos segundos)...")
        tts = TTS("tts_models/multilingual/multi-dataset/xtts_v2").to(device)
    
        print(f"\n🚀 Iniciando síntesis por lotes...\n")
    
        if opcion_exp == '1':
            audio_maestro = AudioSegment.empty()
            for i, p in enumerate(parrafos):
                print(f"[{i+1}/{len(parrafos)}] Procesando párrafo: {p[:50]}...")
                temp_file = f"temp_chunk_{i}.wav"
                
                if speaker_id:
                    tts.tts_to_file(
                        text=p, language="es", speaker=speaker_id, file_path=temp_file, 
                        split_sentences=False, speed=CONFIG_VELOCIDAD, temperature=CONFIG_TEMPERATURA, repetition_penalty=CONFIG_PENALIZACION
                    )
                else:
                    tts.tts_to_file(
                        text=p, language="es", speaker_wav=speaker_wav_path, file_path=temp_file, 
                        split_sentences=False, speed=CONFIG_VELOCIDAD, temperature=CONFIG_TEMPERATURA, repetition_penalty=CONFIG_PENALIZACION
                    )
                
                chunk_audio = AudioSegment.from_wav(temp_file)
                silencio = AudioSegment.silent(duration=500) # Pausa natural de respiración
                audio_maestro += chunk_audio + silencio
                os.remove(temp_file)
                
            audio_maestro.export(archivo_final, format="wav")
            print(f"\n🎉 ¡Proceso finalizado! Tu pódcast maestro está guardado como: '{archivo_final}'")
            
        elif opcion_exp == '2':
            carpeta_salida = os.path.splitext(archivo_final)[0] + "_fragmentos"
            os.makedirs(carpeta_salida, exist_ok=True)
            
            for i, p in enumerate(parrafos):
                print(f"[{i+1}/{len(parrafos)}] Procesando párrafo: {p[:50]}...")
                out_file = f"{carpeta_salida}/bloque_{i+1:03d}.wav"
                
                if speaker_id:
                    tts.tts_to_file(
                        text=p, language="es", speaker=speaker_id, file_path=out_file, 
                        split_sentences=False, speed=CONFIG_VELOCIDAD, temperature=CONFIG_TEMPERATURA, repetition_penalty=CONFIG_PENALIZACION
                    )
                else:
                    tts.tts_to_file(
                        text=p, language="es", speaker_wav=speaker_wav_path, file_path=out_file, 
                        split_sentences=False, speed=CONFIG_VELOCIDAD, temperature=CONFIG_TEMPERATURA, repetition_penalty=CONFIG_PENALIZACION
                    )
                
            print(f"\n🎉 ¡Proceso finalizado! Fragmentos independientes guardados en: '{carpeta_salida}/'")
    
    if __name__ == "__main__":
        main()

    Conclusión

    Con esta infraestructura montada en tu equipo, cuentas con un estudio de locución automatizado de nivel profesional. Al correr localmente sobre tu propia GPU, garantizas la privacidad absoluta de tus contenidos, controlas los tiempos de renderizado y eliminas por completo los costos recurrentes de servicios en la nube.

    Aquí demostramos que, combinando código abierto, herramientas de Linux en Windows (me habría gustado haber trabajado nativamente sobre GNU/Linux pero, no podía, ya que hice esto en una laptop prestada) y un procesamiento inteligente de textos, podemos crear soluciones potentes y elegantes. ¡Es momento de exportar tus guiones y comenzar a producir tus propios podcasts!

  • Cómo instalar TextSnatcher en Debian 13/MX Linux con soporte en Español (¡Parcheando el código fuente!)

    Si alguna vez has utilizado Windows, probablemente conozcas la comodidad de usar herramientas OCR (Reconocimiento Óptico de Caracteres) integradas como la de PowerToys (Text Extractor) para copiar texto directamente de imágenes, videos o PDFs bloqueados. En el ecosistema Linux, TextSnatcher es la alternativa perfecta que cumple esta misma función con tan solo arrastrar el ratón.

    Sin embargo, si eres usuario de MX Linux o Debian 13, es muy probable que te hayas topado con un muro frustrante al instalarlo desde Flathub: el idioma español no aparece por ningún lado y la aplicación no reconoce correctamente nuestras palabras, obligándonos a corregir el texto manualmente.

    Hoy te voy a explicar por qué sucede esto y, aplicando la regla de Simplicity First, cómo resolverlo de raíz compilando la aplicación de forma nativa y agregando nosotros mismos la opción faltante en su código fuente.

    El problema: Flatpak y un “olvido” en el código

    El principal inconveniente de instalar TextSnatcher mediante Flatpak es su naturaleza de sandbox. Al estar aislado, el paquete empaquetado a menudo no incluye diccionarios adicionales para ahorrar espacio y, peor aún, no tiene permisos para leer los paquetes de idioma (tessdata) que ya tienes instalados en tu sistema operativo.

    La solución lógica es purgar esa versión e instalar la herramienta de forma nativa desde su código fuente. Sin embargo, al hacerlo, descubrimos algo insólito: el motor OCR (Tesseract) sí detectaba el español, pero el menú desplegable de la aplicación no lo mostraba.

    Analizando el código fuente escrito en Vala, nos dimos cuenta de que el desarrollador sí había programado la lógica del botón para el español, pero olvidó agregarlo al contenedor visual de la interfaz.

    ¡Manos a la obra para solucionarlo!

    Paso 1: Purgar Flathub y limpiar residuos

    Primero, debemos eliminar la versión aislada y cualquier dato residual que pueda causar conflictos o quedarse en la memoria caché de nuestro menú de aplicaciones. Abre tu terminal y ejecuta:

    Bash

    flatpak uninstall --delete-data com.github.rajsolai.textsnatcher
    flatpak uninstall --unused
    

    Paso 2: Instalar dependencias base en Debian 13

    Ahora, dotaremos a nuestro sistema de las herramientas necesarias para compilar el programa y del motor OCR con su respectivo diccionario en español.

    Bash

    sudo apt update
    sudo apt install tesseract-ocr tesseract-ocr-spa scrot git meson valac libgranite-dev libgtk-3-dev libhandy-1-dev libportal-dev ninja-build
    

    Paso 3: Clonar el código fuente

    Descargamos el repositorio oficial de TextSnatcher en nuestro equipo:

    Bash

    git clone https://github.com/RajSolai/TextSnatcher.git
    cd TextSnatcher
    

    Paso 4: El Parche (Agregando el español a la interfaz)

    Aquí viene la magia. Vamos a corregir el pequeño olvido del desarrollador. Utiliza tu editor de texto favorito (por ejemplo, nano o Gedit) para abrir el archivo donde se gestionan los botones de los idiomas.

    Dependiendo de la versión del repositorio, suele estar en src/components/LanguageButton.vala o directamente en src/MainWindow.vala.

    Bash

    nano src/components/LanguageButton.vala
    

    Busca el bloque de código cerca del final del archivo donde verás una lista que dice menu_list.add (eng) ;, seguida de otros idiomas como chi_sim, jpn, fra, etc.

    El botón spa ya está declarado más arriba en el código, así que solo necesitas insertar esta línea dentro de ese bloque para que se muestre en pantalla:

    Vala

            menu_list.add (spa) ;
    

    Guarda los cambios (en nano: Ctrl+O, Enter, y Ctrl+X).

    Paso 5: Compilar e Instalar

    Con el código modificado, procedemos a construir la aplicación.

    Bash

    meson setup build --prefix=/usr
    cd build
    meson compile
    sudo ninja install
    

    Nota para solucionar errores de permisos: Si en algún momento ejecutaste comandos previos como administrador y ahora meson compile te arroja un error de escritura (ej. unable to open [...] for writing), simplemente devuélvele la propiedad de la carpeta a tu usuario con sudo chown -R $USER:$USER /ruta/a/tu/TextSnatcher y vuelve a intentarlo, o compila con sudo meson compile.

    ¡Resultado óptimo!

    ¡Y eso es todo! Ahora puedes buscar TextSnatcher en tu lanzador de aplicaciones de MX Linux. Al abrirlo y hacer clic en el ícono de traductor, verás con orgullo que el idioma Spanish ya está disponible en la lista.

    Al ser una instalación nativa, la herramienta se comunicará perfectamente con Tesseract OCR y tus capturas de texto en español serán rápidas, precisas y sin necesidad de correcciones manuales.

    ¡Espero que este tutorial te haya ahorrado un buen dolor de cabeza! Déjame tus comentarios si te funcionó o si tienes alguna duda con el proceso de compilación.

  • Habitar lo digital: entre algoritmos, materia e inteligencia artificial

    En marzo – abril de 2026, recibí la invitación del Mtro. Yoan Beltrán para participar en un panel con estudiantes de Arquitectura en el Instituto de Ciencias Básicas e Ingeniería en donde reflexionamos sobre “Habitar lo digital: entre algoritmos, materia e inteligencia artificial”.

    Comparto aquí mis notas.

    La Materia de lo Digital y la Evolución Industrial

    Para entender dónde estamos, tenemos que mirar cómo hemos transformado nuestro entorno. Pensemos en las revoluciones industriales como grandes reconfiguraciones de nuestro hábitat:

    • La Primera (1760-1840), con la máquina de vapor, nos trajo las fábricas y comenzó a concentrar a la población, alterando el paisaje natural.
    • La Segunda (1870-1914), impulsada por la electricidad y la producción en masa, iluminó la noche y nos dio los rascacielos y las líneas de ensamblaje.
    • La Tercera (1969-2000) introdujo la computación y las primeras redes, iniciando la digitalización de la información.
    • Y hoy, estamos inmersos en la Cuarta Revolución Industrial (2000 a la fecha): la era de los sistemas ciberfísicos. Aquí, la frontera entre nuestra realidad biológica, el mundo físico y las infraestructuras digitales se ha borrado por completo.

    A menudo hablamos de ‘la nube’ como si fuera algo etéreo, algo mágico que flota sobre nosotros. Pero quiero lanzarles una pregunta:

    ¿Alguna vez se han puesto a pensar cuánto espacio territorial físico, cuánta agua para enfriamiento y cuánta energía eléctrica exige esa nube?

    Lo digital tiene una huella material colosal. Los inmensos centros de datos, los kilómetros de cableado submarino y las torres de telecomunicaciones son las verdaderas catedrales y acueductos del siglo XXI.

    Urbanismo Algorítmico

    Y si esa es la infraestructura física, ¿quién diseña las ‘calles’ del ciberespacio? Los algoritmos son los arquitectos invisibles de nuestra movilidad digital.

    Pensemos en la plaza pública, el ágora tradicional donde se construía la democracia. Mark Zuckerberg intentó literalmente recrear y privatizar este espacio con el concepto del Metaverso. Sin embargo, al diseñar estas plazas virtuales nos enfrentamos a un dilema social profundo: algo que Umberto Eco advirtió crudamente (poco antes de morir) cuando habló de cómo estas redes le dieron el derecho de hablar a ‘legiones de idiotas’. Sin la curaduría social del espacio físico, la plaza pública virtual puede convertirse en un campo de batalla polarizado. He aquí otra gran pregunta:

    ¿Cómo diseñamos espacios digitales que fomenten la cohesión social en lugar de encerrarnos en barrios ideológicos y batallas interminables sin sentido?

    Para entender cómo nos movemos en estas ciudades virtuales, los ingenieros aquí presentes pensarán de inmediato en la Teoría de Grafos. Durante años, servicios como Google la han implementado a través de nodos y aristas para calcular la distancia más corta entre un punto A y un punto B. Pero esto ha volucionado. Con la Inteligencia Artificial, ya no solo calculan la ruta estática más corta, sino que es posible predecir atascos, analizar flujos de comportamiento en tiempo real y reconfigur la ruta dinámicamente. La IA ha convertido la rigidez de los grafos tradicionales en un tejido vivo y palpitante.

    La IA como Co-habitante

    Y esto nos lleva al punto final de nuestra reflexión. La Inteligencia Artificial ya no es solo una herramienta con la que dibujamos planos o calculamos estructuras; se ha convertido en un co-habitante de nuestros espacios.

    Hablamos de domótica, de sensores y de casas inteligentes. Pero estamos presenciando un salto evolutivo: la transición del ‘asistente’ al ‘agente’ autónomo.

    Un asistente virtual requiere de una instrucción: ‘Apaga la luz’. Un agente de inteligencia artificial no espera instrucciones. Un agente conoce tus patrones, percibe la temperatura ambiente, anticipa tu llegada, negocia tarifas de electricidad en tiempo real con la red pública y adapta el entorno para ti antes de que cruces la puerta.

    La ciudad y la casa están dejando de ser refugios estáticos para convertirse en organismos responsivos que nos observan, nos aprenden y toman decisiones de forma autónoma.

    Cierre

    Por lo tanto, el reto para quienes diseñan, construyen y piensan los espacios en el siglo XXI ya no es solo alojar cuerpos humanos. Es innegable que el reto inicial planteado en este ejercicio es el de diseñar para la convivencia simbiótica entre la materia física, el flujo incesante de datos y los agentes de inteligencia artificial que ahora cohabitan con nosotros.

    Sin embargo, quiero dejarles con una última reflexión. Y es que en estos minutos no he hablado de ecología, de sostenibilidad ni de sustentabilidad. Y omitirlo tiene una razón fundamental: esta nueva materia digital, por deslumbrante y avanzada que parezca, por sí sola no dignificará la posición del ser humano en el universo.

    Si la tecnología nos da la infraestructura y los algoritmos ya gestionan nuestros espacios…

    ¿Cuál es entonces realmente nuestro reto?

    Otras reflexiones

    1. La sed material de la IA (Para conectar con la ecología)

    • El argumento: La IA no vive en la nube, vive en servidores que se calientan.
    • El dato impactante: Estudios recientes de investigadores de la Universidad de California estiman que entrenar modelos como GPT-3 consumió alrededor de 700,000 litros de agua dulce solo para enfriar los servidores. Además, una consulta promedio a una IA generativa consume casi 10 veces más energía que una búsqueda tradicional en Google.
    • Momento de uso: Cuando hablen de “ciudades inteligentes”, mencionar que “el urbanismo del futuro debe contemplar la zonificación de los centros de datos como si fueran industria pesada, porque su demanda de recursos hídricos y energéticos es brutal.”

    2. El ‘Redlining’ Algorítmico (Los muros urbanos invisibles)

    • El argumento: Los algoritmos aprenden del pasado para predecir el futuro. Si los datos históricos de una ciudad tienen sesgos de desigualdad, la IA los va a replicar y automatizar.
    • El concepto: Así como antes se construían puentes bajos o vías de tren para segregar barrios, hoy los algoritmos de plataformas de entrega, transporte o vigilancia policial pueden crear “zonas muertas” o sobre-vigiladas en la ciudad.
    • Momento de uso: Preguntar a los arquitectos e ingenieros de la mesa: “¿Cómo garantizamos que el código que gestiona la ciudad no se convierta en el nuevo muro de segregación social?”

    3. El Panóptico de la ‘Smart City’ (La IA como co-habitante vigilante)

    • El argumento: La eficiencia extrema tiene un costo: la privacidad en el espacio público.
    • El concepto: Para que una ciudad sea verdaderamente responsiva (que los semáforos cambien mágicamente, que la iluminación te siga al caminar), necesita saber exactamente dónde estás, a qué velocidad te mueves y con quién vas. El espacio público deja de ser anónimo.
    • Momento de uso: ¿Cuál es la línea entre una “ciudad inteligente” y una “ciudad de vigilancia”? El diseño arquitectónico debe ahora incluir el diseño de la privacidad de los datos de quienes transitan esos espacios.

    4. La des-sincronización de la ciudad

    • El argumento: Lo digital rompió los horarios de la ciudad física.
    • El concepto: La primera y segunda revolución industrial sincronizaron a la humanidad: todos entraban a la fábrica a las 8 am y salían a las 5 pm, creando la “hora pico” y dictando el ancho de las avenidas. El trabajo remoto, la IA y los servicios bajo demanda han asincronizado nuestras vidas. El flujo urbano ya no es un pico predecible, es un ruido blanco constante.
    • Cómo usarlo en el panel: Úsalo para replantear la teoría de grafos. Menciona que la IA no solo debe encontrar la ruta más corta, sino gestionar una ciudad que ahora está viva, pulsando y demandando energía las 24 horas del día de formas impredecibles.

    La Tétrada de “Habitar lo digital”

    Las Leyes de los medios, formuladas por Marshall McLuhan en La Aldea Global, son un modelo teórico (tétrada) para analizar el impacto social de cualquier tecnología o medio de comunicación. Estas leyes —potenciación, obsolescencia, recuperación y reversión— explican cómo los medios extienden facultades humanas, desplazan experiencias previas, recuperan elementos obsoletos y se invierten al llevarse al extremo.

    Si pasamos nuestra realidad urbana y digital por el filtro de las Leyes de los Medios de McLuhan, el espacio que habitamos hoy nos revela cuatro verdades:

    • 1. ¿Qué amplifica, acrecienta o intensifica? (Enhancement) La IA y los algoritmos amplifican nuestra capacidad de gestión, eficiencia y omnipresencia. Amplifican la “memoria” del espacio: la ciudad inteligente y el hogar digital ahora recuerdan nuestros hábitos, rutas y preferencias de temperatura, extendiendo nuestro sistema nervioso por toda la infraestructura urbana.
    • 2. ¿Qué se vuelve obsoleto? (Obsolescence) Vuelve obsoleta la noción tradicional de “refugio anónimo” y las barreras geográficas. El hogar como un ente estático, mudo y desconectado, al igual que la ciudad “ciega” que no recopila datos, quedan en el pasado. Se extingue la soledad arquitectónica absoluta.
    • 3. ¿Qué recupera que antes había perdido? (Retrieval) De manera fascinante, la tecnología digital recupera el animismo antiguo. En el pasado distante, las culturas creían que el bosque, el río o la cabaña tenían “espíritus” que escuchaban y reaccionaban a la presencia humana. Al dotar a nuestros espacios físicos de sensores y agentes de IA con los que podemos hablar en voz alta (asistentes, domótica), hemos recuperado la sensación de que nuestro entorno está “vivo”, nos observa y nos responde.
    • 4. ¿En qué se revierte cuando se lleva al extremo? (Reversal) Llevado a su límite, este control algorítmico sobre el espacio se revierte en pérdida de agencia e hipervigilancia. El espacio diseñado para darnos libertad y comodidad (la Smart City o la Smart Home) se transforma en un panóptico1, una jaula de cristal donde la privacidad desaparece. La plaza pública digital, creada para conectarnos masivamente, se revierte en aislamiento extremo a través de burbujas de filtro.

    Mensaje Final derivado de este ejercicio:

    El diseño consciente —ya sea arquitectónico, urbano o de código— es el único freno que tenemos para evitar que la tecnología llegue a su fase de “reversión”. Nuestro papel, y el de los arquitectos e ingenieros en ese auditorio, muy probablemente es el de diseñar entornos que aprovechen la amplificación de lo digital, sin sacrificar la privacidad y la dignidad humana en el proceso.

    1 Un diseño arquitectónico carcelario ideado por el filósofo Jeremy Bentham a finales del siglo XVIII, concebido para vigilar a todos los reclusos desde un solo punto central sin que estos sepan si están siendo observados.