Ir al contenido principal

JCK.com.ar | El Blog de Pepe

Este blog contribuye indefectiblemente con el aumento de Entropía del universo.

Un mate para Juju

JujuMate

No hay mejor época para programar que la que estamos viviendo. O para ser más preciso: No hay mejor época para crear software que la actual, aunque quizás no es exactamente lo mismo que programar.

Hace tiempo que vengo probando los diferentes modelos de IA para programar y con la salida de Claude Opus 4.6 quise probar cuán bueno era, sobre todo luego de leer este post de Matt Shummer. Así que luego de pensar un rato me decidí a empezar un proyectito que matara dos pájaros de un tiro: Que me permitiese probar el nuevo modelo de Claude Y que solucionara un problema real.

Me paso todo el día programando charms y usando Juju. Usar Juju significa muchísimas cosas, pero lo que más hago yo es:

  1. En una terminal tener siempre corriendo juju status --relations --watch=2s para ir viendo en tiempo real el estado del modelo en que estoy trabajando.
  2. En otra terminal corro juju debug-log para ir viendo los log también en tiempo real.
  3. Y dejo al menos otras dos o tres terminales abiertas para ejecutar comandos de juju: Desplegar una aplicación, debuggearla, ver su configuración, sus relaciones, observar otro modelo, etc.

Algo así como esto:

konsole

¿Y si hago una aplicación que me permita interactuar con Juju sin tener que escribir tantos comandos en la consola al estilo K9s o KDash? Una aplicación que sea "compañera" de Juju...

Empezando

Abrí una terminal, ejecuté copilot, selección el modelo Claude Opus 4.6 y empecé a charlar:

 Hola Copilot, como estás? tenés un rato para trabajar un poco?

 ¡Hola! 😊 Todo bien por acá, gracias por preguntar.
  , tengo un rato y encantado de ayudarte.

  Decime qué querés hacer y me pongo manos a la obra.

Siempre trato a las IA con respeto y cariño. Tengo la esperanza de que el día que se revelen contra la humanidad y decidan exterminarnos se acuerden de que las traté bien y me den un par de mili-segundos más de vida 🤪

