# Informe técnico: IBMTTS SuperAddon 26.7.19 para NVDA

Fecha de la auditoría: 19 de julio de 2026.

## Entregables

- Código fuente renovado: `adjuntos/IBMTTS-SuperAddon`.
- Complemento instalable: `adjuntos/IBMTTS-SuperAddon/IBMTTS-26.7.19.nvda-addon`.
- Fuente reproducible del puente de 32 bits: `adjuntos/IBMTTS-SuperAddon/bridgeSource`.
- Binario compilado del puente: `addon/synthDrivers/ibmtts_host32.dll`.
- Pruebas de regresión: `adjuntos/IBMTTS-SuperAddon/tests`.
- Este informe: `adjuntos/INFORME_IBMTTS_SUPERADDON.md`.

El complemento original `adjuntos/NVDA-IBMTTS-Driver` no se ha modificado.

## Material analizado

- Las 117 incidencias exportadas: 27 abiertas y 90 cerradas, incluyendo cuerpos y comentarios.
- La referencia completa `apiReference/tts.txt` y el PDF de IBM TTS.
- Todo el código Python del complemento, su sistema de compilación, traducciones y documentación.
- El código de NVDA incluido en los adjuntos, revisión `ded90f972` (serie NVDA 2026.3 en desarrollo).
- El complemento base, revisión `8f65f9c`.
- El puente oficial `ibmtts-host32-bridge`, revisión base `f85ea7d`, necesario para NVDA de 64 bits. Su fuente no estaba inicialmente en los adjuntos y se incorporó al nuevo árbol para que la compilación sea reproducible.

## Cambios principales

### Asociación explícita del catálogo del complemento 26.9.5

- Se verificó el perfil real: la versión 26.9.4 estaba instalada, su `locale/es/LC_MESSAGES/nvda.mo` contenía la traducción correcta y NVDA se había reiniciado después de instalarla. Por tanto, no era un PO incompleto ni un catálogo antiguo en memoria.
- En el mismo perfil está instalado `IBMTTS64`, que publica módulos con los mismos nombres de extensión `globalPlugins.ibmtts` y `synthDrivers._ibmeci`. Esta colisión puede volver ambigua la asociación automática realizada por `addonHandler.initTranslation()`.
- Los módulos de interfaz de este SuperAddon cargan ahora directamente el dominio `nvda` desde su propio directorio `locale`, utilizando el idioma efectivo devuelto por `languageHandler.getLanguage()` y normalizando guiones. Ya no dependen de resolver el complemento a partir del nombre compartido del módulo.
- El código fuente conserva todas las cadenas originales en inglés. El texto español permanece exclusivamente en `addon/locale/es/LC_MESSAGES/nvda.po` y su catálogo compilado.
- Una prueba impide que los módulos del panel, herramientas o exportador dejen de usar esta asociación explícita. La suite suma 43 pruebas.

### Corrección del instalador 26.7.19

- El registro de instalación mostró `ImportError: attempted relative import with no known parent package` dentro de `IBMTTS.pendingInstall`.
- `installTasks.py` añade temporalmente `globalPlugins` a `sys.path` e importa `_ibmttsUtils` como módulo superior. En ese contexto no existe un paquete padre y la importación relativa de `_translation` no puede funcionar.
- `_ibmttsUtils` admite ahora ambos contextos: importación relativa durante el funcionamiento normal e importación superior durante la instalación. La traducción continúa cargándose desde `locale` en ambos casos.
- Se añadió una comprobación de regresión específica y la suite suma 44 pruebas.

### Catálogo español completo regenerado desde el código 26.9.4

- Se regeneró `IBMTTS.pot` desde todo el código fuente actual, en vez de medir la traducción contra el POT histórico.
- El POT actual contiene 232 cadenas. Las 232 tienen traducción española: no queda ningún `msgstr` vacío ni ninguna entrada marcada como difusa.
- La auditoría detectó 30 errores del motor, los diccionarios y el renderizador aislado escritos directamente en inglés y, por tanto, invisibles para gettext. Se hicieron traducibles y se incorporaron al POT y al PO español.
- Se recompiló `addon/locale/es/LC_MESSAGES/nvda.mo`. Las pruebas abren el catálogo binario real y verifican tanto cadenas de interfaz como errores del motor.
- Una prueba adicional compara automáticamente el POT completo con el PO español y falla si falta una cadena, está vacía o queda difusa. La suite suma 42 pruebas.

