gioser

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

accesibilidad: el BRAILLE, con displays y contracción

brltty sella con 50 drivers de hardware, 307 tablas propias y liblouis
cableada; liblouis con 478 tablas de traducción. Braille no es una alternativa
de gusto a la voz: es lo que permite leer código, ortografía y matemáticas
—donde escuchar no alcanza— y el único camino para quien además no oye.

liblouis cuesta CERO deps nuevas: medido sobre su configure.ac, su única dep
externa es yaml y va detrás de --with-yaml (tests). Y tarball de release con
configure generado — tercera vez que esa comprobación ahorra escribir un
autoconf.

⚠ EL CONTROL QUE IMPORTA: --enable-liblouis no garantiza nada por sí solo.
brltty la busca por pkg-config y le pide la variable `tablesdir` para hornearla
como LOUIS_TABLES_DIRECTORY (configure.ac:1214-1220). Si el .pc no la definiera,
el valor quedaría VACÍO, el build saldría igual, y brltty tendría «soporte de
braille contraído» sin saber dónde están las tablas. Se comprueba en el BINARIO,
que es donde el valor vive.

⚠ TRES MUROS, Y DOS SON «UN FALLO QUE SALE CON ÉXITO»:

1. -Werror=date-time del lab paró el build en los headers de los drivers de voz
   y pantalla. Desactivarlo es seguro porque el sandbox exporta
   SOURCE_DATE_EPOCH=1 y clang hace que __DATE__/__TIME__ rindan esa fecha fija:
   el binario sigue determinista. Mismo razonamiento que fastfetch/cairo/
   appstream — no es una excepción nueva.

2. brltty IGNORA DESTDIR: su common.mk no lo menciona ni una vez, usa
   INSTALL_ROOT. El sandbox exporta DESTDIR=/out para todos, así que la forma
   habitual era un no-op y el install escribía en la raíz del sandbox. Falló SÓLO
   porque el overlay no lo dejó: si lo hubiera dejado, el build habría terminado
   con éxito y /out VACÍO ⇒ artefacto vacío sellado y cache-hit envenenado. La
   única razón de que no pasara fue un permiso. Corolario en la receta: que
   `make install` no falle no prueba que haya instalado donde creés.

3. El msgfmt de gettext-tiny NO soporta la opción larga --output-file, y
   devuelve rc=0 sin escribir nada. Aislado parte por parte: con -o el mismo
   ar.po compila (67 300 bytes); con --output-file falla igual el francés ⇒ no es
   el idioma, es la opción, y ar.po era sólo el primero por orden alfabético.
   Parche a -o: entran las NUEVE traducciones en vez de perderlas con
   --disable-i18n.

   ⚠ Y me equivoqué en el camino: este repo tiene documentado que ese msgfmt
   muere con el árabe, así que iba a quitar ar.po; probé primero y «compilaba
   bien» — cierto, pero con -o, que no es la invocación del build. Verifiqué el
   fichero correcto con el comando equivocado, y la conclusión era correcta por
   el motivo equivocado. Al reproducir un fallo a mano, la invocación EXACTA del
   log.

--disable-python-bindings explícito y dicho: el binding es un .pyx y necesita
Cython, que no está en el corpus ni en ninguna cola, y el configure sólo emite
un WARNING si falta — o sea que sin apagarlo se saltaría en silencio. Lo que
bloquea es preciso: brltty sirve para la consola y para cualquier app que use
BrlAPI en C, pero orca no podrá usar braille hasta que exista una receta de
cython.

DECLARADO en los 5 escritorios: speech-dispatcher, dotconf, brltty, liblouis.
135 MB. Y queda anotado un hueco que esta línea NO tapa: el perfil `cli` no
lleva ninguna pieza de accesibilidad, y brltty es justamente lo que lee la
consola de texto, que es lo que ese perfil ES. No se declara ahí por simetría
con espeak-ng, pero es una decisión pendiente y no un olvido.

1c2c5a6db2f9bab4fc401aa6be819955a1d57bf2 · por Sergio · (hace 1 d) · padre a709d76c2 · ver el árbol en este commit

Delta semántico

Ninguna definición cambió: el cambio es de texto, documentación o datos.

3 archivos · +425 −0