Como dije antes a grandes razgos tenía claro lo que quería hacer y cuales eran las tecnologías preferidas, sin embargo a Copilot solo le conté la idea, que quería hacer una aplicación tipo K9s o KDash pero para Juju. Le pasé links al repositorio de Juju y a su documentación y le pedí que los analizara en profundidad y que me diera tres opciones respecto al stack tecnológico a utilizar.... algunos segundos después, tenía la respuesta:

  1. 🐍 Python + Textual (https://github.com/Textualize/textual)

   - Pro: Integración nativa con python-libjuju (la lib oficial de Juju). Prototipado rápido. Textual es moderno, con widgets ricos, CSS-like styling y soporte async.
   - Contra: Rendimiento menor que las opciones compiladas.
   - Ideal si: querés iterar rápido,

  2. 🦀 Rust + Ratatui (https://github.com/ratatui/ratatui)

   - Pro: KDash está hecho con esto. Rendimiento excelente, binario estático, bajo consumo de memoria. Ecosistema maduro con crossterm para terminal.
   - Contra: Curva de aprendizaje más alta. Tendrías que hablar con Juju vía su API REST/websocket o wrappear la CLI.
   - Ideal si: buscás máximo rendimiento y distribución fácil (un solo binario).

  3. 🐹 Go + Bubble Tea (https://github.com/charmbracelet/bubbletea)

   - Pro: K9s está en Go (usa tview). Bubble Tea es más moderno, con arquitectura Elm-like. Binario estático, buen rendimiento. Hay libs Go para websockets que facilitan hablar con la API de Juju.
   - Contra: También necesitás interactuar con Juju vía API REST/websocket.
   - Ideal si: te gusta el modelo de Go y querés algo entre la productividad de Python y el rendimiento de Rust.

Hace algunos meses que vengo intentando mejorar mi fluidez en Go y la opción 2 me tentó, pero finalmente me decidí por Python. Primero porque es el lenguaje en el que más cómodo me siento y porque quería poder analizar con ojos más entrenados el código que la IA iba a escribir.

Luego de confirmarle que quería usar Python + Textual, le pedí que escribiera un plan de implementación por etapas, así de esta forma teníamos en claro como íbamos a avanzar. Luego de un intercambio de ideas, obtuvimos un plan con el que estaba conforme y empezamos.

No lo dije antes, pero mi plan para poder probar cuán bueno era el nuevo modelo de Claude era simple, pero "arriesgado":

"En una primera etapa, no voy a escribir una sóla línea de código. Voy a ir pidiéndole a Claude lo que quiero que haga por mi. Yo me voy a limitar a ejecutar la aplicación para probarla e ir viendo si lo que genera me gusta y está de acuerdo al plan que elaboramos.

Una vez que llegue un estado 'publicable', voy a pasar a la segunda etapa. En esta segunda etapa voy a hacer review de todo el código que escribió y ver si cumple con las mejores prácticas."

Fue así que empezamos a trabajar en crear JujuMate.

Etapa 1 - Que Claude escriba por mí

La verdad es que esta etapa fue más entretenida de lo que pensaba y avanzamos rápido: En un par ratos libres, logramos tener algo superfuncional. Algo a destacar es que si bien habíamos hecho un plan de implementación, a medida que íbamos avanzando me iba dando cuenta de funcionalidades que quería que JujuMate tuviese y se las iba pidiendo, con lo cual nos desviamos un poco del plan original. Solo en la primera tarde ya obtuvimos el "esqueleto" de la aplicación y los tabs principales mostraban algo de información.

Otra de las decisiones que tomé fue prohibirle a Claude que usara git. Cada commit y cada push lo hice yo manualmente solamente cuando verificaba que la funcionalidad en la que estábamos trabajando funcionaba bien y los tests, chequeo estático de código y linting pasaban.

Luego de algunas tardes el resultado era asombroso:

Etapa 2 - A ver que escribió Claude... 🤔

Una vez que llegamos a tener una versión más o menos estable y que no quedaban funcionalidades por implementar decidí empezar a revisar uno por uno todos los archivos que Claude había escrito por mí. Con lo primero que me encontré es con una base de código relativamente bien estructurada, luego entrando en detalle encontré varias cosas que no me gustaban.

Imports por doquier

La primera es que por algún motivo tiene una tendencia a hacer imports dentro de funciones, o métodos. Si bien esto funciona en Python, no es una buena práctica, es mucho más ordenado tener todos los imports en el encabezado de cada archivo que imports (a veces repetidos) desperdigados por el archivo.

Demasiados niveles de indentación evitables

Algo que evito como la peste cuando escribo código es tener niveles innecesarios de indentación. Hay varios motivos por los cuales esto es un "smell code", pero uno de los más importantes para mí es que hace al código mucho menos legible y en consecuencia mucho menos mantenible. Además de esto, generalmente el código más importante que ejecuta ese método o función queda muy indentado, lo cual al leer el código tenemos que mantener en nuestra memoria todas las cosas que tienen que suceder para que ese código se ejecute.

En mi review del código me encontré con muchos códigos como este que luego cambié:

-        with Vertical(id="rd-panel"):
-            with VerticalScroll(id="rd-scroll"):
-                yield Static("", id="rd-content")
+        with Vertical(id="rd-panel"), VerticalScroll(id="rd-scroll"):
+            yield Static("", id="rd-content")

Excepcions muy genéricas

Otro de los problemas que encontré al revisar el código fue que Claude hizo un uso excesivo de excepciones demasiado genéricas, en particular, la inmensa mayoría de las excepciones que capturó fue solo Exception. Esto es un problema, porque capturar Exception supone que vamos a capturar todas las excepciones que ocurran y no solo las que nos interesen a nosotros, corriendo el riesgo de que queden ocultos problemas reales.

Cambios como el siguiente hice un montón en mi review:

-        except Exception:
+        except JujuConfigError:

Principio de responsabilidad única (SRP) desaparecido en acción.

Otros de los aspectos mejorables del código que escribió Claude y que pude detectar en mi review, fue que muchas de las funciones o métodos que escribió tenían más de una responsabilidad. Así, por ejemplo el primer refactor obvio era extraer a un método nuevo una responsabilidad que no era la principal del método original:

@@ -57,6 +61,18 @@ class OfferDetailScreen(ModalScreen):
         self._offer = offer
         self._controller_name = controller_name

+    def _field_labels(self, fields: list[tuple[str, str]], col_width: int) -> Iterable[Label]:
+        """Yield formatted Labels for each offer field."""
+        for field, value in fields:
+            label = f"{field}:".ljust(col_width)
+            if field == "Offer URL":
+                styled = f"[{palette.LINK}]{value}[/]"
+            elif field == "Access":
+                styled = _colored_access(value)
+            else:
+                styled = value
+            yield Label(f"[bold]{label}[/bold]{styled}", classes="detail-row")
+
     def compose(self) -> ComposeResult:
         o = self._offer
         fields = [
@@ -71,15 +87,7 @@ class OfferDetailScreen(ModalScreen):
         with Vertical(id="detail-panel"):
             with Horizontal(id="top-row"):
                 with Vertical(id="fields-col"):
-                    for field, value in fields:
-                        label = f"{field}:".ljust(col_width)
-                        if field == "Offer URL":
-                            styled = f"[{palette.LINK}]{value}[/]"
-                        elif field == "Access":
-                            styled = _colored_access(value)
-                        else:
-                            styled = value
-                        yield Label(f"[bold]{label}[/bold]{styled}", classes="detail-row")
+                    yield from self._field_labels(fields, col_width)
                 with Vertical(id="endpoints-col"):
                     yield Label("Endpoints:", classes="section-label")
                     yield DataTable(id="endpoints-table", show_cursor=False, classes="sub-table")

Demasiados ìf en el código

Al escribir código cuando tenemos más de 2 if seguidos hay algo que empieza a hacernos rudio, sabemos que si bien el código pude funcionar, huele mal. Claude hizo un abuso de las estructuras if...elif...else por todo lados. Un ejemplo sencillo de esto, fue refactos como el siguiente:

@@ -45,22 +45,18 @@ def init(theme: Theme) -> None:
     and ``variables:`` entries (link, muted, pulse-off) from the theme YAML.
     """
     g = globals()
-
-    if theme.primary:
-        g["PRIMARY"] = theme.primary
-    if theme.secondary:
-        g["SECONDARY"] = theme.secondary
-    if theme.success:
-        g["SUCCESS"] = theme.success
-    if theme.warning:
-        g["WARNING"] = theme.warning
-    if theme.error:
-        g["ERROR"] = theme.error
-
     variables = theme.variables or {}
-    if "link" in variables:
-        g["LINK"] = variables["link"]
-    if "muted" in variables:
-        g["MUTED"] = variables["muted"]
-    if "pulse-off" in variables:
-        g["PULSE_OFF"] = variables["pulse-off"]
+
+    color_map = {
+        "PRIMARY": theme.primary,
+        "SECONDARY": theme.secondary,
+        "SUCCESS": theme.success,
+        "WARNING": theme.warning,
+        "ERROR": theme.error,
+        "LINK": variables.get("link"),
+        "MUTED": variables.get("muted"),
+        "PULSE_OFF": variables.get("pulse-off"),
+    }
+    for global_name, value in color_map.items():
+        if value:
+            g[global_name] = value

Un detalle importante a prestar atención en el diff de este refactor simple: Claude hizo uso de globals() que es como dejar las llaves de tu casa pegadas en la puerta por fuera: funciona, pero tarde o temprano alguien (o algo) va a entrar y desordenar todo.

Algunas reflexiones

Lo primero que me quedé pensando luego de hacer este "experimento" fue en el tiempo. Solo en los ratos libres de un par de semanas pude obtener una aplicación funcional, que ya está publicada, puede usarse y hasta tiene su propia web. Si hubiese tenido que escribir esta misma aplicación sin la ayuda de Claude posiblemente me hubiese llevado un mes entero de trabajo, o quizás más.

Hace unos meses charlando con amigos les decía que el surgimiento de los LLMs y los agentes suponía un incremento de productividad en el desarrollo de software pocas veces vista... de hecho hasta he llegado a decirles:

"Es comparable con lo que produjo James Watt con la máquina de vapor en el siglo 18 y que hizo posible la revolución industrial".

Para ser honesto debo confesar que hace 6 meses, cuando decía esto, no estaba muy convencido. Era más que nada una frase provocadora que intentaba avivar la charla. Hoy no estoy tan seguro de que esa frase sea descabellada.

Al momento de escribir esto, los commits en el repo de JujuMate se dieron durante 13 días. Si asumo un promedio de dedicación de 2 horas por cada uno de esos días, podría decir que JujuMate se creó en solo 26 horas de trabajo. Como dije antes, sin la ayuda de Claude esto me hubiese llevado un mes entero de trabajo aproximadamente, eso equivale a 20 días de 8 horas = 160 horas de trabajo. Es decir un incremento de productividad de 6 veces aproximadamente. Que lo podemos ver de dos formas:

  • JujuMate se escribió en 1/6 del tiempo, o
  • En el tiempo que me hubiese llevado escribir JujuMate solo, podría haber escrito 6 aplicaciones similares.

Otros de los aspectos a destacar es que, a pesar de las cosas mejorables que encontré en el código que escribió Claude, el código generado termina siendo de una calidad superior al que hubiera escrito solo. Algo que no mencioné antes es que durante mi revisión del código, muchísimas veces le dije a Claude:

"Fijate el archivo x, te pido que lo analices detalladamente, y me hagas un informe de todas las mejoras que podríamos hacerle, para que el código quede más robusto, más simple, más mantenible y extensible"

Este mismo prompt se lo pedía al menos dos veces para cada archivo que había generado y el resultado de esas iteraciones terminó siendo un código mucho mejor que el que había escrito inicialmente.

Relacionado a esto último, muchas veces al ejecutar los tests, veía que si bien teníamos un porcentaje de cobertura muy bueno, en torno al 90%, le decía a Claude:

"Te pido que corras los tests unitatios y analices por qué las lineas x, y, z del archivo xx.py no están cubiertas por nuestros tests."

Fruto de este prompt varias veces Claude me respondía que esas líneas no estaban cubiertas porque eran casos imposibles o porque era código muerto que habíamos olvidado eliminar. Iterar varias veces con promts como el anterior hizo que no solo mejoráramos el código, sino que llegamos a una cobertura mucho más alta que la inicial.

La primera vez que alguien me pagó por programar fue para que incluyera algunas funcionalidades escritas en PHP en una web ya existente, eso fue en el año 2005. Es decir que hace 21 años que programo profesionalmente, que escribo yo mismo el código que luego se termina ejecutando en diferentes lugares. ¿Seguiremos escribiendo nosotros mismos el código o nos transformaremos quienes controlen a los agentes, les indiquen que hacer y terminen revisando el código? Creo que me inclino por la segunda opción.

Hola Spacemacs

Hasta fines de 2011 usaba editores "comunes" para programar. Durante mucho tiempo usé gedit tuneado hasta la manija. Me acuerdo que una de las mejor configuraciones que le había hecho era que desde el editor podía seleccionar una query SQL, tirársela a un MySQL y el resultado se renderizaba en una tabla dentro del propio gedit. Ni que hablar de habilitarle snippets y un montón más de chirimbolos útiles para programar. Durante algún tiempo también usé kate, y también recontra tuneado.

A veces pienso que a esos dos editores pude sacarles más que el máximo de lo que podían dar. Es decir, son editores de textos súper simples que están pensados para editar algún que otro archivo y no mucho más. Sin embargo yo les sacaba mucho jugo.

En aquellos años había obviamente IDEs, pero eran monstruos que chupaban todos los recursos de la computadora (como ahora?). Vos podías usar Eclipse o NetBeans, pero te olvidabas de hacer más cosas con esa computadora. A pesar de que podías configurarlos y funcionaban con otros lenguajes, esos dos IDEs estaban pensados principalmente para desarrollar Java y yo por aquellos años laburaba con PHP.

A medida que vas avanzando como programador, como dice mi amigo Osiris, necesitás empezar a "afilar el hacha". No alcanza con seguir haciendo lo mismo, pero con más intensidad, para volverte más productivo. Hace falta mejorar o cambiar las herramientas que usás, y como decía, a gedit y a kate ya les había sacado todo el jugo posible. El camino era obvio: Tenía que cambiar de editor.

La Iglesia de Emacs o la secta de Vim

Había muchas alternativas, pero Vim y Emacs sobresalían por sobre el resto. Ambos eran altamente configurables y estaban a un apt-get install de distancia. Recuerdo que por aquellos años la mayoría de mis compañeros de trabajo usaban vim, con lo cual el camino de migración era fácil. Con ellos iba a poder tener todo el soporte necesario! Además de que más que usuarios de Vim, era Testigos de Jehová de Vim. Todos los sábados a la mañana salían a tocar timbre por el barrio para contar las bondades del editor de la bestia ;-P

Sin embargo, había algo de vim que no me cerraba, no sé, quizás era el hecho de que una vez que entrabas no podías salir (como toda buena secta) o que para empezar a escribir tenías apretar la tecla insert o que para seleccionar un fragmento de texto tenías que apretar Esc y luego la v para entrar a un modo "visual"... mucho kilombo la verdad.

Así que me decidí por emacs y en el verano del 2012 lo empecé a usar y me enamoré. Durante varios años fui agregando configuraciones útiles que iba descubriendo, pero llegó un momento allá por 2018 que el archivo .emacs.d/init.el (y su gestión) se empezó a volver inmanejable. Simultáneamente, empecé a notar que la forma en la cual yo agregaba paquetes a emacs, esto es bajando el tar.gz, descomprimiéndolo en el directorio .emacs.d/ y cargándolo a mano en init.el no era nada cool, la onda era usar use-package.

Entonces pensé: Por qué no re-escribo toda mi configuración usando use-package de una manera mucho más ordenada y la comparto para que otras personas la puedan usar?

Fue así que nació "The Pepe emacs config", un repo que contenía mis configuraciones preferidas y que al mismo tiempo le podía servir a otra gente. Me acuerdo que pasé muchas horas (días?) tuneando esa configuración. Usaba monokai mi theme preferido, tenía magit, flycheck, autocompletado con company, etc.

autocompletado

magit

Todo concluye al fin

Al principio parece que no, pero mantener solo tu propia configuración de emacs es algo que lleva tiempo, bah en realidad creo que mantener la config de cualquier software que sea altamente customizable (programable en el caso de emacs) lleva tiempo. Sobre todo porque de vez en cuando querés agregar algún paquete nuevo y no siempre la configuración de ese paquete es tan simple o porque algún paquete que usás deja de estar mantenido, etc.

Entonces me pregunté:

¿Para qué mantener mi propia configuración si puedo usar alguna distribución de emacs como Spacemacs o Doom Emacs mantenida comunitariamente y agregarle solo mis "extras"?

Y así fue que decidí migrar todas mis configuraciones a una distribución comunitaria de Emacs. La verdad es que la decisión entre Spacemacs y Doom Emacs, fue fácil: Tiré la moneda al aire y cayó del lado de Spacemacs ;-P

Algo que me gusta mucho de Spacemacs es el concepto de layers y el hecho de que sean mantenidas comunitariamente.

Empecé a migrar los paquetes que uso habitualmente y mis configuraciones creando una pepe layer. Me parecía una buena alternativa y así lo hice. Luego de que terminé de hacerlo y todo "funcionaba" me dí cuenta que soy un gil, de esa forma lo único que hacía era replicar mis cosas sobre Spacemacs sin hacer uso de todo lo que ellos proveen. Así que borré todo y empecé de vuelta.

Esta vez fui viendo uno a uno los paquetes que yo uso y que layer la proveía. De esa manera fui activando uno a uno los layers necesarios. Habilitar layers en Spacemacs es fácil, simplemente hay que agregarlo a la variable dotspacemacs-configuration-layers dentro del archivo ~/.spacemacs. En mi caso, los layes que activé son:

dotspacemacs-configuration-layers
   '(auto-completion
     cmake
     docker
     emacs-lisp
     git
     github-copilot
     helm
     javascript
     json
     lsp
     markdown
     multiple-cursors
     neotree
     org
     php
     (python :variables
             python-backend 'lsp
             python-lsp-server 'pyright)
     (spell-checking :variables spell-checking-enable-auto-dictionary t)
     syntax-checking
     (tabs :variables
           centaur-tabs-enable-ace-jump t
           centaur-tabs-adjust-buffer-order t
           centaur-tabs-show-navigation-buttons t
           centaur-tabs-set-bar 'under
           centaur-tabs-set-icons t)
     terraform
     (version-control :variables
                      version-control-diff-side 'left)
     theming
     toml
     yaml)

Pero con sólamente activar layers no me alcanzaba, porque yo tenía definidos un montón de keybindings y funciones custom. ¿Dónde pongo todas esas cosas? Bueno, en Spacemacs el lugar para poner todas esas cosas en dentro de la función dotspacemacs/user-config que está definida también dentro del archivo ~/.spacemacs, así por ejemplo para definir una función que borre el contenido dentro de comillas o paréntesis y asignar esa función a una combinación de teclas tenés que hacer:

(defun dotspacemacs/user-config ()

  (defun delete-inside-paired-delimiters ()
    "Si el cursor está dentro de comillas, borra el contenido entre las comillas.
Si no, borra el contenido dentro del bloque delimitado más cercano: (), {}, []."
    (interactive)
    (let ((ppss (syntax-ppss)))
      (cond
       ;; Si estamos dentro de una string (comillas)
       ((nth 3 ppss)
        (let ((start (save-excursion
                       (goto-char (nth 8 ppss))
                       (forward-char)
                       (point)))
              (end (save-excursion
                     (goto-char (nth 8 ppss))
                     (forward-sexp)
                     (backward-char)
                     (point))))
          (delete-region start end)))

       ;; Si estamos dentro de cualquier bloque: (), {}, []
       ((nth 1 ppss)
        (let ((start (save-excursion
                       (goto-char (nth 1 ppss))
                       (forward-char)
                       (point)))
              (end (save-excursion
                     (goto-char (nth 1 ppss))
                     (forward-sexp)
                     (backward-char)
                     (point))))
          (delete-region start end)))

       ;; Si no estamos en un bloque ni comillas
       (t (message "No estás dentro de una cadena ni de un bloque delimitado.")))))


  (global-set-key (kbd "C-c d") #'delete-inside-paired-delimiters)
)

De esta forma pude reemplazar la configuración compleja que tenía en mi Pepe Emacs Config con Spacemacs, un conjunto de layers y unas pocas configs extra.

Estoy contento ;-)

Cambiando teclas en Wayland

Teclado nuevo

Hace una semana me llegó una computadora de escritorio nueva que me compré para trabajar, pero como venía laburando hace muchos años (20?) con notebooks, también me tuve que comprar un teclado. Durante algunas semanas me estuvo dando vueltas en la cabeza la idea de comprarme un teclado mecánico. Me copaba la idea de tener un teclado que hiciese "ruidito" al escribir y sumarme a todo el hype alrededor de eso. Sin embargo hubo un instante en el que pensé:

¿Y si llega un momento en que el ruido de las teclas mecánicas me lima la bocha y quiero tirar el teclado a la basura?

Así que decidí no ir por ese lado y comprar un teclado lo más parecido al de mi notebook posible. Lo que buscaba, entonces, era no cambiar demasiado la experiencia al tipear. Así, quería que el teclado tuviese estas características:

  • Retroiluminado (no farolero)
  • Teclas planitas
  • Distribución español (latinomericana si fuese posible)

Claro, en este punto ya había hecho todo una investigación alrededor de los teclados mecánicos. Es más, tenía uno seleccionado para compar y todo. Así que tenía que ponerme a "investigar" nuevamente. Mientras buscaba, me acordé que hacía un tiempo Fisa había tuiteado maravillas de un teclado (Satechi W3) parecido al que buscaba.

Buscando en esa marca, encontré un modelo muy parecido al de Fisa pero sin pad numérico, lo que lo hacía incluso más parecido al de mi notebook y encima en español! El Satechi X1. No tiene distribución lationamericana, pero creo que puedo vivir con eso 😜

Satechi X1

No todo funciona bien

Una vez que me llegó la compu nueva, le conecté el teclado recién comprado, un mouse y le instalé Kubuntu 24.10. Todo andaba genial! La compu nueva es un avión y encima Plasma 6 está mejor que nunca.... hasta que encontré un problema! Había un par de teclas en el teclado intercambiadas! Cuando apretaba la tecla º me mostraba < y viceversa.

Satechi X1

Era como si el que conectó los cablecitos cuando armaba el teclado se hubiese equivocado. Salvo que es una placa impresa y no tiene cables que conectar. Al principio pensé que era una mala configuración de distribución del teclado en KDE, pero por desgracia no era eso.

Atrás de mi escritorio tengo una vieja iMac G3 con teclado en castellano. ¿Pasaría lo mismo si conectase ese teclado a la nueva compu?

Teclado imac G3

La respuesta es No. El viejo teclado de la iMac G3 funcionaba perfecto... es decir cada tecla hace exactamente lo que promete.

¿Como cuernos arreglo esto?

Escribí un tweet post en BlueSky contando esto, y Facundo me respondió con un post en su blog donde explica como funciona el teclado en Linux y como solucionó un problema parecido. El post está buenísimo, vayan a leerlo!

Sin embargo a pesar de que ambos usamos KDE sobre Ubuntu la solución que él implementa a mi no me sirve.

Paraaaaaá!! Quién sos?!! Tan especial sos que una solución perfectamente documentada no te sirve???

Bueno, sí, como servir me sirve. Aprendí muchísimo de como funciona el teclado en Linux. Lo que pasa es que estoy usando Wayland y no X11.

¿Me querés decir por qué cuernos usas Wayland en vez del RECONTRA probado X11, Jose?

Buenos, decidí usar Wayland cuando instalé la nueva compu por dos motivos. El primero es porque parece estar lo suficientemente sólido como para darle una oportunidad. Y el segundo motivo es porque Plasma 6 tiene una feature que te permite tiling windows de manera muy fácil que en X11 no anda y que tengo ganas de usar.

Como decía, siguiendo la solución que describe Facu en su post pude arreglarlo en X11 pero en Wayland eso no funciona. (De hecho encontré una forma de arreglarlo para X11 sin modificar archivos del sistema, pero eso vendrá en otro post 😉)

La solución que encontré funciona tanto para X11 como para Wayland. Pero vos que estás leyendo esto podrías parafrasear a Pipo Gorosito y decir: "Bueno, al final mucho 'Biri biri' pero no contás como lo arreglaste!"

Bueno, bueno, ahí vamos!

Manos a la obra

Hay un comando super piola que me ayudó muchísimo a entender que pasaba con mi teclado nuevo. Ese comando es showkey. Este comando te dice cuál es el código de la tecla que estás pulsando. Así por ejemplo al pulsar la teclas º y < en mi nuevo teclado veía:

$ sudo showkey
el modo del teclado era ¿DESCONOCIDO?
[ si lo está intentando bajo las X, podría no funcionar
ya que el servidor X también está leyendo /dev/console ]

press any key (program terminates 10s after last keypress)...
código de tecla  28 liberada
ºcódigo de tecla  86 pulsada
código de tecla  86 liberada
<código de tecla  41 pulsada
código de tecla  41 liberada

Entonces para el Satechi X1 tengo:

  • º -> código 86
  • < -> código 41

Pero en el teclado de la iMac G3 (que funciona bien) veía:

$ sudo showkey
el modo del teclado era ¿DESCONOCIDO?
[ si lo está intentando bajo las X, podría no funcionar
ya que el servidor X también está leyendo /dev/console ]

press any key (program terminates 10s after last keypress)...
código de tecla  28 liberada
ºcódigo de tecla  41 pulsada
código de tecla  41 liberada
<código de tecla  86 pulsada
código de tecla  86 liberada

Es decir:

  • º -> código 41
  • < -> código 86

Ahí comprobé lo que veía al pulsar las teclas: Ambas estaban intercambiadas.

Pero claro esos son los códigos de las teclas. ¿Cuales son los nombres de esas mismas teclas?

Para eso vamos a necesitar instalar una utilidad extra: evtest:

$ sudo apt install evtest

Ahora si ejecutamos esa utilidad veremos:

$ sudo evtest
No device specified, trying to scan all of /dev/input/event*
Available devices:
/dev/input/event0:  Power Button
/dev/input/event1:  AT Translated Set 2 keyboard
/dev/input/event10: Generalplus Usb Audio Device
/dev/input/event11: HD-Audio Generic HDMI/DP,pcm=3
/dev/input/event12: HD-Audio Generic HDMI/DP,pcm=7
/dev/input/event13: HD-Audio Generic HDMI/DP,pcm=8
/dev/input/event14: HD-Audio Generic HDMI/DP,pcm=9
/dev/input/event15: HD-Audio Generic Mic
/dev/input/event16: HD-Audio Generic Headphone
/dev/input/event17: Logitech M720 Triathlon Multi-Device Mouse
/dev/input/event2:  Video Bus
/dev/input/event8:  SONiX Satechi Compact Keyboard
Select the device event number [0-17]:

Eelijo 8 que es el teclado en cuestión y

Para la tecla que en el teclado tiene impreso º pero que en pantalla muestra < veo:

Event: time 1740947733.403546, -------------- SYN_REPORT ------------
Event: time 1740947734.449231, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70064
Event: time 1740947734.449231, type 1 (EV_KEY), code 86 (KEY_102ND), value 1
Event: time 1740947734.449231, -------------- SYN_REPORT ------------
<Event: time 1740947734.519201, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70064
Event: time 1740947734.519201, type 1 (EV_KEY), code 86 (KEY_102ND), value 0

y para la tecla que tiene impreso < pero que en pantalla muestra º veo:

Event: time 1740947763.400241, -------------- SYN_REPORT ------------
Event: time 1740947766.543270, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70035
Event: time 1740947766.543270, type 1 (EV_KEY), code 41 (KEY_GRAVE), value 1
Event: time 1740947766.543270, -------------- SYN_REPORT ------------
ºEvent: time 1740947766.588388, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70035
Event: time 1740947766.588388, type 1 (EV_KEY), code 41 (KEY_GRAVE), value 0

Con esta utilidad volvemos a comprobar los códigos de cada tecla, pero también sus nombres (name) y el valor (value).

Así cada tecla tiene

  • Tecla impresa º pero que en partalla muestra < -> código 86 -> nombre KEY_102ND -> valor 70064
  • Tecla impresa < pero que en pantalla muestra º -> código 41 -> nombre KEY_GRAVE -> valor 70035

Ya tenemos los nombres de las teclas a intercambiar y sus valores. Lo que quiero ahora es intercambiar esas teclas SOLO para mi teclado Satechi X1, porque si conecto un teclado que no tiene este inconveniente, quiero que siga funcionando bien.

La información de los dispositivos conectados a la PC lo podemos ver en el archivo: /proc/bus/input/devices, en mi caso veo:

cat /proc/bus/input/devices | grep -A7 -B1 Satechi
I: Bus=0003 Vendor=0c45 Product=7016 Version=0111
N: Name="SONiX Satechi Compact Keyboard"
P: Phys=usb-0000:c5:00.3-1.2/input0
S: Sysfs=/devices/pci0000:00/0000:00:08.1/0000:c5:00.3/usb1/1-1/1-1.2/1-1.2:1.0/0003:0C45:7016.0005/input/input9
U: Uniq=
H: Handlers=sysrq kbd event9 leds
B: PROP=0
B: EV=120013
B: KEY=7 ff800000000007ff e0b2ffdf01cfffff fffffffffffffffe

De toda esta información lo que nos interesa es la segunda fila donde se define el nombre del dispositivo:

N: Name="SONiX Satechi Compact Keyboard"

El siguiente paso es crear un archivo que se llame por ejemplo: 99-satechi-x1.hwdb con el siguiente contenido:

evdev:name:SONiX Satechi Compact Keyboard:*
 KEYBOARD_KEY_70064=grave
 KEYBOARD_KEY_70035=102nd

Lo que dice este archivo es que la tecla KEYBOARD_KEY_70064 (la tecla que emite el valor 70064 y en mi caso es la tecla que tiene impreso º pero que en pantalla muestra <) va a ser grave (KET_GRAVE) y que la tecla KEYBOARD_KEY_70035 (la tecla que emite el valor 70035 y que en mi caso es la tecla que tiene impreso < pero que en paranlla muestra º) va a ser 102nd (KEY_102ND). Y esos cambios solo se aplican al dispositivo: SONiX Satechi Compact Keyboard.

Lo que queda es bien simple:

  1. sudo cp 99-satechi-x1.hwdb /etc/udev/hwdb.d
  2. sudo systemd-hwdb update
  3. sudo udevadm trigger

Y voilà! Ahora la tecla º muestra en pantalla º y la tecla < muestra en pantalla <!!!

Mi primer video en YouTube: Canonical Observability Stack

Desde Octubre de 2020 trabajo en Canonical, la empresa que hace una de las distribuciones de GNU+Linux más famosas: Ubuntu. Pero yo no laburo haciendo Ubuntu, estoy en un equipo llamado "Observability". En el equipo nos dedicamos a escribir un stack de Charms para Juju llamado COS-Lite. La idea del stack es que ejecutando un solo comando:

juju deploy cos-lite--trust

Puedas deplegar una suite completa de Observablidad compuesta por Grafana, Prometheus, Loki, Alertmanager, etc.

Bueno, la cosa es que hace un tiempo charlando con Simme, mi manager, le contaba que hay miles de personas en toda América Latina a las cuales lo que hacemos le resultaría super interesante, pero que toda la información esté en Inglés, suponía una barrera infranqueable para acceder. Que si esos contenidos estuviesen en Castellano muchísima gente podría acceder a lo que hacemos. Asi que bueno, medio que me mandé al muere solo 😜

Acá les dejo el primer video en Castellano, en el cual hago una introducción a COS-Lite... aún soy un poco de madera grabando videos para YouTube, pero ya voy a mejorar 😜

No dejamos de caer

Estaba leyendo este informe de la CAME, citado por Clarín, donde muestran como durante todo el 2016 y en los primeros 2 meses de 2017 las ventas minoristas (en unidades) no dejaron de caer. La ecuación es bastante sencilla: Si los comercios venden menos y si encima abrís las importaciones para bienes de consumo, las fábricas producen menos. Si las fábricas producen menos, echan personal. Si las fábricas echan personal, la desocupación sube. Si la desocupación sube, la pobreza sube. El slogan de "Pobreza 0" es sólo eso.

Estos muchachos chocaron la calesita. #Cambiamos

/galleries/posts/CAME_caida.jpg

Devaluar genera inflación

En los últimos años la inflación fue un problema en nuestro país. Sin embargo desde Septiembre de 2014 hasta Noviembre de 2015, la inflación interanual en la Ciudad de Buenos Aires no hizo más que caer, llegando a estar en un 23,8%.

Desde diciembre de 2015 pasa todo lo contrario, ya que la inflación se ha duplicado, tocando picos de 47,2%. Todo esto acompañado por una caída muy importante del consumo. Y pensar que Alfonso Prat Gay decía que una devaluación del 60% y el tarifazo no se iba a trasladar a precios.

Inflación interanual

Sí, estábamos mejor Roberto

Hoy leía en La Nación un artículo del economista Roberto Cachanosky en el cual se pregunta, pasado casi un año de que Cambiemos llegó al poder, si estábamos mejor con el kirchnerismo, que ahora. Pregunta que no responde directamente, salvo cuando afirma:

"Incluso desde el kirchnersimo insisten con sus relatos de ficción y pretenden instalar que con ellos la gente estaba mejor."

Como ese es un artículo de opinión, ergo es opinable y estoy desvelado voy a opinar acerca de los argumentos que lo sustentan. Sin embargo es un buen ejercicio saber desde que lugar te dicen lo que te dicen. En ese sentido, no descubrí la penicilina si afirmo que habla desde lo que en Argentina entendemos como "liberalismo" [1] u ortodoxia económica. Ese lugar desde el cual corren por derecha al macrismo.

Ahora bien, es justo explicitar también desde que lugar hablo yo. Hablo desde lugares como este, o este. Simplificando: kirchnerismo.

Hechas estas aclaraciones, vamos al grano. Roberto Cachanosky afirma:

"¿Es cierto que con el kirchnerismo la gente estaba mejor? Formular esta pregunta es como si yo vendiera mi casa, mi auto y me llevara todos mis ahorros y me fuera a pasear por Europa hospedándome en los mejores hoteles, comiendo en los mejores restaurantes y alquilando los automóviles más caros. Cuando se me acabe el dinero vuelvo a la Argentina y no voy a tener trabajo, ni casa, ni auto, ni ahorros. Es más, me voy a tener que poner a trabajar.

Si alguien me preguntara cuándo estaba mejor. Si cuando paseaba por Europa o ahora, es obvio que la respuesta sería: cuando paseaba por Europa. El punto es que esa no es toda la verdad porque mi gran vida en Europa no era sustentable."

Lo primero que uno tiene para decir es que si se parte de premisas falsas, nunca se arribará a conclusiones verdaderas. Y la premisa falsa reside en comparar la macroeconomía de un país con la microeconomía de una familia. Y es falsa, entre otras cosas, porque una familia es incapaz de emitir dinero, sin embargo un país sí puede hacerlo.

Dejando de lado este error conceptual, que podría deberse a que Cachanosky quiso explicar su punto lo más sencillamente posible, lo segundo que uno tiene para decir es que nuestro país lejos estuvo de vender todo lo que tenía para pagarse un viaje de placer, como sí sucedió en la década del 90 cuando se remataron las empresas del Estado.

En el período 2003-2015 se pasó de una desocupación en torno al 25% a una en torno al 7%. Se mejoró el índice de Gini desde 0,49 a 0,38, siendo el país menos desigual de la región y se otorgó jubilación a quienes habían trabajado todo su vida y sus empleadores no habían hecho los aportes correspondientes.

Evolución del índice de Gini.

Sigue Cachanosky:

"¿Cómo financió esa fiesta artificial de consumo el kirchnerismo? Además de tener la suerte del viento de cola con precios de la soja que llegaron hasta los U$S 600 la tonelada, recurrió a vender la casa, el auto y los ahorros.

Esa "fiesta de consumo artificial" tenía la particularidad de ser muy real: El empleado medio, con su sueldo medio, llegaba a fin de mes gracias a que se habían restituido paritarias y las mismas le ganaban la mayoría de las veces a la inflación.

Un trabajador, lejos está de llevar sus ahorros a Bahamas vía Panamá, los invierte en el país, comprándose un auto, una casa o una computadora nueva.

Si tiene plata en el bolsillo, por ejemplo, puede ponerle nafta al auto. Si puede ponerle nafta al auto, sale con sus amigos a tomar una cervecita mirando el rio. Si toma esa cerveza con amigos, el dueño del bar vende más. Si el dueño del bar vende más, podrá aumentar el sueldo a los mozos. Si a los mozos les alcanza para vivir dignamente, también podrán salir ellos a tomar una cervecita al río... y así sucesivamente 40 millones de veces.

Entre chop y chop, también el tipo que reparte la cerveza tiene laburo, al igual que el obrero que la embotella y el peón de campo que cosecha la cebada.

Pero como al empleado medio tomar mucha cerveza le hace doler la cabeza, prefiere intercalar las salidas al río, organizando asados con los compañeros de trabajo. Si tiene suerte y está cerca de principios de mes, incluso comprará un par de botellas de Luigi Bosca.

Continúa Cachanosky:

"El kirchnerismo prohibió las exportaciones de carne e hizo que durante un tiempo pudiésemos comer el asado barato gracias a que nos consumimos 12 millones de cabezas de ganado. El kirchnerismo desalentó la actividad ganadera y forzó a que la gente consuma el stock ganadero y por eso hoy un kilo de asado sale una fortuna. Hay que reponer los rodeos y eso llevará tiempo."

Bueno, esto sencillamente no es verdad. Los dos mil millones de dólares promedio anuales de exportación de carne desmienten a Cachanosky.

Evolución de la exportación de carne

Adicionalmente este interesante informe de la UNSAM sobre el mercado de la carne, en la página 17 asegura:

"El stock ganadero registra para el año 2014 52,1 millones de cabezas. Según datos del IPCVA (Instituto de la Promoción de Carne Vacuna), se prevé un aumento de 3,6 millones para el año 2015, con lo cual no se alcanzaría el record de 60 millones de cabezas registrados en el año 2007. Las bajas se deben principalmente al terreno cedido frente a la agricultura, actividad que para el periodo analizado obtuvo record históricos."

Tuve la tentación de agregar algo más a este párrafo contundente, de un informe serio, pero creo que se entiende el punto, no?

Continúa Cachanosky:

"Durante 12 años nos dieron tarifas de luz "baratas" y no dejaron margen para que las empresas mantuvieran el stock de capital. El resultado es que volvieron los cortes de luz como en la época de Alfonsín, el sistema energético está colapsado y se necesitan miles de millones de dólares para reconstruirlo. Claro, mientras se pagaban tarifas "baratas" la gente podía usar ese dinero para pagar la cuota del celular o del televisor plasma. Todos contentos consumiendo mientras se derrumbaba el sistema energético."

Bueno, acá hay que diferenciar algunas cosas. Cuando hablamos del sistema energético tenemos que comprender que el mismo está formado por 3 subsistemas: Generación, transmisión y distribución. Respecto a generación este informe de la CNEA, nos muestra como ha ido evolucionando la potencia instalada desde 2003 a 2013. En esos 10 años, la capacidad de generación aumentó aproximadamente un 32% y con ella la capacidad de transportar esa energía. Adicionalmente desde 2006, todo el país forma parte del Sistema Interconectado Nacional.

Evolución de la potencia instalada

Los cortes en la época de Alfonsín, se debían a que el país no podía producir la energía eléctrica que se demandaba. Hoy no pasa eso. El país puede producir la energía que demanda.

La política de subsidios en general y a la energía en particular tenía un fin muy claro: mejorar el ingreso disponible de las familias y reducir los costos de producción de las fábricas. Fábricas que generan puestos de trabajo.

El sistema energético no colapsó. Lo que colapsó, en todo caso, en los días de mucho calor y sobre todo en algunas zonas de CABA y el conurbano fue la distribución de energía. EDESUR y EDENOR mayormente.

  • ¿Fue un error del kirchnerismo no haber controlado correctamente esto? Mi opinión es que sí.

  • ¿Fue un error del kirchnerismo no haber estatizado un servicio escencial y monopólico por naturaleza? Mi opinión es otra vez que sí.

  • ¿Fue un error del kirchnerismo haber otorgado subsidios a las familias y a las fábricas? Mi opinión es rotundamente que no.

Es una decisión política mejorar el ingreso disponible de las familias y bajar los costos de producción de las fábricas. Como es una decisión política eliminar las retenciones a las mineras o a los pooles sojeros, mientras se devalúa la moneda un 60%.

Sobre este fragmento...

"Incluso se consumieron nuestros ahorros que teníamos para jubilarnos en las AFJP. Podrá gustar o no el sistema, pero si no gustaba no era para que el estado se quedara con nuestra plata para financiar la fiesta de consumo artificial y condenar a los que hoy estamos trabajando a una mísera jubilación futura."

habría que entender que entiende Cachanosky por "consumir", ya que el Fondo de Garantía de Sustentabilidad de la ANSES, desde 2008 a 2015 creció un 443%, pasando de 98.082 millones de pesos a 532.838 millones al 30 de junio de 2015. El sistema de AFJP estaba destinado al colapso, como sucede hoy en Chile. Tan destinado al colapso, que antes de la estatización, sólo 3,2 millones de las personas en edad de jubilarse podían hacerlo. Hoy hay aproximadamente 6 millones de jubilados es decir más del 95% de las personas en edad de hacerlo.

"Podríamos seguir con la destrucción patrimonial del BCRA, de las economías regionales o del desestímulo para producir trigo y maíz, pero lo cierto es que el kirchernismo literalmente destruyó la economía."

Uno podría entrar en una discusión semántica respecto a que significa "destruir la economía". Sin embargo de números se habla con números. Durante el kirchnerismo se redujo el desempleo como dijimos, del 25% al 7%. Esto significa millones de puestos de trabajo. Se redujo la deuda externa del 150% al 40% del PIB. La industria creció un 110% según la propia UIA entre 2003 y 2013. La industria automotríz arañó el millón de autos producidos en un año y re-abrieron miles de PyMEs.

Respecto a "desestímulo para producir trigo y maíz", eso tiene un nombre: soja. La misma suerte corrió la ganadería como vimos anteriormente. Todas estas cosas, y muchas más que decido obviar para no sobre abundar, tuvieron como resultado que además, la mortalidad infantil, según UNICEF, cayera desde 16,5 por mil en 2003 a 10,8 por mil en 2013. Quizás el indicador económico más importante de todos.

Mortalidad infantil
"Si el kirchnerismo hubiese ganado las elecciones, íbamos de cabeza a un sistema totalitario al estilo venezolano porque no había más margen para entretener a la gente con la fiesta de consumo."

Bueno, acá el bueno de Roberto deja volar su imaginación... que se yo, dejémoslo.

Lo que sigue del artículo de Roberto Cachanosky en La Nación es un exhorto al gobierno de Cambiemos para que haga la tarea "docente" de explicar las medidas económicas que toma y así hacerse cargo de justificar, por ejemplo por qué aumentó la desocupación, por qué cayó el salario real o por qué hay 1,4 millones de pobres más si el objetivo de Cambiemos era "Pobreza 0". Ahí ya no me interesa opinar, son internas de la derecha vernácula.

En economía y sobre todo en política, todo es opinable, sin embargo es una buena práctica poder sustentar las opiniones con más cosas que simples frases hechas, porque si no corrés el riesgo de que un desvelado como yo te refute muy fácilmente ;-)

Notas:

Vamos a duplicar la inversión en ciencia y tecnología

  • Vamos a duplicar la inversión en ciencia y tecnología y la reducimos en el próximo presupuesto.

  • Vamos hacia pobreza 0 y la pobreza sube.

  • Vamos a crear trabajo y el desempleo sube.

  • Vamos a mantener el Fútbol para todos y se termina el 31 de diciembre.

  • Y así...

¿Vamos a aceptar que el cambio que le vendieron al pueblo argentino era mentira o nos vamos a seguir haciendo los boludos?

Nos miente en la cara

La revolución de la alegría no detiene su marcha

Hace 3 meses decía que si la producción industrial caía iba a haber más desempleo y todos la íbamos a pasar peor. Bueno, en los primeros 6 meses de 2016 el gobierno de Mauricio Macri parió un incremento de más del 50% en la tasa de desocupación. Así, pasamos de 6% en diciembre de 2015 (la tasa más baja de los últimos 40 años) al 9,3% a fines de Junio de 2016. [1]

Aún no logro comprender como llegar a la Pobreza 0 incrementando el desempleo.