### Traducción completa al español de las herramientas nuevas 26.9.3

- «IBMTTS WAV recorder» se presenta ahora como «Grabador WAV de IBMTTS» tanto en el título del diálogo como en el botón de Herramientas y diagnóstico.
- Se tradujeron todos sus campos, filtros de archivo, configuraciones de voz, botones, progreso, estados de reproducción y cancelación, validaciones y mensajes de error; no solo el título.
- También se tradujo íntegramente la prueba aislada de voces sin guardar.
- El catálogo español compilado contiene 205 de 205 mensajes traducidos, un 100 % según Babel. Tres pruebas abren el `.mo` que utilizará NVDA y verifican el título, el botón del grabador y la prueba de voz sin guardar; la suite suma 40 pruebas.

### Compatibilidad con la distribución congelada de NVDA 26.9.2

- NVDA 2026.1.1 no distribuye el módulo estándar de Python `secrets`. Su importación en el puente aislado impedía importar `_proxyEci`, por lo que fallaban tanto el sintetizador IBMTTS como el complemento global y NVDA recurría a OneCore.
- Se eliminó esa dependencia. Los identificadores privados de las instancias auxiliares se generan ahora directamente con `os.urandom`, disponible en el núcleo congelado de NVDA, se limitan al intervalo positivo de 31 bits y nunca reutilizan el PID de NVDA.
- La corrección ocurre en tiempo de importación: IBMTTS y su panel vuelven a cargar aunque el usuario no utilice todavía la prueba de voces ni el grabador WAV.
- Una prueba fuerza primero el identificador cero y después el PID reservado para verificar la regeneración. La suite suma 37 pruebas.

### Registro normal silencioso para documentos grandes 26.9.1

- El registro aportado confirma que una locución de 124.955 caracteres se encoló, procesó y terminó su despacho correctamente, sin excepción ni cambio incorrecto de voz.
- Las trazas de encolado, comienzo y final de documentos grandes eran instrumentación de diagnóstico, no avisos de error. Se movieron de nivel INFO a DEBUG.
- En el nivel de registro normal de NVDA ya no aparecerán esas tres entradas por cada archivo grande. Permanecen disponibles únicamente cuando se activa deliberadamente el registro de depuración.

### Prueba aislada de voces, grabador WAV y recuperación de ráfagas 26.9.0

- La pestaña «Voces personalizadas» incorpora «Probar voz sin guardar». Toma directamente los controles visibles, por lo que permite escuchar una modificación antes de almacenarla o pulsar Aceptar.
- La prueba no cambia temporalmente la voz activa. Arranca un proceso host de 32 bits con identificador propio, una tubería privada, memoria compartida privada y un manejador ECI independiente. El host se termina al acabar o ante cualquier error de inicialización.
- La pestaña «Herramientas y diagnóstico» incorpora «Grabador WAV de IBMTTS». Acepta texto escrito o archivos TXT, Markdown, JSON y LOG; permite utilizar la configuración activa, cualquiera de las ocho voces base o una voz personalizada; selecciona el destino WAV y ofrece prueba previa.
- La exportación se ejecuta en un trabajador y presenta estado, porcentaje por bloques y cancelación. Genera PCM mono de 16 bits a 8.000, 11.025 o 22.050 Hz según la frecuencia activa. El texto pasa por las mismas protecciones de caracteres, idioma y palabras problemáticas que la voz de NVDA.
- La prueba reproduce un WAV temporal con la misma ruta aislada. La voz normal de NVDA continúa disponible durante la preparación y reproducción.
- Tras el atasco de Notepad++ observado en el registro, NVDA podía entregar una acumulación de anuncios idénticos. Se añadió una compactación deliberadamente conservadora: solo entra en funcionamiento con ocho trabajos ya pendientes y cuando existen dos copias idénticas en cola. La navegación ordinaria y las primeras dos repeticiones se conservan.
- Se añadieron pruebas para demostrar que los nombres de tubería y memoria del motor auxiliar no coinciden con los de NVDA y para fijar los umbrales de la compactación. La suite suma 36 pruebas.

### Índices entregados al encolar el audio 26.8.9