diff --git a/docs/state/targets.toml b/docs/state/targets.toml
index 1de0061f..675275f2 100644
--- a/docs/state/targets.toml
+++ b/docs/state/targets.toml
@@ -606,6 +606,42 @@ paquetes = [
   # `librsvg`, con el mismo coste medido (promover arrastra 35 recetas de GNOME). Con esto la distro
   # HABLA; que además LEA LA PANTALLA es la otra mitad y es una decisión de campaña.
   "espeak-ng", "pcaudiolib", "sonic",
+  # ── Y LA ACCESIBILIDAD COMPLETA (2026-10-20), salvo el lector de pantalla ───────────────────
+  # `espeak-ng` dio la VOZ. Lo que entra acá es el resto de la cadena, y cada pieza cubre un hueco
+  # distinto que las otras no tapan:
+  #
+  #   `speech-dispatcher`  el SERVIDOR de voz. espeak-ng es un sintetizador: si cada app lo invoca
+  #   `dotconf`            sola, dos que hablen a la vez se pisan y nadie puede interrumpir a nadie.
+  #                        Esto es la cola con PRIORIDADES, la API estable en C (`libspeechd`) y en
+  #                        Python (`speechd`, que es por donde hablará `orca`), y los sintetizadores
+  #                        detrás como módulos. `dotconf` es su parser de configuración, la única
+  #                        dep que faltaba
+  #   `brltty`             los DISPLAYS BRAILLE, con 50 drivers de hardware. No es una alternativa
+  #                        de gusto a la voz: braille es lo que permite leer código, ortografía y
+  #                        matemáticas —donde escuchar no alcanza— y el ÚNICO camino para quien
+  #                        además no oye. Con voz y sin braille se deja afuera a sordociegos por
+  #                        completo
+  #   `liblouis`           braille CONTRAÍDO, con 478 tablas. Sin esto brltty escribe una celda por
+  #                        carácter: legible, tres veces más largo, y nadie lee así un texto
+  #
+  # ⚠ PESO: 135 MB los cuatro (brltty 62, speech-dispatcher 58, liblouis 15, dotconf <1).
+  #
+  # ⚠ **LO QUE SIGUE FALTANDO, con nombre y causa**: `orca`, el lector de pantalla. Es la pieza que
+  # convierte «el sistema puede hablar» en «el sistema me cuenta qué hay en la pantalla», y está
+  # bloqueada por DOS cosas distintas:
+  #   · para hablar necesita `at-spi2-core` y `py3-gobject`, que viven en el mundo paralelo
+  #     dinámico+introspectado de `incoming-gnome`. Promover esa raíz cuesta 35 recetas de GNOME
+  #     rearmando (medido promoviendo y revirtiendo); la alternativa es duplicar en el corpus.
+  #     Es una decisión de operador, no algo que se hace de paso
+  #   · para el BRAILLE necesita además el módulo Python `brlapi`, que exige **Cython** — no está
+  #     en el corpus ni en ninguna cola. Ver la nota de `recipes/brltty.toml`
+  #
+  # ⚠ **Y UN HUECO QUE ESTA LÍNEA NO TAPA, dicho en vez de disimulado:** el perfil `cli` no lleva
+  # NINGUNA pieza de accesibilidad. Y `brltty` es justamente lo que lee la **consola de texto**, que
+  # es lo que ese perfil ES: alguien que no ve y instala la imagen `cli` no tiene forma de usar la
+  # máquina. No se declara acá por simetría —se sigue dónde estaba `espeak-ng`— pero es una decisión
+  # pendiente y no un olvido.
+  "speech-dispatcher", "dotconf", "brltty", "liblouis",
   "fontconfig-shared",     # /etc/fonts/fonts.conf y el soname que enlaza el riel
   "freetype-shared",       # libfontconfig.so.1 pide libfreetype.so.6: sin ella el riel muere al cargar
   "libpng-shared",         # y freetype pide libpng16.so.16 — la cadena entera entro por querer letras
@@ -803,6 +839,42 @@ paquetes = [
   # `librsvg`, con el mismo coste medido (promover arrastra 35 recetas de GNOME). Con esto la distro
   # HABLA; que además LEA LA PANTALLA es la otra mitad y es una decisión de campaña.
   "espeak-ng", "pcaudiolib", "sonic",
+  # ── Y LA ACCESIBILIDAD COMPLETA (2026-10-20), salvo el lector de pantalla ───────────────────
+  # `espeak-ng` dio la VOZ. Lo que entra acá es el resto de la cadena, y cada pieza cubre un hueco
+  # distinto que las otras no tapan:
+  #
+  #   `speech-dispatcher`  el SERVIDOR de voz. espeak-ng es un sintetizador: si cada app lo invoca
+  #   `dotconf`            sola, dos que hablen a la vez se pisan y nadie puede interrumpir a nadie.
+  #                        Esto es la cola con PRIORIDADES, la API estable en C (`libspeechd`) y en
+  #                        Python (`speechd`, que es por donde hablará `orca`), y los sintetizadores
+  #                        detrás como módulos. `dotconf` es su parser de configuración, la única
+  #                        dep que faltaba
+  #   `brltty`             los DISPLAYS BRAILLE, con 50 drivers de hardware. No es una alternativa
+  #                        de gusto a la voz: braille es lo que permite leer código, ortografía y
+  #                        matemáticas —donde escuchar no alcanza— y el ÚNICO camino para quien
+  #                        además no oye. Con voz y sin braille se deja afuera a sordociegos por
+  #                        completo
+  #   `liblouis`           braille CONTRAÍDO, con 478 tablas. Sin esto brltty escribe una celda por
+  #                        carácter: legible, tres veces más largo, y nadie lee así un texto
+  #
+  # ⚠ PESO: 135 MB los cuatro (brltty 62, speech-dispatcher 58, liblouis 15, dotconf <1).
+  #
+  # ⚠ **LO QUE SIGUE FALTANDO, con nombre y causa**: `orca`, el lector de pantalla. Es la pieza que
+  # convierte «el sistema puede hablar» en «el sistema me cuenta qué hay en la pantalla», y está
+  # bloqueada por DOS cosas distintas:
+  #   · para hablar necesita `at-spi2-core` y `py3-gobject`, que viven en el mundo paralelo
+  #     dinámico+introspectado de `incoming-gnome`. Promover esa raíz cuesta 35 recetas de GNOME
+  #     rearmando (medido promoviendo y revirtiendo); la alternativa es duplicar en el corpus.
+  #     Es una decisión de operador, no algo que se hace de paso
+  #   · para el BRAILLE necesita además el módulo Python `brlapi`, que exige **Cython** — no está
+  #     en el corpus ni en ninguna cola. Ver la nota de `recipes/brltty.toml`
+  #
+  # ⚠ **Y UN HUECO QUE ESTA LÍNEA NO TAPA, dicho en vez de disimulado:** el perfil `cli` no lleva
+  # NINGUNA pieza de accesibilidad. Y `brltty` es justamente lo que lee la **consola de texto**, que
+  # es lo que ese perfil ES: alguien que no ve y instala la imagen `cli` no tiene forma de usar la
+  # máquina. No se declara acá por simetría —se sigue dónde estaba `espeak-ng`— pero es una decisión
+  # pendiente y no un olvido.
+  "speech-dispatcher", "dotconf", "brltty", "liblouis",
   # `dejavu-fonts-nerd` es la MISMA DejaVu Sans Mono parcheada con Nerd Fonts (~10.000 glifos de
   # iconos: Powerline, Font Awesome, Devicons…) que usan starship, lsd, eza, neovim y las
   # statuslines; sin ellos dibujan cuadraditos. Se AÑADE, no reemplaza: la licencia de Bitstream
@@ -1510,6 +1582,42 @@ paquetes = [
   # `librsvg`, con el mismo coste medido (promover arrastra 35 recetas de GNOME). Con esto la distro
   # HABLA; que además LEA LA PANTALLA es la otra mitad y es una decisión de campaña.
   "espeak-ng", "pcaudiolib", "sonic",
+  # ── Y LA ACCESIBILIDAD COMPLETA (2026-10-20), salvo el lector de pantalla ───────────────────
+  # `espeak-ng` dio la VOZ. Lo que entra acá es el resto de la cadena, y cada pieza cubre un hueco
+  # distinto que las otras no tapan:
+  #
+  #   `speech-dispatcher`  el SERVIDOR de voz. espeak-ng es un sintetizador: si cada app lo invoca
+  #   `dotconf`            sola, dos que hablen a la vez se pisan y nadie puede interrumpir a nadie.
+  #                        Esto es la cola con PRIORIDADES, la API estable en C (`libspeechd`) y en
+  #                        Python (`speechd`, que es por donde hablará `orca`), y los sintetizadores
+  #                        detrás como módulos. `dotconf` es su parser de configuración, la única
+  #                        dep que faltaba
+  #   `brltty`             los DISPLAYS BRAILLE, con 50 drivers de hardware. No es una alternativa
+  #                        de gusto a la voz: braille es lo que permite leer código, ortografía y
+  #                        matemáticas —donde escuchar no alcanza— y el ÚNICO camino para quien
+  #                        además no oye. Con voz y sin braille se deja afuera a sordociegos por
+  #                        completo
+  #   `liblouis`           braille CONTRAÍDO, con 478 tablas. Sin esto brltty escribe una celda por
+  #                        carácter: legible, tres veces más largo, y nadie lee así un texto
+  #
+  # ⚠ PESO: 135 MB los cuatro (brltty 62, speech-dispatcher 58, liblouis 15, dotconf <1).
+  #
+  # ⚠ **LO QUE SIGUE FALTANDO, con nombre y causa**: `orca`, el lector de pantalla. Es la pieza que
+  # convierte «el sistema puede hablar» en «el sistema me cuenta qué hay en la pantalla», y está
+  # bloqueada por DOS cosas distintas:
+  #   · para hablar necesita `at-spi2-core` y `py3-gobject`, que viven en el mundo paralelo
+  #     dinámico+introspectado de `incoming-gnome`. Promover esa raíz cuesta 35 recetas de GNOME
+  #     rearmando (medido promoviendo y revirtiendo); la alternativa es duplicar en el corpus.
+  #     Es una decisión de operador, no algo que se hace de paso
+  #   · para el BRAILLE necesita además el módulo Python `brlapi`, que exige **Cython** — no está
+  #     en el corpus ni en ninguna cola. Ver la nota de `recipes/brltty.toml`
+  #
+  # ⚠ **Y UN HUECO QUE ESTA LÍNEA NO TAPA, dicho en vez de disimulado:** el perfil `cli` no lleva
+  # NINGUNA pieza de accesibilidad. Y `brltty` es justamente lo que lee la **consola de texto**, que
+  # es lo que ese perfil ES: alguien que no ve y instala la imagen `cli` no tiene forma de usar la
+  # máquina. No se declara acá por simetría —se sigue dónde estaba `espeak-ng`— pero es una decisión
+  # pendiente y no un olvido.
+  "speech-dispatcher", "dotconf", "brltty", "liblouis",
   # `dejavu-fonts-nerd` es la MISMA DejaVu Sans Mono parcheada con Nerd Fonts (~10.000 glifos de
   # iconos: Powerline, Font Awesome, Devicons…) que usan starship, lsd, eza, neovim y las
   # statuslines; sin ellos dibujan cuadraditos. Se AÑADE, no reemplaza: la licencia de Bitstream
@@ -1857,6 +1965,42 @@ paquetes = [
   # `librsvg`, con el mismo coste medido (promover arrastra 35 recetas de GNOME). Con esto la distro
   # HABLA; que además LEA LA PANTALLA es la otra mitad y es una decisión de campaña.
   "espeak-ng", "pcaudiolib", "sonic",
+  # ── Y LA ACCESIBILIDAD COMPLETA (2026-10-20), salvo el lector de pantalla ───────────────────
+  # `espeak-ng` dio la VOZ. Lo que entra acá es el resto de la cadena, y cada pieza cubre un hueco
+  # distinto que las otras no tapan:
+  #
+  #   `speech-dispatcher`  el SERVIDOR de voz. espeak-ng es un sintetizador: si cada app lo invoca
+  #   `dotconf`            sola, dos que hablen a la vez se pisan y nadie puede interrumpir a nadie.
+  #                        Esto es la cola con PRIORIDADES, la API estable en C (`libspeechd`) y en
+  #                        Python (`speechd`, que es por donde hablará `orca`), y los sintetizadores
+  #                        detrás como módulos. `dotconf` es su parser de configuración, la única
+  #                        dep que faltaba
+  #   `brltty`             los DISPLAYS BRAILLE, con 50 drivers de hardware. No es una alternativa
+  #                        de gusto a la voz: braille es lo que permite leer código, ortografía y
+  #                        matemáticas —donde escuchar no alcanza— y el ÚNICO camino para quien
+  #                        además no oye. Con voz y sin braille se deja afuera a sordociegos por
+  #                        completo
+  #   `liblouis`           braille CONTRAÍDO, con 478 tablas. Sin esto brltty escribe una celda por
+  #                        carácter: legible, tres veces más largo, y nadie lee así un texto
+  #
+  # ⚠ PESO: 135 MB los cuatro (brltty 62, speech-dispatcher 58, liblouis 15, dotconf <1).
+  #
+  # ⚠ **LO QUE SIGUE FALTANDO, con nombre y causa**: `orca`, el lector de pantalla. Es la pieza que
+  # convierte «el sistema puede hablar» en «el sistema me cuenta qué hay en la pantalla», y está
+  # bloqueada por DOS cosas distintas:
+  #   · para hablar necesita `at-spi2-core` y `py3-gobject`, que viven en el mundo paralelo
+  #     dinámico+introspectado de `incoming-gnome`. Promover esa raíz cuesta 35 recetas de GNOME
+  #     rearmando (medido promoviendo y revirtiendo); la alternativa es duplicar en el corpus.
+  #     Es una decisión de operador, no algo que se hace de paso
+  #   · para el BRAILLE necesita además el módulo Python `brlapi`, que exige **Cython** — no está
+  #     en el corpus ni en ninguna cola. Ver la nota de `recipes/brltty.toml`
+  #
+  # ⚠ **Y UN HUECO QUE ESTA LÍNEA NO TAPA, dicho en vez de disimulado:** el perfil `cli` no lleva
+  # NINGUNA pieza de accesibilidad. Y `brltty` es justamente lo que lee la **consola de texto**, que
+  # es lo que ese perfil ES: alguien que no ve y instala la imagen `cli` no tiene forma de usar la
+  # máquina. No se declara acá por simetría —se sigue dónde estaba `espeak-ng`— pero es una decisión
+  # pendiente y no un olvido.
+  "speech-dispatcher", "dotconf", "brltty", "liblouis",
   # ⚠ «sin puntero» lo decía este comentario desde que se escribió y NO era cierto: `cosmic-icons`
   # instala iconos de aplicación, no cursores, así que este perfil llegó a 43/43 con el puntero
   # INVISIBLE. Lo cazó `vigia-imagen.py` el 2026-09-04. Los cursores son su propia raíz.
@@ -2223,6 +2367,42 @@ paquetes = [
   # `librsvg`, con el mismo coste medido (promover arrastra 35 recetas de GNOME). Con esto la distro
   # HABLA; que además LEA LA PANTALLA es la otra mitad y es una decisión de campaña.
   "espeak-ng", "pcaudiolib", "sonic",
+  # ── Y LA ACCESIBILIDAD COMPLETA (2026-10-20), salvo el lector de pantalla ───────────────────
+  # `espeak-ng` dio la VOZ. Lo que entra acá es el resto de la cadena, y cada pieza cubre un hueco
+  # distinto que las otras no tapan:
+  #
+  #   `speech-dispatcher`  el SERVIDOR de voz. espeak-ng es un sintetizador: si cada app lo invoca
+  #   `dotconf`            sola, dos que hablen a la vez se pisan y nadie puede interrumpir a nadie.
+  #                        Esto es la cola con PRIORIDADES, la API estable en C (`libspeechd`) y en
+  #                        Python (`speechd`, que es por donde hablará `orca`), y los sintetizadores
+  #                        detrás como módulos. `dotconf` es su parser de configuración, la única
+  #                        dep que faltaba
+  #   `brltty`             los DISPLAYS BRAILLE, con 50 drivers de hardware. No es una alternativa
+  #                        de gusto a la voz: braille es lo que permite leer código, ortografía y
+  #                        matemáticas —donde escuchar no alcanza— y el ÚNICO camino para quien
+  #                        además no oye. Con voz y sin braille se deja afuera a sordociegos por
+  #                        completo
+  #   `liblouis`           braille CONTRAÍDO, con 478 tablas. Sin esto brltty escribe una celda por
+  #                        carácter: legible, tres veces más largo, y nadie lee así un texto
+  #
+  # ⚠ PESO: 135 MB los cuatro (brltty 62, speech-dispatcher 58, liblouis 15, dotconf <1).
+  #
+  # ⚠ **LO QUE SIGUE FALTANDO, con nombre y causa**: `orca`, el lector de pantalla. Es la pieza que
+  # convierte «el sistema puede hablar» en «el sistema me cuenta qué hay en la pantalla», y está
+  # bloqueada por DOS cosas distintas:
+  #   · para hablar necesita `at-spi2-core` y `py3-gobject`, que viven en el mundo paralelo
+  #     dinámico+introspectado de `incoming-gnome`. Promover esa raíz cuesta 35 recetas de GNOME
+  #     rearmando (medido promoviendo y revirtiendo); la alternativa es duplicar en el corpus.
+  #     Es una decisión de operador, no algo que se hace de paso
+  #   · para el BRAILLE necesita además el módulo Python `brlapi`, que exige **Cython** — no está
+  #     en el corpus ni en ninguna cola. Ver la nota de `recipes/brltty.toml`
+  #
+  # ⚠ **Y UN HUECO QUE ESTA LÍNEA NO TAPA, dicho en vez de disimulado:** el perfil `cli` no lleva
+  # NINGUNA pieza de accesibilidad. Y `brltty` es justamente lo que lee la **consola de texto**, que
+  # es lo que ese perfil ES: alguien que no ve y instala la imagen `cli` no tiene forma de usar la
+  # máquina. No se declara acá por simetría —se sigue dónde estaba `espeak-ng`— pero es una decisión
+  # pendiente y no un olvido.
+  "speech-dispatcher", "dotconf", "brltty", "liblouis",
   # `dejavu-fonts-nerd` es la MISMA DejaVu Sans Mono parcheada con Nerd Fonts (~10.000 glifos de
   # iconos: Powerline, Font Awesome, Devicons…) que usan starship, lsd, eza, neovim y las
   # statuslines; sin ellos dibujan cuadraditos. Se AÑADE, no reemplaza: la licencia de Bitstream
diff --git a/recipes/brltty.toml b/recipes/brltty.toml
new file mode 100644
index 00000000..fb551e8b
--- /dev/null
+++ b/recipes/brltty.toml
@@ -0,0 +1,175 @@
+# brltty 6.9.1 — el driver de DISPLAYS BRAILLE. Cierra el hueco de accesibilidad para quien no ve
+# y no usa voz.
+#
+# ══ POR QUÉ ES SU PROPIO FRENTE, Y NO «lo mismo que la voz» ════════════════════════════════════
+# La voz y el braille no son alternativas de gusto. Braille es lo que permite leer código,
+# ortografía, tablas y matemáticas —cosas donde escuchar no alcanza— y es el único camino para quien
+# además no oye. Una distro con voz y sin braille deja afuera a sordociegos por completo.
+#
+# brltty lee la pantalla (la consola de Linux y, vía BrlAPI, las apps) y la escribe en el display.
+# Trae **42 drivers de braille** para hardware de distintos fabricantes.
+#
+# ══ LAS PERILLAS QUE IMPORTAN ══════════════════════════════════════════════════════════════════
+#   --enable-api        **BrlAPI**: el protocolo por el que una aplicación —`orca`, un editor— toma
+#                       el display en vez de dejárselo a la lectura de pantalla. Sin esto brltty
+#                       sirve para la consola y nada más
+#   --enable-liblouis   braille CONTRAÍDO. Sin liblouis es una celda por carácter: legible, tres
+#                       veces más largo, y nadie lee así un texto. Ver `recipes/liblouis.toml`
+#   --disable-x         la distro es Wayland-only
+#   --disable-icu       `icu` no está en el corpus. Lo que se pierde es normalización Unicode fina
+#                       en la conversión de texto; `--enable-iconv` cubre el caso general
+#   --disable-polkit    no hay polkit en esta pila
+#   --disable-gpm       el ratón de consola; no está en el corpus
+#
+# ⚠ **`--disable-python-bindings`, Y ES UNA PÉRDIDA CONCRETA QUE SE DICE EN VEZ DE DISIMULARSE.**
+# El binding es un `.pyx` y necesita **Cython** (`Bindings/Python/bindings.m4:108`:
+# `AC_PATH_PROGS([CYTHON], [cython3 cython])`), que **no está en el corpus ni en ninguna cola**
+# (comprobado). Y el `configure` sólo emite un `AC_MSG_WARN` si falta: o sea que **sin apagarlo
+# explícitamente el binding se salta EN SILENCIO** y el artefacto sale igual.
+#
+# Lo que eso bloquea, concreto: `orca` importa el módulo `brlapi` de Python para manejar el
+# display. ⇒ con esta receta brltty funciona para la CONSOLA y para cualquier app que use BrlAPI en
+# C, pero **orca no podrá usar braille hasta que exista una receta de `cython`**. Se apaga a
+# propósito para que el día que alguien lo necesite el motivo esté escrito, en vez de descubrir un
+# hueco que nadie decidió.
+#
+# Los otros bindings (java, lisp, lua, ocaml, tcl, emacs) se apagan por lo mismo: sus lenguajes no
+# están en el corpus y el `configure` los saltaría sin avisar.
+#
+# ⚠ `--sysconfdir=/etc`: con `--prefix=/usr` a secas autotools deriva `/usr/etc` en SILENCIO, y el
+# binario hornea esa ruta — el artefacto queda auto-consistente y la configuración, donde nadie la
+# busca. Pasó con `speech-dispatcher` el mismo día; 31 recetas del repo ya lo pasan.
+#
+# ✅ **`--enable-i18n` CON las nueve traducciones, incluida `ar.po` — y eso es MEDIDO, no un
+# descuido.** Este repo tiene documentado que el `msgfmt` de `gettext-tiny` muere con el árabe
+# (SIGILL y un `.mo` de 0 bytes), así que iba a parchear `ar.po` fuera. Antes lo probé: el
+# `ar.po` de brltty **compila bien** (rc=0, `.mo` de 67 300 bytes), igual que `fr` y `zh`. La nota
+# de la memoria vale para un caso concreto de plurales que este fichero no dispara. *Una memoria
+# describe lo que era cierto cuando se escribió; se verifica antes de actuar sobre ella.*
+name    = "brltty"
+version = "6.9.1"
+license = "LGPL-2.1-or-later"
+
+[source]
+# ⚠ El tarball OFICIAL de brltty.app, no el archive del tag de GitHub: el del tag trae sólo
+# `configure.ac` y el lab no tiene autoconf ni automake. Tercera vez que mirar si upstream publica
+# un tarball de release ahorra escribir un autoconf.
+tarball = "https://brltty.app/archive/brltty-6.9.1.tar.xz"
+sha256  = "cf4619ce1471690e4ecc5544ee6289cef6c5bdf5855a1ac8dcc63f5beedf108d"
+
+[build]
+compiler = "zig-cc"
+target   = "x86_64-linux-musl"
+link     = "dynamic"
+flags    = []
+
+[build.phases]
+configure = '''
+set -e
+[ -f ./configure ] || { echo "brltty: el tarball no trae ./configure — ¿se cambió por el archive del tag?" >&2; exit 1; }
+# ⚠ **PARCHE: el `msgfmt` de `gettext-tiny` no soporta la opción LARGA `--output-file`.**
+# `Messages/Makefile.in:57` invoca así:
+#
+#     $(MSGFMT) --output-file $@ -- <fichero>.po
+#     ⇒ fopen: No such file or directory          … y **sale con rc=0**
+#
+# Medido aislando cada parte: con `-o` el mismo `ar.po` compila (67 300 bytes); con `--output-file`
+# falla, y falla igual el francés. O sea que **no es el idioma, es la opción**, y `ar.po` sólo era
+# el primero por orden alfabético.
+#
+# ⚠ **Y me equivoqué antes de llegar acá, en dos pasos que vale anotar.** (1) Este repo tiene
+# documentado que el msgfmt de gettext-tiny muere con el árabe, así que iba a quitar `ar.po`; probé
+# primero y «compilaba bien» — cierto, pero con `-o`, que **no es la invocación que usa el build**.
+# Verifiqué el fichero correcto con el comando equivocado. (2) El tool devuelve **rc=0 mientras no
+# escribe nada**: si `make` no comprobara que el `.mo` existe, esto habría pasado en silencio. Es la
+# misma familia que el `INSTALL_ROOT` de abajo — *un fallo que sale con éxito.*
+#
+# ⇒ Se cambia a `-o`, que gettext-tiny sí entiende, y así entran **las nueve traducciones** en vez
+# de perderlas con `--disable-i18n`. El hueco de `gettext-tiny` es real y va a morder a otras
+# recetas; barrerlo es su propia unidad de trabajo, no algo que se hace de paso.
+MF=Messages/Makefile.in
+grep -q -- '--output-file' "$MF" || { echo "brltty: $MF ya no usa --output-file; el parche de gettext-tiny sobra o cambió de forma — REVISAR" >&2; exit 1; }
+sed -i 's/--output-file \$@ --/-o $@/' "$MF"
+grep -q -- '--output-file' "$MF" && { echo "brltty: el parche de --output-file no se aplicó" >&2; grep -n 'MSGFMT' "$MF" >&2; exit 1; }
+grep -q -- '-o \$@' "$MF" || { echo "brltty: tras el parche no aparece '-o \$@' en $MF" >&2; grep -n 'MSGFMT' "$MF" >&2; exit 1; }
+echo "brltty: msgfmt pasa a la opción corta (gettext-tiny no entiende --output-file)"
+
+# ⚠ `-Wno-error=date-time`: `Headers/spk_driver.h:52` y `scr_driver.h:46` usan `__DATE__`/`__TIME__`
+# y **el lab compila con `-Werror=date-time`** — su política de reproducibilidad, que acá hizo
+# exactamente su trabajo y paró el build en los drivers de voz y de pantalla.
+#
+# Desactivarlo es SEGURO **porque el sandbox exporta `SOURCE_DATE_EPOCH=1`**: con esa variable
+# definida clang hace que `__DATE__`/`__TIME__` rindan esa fecha fija en vez de la del reloj, así
+# que el binario sigue siendo determinista. Sin `SOURCE_DATE_EPOCH` esto sería hornear la hora del
+# build dentro del artefacto, que es justo lo que el warning previene. Mismo razonamiento y misma
+# cura que `fastfetch`, `cairo` y `appstream` en este repo — no es una excepción nueva.
+CFLAGS="${CFLAGS:-} -Wno-error=date-time" \
+./configure --build=$CBUILD --host=$CHOST --prefix=/usr --sysconfdir=/etc \
+  --enable-api --enable-liblouis --enable-i18n --enable-iconv --enable-expat \
+  --disable-x --disable-icu --disable-polkit --disable-gpm \
+  --disable-python-bindings --disable-java-bindings --disable-lisp-bindings \
+  --disable-lua-bindings --disable-ocaml-bindings --disable-tcl-bindings \
+  --disable-emacs-bindings
+'''
+compile = 'make -j"$(nproc)"'
+# El guardián se escribe contra lo que el `install` de upstream PROMETE —leído en
+# `Programs/Makefile.in:1199`: programas, tablas, drivers, cabeceras, mensajes, el `.pc` y la
+# API— y busca por PATRÓN y no por ruta exacta, para no rechazar un build correcto por haberme
+# equivocado de directorio. Es la lección de los cuatro guardianes demasiado estrictos del 20-oct.
+install = '''
+set -e
+# ⚠ **`INSTALL_ROOT` Y NO `DESTDIR`, Y ESTO ESTUVO A UN PASO DE SELLAR UN ARTEFACTO VACÍO.**
+# `common.mk` de brltty no menciona `DESTDIR` **ni una vez** (medido): usa su propia variable,
+# `INSTALL_ROOT`, para prefijar cada directorio de instalación. El sandbox exporta `DESTDIR=/out`
+# para todos, así que la forma habitual pasa inadvertida — y acá era un no-op:
+#
+#     /usr/bin/install -c -d /usr/bin
+#     /usr/bin/install -c -d /usr/libexec/brltty
+#     install: cannot create directory '/usr/libexec/brltty': Value too large for data type
+#
+# Estaba instalando **en la raíz del sandbox**, no en `/out`. Falló sólo porque el overlay no lo
+# dejó escribir. **Si lo hubiera dejado, el build habría terminado con éxito y `/out` vacío** — y
+# un directorio vacío en el store no es un artefacto, es un nombre: se sella, es un cache-hit, y el
+# fallo llega hasta el final diciendo que todo fue bien. Es exactamente la regla 3 del repo, y la
+# única razón de que no pasara fue el permiso.
+#
+# ⇒ Corolario para cualquier receta nueva: **que `make install` no falle no prueba que haya
+# instalado donde creés.** El guardián de abajo lo comprueba igual, buscando los ficheros en `/out`.
+make install INSTALL_ROOT=/out
+b=$(find /out -name brltty -type f -perm -u+x 2>/dev/null | head -1)
+[ -n "$b" ] || { echo "brltty: no quedó el binario — NO sello" >&2; find /out -maxdepth 4 -type f -perm -u+x 2>/dev/null | head -20 >&2; exit 1; }
+
+# ⚠ EL CONTROL DECISIVO: que liblouis esté CABLEADA de verdad.
+# `--enable-liblouis` no garantiza nada por sí solo: brltty la busca por pkg-config y le pide la
+# variable `tablesdir` para hornearla como `LOUIS_TABLES_DIRECTORY` (`configure.ac:1214-1220`). Si
+# el `.pc` no definiera `tablesdir`, el valor quedaría VACÍO, el build saldría igual y brltty
+# tendría «soporte de braille contraído» sin saber dónde están las tablas — sellado, con contenido
+# y sin la función. Se comprueba en el BINARIO, que es donde el valor vive:
+strings "$b" 2>/dev/null | grep -q 'liblouis/tables' || {
+  echo "brltty: el binario no trae la ruta de tablas de liblouis — el braille CONTRAÍDO no funcionaría, y sin error" >&2
+  strings "$b" 2>/dev/null | grep -i louis | head -5 >&2
+  exit 1
+}
+
+# BrlAPI: sin esto ninguna aplicación puede tomar el display.
+api=$(find /out -name 'libbrlapi.so*' -o -name 'libbrlapi.a' 2>/dev/null | head -1)
+[ -n "$api" ] || { echo "brltty: no quedó libbrlapi — ninguna app podría manejar el display, sólo la consola" >&2; exit 1; }
+
+# Los drivers: 42 en el árbol. El piso es bajo a propósito —según cómo se enlacen pueden quedar
+# dentro del binario en vez de como módulos— pero CERO ficheros de datos de driver sí es un fallo.
+drv=$(find /out -name 'libbrltty*.so' 2>/dev/null | wc -l)
+# Las tablas de texto y de contracción propias de brltty.
+tbl=$(find /out -path '*brltty*' \( -name '*.ttb' -o -name '*.ctb' -o -name '*.ktb' -o -name '*.tti' \) 2>/dev/null | wc -l)
+[ "$tbl" -gt 50 ] || { echo "brltty: sólo $tbl tabla(s) propias — esperaba >50; sin ellas no sabe traducir a celdas" >&2; find /out -path '*brltty*' -name '*.ttb' 2>/dev/null | head -5 >&2; exit 1; }
+# La herramienta de tablas de contracción: es lo que prueba que ese camino se construyó.
+[ -n "$(find /out -name 'brltty-ctb' -type f 2>/dev/null | head -1)" ] || { echo "brltty: falta brltty-ctb — el compilador de tablas de contracción no se construyó" >&2; exit 1; }
+echo "brltty: binario + brlapi + $drv driver(s) externo(s) + $tbl tabla(s) + liblouis cableada"
+'''
+
+[deps]
+build = ["make", "pkgconf", "liblouis", "ncurses", "expat", "alsa-lib",
+         "gettext-tiny", "libtool", "python3",
+         # ⚠ `binutils` por el GUARDIÁN (aporta `strings`), no por el build: sin declararlo el
+         # control de liblouis fallaría por falta de herramienta y acusaría a la receta de algo que
+         # no hizo. Misma nota que `espeak-ng` y `speech-dispatcher`.
+         "binutils"]
diff --git a/recipes/liblouis.toml b/recipes/liblouis.toml
new file mode 100644
index 00000000..2936611a
--- /dev/null
+++ b/recipes/liblouis.toml
@@ -0,0 +1,70 @@
+# liblouis 3.39.0 — el traductor de braille LITERARIO. Es lo que separa «hay un display braille»
+# de «se puede leer cómodamente en braille».
+#
+# ══ QUÉ AÑADE, Y POR QUÉ NO ES UN LUJO ═════════════════════════════════════════════════════════
+# Sin liblouis, `brltty` escribe braille de ORDENADOR: una celda por carácter, uno a uno. Se puede
+# leer, pero ocupa el triple y nadie lee así un texto largo. El braille de verdad es **contraído**:
+# contracciones, signos compuestos y reglas que dependen del idioma y del contexto — «and» es una
+# celda, no tres. Eso son tablas, y liblouis es el motor que las aplica.
+#
+# ⇒ Entra junto con `brltty` y no después, por el mismo criterio que `libime-table` con el chino:
+# publicar el display sin el traductor sería dar la capacidad a medias y llamarla cerrada.
+#
+# ══ LO QUE CUESTA: NADA NUEVO ══════════════════════════════════════════════════════════════════
+# Medido sobre su `configure.ac`: su única dep externa es `yaml`, y va detrás de `--with-yaml`
+# (es para su suite de tests). ⇒ **cero deps nuevas en el corpus.**
+#
+# Se usa el tarball del RELEASE y no el archive del tag: trae `configure` ya generado y el lab no
+# tiene autoconf ni automake. Tercera vez que esa comprobación ahorra escribir un autoconf
+# (`pcaudiolib`, `speech-dispatcher`, ésta).
+#
+# `--enable-ucs4`: los `widechar` de 4 bytes. Con 2 bytes, los caracteres fuera del BMP —y buena
+# parte de las matemáticas y la notación musical braille están ahí— se truncan en silencio.
+name    = "liblouis"
+version = "3.39.0"
+license = "LGPL-2.1-or-later"
+
+[source]
+tarball = "https://github.com/liblouis/liblouis/releases/download/v3.39.0/liblouis-3.39.0.tar.gz"
+sha256  = "629fa8cb0dfd9ad457c5bf47a42f0953b673e62c8ad6b1d03ddc4e2bd20008f1"
+
+[build]
+compiler = "zig-cc"
+target   = "x86_64-linux-musl"
+link     = "dynamic"
+flags    = []
+
+[build.phases]
+configure = '''
+set -e
+[ -f ./configure ] || { echo "liblouis: el tarball no trae ./configure — ¿se cambió por el archive del tag?" >&2; exit 1; }
+./configure --build=$CBUILD --host=$CHOST --prefix=/usr --sysconfdir=/etc \
+  --enable-ucs4 --without-yaml --disable-static
+'''
+compile = 'make -j"$(nproc)"'
+# ⚠ EL GUARDIÁN DE LAS TABLAS ES EL PUNTO DE ESTA RECETA.
+#
+# `libllouis.so` sin sus tablas **carga, enlaza y traduce la identidad**: devolvería el texto sin
+# contraer y sin un solo error. Es exactamente la figura que este catálogo persigue —sellado, con
+# contenido, reproducible y sin la función que promete—, y acá es peor que en otros frentes porque
+# la promesa es que alguien que no ve pueda LEER.
+#
+# El piso de 100 no es un número inventado: upstream distribuye más de mil ficheros de tabla (una o
+# varias por idioma). Con una docena, algo se recortó; con cero, el motor es un `.so` mudo.
+install = '''
+set -e
+make install DESTDIR=/out
+so=$(find /out/usr/lib -name 'liblouis.so*' -type f 2>/dev/null | head -1)
+[ -n "$so" ] || { echo "liblouis: no quedó liblouis.so — brltty la carga dinámicamente" >&2; exit 1; }
+[ -f /out/usr/include/liblouis/liblouis.h ] || { echo "liblouis: falta include/liblouis/liblouis.h" >&2; find /out/usr/include -name 'liblouis*' >&2; exit 1; }
+t=$(find /out/usr/share/liblouis/tables -type f 2>/dev/null | wc -l)
+[ "$t" -gt 100 ] || { echo "liblouis: sólo $t fichero(s) de tabla — esperaba >100; sin tablas traduce la identidad EN SILENCIO" >&2; find /out/usr/share -maxdepth 3 2>/dev/null | head -20 >&2; exit 1; }
+# Y el `.pc`, que es como brltty la encuentra.
+[ -f /out/usr/lib/pkgconfig/liblouis.pc ] || { echo "liblouis: falta liblouis.pc — brltty no la detectaría" >&2; exit 1; }
+# `lou_translate` es la herramienta que permite comprobar a mano que traduce de verdad.
+[ -x /out/usr/bin/lou_translate ] || echo "liblouis: aviso — no quedó lou_translate; no es fatal, pero es la única forma de probar la traducción sin un display" >&2
+echo "liblouis: $(basename "$so") + $t tabla(s) + liblouis.pc"
+'''
+
+[deps]
+build = ["make", "pkgconf", "libtool"]