- El dato “el texto aparece en el historial de voz pero IBM no lo pronuncia” permitió aislar la diferencia crítica. NVDA inserta `IndexCommand` en sus secuencias y no envía o retira determinadas continuaciones hasta recibir esos índices del sintetizador.
- El SuperAddon había cambiado `playStream`: el audio se enviaba a `WavePlayer.feed(..., onDone=...)` y los índices se notificaban solo desde `onDone`, cuando el bloque terminaba físicamente de reproducirse. Si un cambio rápido de foco cancelaba el bloque, `onDone` podía no ejecutarse; el índice desaparecía y el gestor de voz de NVDA quedaba detenido aunque Speech Viewer ya hubiese mostrado la información.
- Se restauró la semántica original: primero se encola el audio y, acto seguido, se encolan sus índices y el final ECI en el hilo de callbacks. Ya no dependen de que el audio sobreviva hasta su callback de reproducción.
- Se creó un despachador puro usado por producción y tres pruebas de integración: orden audio→índices→final, finalización sin audio y bloque no final sin notificación de término. La suite suma 32 pruebas.

### Núcleo de callbacks original restaurado 26.8.8

- Se verificó directamente que el equipo tenía instalada 26.8.7 y que el silencio afectaba tanto al Explorador como al foco de la pestaña “General” del panel IBMTTS.
- La ruta corta de `speak` ya era equivalente a la original en 26.8.7; por tanto se retiraron por completo los cambios restantes del núcleo de finalización introducidos durante el diagnóstico: índices finales únicos, asociación por generación y filtrado de callbacks tardíos.
- Vuelven el marcador ECI fijo `65535`, el contador y `endStringEvent` con la misma semántica del complemento original. Los índices de NVDA vuelven a admitir todo el intervalo histórico inferior a `65535`.
- La protección de documentos grandes permanece antes de este núcleo y no modifica cómo terminan ni avanzan las frases breves de interfaz.
- Al retirar código experimental, la suite queda en 29 pruebas activas, todas superadas.

### Ruta original para voz de interfaz 26.8.7

- Las pruebas repetidas demostraron que el problema no estaba limitado al Explorador: también se perdía la pestaña enfocada al tabular en Preferencias → IBMTTS. Esto identifica como regresión el cambio general de temporización aplicado a todas las frases en 26.8.1.
- Toda locución de hasta 3.200 caracteres vuelve a utilizar la ruta del controlador original: `setParam(eciDictionary)` síncrono como barrera, procesamiento inmediato del texto, etiquetas embebidas `` `ts1``/`` `ts0`` para modo carácter y envío de volumen/velocidad activado de forma predeterminada.
- La barrera no es solo un ajuste de diccionario: garantiza que un `WM_SILENCE` anterior haya terminado antes de encolar el anuncio del nuevo foco. El procesamiento diferido había eliminado esa garantía y permitía que cancelaciones de eventos anteriores alcanzaran etiquetas, pestañas y selecciones posteriores.
- Solo las locuciones que superan 3.200 caracteres usan el trabajador, el troceado y la cancelación por generación. Por tanto el JSON de 20.001 caracteres conserva la protección contra procesamiento pesado sin alterar la voz cotidiana de NVDA.
- Dos pruebas fijan la frontera: la voz normal de interfaz usa la ruta original y el documento de 20.001 caracteres usa el trabajador. La suite suma 34 pruebas.

### Marcadores finales ECI únicos 26.8.6

- La asociación por generación de 26.8.4 todavía reutilizaba el índice ECI `65535` y asignaba generaciones por orden. Un callback tardío del título cancelado podía extraer de la cola la generación del anuncio nuevo; cuando llegaba el final real de la unidad seleccionada ya no encontraba identidad, no se emitía `synthDoneSpeaking` y NVDA quedaba mudo.
- Cada locución recibe ahora un índice final único dentro del intervalo interno `61440–65535`. El callback devuelve ese mismo índice, que localiza directamente su generación sin depender del orden de llegada.
- Los índices cancelados se eliminan sin afectar a índices nuevos. Un callback tardío cuyo identificador ya fue cancelado se reconoce como interno y se descarta; nunca se interpreta como un índice de voz de NVDA.
- Se añadieron pruebas de unicidad, aislamiento después de cancelar y reserva del intervalo. La suite suma 32 pruebas.

### Entrega de documentos largos y diagnóstico 26.8.5

- Se midió el archivo real `D:\\Descargas\\json_20000_caracteres.json`: 20.001 caracteres, dividido en 13 bloques de hasta 1.600 caracteres en 0,047 ms. Por tanto, el troceado del controlador no explica una congelación del hilo principal de 15,87 segundos.
- El registro aportado no contiene una excepción IBMTTS; sí contiene un fallo COM en `EditableText.script_caret_newLine` sobre un objeto IA2 del editor. Esto demuestra que al menos una parte del atasco ocurre antes o fuera del hilo ECI.
- Se corrigió, no obstante, otra ruta del controlador: si NVDA cancelaba una transición de foco después de encolar el documento pero antes de que el trabajador lo recogiera, el texto largo se descartaba entero. Ahora solo se interrumpe por generación cuando ya había empezado a procesarse.
- Las locuciones de más de 3.200 caracteres conservan en nivel DEBUG cuatro datos no sensibles para diagnóstico: caracteres y generación al encolarse, comienzo en el trabajador, final de despacho y si fueron abortadas. No aparecen en el registro normal y nunca se registra el contenido hablado.
- Se añadió una prueba que conserva documentos pendientes y se mantuvo la que cancela un documento ya activo. La suite suma 29 pruebas.

### Finalización de voz aislada por generación 26.8.4

- La nueva reproducción aportó el dato decisivo: IBMTTS cargaba, pronunciaba el título “Este equipo - Explorador de archivos” y después dejaba de completar la cadena del objeto enfocado.
- La causa estaba en el callback asíncrono del marcador final. Tras una cancelación se ponía `endMarkersCount` a cero, pero un callback de audio antiguo podía llegar más tarde y reducirlo a `-1`. El siguiente marcador lo elevaba solo a cero y su callback volvía a `-1`; por tanto nunca se ejecutaba la condición de finalización ni se notificaba `synthDoneSpeaking` a NVDA.
- Cada marcador final conserva ahora la generación de la locución que lo creó. El callback transporta esa generación hasta que termina realmente el audio y se acepta solo si todavía pertenece a la voz vigente.
- La cancelación limpia marcadores ECI todavía pendientes, índices y estado de fin, mientras que los callbacks de audio que ya estaban en vuelo quedan etiquetados y no pueden consumir el contador de una locución nueva.
- El contador queda protegido frente a valores negativos. Se añadieron dos pruebas para garantizar que una finalización cancelada se ignora y una vigente se entrega. La suite suma 28 pruebas.

### Migración de modos antiguos 26.8.3

- El registro de NVDA 2026.1.1 mostró `ValueError: invalid literal for int() with base 10: 'False'` al cargar `numberMode`. El perfil real conservaba `numberMode = False` de una versión anterior, mientras que el nuevo selector ECI usa los valores `0` y `1`.
- Los valores booleanos antiguos (`True`/`False`, también sus equivalentes habituales) se convierten ahora a `1`/`0`. Los valores numéricos válidos se conservan y cualquier texto corrupto o número fuera de rango vuelve al valor predeterminado seguro.
- La misma validación defensiva se aplica a frecuencia de muestreo, modo de pausas, números de cuatro cifras y deletreo, evitando que otro valor histórico impida arrancar el sintetizador.
- Se añadieron cinco pruebas con el valor exacto del registro, booleanos positivos, valores numéricos, texto corrupto y números fuera de rango. La suite suma 26 pruebas.

### Restauración de anuncios del Explorador 26.8.2

- La prueba de uso de 26.8.1 reveló una regresión: la generación de cancelación descartaba por igual documentos grandes y anuncios breves que NVDA ya había encolado como parte de una transición rápida de foco. Por eso podían oírse “Vista Elementos, lista” y el título, pero perderse “Dispositivos y unidades” y la unidad seleccionada.
- Los anuncios cortos de interfaz conservan ahora el orden histórico de la cola y llegan hasta `WM_SILENCE`, como en el controlador anterior. Solo las locuciones de más de 3.200 caracteres se marcan como interrumpibles y pueden descartarse inmediatamente al quedar obsoletas.
- Se mantiene el procesamiento fuera del hilo principal y el troceado de 1.600 caracteres, por lo que el JSON grande continúa sin bloquear la interfaz.
- Se añadieron dos pruebas específicas: una impide volver a descartar anuncios breves obsoletos y otra garantiza que un documento largo obsoleto sí se cancela.

### Corrección de bloqueos y cierre 26.8.1

- Se analizó el registro real de NVDA 2026.1.1 al abrir un JSON de 20.000 caracteres. El controlador ejecutaba `setParam` de forma síncrona para cada petición de voz: esa espera ocurría en el hilo principal de NVDA y explica las recuperaciones del watchdog y los anuncios tardíos.
- El modo de diccionario se encola ahora junto con la propia locución. Las expresiones regulares, la codificación y el troceado ECI final también se ejecutan como trabajos acotados en el hilo del motor; abrir un documento grande ya no obliga al hilo de interfaz a procesarlo entero antes de responder.
- La cancelación usa generaciones thread-safe. Al cambiar foco o selección, todos los trabajos anteriores quedan invalidados inmediatamente y el motor comprueba la generación entre bloques de hasta 1.600 caracteres. Así, una lectura grande ya no mantiene detrás los anuncios recientes del Explorador.
- La unión de cadenas adyacentes dejó de usar concatenación repetida potencialmente cuadrática y ahora acumula partes y realiza una única unión lineal.
- Se corrigió el falso `RuntimeError: The IBMTTS engine stopped while trying to stop`: después de `WM_KILL`, que el hilo esté muerto es el resultado correcto, no un fallo.
- Se añadieron cinco pruebas de regresión para invalidación, concurrencia, cierre correcto, detección de muerte inesperada y timeout.
- Estos cambios corrigen las rutas atribuibles a IBMTTS del registro. El mismo registro contiene errores independientes de TeleNVDA, TranslateAdvanced y TiendaNVDA; no se han modificado porque quedan fuera del complemento IBMTTS.

### Ampliación SuperAddon 26.8.0

- La categoría IBMTTS de las opciones de NVDA se reorganizó en cinco pestañas accesibles: General, Biblioteca, Diccionarios, Voces personalizadas y Herramientas y diagnóstico.
- Se añadió negociación de capacidades entre Python y el host Rust. Las funciones ECI nuevas son opcionales: una DLL antigua que no las exporte puede seguir cargando y la interfaz informa de la función no disponible.
- El gestor de diccionarios permite paginar, buscar claves exactas, añadir, editar, borrar, importar y exportar los volúmenes principal, raíces y abreviaturas del idioma activo.
- Las ranuras ECI 9–16 se usan para voces personalizadas persistentes con nombre, voz base y los ocho parámetros de voz. Aparecen como variantes en las opciones de voz de NVDA y se reconstruyen después de cambiar de idioma.
- Las opciones de voz incluyen ahora el tratamiento de números de cuatro cifras y los cuatro modos nativos de deletreo de ECI.
- Se añadió compatibilidad segura con `PhonemeCommand`: el subconjunto IPA inglés reconocido se convierte a SPR de IBM; cualquier entrada no convertible usa el texto alternativo de NVDA o una lectura literal.
- Las herramientas nuevas ofrecen diagnóstico de versión, idiomas, estado, índices, nombres de voz y capacidades; prueba de voz por la ruta real de NVDA; y recuperación controlada del estado del motor.
- La cancelación limpia la entrada manual pendiente y restaura los parámetros de la voz activa, tal como exige la documentación de ECI después de `eciStop`.
- El puente incorpora como capacidades opcionales `eciSpeaking`, `eciGetIndex`, `eciReset`, nombres de voz, consulta y mantenimiento de diccionarios, guardado y frase de prueba.
- Se actualizaron la documentación inglesa y española y se tradujeron al español todas las cadenas nuevas de la interfaz.

### Corrección de compatibilidad 26.7.1

- Se analizó el registro de NVDA 2026.1.1 aportado después de la primera entrega. El fallo de importación procedía de la migración de `ratesByVoice`: el descriptor intentaba leer la opción nueva antes de registrarla en el perfil general de una instalación existente.
- Los valores ausentes se obtienen ahora de su valor predeterminado declarado en la especificación ConfigObj, se validan y después se registran. Esto permite actualizar desde configuraciones antiguas sin `KeyError` y evita que fallen tanto el controlador como el complemento global.
- Se añadió una prueba de regresión que parte expresamente de un perfil antiguo sin `ratesByVoice` ni `autoUpdate` y verifica la migración de los valores de cadena y booleanos.
- La revisión se elevó a 26.7.1 y se retiró el instalador 26.7.0 defectuoso para impedir su instalación accidental.
- Durante la inspección del ZIP se detectó y eliminó bytecode local de Python 3.13 que había sido incorporado por una comprobación de sintaxis. El paquete final se reconstruyó limpio y no contiene `__pycache__` ni archivos `.pyc`.

### Estabilidad y concurrencia

- Cada proceso de NVDA usa ahora nombres propios para la tubería, mutex, memoria compartida y eventos. Esto evita que dos usuarios de Windows o dos procesos de NVDA compartan el host equivocado.
- Las llamadas RPC están serializadas de principio a fin. Antes, dos hilos podían escribir peticiones y consumir respuestas cruzadas, una causa plausible de la corrupción con bytes nulos descrita en la incidencia 143.
- Se añadieron tipos Win32, comprobaciones de handles, longitudes exactas, límites de enteros, validación de respuestas y cierre determinista de recursos.
- Todas las esperas importantes tienen límite de tiempo. El arranque, los cambios de parámetros y la finalización ya no pueden bloquear NVDA indefinidamente.
- El cierre soporta que Windows haya terminado antes el host de 32 bits. Las operaciones de emergencia comprueban el estado del proceso y la finalización tiene rutas de recuperación acotadas.
- Se evita el borrado doble del mismo handle ECI y se cierran cliente, memoria, eventos, reproductor e hilos incluso si una fase falla.

### Texto largo y prevención de caídas

- El texto se divide antes de pasar por las expresiones regulares, limitando el coste por bloque.
- El resultado codificado se vuelve a dividir antes de `eciAddText`, respetando palabras y fronteras de caracteres multibyte (`gb18030`, `cp932`, `cp949` y `big5`).
- Los bloques se sintetizan de forma incremental usando el modo manual de ECI, tal como permite la documentación de IBM.
- Los índices se validan antes de entrar en el protocolo. El marcador interno queda reservado y no se aceptan valores fuera de rango.
- Los errores de `eciAddText`, `eciInsertIndex` y `eciSynthesize` ya no son silenciosos: se consultan `eciProgStatus` y `eciErrorMessage`, se limpian los bits de error y se genera un diagnóstico útil.
- Se corrigieron condiciones de idioma que comparaban identificadores numéricos con cadenas imposibles y que dejaban rutas españolas sin aplicar.

### Audio, pausa e índices

- Se añadió `eciPause` al puente y al controlador. Al pausar se detienen tanto el motor como el reproductor de NVDA; al reanudar se hace en orden seguro.
- Los índices de voz se notifican mediante `WavePlayer.feed(..., onDone=...)`, después de reproducirse el bloque WASAPI asociado. Esto evita ejecutar callbacks mientras todavía se está pronunciando el texto anterior.
- Se conserva el uso de `nvwave.WavePlayer` de NVDA y su dispositivo configurado. NVDA 2026 vuelve a cargar el sintetizador cuando cambia `audio.outputDevice`, por lo que no se usa la salida directa de IBM, que eludiría WASAPI y la configuración de NVDA.
- Se mantienen las frecuencias nativas de 8, 11,025 y 22,05 kHz cuando la biblioteca las ofrece.

### Voces y configuración

- Un identificador guardado inválido, incluido `4294967295`, se sustituye por la voz del idioma de NVDA o por la primera voz realmente disponible.
- Los archivos `.syn` desconocidos ya no producen `KeyError` al enumerar voces.
- La velocidad se recuerda por voz de idioma en JSON compacto.
- Al cambiar variante se guardan tono, inflexión, volumen, tamaño de cabeza, aspereza y respiración derivados de esa variante. Durante la carga normal de una configuración existente no se sobrescriben personalizaciones del usuario.
- “Enviar siempre los parámetros” queda desactivado de forma predeterminada para evitar pausas innecesarias; las etiquetas de voz con acento grave activan automáticamente el comportamiento que necesitan.
- La pausa real de ECI complementa los tres modos ya existentes para acortar pausas.

### Actualizaciones, instalación y seguridad

- Las comprobaciones automáticas anuncian una actualización con voz sin abrir un diálogo ni robar el foco. El diálogo de revisión aparece únicamente tras una comprobación manual, enfoca las notas y deja “No” como acción predeterminada.
- El estado de actualización dejó de usar `pickle`. Se guarda como JSON mediante sustitución atómica, eliminando el error de serialización de la incidencia 86 y el riesgo de ejecutar objetos serializados.
- Se eliminó `eval` del validador booleano de configuración.
- Tras cambiar o copiar bibliotecas se ofrece reiniciar NVDA. También se impiden copias recursivas cuando el destino está dentro del origen.
- El host se lanza sin `shell=True`, con ruta fija de Windows, y se valida su ciclo de vida.
- La inspección de DLL continúa sin cargarlas dentro del proceso de 64 bits.

### API de IBM incorporada o evaluada

Se añadieron al puente `eciPause`, `eciProgStatus`, `eciErrorMessage`, `eciClearErrors` y `eciClearInput`. Las cuatro primeras ya se usan en el flujo normal o de diagnóstico; `eciClearInput` queda disponible para recuperación futura.

Se mantienen `eciNewEx`, callbacks, buffer de salida, texto, índices, síntesis, parada, parámetros, parámetros de voz, variantes, idiomas y diccionarios. No se usan deliberadamente:

- `eciSpeakText`/`eciSpeakTextEx`, porque son bloqueantes y envían audio fuera de `nvwave`.
- `eciSetOutputDevice`, porque ignoraría el dispositivo WASAPI elegido en NVDA.
- Salida a archivo y síntesis de archivo, que no forman parte del contrato de un controlador de voz de NVDA.
- Fonemas y animación bucal, para los que NVDA no tiene consumidor en este controlador.
- Mutación interactiva de diccionarios y filtros propietarios. El complemento carga diccionarios externos por idioma sin inventar un editor de formatos binarios.

## Matriz de las 27 incidencias abiertas

| Incidencia | Resultado de la auditoría |
|---|---|
| #158 Calidad de voces | Se conservan todas las frecuencias nativas, incluida 22 kHz en IBM. No se inventa información mediante sobremuestreo posterior. |
| #157 Varias instancias de host32 | Solucionada: espacios de nombres por PID en Python y Rust. |
| #153 Envío de parámetros | Solucionada: predeterminado desactivado y activación implícita con etiquetas de voz. |
| #150 Sobremuestreo DSP | No integrado: es procesamiento global de audio, no una función ECI. La propuesta reconoce que no recupera frecuencias perdidas y pertenece a un complemento DSP separado. |
| #148 Cierre seguro del sistema | Endurecido con monitor de proceso, comprobaciones de vida, cierres idempotentes y tiempos límite. Requiere prueba final durante cierre de sesión con una biblioteca real. |
| #146 Texto largo sin voz | Solucionada mediante división previa a regex y envío incremental a ECI. |
| #143 Corrupción en `eciInsertIndex` | Corregidas las carreras RPC, validación de índices y respuestas. El caso Gemini debe confirmarse con la biblioteca que originó el informe. |
| #139 Pausa | Solucionada con `eciPause` nativo además de la pausa del reproductor. |
| #131 Velocidad por voz | Implementada persistencia por voz de idioma; también se corrigió la persistencia de variantes. |
| #129 Dispositivo de audio | Ajustado al contrato WASAPI y a `audio.outputDevice` de NVDA actual, conservando compatibilidad antigua. |
| #114 Descargar diccionarios | No se añadió descarga automática de contenido de terceros sin firma. Los dos repositorios comunitarios siguen enlazados en la ayuda y los diccionarios se cargan por idioma. |
| #112 Tienda de complementos | Se genera paquete sin bibliotecas propietarias y metadatos JSON de tienda. Publicarlo exige una acción externa del mantenedor. |
| #110 Trocear texto grande | Solucionada, con límites comprobados por pruebas. |
| #102 Reiniciar al copiar voces | Solucionada: se ofrece reinicio tras copiar o cambiar bibliotecas. |
| #100 WASAPI | Cubierta mediante `nvwave.WavePlayer` de NVDA 2026. |
| #99 Caída en WhatsApp | Mitigada con texto acotado, validación, anticrash conservado y recuperación del hilo. No había una cadena exacta reproducible exportada. |
| #97 Perfiles con distintos sintetizadores | Endurecido el cambio/finalización con esperas acotadas y limpieza idempotente. |
| #91 Robo de foco | Solucionada para comprobaciones automáticas; diálogo manual con foco seguro. |
| #89 Caracteres chinos | Se conserva `gb18030` y se prueba que el troceado nunca corta un carácter multibyte. La cobertura final depende de los diccionarios de la voz instalada. |
| #86 Errores del actualizador | Solucionada reemplazando `pickle` por JSON atómico. |
| #76 Expansiones extrañas | Es comportamiento interno de determinadas DLL. Se mantiene el control de diccionarios y la posibilidad de corrección con el diccionario de NVDA. |
| #73 Fechas | Se conservan y corrigen las reglas existentes para fechas británicas y falsos meses. Parte del análisis de fechas ocurre dentro de la DLL y no tiene conmutador ECI documentado. |
| #71 Puntuación en IBMECI 6.7.4 | Es una regresión de esa DLL. El controlador conserva los filtros específicos de IBM sin afirmar que puede modificar su parser interno. |
| #61 Voz inválida | Solucionada con validación contra los idiomas que devuelve la propia DLL. |
| #38 Diccionario interno de NVDA | El diccionario de voz de NVDA se aplica antes de llamar al controlador. El ajuste `ABRDICT` controla los diccionarios ECI, que son una capa distinta. |
| #23 Variante no persistente | Solucionada sin destruir ajustes personalizados durante el arranque. |
| #22 Callbacks adelantados | Mejorada mediante callbacks de final de reproducción de WASAPI. |

Las 90 incidencias cerradas también se revisaron. Se conservaron las expresiones anticrash y de pronunciación, compatibilidad de codificación, idiomas, diccionarios, instalación, actualizaciones y NVDA antiguo que seguían siendo pertinentes. Sobre esa base se eliminaron excepciones genéricas, recursos sin cerrar, operaciones duplicadas y rutas de configuración inseguras. Los JSON originales permanecen dentro de la nueva carpeta para trazabilidad.

## Verificación realizada

- Ruff 0.15.15: análisis del complemento, pruebas y sistema de compilación mantenidos, cero errores. Los prototipos heredados de `bridgeSource/tests` se conservan fuera de esta afirmación porque tienen avisos de estilo preexistentes y no se distribuyen en el complemento.
- `compileall` con Python 3.13.13: sintaxis válida.
- 32 pruebas unitarias: todas correctas, incluidas migración de configuración y modos antiguos, protocolo, texto multibyte, orden real de callbacks, selección de ruta, invalidación concurrente, preservación de anuncios cortos y cierre del hilo.
- Rust 1.92.0, destino `i686-pc-windows-msvc`: formato correcto, compilación `release` y pruebas correctas, sin advertencias.
- Prueba de humo real del puente: `rundll32` inició la DLL, apareció la tubería exclusiva del PID y el proceso terminó correctamente.
- SCons 4.10.1: traducciones, manifiestos, documentación y complemento generados correctamente.
- Inspección ZIP: 67 archivos; contiene manifiesto, puente, herramientas, controles de generación, migración y despacho de callbacks y todos los controladores; no contiene `__pycache__` ni `.pyc` de la máquina de compilación.

Hashes SHA-256 de la entrega verificada:

- `IBMTTS-26.8.9.nvda-addon`: `74E12F7DD1C0EADB34F64C70C745A3BE8E2D9D7E8BB1262E935FCF46D656F63B`.
- `ibmtts_host32.dll`: `274D965682A3AD4559C82C87AE79B2CD1B045334731DA0CDB5E73B00A2913F4D`.

## Límite de la garantía

No se incluyeron las bibliotecas propietarias `eci.dll`/`ibmeci.dll`, voces ni diccionarios comerciales, porque no estaban en el árbol del complemento y sus licencias impiden redistribuirlos. Por ello se verificaron por completo código, protocolo, puente, empaquetado y arranque del host, pero no fue posible escuchar síntesis real ni reproducir fallos que dependen de una versión concreta de IBM/Eloquence, del dispositivo de audio o del cierre de sesión de Windows.

No es técnicamente honesto prometer “ni un solo error” para todas las DLL y equipos sin ejecutar esa matriz de hardware y bibliotecas. La entrega elimina los defectos reproducibles encontrados, añade pruebas y convierte los fallos restantes en errores acotados y diagnosticables en lugar de bloqueos de NVDA.

## Prueba manual recomendada antes de uso diario

1. Instalar `IBMTTS-26.8.9.nvda-addon` en una copia portable de NVDA 2026.1.1 o posterior.
2. Seleccionar la DLL con la categoría IBMTTS y aceptar el reinicio.
3. Probar cada idioma, variante, frecuencia y dispositivo de audio disponible.
4. Abrir `json_20000_caracteres.json`, interrumpir su lectura, cambiar rápidamente de selección en el Explorador y comprobar que no aparecen recuperaciones del watchdog atribuibles a IBMTTS.
5. Probar pausa, lectura continua, sonidos/callbacks, perfiles por aplicación y dos sesiones de Windows simultáneas.
6. Salir de NVDA y después cerrar sesión/apagar Windows, comprobando que no vuelve a aparecer `The IBMTTS engine stopped while trying to stop`.
