<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Anotaciones por Pablo Caro</title><link href="https://pablocaro.es/" rel="alternate"></link><link href="https://pablocaro.es/feeds/all.atom.xml" rel="self"></link><id>https://pablocaro.es/</id><updated>2026-08-03T00:00:00+02:00</updated><subtitle>Anotaciones</subtitle><entry><title>ku: Kubernetes desde la terminal como un ninja</title><link href="https://pablocaro.es/ku-kubernetes-tui" rel="alternate"></link><published>2026-08-03T00:00:00+02:00</published><updated>2026-08-03T00:00:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-08-03:/ku-kubernetes-tui</id><summary type="html">&lt;p&gt;ku es una TUI para Kubernetes rápida, navegable solo con teclado e inspirada en k9s y lazygit. Read-only por defecto, con cockpit de cluster, logs, shell en pods, port-forward y edición de objetos.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="ku TUI" src="/images/ku-kubernetes-tui.jpg"&gt;&lt;/p&gt;
&lt;p&gt;He vuelto a trabajar con Kubernetes. Tengo desplegada en local una arquitectura de micrositios — varios namespaces, deployments, servicios, ingresses — y necesitaba una forma rápida de moverme por todo sin estar constantemente tirando de &lt;code&gt;kubectl get&lt;/code&gt; y &lt;code&gt;kubectl describe&lt;/code&gt; a mano. La memoria muscular del &lt;code&gt;kubectl&lt;/code&gt; está bien, pero para navegar un cluster con soltura necesitas ver el panorama completo.&lt;/p&gt;
&lt;p&gt;Entré a GitHub y encontré &lt;a href="https://github.com/bjarneo/ku"&gt;ku&lt;/a&gt;, una TUI para Kubernetes de un desarrollador noruego. Es rápida, va solo con teclado y está claramente inspirada en k9s, Lens y lazygit. Me gustó desde el primer &lt;code&gt;ku&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="instalacion"&gt;Instalación&lt;a class="headerlink" href="#instalacion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-fsSL&lt;span class="w"&gt; &lt;/span&gt;https://raw.githubusercontent.com/bjarneo/ku/main/install.sh&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;O con Go:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;go&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;github.com/bjarneo/ku@latest
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="lo-que-me-ha-convencido"&gt;Lo que me ha convencido&lt;a class="headerlink" href="#lo-que-me-ha-convencido" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Read-only por defecto&lt;/strong&gt;. Arrancas y no puedes romper nada. Para editar, borrar o escalar tienes que activar el modo edición con &lt;code&gt;Shift+E&lt;/code&gt;. El header muestra un chip verde &lt;code&gt;● READ-ONLY&lt;/code&gt; o rojo &lt;code&gt;● EDIT&lt;/code&gt; para que siempre sepas en qué modo estás. Para los que tenemos dedos de mantequilla, esto es paz mental.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cockpit al arrancar&lt;/strong&gt;. Una vista general del cluster: versión del server, estado de los nodos, gauges de CPU y memoria en vivo, pods por fase, deployments listos y warnings recientes (deduplicados, con contador de recurrencia). Se refresca solo cada pocos segundos.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Tablas con las columnas correctas&lt;/strong&gt;. Usa la misma representación &lt;code&gt;Table&lt;/code&gt; de la API que &lt;code&gt;kubectl get&lt;/code&gt;, así que las columnas son exactamente las que esperas para cada recurso. Los CRDs también funcionan. Las celdas tienen color semántico: verde sano, amarillo transitorio, rojo roto. La columna ordenada muestra la dirección.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Layout lazygit&lt;/strong&gt;: panel izquierdo con la navegación de recursos, &lt;code&gt;Tab&lt;/code&gt; para cambiar entre paneles, barra de estado inferior que muestra solo las teclas relevantes para lo que tienes seleccionado.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Resúmenes curados&lt;/strong&gt;. &lt;code&gt;Enter&lt;/code&gt; sobre un objeto abre un resumen específico para ese tipo: los pods muestran uso live, health, requests/limits; los servicios, ingress, configmaps y secrets tienen vistas adaptadas. &lt;code&gt;d&lt;/code&gt; o &lt;code&gt;y&lt;/code&gt; abre el YAML completo con syntax highlighting.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Logs con follow&lt;/strong&gt;. &lt;code&gt;l&lt;/code&gt; sobre un pod streamea los logs. Puedes filtrar con regex, copiar líneas, cambiar entre instancia actual y anterior, y hacer toggle de auto-scroll. Todo dentro de la TUI.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Shell en pods y nodos&lt;/strong&gt;. &lt;code&gt;s&lt;/code&gt; sobre un pod abre una shell interactiva (&lt;code&gt;bash&lt;/code&gt; o &lt;code&gt;sh&lt;/code&gt;) en un overlay con terminal virtual. Sobre un nodo, levanta un pod de debug privilegiado con &lt;code&gt;chroot /host&lt;/code&gt;. &lt;code&gt;Ctrl+\&lt;/code&gt; detacha.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Port-forward de servicios&lt;/strong&gt;. &lt;code&gt;p&lt;/code&gt; sobre un Service y eliges el puerto. Puedes mapear &lt;code&gt;8080:http&lt;/code&gt; o &lt;code&gt;18080:80&lt;/code&gt;. Los forwards activos se ven en el header y los gestionas desde la paleta de comandos.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Comando &lt;code&gt;kubectl&lt;/code&gt; equivalente&lt;/strong&gt;. &lt;code&gt;C&lt;/code&gt; te muestra el comando &lt;code&gt;kubectl&lt;/code&gt; que replica la vista actual. Ideal para aprender o para copiarlo a un script.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Documentación de Kubernetes integrada&lt;/strong&gt;. &lt;code&gt;O&lt;/code&gt; abre la doc upstream del recurso seleccionado en el navegador.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Sidebar configurable&lt;/strong&gt;. Con &lt;code&gt;ku config init&lt;/code&gt; generas un &lt;code&gt;~/.config/ku/config.yaml&lt;/code&gt; donde puedes añadir CRDs al menú lateral. Soporta recursos con grupo (&lt;code&gt;scaledobjects.keda.sh&lt;/code&gt;, &lt;code&gt;certificates.cert-manager.io&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Modo desarrollador&lt;/strong&gt;. &lt;code&gt;ku --dev&lt;/code&gt; oculta recursos de administración del cluster (nodos, PVs, namespaces, eventos) y desactiva operaciones de nodo. Para cuando solo gestionas tus propias apps.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Recuerda contexto y namespace&lt;/strong&gt;. Los persiste en &lt;code&gt;~/.config/ku/state.json&lt;/code&gt; entre sesiones.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="lo-que-no-tiene-aun"&gt;Lo que no tiene (aún)&lt;a class="headerlink" href="#lo-que-no-tiene-aun" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;No hay vista de gráficos de dependencias ni topología.&lt;/li&gt;
&lt;li&gt;El port-forward es solo para Services, no para pods individuales.&lt;/li&gt;
&lt;li&gt;No tiene integración con Helm.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Es un proyecto joven (~500 estrellas, ~100 commits) pero muy bien pensado. La sensación al usarlo es la de una herramienta que entiende cómo trabajas con Kubernetes y te quita fricción en cada paso. Para mi caso actual — una arquitectura de micrositios con varios namespaces — me permite saltar entre recursos, seguir logs y ver el estado general del cluster sin salir de la terminal.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Fuente original&lt;/em&gt;: &lt;a href="https://github.com/bjarneo/ku"&gt;github.com/bjarneo/ku&lt;/a&gt;&lt;/p&gt;</content><category term="Herramientas"></category><category term="kubernetes"></category><category term="herramientas"></category><category term="terminal"></category><category term="tui"></category><category term="k8s"></category></entry><entry><title>Pluggy — Un framework de plugins para usar en tus proyectos</title><link href="https://pablocaro.es/pluggy-plugins-python" rel="alternate"></link><published>2026-06-15T00:00:00+02:00</published><updated>2026-06-15T00:00:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-06-15:/pluggy-plugins-python</id><summary type="html">&lt;p&gt;Pluggy es el framework de plugins que usa pytest, Datasette, y otros proyectos Python. Apareció en un artículo reciente de Eli Bendersky donde analiza si merece la pena, y la respuesta es: depende.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Los dos usos que más conozco de Pluggy son &lt;strong&gt;pytest&lt;/strong&gt; y &lt;strong&gt;Datasette&lt;/strong&gt;. pytest tiene posiblemente el ecosistema de plugins más rico de todo el ecosistema Python, y todo ese sistema — los &lt;code&gt;conftest.py&lt;/code&gt;, los markers, los fixtures, los reporters — funciona sobre Pluggy. Datasette es el otro ejemplo que sigo de cerca: Simon Willison lo usa para que cualquiera pueda extender Datasette con nuevos hooks sin tocar el core. De hecho, Pluggy nació extrayendo el sistema de plugins de pytest para que otros proyectos como Datasette pudieran reutilizarlo.&lt;/p&gt;
&lt;p&gt;Hace poco cayó en mis manos un artículo de &lt;strong&gt;Eli Bendersky&lt;/strong&gt; — de esos que escribe cada cierto tiempo y que merecen la pena — donde analiza Pluggy como caso de estudio de sistemas de plugins. Eli tiene toda una &lt;a href="https://eli.thegreenplace.net/tag/plugins"&gt;serie sobre fundamentos de plugins&lt;/a&gt; y aquí aplica su framework conceptual a Pluggy.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Eli Bendersky plugins" src="/images/pluggy-pluggy-logo.png"&gt;&lt;/p&gt;
&lt;h2 id="es-pluggy-una-shallow-api"&gt;¿Es Pluggy una shallow API?&lt;a class="headerlink" href="#es-pluggy-una-shallow-api" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Eli es bastante claro en su veredicto, y estoy de acuerdo:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Plugin frameworks are very easy to create, and the functionality they provide is relatively small compared to their large surface area. In other words, this is a &lt;em&gt;shallow API&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;O sea: los frameworks de plugins son fáciles de crear, y la funcionalidad que aportan es pequeña en comparación con su superficie de API. Una API &lt;em&gt;shallow&lt;/em&gt; — poca profundidad para mucho interfaz. Tiene razón. Hacer un sistema de plugins cutre en Python son cuatro líneas con &lt;code&gt;__init_subclass__&lt;/code&gt; o &lt;code&gt;entry_points&lt;/code&gt;. Pero la cuestión es qué necesitas exactamente.&lt;/p&gt;
&lt;h2 id="lo-que-realmente-aporta-pluggy"&gt;Lo que realmente aporta Pluggy&lt;a class="headerlink" href="#lo-que-realmente-aporta-pluggy" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Donde Pluggy marca la diferencia es cuando necesitas algunas de estas funcionalidades:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Registro automático via entry points&lt;/strong&gt; — si lo necesitas&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Validación de firmas&lt;/strong&gt; de los hooks&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Recolección consistente de resultados&lt;/strong&gt; entre múltiples hooks en un mismo plugin y entre varios plugins&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ordenación de plugins&lt;/strong&gt; con &lt;code&gt;firstresult&lt;/code&gt;, &lt;code&gt;tryfirst&lt;/code&gt;, &lt;code&gt;trylast&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hook "wrappers"&lt;/strong&gt; para casos de uso avanzados&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Las dos que más me interesan son la validación de firmas y la ordenación de plugins. La validación de firmas te evita el clásico bug de que tu plugin tenga una función con parámetros incorrectos y el sistema lo ignore silenciosamente. Y la ordenación es clave cuando tienes varios plugins compitiendo por el mismo hook; poder decir "este va primero" o "este va al final" sin tener que implementar un priority system tú mismo ahorra mucho.&lt;/p&gt;
&lt;p&gt;Eli concluye con la pregunta correcta:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Are these worthwhile for your project? It really depends on the project, and it's always worth keeping the tradeoff between dependencies and project effort in mind.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Pluggy es una dependencia ligera, pero sigue siendo una dependencia. Si tu sistema de plugins tiene tres hooks y dos plugins, probablemente no la necesites. Si tienes un ecosistema de plugins abierto como Datasette o pytest, tiene todo el sentido.&lt;/p&gt;
&lt;h2 id="como-se-usa-pluggy-en-tests"&gt;Cómo se usa Pluggy en tests&lt;a class="headerlink" href="#como-se-usa-pluggy-en-tests" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Simon Willison tiene dos TILs prácticos sobre Pluggy que merecen ser guardados aquí como referencia.&lt;/p&gt;
&lt;h3 id="registrar-plugins-temporales-en-tests"&gt;Registrar plugins temporales en tests&lt;a class="headerlink" href="#registrar-plugins-temporales-en-tests" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;En &lt;a href="https://til.simonwillison.net/pytest/registering-plugins-in-tests"&gt;Registering temporary pluggy plugins inside tests&lt;/a&gt;, Simon muestra cómo registrar un plugin de Datasette para la duración de un test concreto usando &lt;code&gt;pm.register()&lt;/code&gt; y &lt;code&gt;pm.unregister()&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;datasette&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hookimpl&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;datasette.plugins&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pm&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pytest&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_using_test_plugin&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestPlugin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;TestPlugin&amp;quot;&lt;/span&gt;

        &lt;span class="nd"&gt;@hookimpl&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;permission_allowed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;datasette&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;insert-api:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;permissions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;insert-api:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;pm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestPlugin&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;undo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Rest of test goes here&lt;/span&gt;
    &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;pm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unregister&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;undo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;El patrón &lt;code&gt;try/finally&lt;/code&gt; es la clave para no dejar el plugin registrado después del test. También se puede hacer con un fixture de pytest:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nd"&gt;@pytest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fixture&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;unsafe&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UnsafeInsertAll&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;UnsafeInsertAll&amp;quot;&lt;/span&gt;

        &lt;span class="nd"&gt;@hookimpl&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;permission_allowed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;insert:all&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;

    &lt;span class="n"&gt;pm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UnsafeInsertAll&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;undo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt;
    &lt;span class="n"&gt;pm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unregister&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;undo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Esto es muy potente para tests de integración donde necesitas mockear comportamientos de plugins sin tener que crear un plugin real.&lt;/p&gt;
&lt;h3 id="registrar-multiples-hooks-en-un-mismo-fichero"&gt;Registrar múltiples hooks en un mismo fichero&lt;a class="headerlink" href="#registrar-multiples-hooks-en-un-mismo-fichero" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;En &lt;a href="https://til.simonwillison.net/pluggy/multiple-hooks-same-file"&gt;Registering the same Pluggy hook multiple times in a single file&lt;/a&gt;, Simon explica cómo registrar dos implementaciones del mismo hook en un solo módulo usando &lt;code&gt;specname&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;datasette&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hookimpl&lt;/span&gt;

&lt;span class="nd"&gt;@hookimpl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;specname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;menu_links&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tryfirst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;menu_links_1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datasette&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;href&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datasette&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;label&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Home&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nd"&gt;@hookimpl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;specname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;menu_links&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;trylast&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;menu_links_2&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;href&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;http://www.example.com/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;label&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Link at the end&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;La función no se llama &lt;code&gt;menu_links&lt;/code&gt; sino &lt;code&gt;menu_links_1&lt;/code&gt; y &lt;code&gt;menu_links_2&lt;/code&gt;, y el parámetro &lt;code&gt;specname&lt;/code&gt; le dice a Pluggy qué hook implementa cada una. Esto requiere &lt;strong&gt;Pluggy 1.0.0+&lt;/strong&gt; (que tiene ya unos años, así que probablemente lo tengas si usas algo moderno). Combinado con &lt;code&gt;tryfirst&lt;/code&gt;/&lt;code&gt;trylast&lt;/code&gt;, es una forma limpia de aportar múltiples contribuciones al mismo hook desde un único plugin.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Fuente original&lt;/em&gt;: &lt;a href="https://eli.thegreenplace.net/2026/plugins-case-study-pluggy/"&gt;Plugins case study: Pluggy&lt;/a&gt; — Eli Bendersky&lt;/p&gt;</content><category term="Programación"></category><category term="plugins"></category><category term="python"></category><category term="pytest"></category><category term="testing"></category><category term="herramientas"></category></entry><entry><title>Unsloth docs: la documentación más clara sobre requisitos hardware para LLMs locales</title><link href="https://pablocaro.es/unsloth-docs" rel="alternate"></link><published>2026-06-14T00:00:00+02:00</published><updated>2026-06-14T00:00:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-06-14:/unsloth-docs</id><summary type="html">&lt;p&gt;La documentación de Unsloth es una referencia imprescindible para saber qué hardware necesitas para ejecutar y hacer fine-tuning de modelos locales. Tablas de VRAM, requisitos por GPU, y guías paso a paso para cada modelo.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="Unsloth docs" src="/images/unsloth-docs.png"&gt;&lt;/p&gt;
&lt;p&gt;Cuando te empiezas a interesar por ejecutar LLMs en local, la primera pregunta es siempre la misma: ¿qué hardware necesito? Y no es fácil encontrar una respuesta clara. Cada modelo publica sus requisitos en su Hugging Face, en blogs, en papers... La documentación de &lt;a href="https://unsloth.ai/docs"&gt;Unsloth&lt;/a&gt; hace un trabajo excelente centralizando y organizando esta información.&lt;/p&gt;
&lt;h2 id="tablas-de-vram-para-fine-tuning"&gt;Tablas de VRAM para fine-tuning&lt;a class="headerlink" href="#tablas-de-vram-para-fine-tuning" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Una de las páginas más útiles es la de &lt;a href="https://unsloth.ai/docs/get-started/fine-tuning-for-beginners/unsloth-requirements"&gt;requisitos del sistema&lt;/a&gt;, que incluye una tabla directa con VRAM necesaria para fine-tuning según el tamaño del modelo:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parámetros&lt;/th&gt;
&lt;th&gt;QLoRA (4-bit)&lt;/th&gt;
&lt;th&gt;LoRA (16-bit)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;3B&lt;/td&gt;
&lt;td&gt;3.5 GB&lt;/td&gt;
&lt;td&gt;8 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7B&lt;/td&gt;
&lt;td&gt;5 GB&lt;/td&gt;
&lt;td&gt;19 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8B&lt;/td&gt;
&lt;td&gt;6 GB&lt;/td&gt;
&lt;td&gt;22 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14B&lt;/td&gt;
&lt;td&gt;8.5 GB&lt;/td&gt;
&lt;td&gt;33 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;27B&lt;/td&gt;
&lt;td&gt;22 GB&lt;/td&gt;
&lt;td&gt;64 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;32B&lt;/td&gt;
&lt;td&gt;26 GB&lt;/td&gt;
&lt;td&gt;76 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;70B&lt;/td&gt;
&lt;td&gt;41 GB&lt;/td&gt;
&lt;td&gt;164 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;405B&lt;/td&gt;
&lt;td&gt;237 GB&lt;/td&gt;
&lt;td&gt;950 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Esto te permite hacer cálculos rápidos: con una RTX 3090/4090 de 24 GB puedes hacer QLoRA de modelos de hasta ~27B parámetros.&lt;/p&gt;
&lt;p&gt;Recomiendan mantener el batch size en 1, 2 o 3 para no OOMear. Y &lt;a href="https://unsloth.ai/docs/basics/unsloth-benchmarks#context-length-benchmarks"&gt;aquí&lt;/a&gt; tienen benchmarks de context length.&lt;/p&gt;
&lt;h2 id="guias-por-modelo-con-requisitos-especificos"&gt;Guías por modelo con requisitos específicos&lt;a class="headerlink" href="#guias-por-modelo-con-requisitos-especificos" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Cada modelo que soportan tiene su propia guía con requisitos detallados. Algunos ejemplos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Gemma 4&lt;/strong&gt;: la 12B corre en 8 GB RAM (4-bit), la 26B-A4B necesita 18 GB y la 31B necesita 20 GB.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Qwen3.5&lt;/strong&gt;: la serie Small (hasta 9B) funciona con &lt;strong&gt;12 GB de RAM/VRAM&lt;/strong&gt;. La 35B-A3B cabe en 22 GB.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Qwen3.6&lt;/strong&gt;: la 27B corre en 18 GB, la 35B-A3B en 22 GB.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kimi K2.5&lt;/strong&gt;: necesitas ~240 GB+ para 10+ tokens/s con cuantización 4-bit. El modelo completo en FP16 ocupa 630 GB.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kimi K2.6&lt;/strong&gt;: la cuantización 2-bit dinámica necesita 350 GB+.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MiniMax M3&lt;/strong&gt;: mínimo 133 GB RAM para la cuantización más ligera, recomendado 159 GB.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GLM-4.6 (355B)&lt;/strong&gt;: recomiendan 205 GB+ para 5 tokens/s.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="lo-que-hace-buena-esta-documentacion"&gt;Lo que hace buena esta documentación&lt;a class="headerlink" href="#lo-que-hace-buena-esta-documentacion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Varias cosas que la hacen especialmente útil:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tablas directas&lt;/strong&gt;: sin rodeos. Necesitas saber VRAM para un modelo de X parámetros con Y método de fine-tuning? La tabla está ahí.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Guías por modelo&lt;/strong&gt;: cada modelo tiene su página con tabla de requisitos, instrucciones de instalación con llama.cpp y ejemplos de uso.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cubre inferencia y fine-tuning&lt;/strong&gt;: no solo para entrenar, también para ejecutar modelos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multiplataforma&lt;/strong&gt;: requisitos específicos para Windows (con y sin WSL), macOS (Intel y Apple Silicon), Linux, y CPU-only.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cuantizaciones&lt;/strong&gt;: explican qué tamaño ocupa cada cuantización (GGUF) y qué hardware necesitas para cada una. Consejo recurrente: &lt;em&gt;tu RAM + VRAM combinada debe superar el tamaño del archivo cuantizado que descargues&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MTP (Multi-Token Prediction)&lt;/strong&gt;: incluyen requisitos específicos para esta técnica que predice varios tokens futuros en paralelo (~1 GB extra sobre la GGUF normal).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Todo está en &lt;a href="https://unsloth.ai/docs"&gt;unsloth.ai/docs&lt;/a&gt;. Si estás mirando hardware para montar un equipo de inferencia local, es la primera página que deberías visitar.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Fuente original&lt;/em&gt;: &lt;a href="https://unsloth.ai/docs"&gt;Unsloth Documentation&lt;/a&gt;&lt;/p&gt;</content><category term="Herramientas"></category><category term="llm"></category><category term="unsloth"></category><category term="hardware"></category><category term="local-llm"></category><category term="documentacion"></category><category term="vram"></category></entry><entry><title>AgentsView: analíticas locales para tus coding agents</title><link href="https://pablocaro.es/agentsview-analytics-agentes" rel="alternate"></link><published>2026-06-10T00:00:00+02:00</published><updated>2026-06-10T00:00:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-06-10:/agentsview-analytics-agentes</id><summary type="html">&lt;p&gt;AgentsView es una herramienta open-source que indexa las sesiones de más de 20 coding agents en una base de datos SQLite local y ofrece dashboards de analíticas, tracking de costes y búsqueda full-text sin enviar datos fuera de tu máquina.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Si usas varios coding agents (Claude Code, Codex, Pi, Cursor...) sabes lo que es perder el hilo de cuánto has gastado, qué herramientas usas más o en qué proyecto pasas más horas. Cada agente guarda sus sesiones en su propio formato y directorio, y hasta ahora no había una forma unificada de verlo todo junto.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/kenn-io/agentsview"&gt;AgentsView&lt;/a&gt; resuelve esto. Es una herramienta open-source (Go + Svelte 5, MIT) que indexa todas tus sesiones de coding agents en una SQLite local y te da dashboards de analíticas, tracking de costes y búsqueda full-text. Sin cuentas, sin telemetría, sin que los datos salgan de tu máquina.&lt;/p&gt;
&lt;p&gt;&lt;img alt="AgentsView dashboard" src="/images/agentsview-dashboard.png"&gt;&lt;/p&gt;
&lt;h2 id="instalacion"&gt;Instalación&lt;a class="headerlink" href="#instalacion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# macOS / Linux&lt;/span&gt;
curl&lt;span class="w"&gt; &lt;/span&gt;-fsSL&lt;span class="w"&gt; &lt;/span&gt;https://agentsview.io/install.sh&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bash

&lt;span class="c1"&gt;# O con Homebrew (app de escritorio)&lt;/span&gt;
brew&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;--cask&lt;span class="w"&gt; &lt;/span&gt;agentsview
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Una vez instalado, arrancas el servidor y abre el dashboard en &lt;code&gt;http://127.0.0.1:8080&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;agentsview&lt;span class="w"&gt; &lt;/span&gt;serve
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;En el primer arranque descubre automáticamente las sesiones de todos los agentes que tengas instalados.&lt;/p&gt;
&lt;h2 id="que-te-muestra"&gt;Qué te muestra&lt;a class="headerlink" href="#que-te-muestra" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Dashboard de costes&lt;/strong&gt;: gasto diario por agente y por modelo, con precios vía LiteLLM. Calcula también los cache tokens (tanto de creación como de lectura).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mapa de calor de actividad&lt;/strong&gt;: visualiza tus días y horas más productivos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Búsqueda full-text (FTS5)&lt;/strong&gt;: busca en el contenido de todas tus sesiones pasadas. Ideal para recuperar ese comando o solución que usaste hace semanas.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Visor de sesiones&lt;/strong&gt;: navega sesión por sesión, exporta a HTML o publícala como GitHub Gist.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Estadísticas por proyecto&lt;/strong&gt;: tokens usados, herramientas más frecuentes, duración media de sesión.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Live updates vía SSE&lt;/strong&gt;: si tienes sesiones activas, el dashboard se actualiza solo.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="cli-para-costes"&gt;CLI para costes&lt;a class="headerlink" href="#cli-para-costes" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Además del dashboard web, tiene una CLI muy rápida que reemplaza a &lt;code&gt;ccusage&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Resumen diario de costes (últimos 30 días)&lt;/span&gt;
agentsview&lt;span class="w"&gt; &lt;/span&gt;usage&lt;span class="w"&gt; &lt;/span&gt;daily

&lt;span class="c1"&gt;# Desglose por modelo&lt;/span&gt;
agentsview&lt;span class="w"&gt; &lt;/span&gt;usage&lt;span class="w"&gt; &lt;/span&gt;daily&lt;span class="w"&gt; &lt;/span&gt;--breakdown

&lt;span class="c1"&gt;# Filtrar por agente y fechas&lt;/span&gt;
agentsview&lt;span class="w"&gt; &lt;/span&gt;usage&lt;span class="w"&gt; &lt;/span&gt;daily&lt;span class="w"&gt; &lt;/span&gt;--agent&lt;span class="w"&gt; &lt;/span&gt;claude&lt;span class="w"&gt; &lt;/span&gt;--since&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2026&lt;/span&gt;-05-01

&lt;span class="c1"&gt;# Salida JSON para scripts&lt;/span&gt;
agentsview&lt;span class="w"&gt; &lt;/span&gt;usage&lt;span class="w"&gt; &lt;/span&gt;daily&lt;span class="w"&gt; &lt;/span&gt;--json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Como los datos ya están indexados en SQLite, las consultas son mucho más rápidas que reparsear los archivos de sesión cada vez.&lt;/p&gt;
&lt;h2 id="agentes-soportados"&gt;Agentes soportados&lt;a class="headerlink" href="#agentes-soportados" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;AgentsView auto-detecta sesiones de más de 20 agentes: Claude Code, Codex, Pi, Cursor, Copilot CLI, Gemini CLI, OpenCode, OpenHands, Qwen Code, Kimi, Warp, Forge, Zencoder y varios más. La lista completa está en el &lt;a href="https://github.com/kenn-io/agentsview#supported-agents"&gt;README&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="postgresql-para-equipos"&gt;PostgreSQL para equipos&lt;a class="headerlink" href="#postgresql-para-equipos" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Si trabajas en equipo, puedes sincronizar los datos a una PostgreSQL compartida:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;agentsview&lt;span class="w"&gt; &lt;/span&gt;pg&lt;span class="w"&gt; &lt;/span&gt;push&lt;span class="w"&gt; &lt;/span&gt;--watch&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# sincronización continua&lt;/span&gt;
agentsview&lt;span class="w"&gt; &lt;/span&gt;pg&lt;span class="w"&gt; &lt;/span&gt;serve&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="c1"&gt;# servir UI desde PostgreSQL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;La UI en modo PostgreSQL es read-only, así que cualquiera del equipo puede ver los dashboards sin modificar datos.&lt;/p&gt;
&lt;h2 id="lo-que-no-hace"&gt;Lo que no hace&lt;a class="headerlink" href="#lo-que-no-hace" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;No envía datos a ningún servidor externo. El único tráfico saliente opcional es un check de actualizaciones al arrancar (se desactiva con &lt;code&gt;--no-update-check&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;No requiere cuenta ni login.&lt;/li&gt;
&lt;li&gt;No es un reemplazo del agente en sí. Es una capa de analíticas sobre lo que ya tienes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AgentsView cubre un vacío real en el ecosistema de coding agents: tener visibilidad unificada sobre cómo y cuánto usas estas herramientas. Si usas más de un agente, merece la pena.&lt;/p&gt;</content><category term="Herramientas"></category><category term="agentsview"></category><category term="ai"></category><category term="coding agents"></category><category term="analytics"></category><category term="claude code"></category><category term="pi"></category><category term="codex"></category><category term="open-source"></category></entry><entry><title>Detectando secretos en git con gitleaks</title><link href="https://pablocaro.es/gitleaks-deteccion-secrets-git" rel="alternate"></link><published>2026-05-28T00:00:00+02:00</published><updated>2026-05-28T00:00:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-05-28:/gitleaks-deteccion-secrets-git</id><summary type="html">&lt;p&gt;gitleaks escanea repositorios git en busca de secretos, claves API y contraseñas filtradas. Se instala en segundos con gah y puede ahorrarte un disgusto.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="gitleaks detectando secretos en un repo git" src="/images/gitleaks-deteccion-secrets-git.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;gitleaks&lt;/code&gt; es una herramienta que escanea repositorios git —incluyendo todo el historial— en busca de secretos: claves API, contraseñas, tokens de AWS, credenciales de base de datos y más de 150 patrones predefinidos.&lt;/p&gt;
&lt;h2 id="instalacion-con-gah"&gt;Instalación con gah&lt;a class="headerlink" href="#instalacion-con-gah" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;La forma más rápida de instalarlo en Linux es con &lt;a href="https://github.com/user/gah"&gt;&lt;code&gt;gah&lt;/code&gt;&lt;/a&gt;, un descargador de assets de GitHub releases:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;gah&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;gitleaks/gitleaks
Fetching&lt;span class="w"&gt; &lt;/span&gt;release&lt;span class="w"&gt; &lt;/span&gt;info&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;gitleaks/gitleaks&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;latest&lt;span class="o"&gt;]&lt;/span&gt;
Found&lt;span class="w"&gt; &lt;/span&gt;release:&lt;span class="w"&gt; &lt;/span&gt;v8.30.1
Downloading:&lt;span class="w"&gt; &lt;/span&gt;gitleaks_8.30.1_linux_x64.tar.gz
&lt;span class="c1"&gt;############################################### 100.0%&lt;/span&gt;
Verifying&lt;span class="w"&gt; &lt;/span&gt;digest&lt;span class="w"&gt; &lt;/span&gt;sha256&lt;span class="w"&gt; &lt;/span&gt;551f6fc8...&lt;span class="w"&gt; &lt;/span&gt;...
Digest&lt;span class="w"&gt; &lt;/span&gt;verification&lt;span class="w"&gt; &lt;/span&gt;succeeded!
Extracting:&lt;span class="w"&gt; &lt;/span&gt;gitleaks_8.30.1_linux_x64.tar.gz
Installing:&lt;span class="w"&gt; &lt;/span&gt;gitleaks
Done!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Binario único, sin dependencias, listo en segundos.&lt;/p&gt;
&lt;h2 id="uso-basico"&gt;Uso básico&lt;a class="headerlink" href="#uso-basico" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Para escanear un repo local, incluyendo todo el historial de commits:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;gitleaks&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;--verbose&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;/ruta/al/repo
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Esto rastrea cada commit y te muestra qué secretos encontró, en qué archivo y en qué línea.&lt;/p&gt;
&lt;p&gt;Algunas flags útiles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--no-banner&lt;/code&gt;: quita el banner ASCII al inicio.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--exit-code 0&lt;/code&gt;: no devuelve error si encuentra leaks (útil en CI).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--config .gitleaks.toml&lt;/code&gt;: usa una configuración personalizada con reglas propias.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--gitleaks-ignore-path .&lt;/code&gt;: respeta el archivo &lt;code&gt;.gitleaksignore&lt;/code&gt; para falsos positivos.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--redact&lt;/code&gt;: oculta los secretos en la salida (útil en logs públicos).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;También puedes escanear directorios sueltos con &lt;code&gt;gitleaks dir&lt;/code&gt; o pipear contenido con &lt;code&gt;gitleaks stdin&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="cuando-usarlo"&gt;¿Cuándo usarlo?&lt;a class="headerlink" href="#cuando-usarlo" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Antes de hacer público un repo&lt;/strong&gt;: escanea el historial completo para asegurarte de que no hay claves coladas.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;En CI/CD&lt;/strong&gt;: añádelo como paso en GitHub Actions para que falle el build si alguien commitea un secreto.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auditorías de seguridad&lt;/strong&gt;: ideal para revisar repos viejos que han pasado por muchas manos.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="pre-commit-hook"&gt;Pre-commit hook&lt;a class="headerlink" href="#pre-commit-hook" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;La forma más efectiva de usarlo es como &lt;a href="https://github.com/BadLiveware/pi/blob/main/.githooks/pre-commit"&gt;hook de pre-commit&lt;/a&gt; para que bloquee automáticamente cualquier commit que contenga secretos:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-euo&lt;span class="w"&gt; &lt;/span&gt;pipefail

&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SKIP_GITLEAKS&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;1&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SKIP_GITLEAKS&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;true&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Skipping gitleaks pre-commit scan because SKIP_GITLEAKS=&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SKIP_GITLEAKS&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;gitleaks&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;/dev/null&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&amp;lt;&amp;#39;EOF&amp;#39;&lt;/span&gt;
&lt;span class="s"&gt;gitleaks is required by this repository&amp;#39;s pre-commit hook but was not found.&lt;/span&gt;
&lt;span class="s"&gt;Install gitleaks, or set SKIP_GITLEAKS=1 for an intentional one-off bypass.&lt;/span&gt;
&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Running gitleaks on staged changes...&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
gitleaks&lt;span class="w"&gt; &lt;/span&gt;protect&lt;span class="w"&gt; &lt;/span&gt;--staged&lt;span class="w"&gt; &lt;/span&gt;--source&lt;span class="w"&gt; &lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;--redact&lt;span class="w"&gt; &lt;/span&gt;--no-banner
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Guarda esto en &lt;code&gt;.githooks/pre-commit&lt;/code&gt;, hazlo ejecutable (&lt;code&gt;chmod +x&lt;/code&gt;) y configura git para que use esa carpeta:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git&lt;span class="w"&gt; &lt;/span&gt;config&lt;span class="w"&gt; &lt;/span&gt;core.hooksPath&lt;span class="w"&gt; &lt;/span&gt;.githooks
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A partir de ahí, cada &lt;code&gt;git commit&lt;/code&gt; escaneará los cambios en busca de secretos. Si necesitas saltártelo puntualmente, &lt;code&gt;SKIP_GITLEAKS=1 git commit&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Es de esas herramientas que no necesitas hasta que la necesitas. Y cuando la necesitas, te alegras de tenerla a un &lt;code&gt;gah install&lt;/code&gt; de distancia.&lt;/p&gt;</content><category term="herramientas"></category><category term="git"></category><category term="seguridad"></category><category term="gitleaks"></category><category term="herramientas"></category><category term="cli"></category></entry><entry><title>Limpiando la caché escondida de npx</title><link href="https://pablocaro.es/limpiar-cache-npx" rel="alternate"></link><published>2026-05-28T00:00:00+02:00</published><updated>2026-05-28T00:00:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-05-28:/limpiar-cache-npx</id><summary type="html">&lt;p&gt;npx deja una caché oculta de paquetes en ~/.npm/_npx/. Aquí explico cómo listar lo que tienes acumulado y cómo borrarlo.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Ejecutas &lt;code&gt;npx howcode&lt;/code&gt; para probar una herramienta, no te convence y sigues con tu vida. Repites esto unas cuantas veces y, sin darte cuenta, tienes cientos de megas acumulados en una caché que ni siquiera sabías que existía.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Limpiando la caché escondida de npx" src="/images/limpiar-cache-npx.png"&gt;&lt;/p&gt;
&lt;h2 id="el-problema"&gt;El problema&lt;a class="headerlink" href="#el-problema" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Cada vez que haces &lt;code&gt;npx &amp;lt;paquete&amp;gt;&lt;/code&gt;, npm descarga el paquete y lo guarda en &lt;code&gt;~/.npm/_npx/&lt;/code&gt;. No es como &lt;code&gt;npm install&lt;/code&gt; — aquí no hay un &lt;code&gt;package.json&lt;/code&gt; ni un &lt;code&gt;node_modules&lt;/code&gt; que puedas inspeccionar. Es una caché opaca que va creciendo sin que te des cuenta.&lt;/p&gt;
&lt;p&gt;En mi caso, después de trastear con varias herramientas, tenía esto:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;cache&lt;span class="w"&gt; &lt;/span&gt;npx&lt;span class="w"&gt; &lt;/span&gt;ls
06d0829110b3b93f:&lt;span class="w"&gt; &lt;/span&gt;skill
135b94ef511a5b29:&lt;span class="w"&gt; &lt;/span&gt;pi-kanban
1bf0be162bc0d832:&lt;span class="w"&gt; &lt;/span&gt;@mvanhorn/printing-press
483d2954a4c5190c:&lt;span class="w"&gt; &lt;/span&gt;madge
69c381f8ad94b576:&lt;span class="w"&gt; &lt;/span&gt;vitest
7439c0f00f94bc1a:&lt;span class="w"&gt; &lt;/span&gt;snipgrapher
9126f0a380537b3d:&lt;span class="w"&gt; &lt;/span&gt;howcode&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 237 MB solo este&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="la-trampa"&gt;La trampa&lt;a class="headerlink" href="#la-trampa" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;El primer impulso es hacer &lt;code&gt;npm cache npx rm vitest howcode&lt;/code&gt; y ya está. Pero no:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;cache&lt;span class="w"&gt; &lt;/span&gt;npx&lt;span class="w"&gt; &lt;/span&gt;rm&lt;span class="w"&gt; &lt;/span&gt;vitest&lt;span class="w"&gt; &lt;/span&gt;snipgrapher&lt;span class="w"&gt; &lt;/span&gt;howcode
npm&lt;span class="w"&gt; &lt;/span&gt;error&lt;span class="w"&gt; &lt;/span&gt;code&lt;span class="w"&gt; &lt;/span&gt;EUSAGE
npm&lt;span class="w"&gt; &lt;/span&gt;error&lt;span class="w"&gt; &lt;/span&gt;Invalid&lt;span class="w"&gt; &lt;/span&gt;npx&lt;span class="w"&gt; &lt;/span&gt;key&lt;span class="w"&gt; &lt;/span&gt;vitest
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;npm cache npx rm&lt;/code&gt; solo acepta el hash hexadecimal, no el nombre del paquete. Tienes que copiar la clave de la primera columna:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;npm&lt;span class="w"&gt; &lt;/span&gt;cache&lt;span class="w"&gt; &lt;/span&gt;npx&lt;span class="w"&gt; &lt;/span&gt;rm&lt;span class="w"&gt; &lt;/span&gt;9126f0a380537b3d&lt;span class="w"&gt; &lt;/span&gt;7439c0f00f94bc1a&lt;span class="w"&gt; &lt;/span&gt;69c381f8ad94b576
Removing&lt;span class="w"&gt; &lt;/span&gt;npx&lt;span class="w"&gt; &lt;/span&gt;key&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;/home/pcaro/.npm/_npx/9126f0a380537b3d
Removing&lt;span class="w"&gt; &lt;/span&gt;npx&lt;span class="w"&gt; &lt;/span&gt;key&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;/home/pcaro/.npm/_npx/7439c0f00f94bc1a
Removing&lt;span class="w"&gt; &lt;/span&gt;npx&lt;span class="w"&gt; &lt;/span&gt;key&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;/home/pcaro/.npm/_npx/69c381f8ad94b576
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Si quieres borrarlo todo de golpe, &lt;code&gt;npm cache clean&lt;/code&gt; vacía la caché entera de npm (incluyendo la de npx). Más drástico pero más rápido.&lt;/p&gt;</content><category term="herramientas"></category><category term="npm"></category><category term="npx"></category><category term="terminal"></category><category term="herramientas"></category></entry><entry><title>Las extensiones de pi que uso en mi día a día</title><link href="https://pablocaro.es/extensiones-pi-que-uso" rel="alternate"></link><published>2026-05-21T00:00:00+02:00</published><updated>2026-05-21T00:00:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-05-21:/extensiones-pi-que-uso</id><summary type="html">&lt;p&gt;Tercera y última entrega de la serie sobre &lt;a href="https://pi.dev/"&gt;pi&lt;/a&gt;. En la &lt;a href="https://pablocaro.es/que-es-pi-coding-agent"&gt;primera parte&lt;/a&gt; expliqué qué es pi y lo comparé con otros agents. En la &lt;a href="https://pablocaro.es/pi-de-principiante-a-avanzado"&gt;segunda&lt;/a&gt; hice una guía completa de uso. Ahora toca lo que realmente diferencia a pi de todo lo demás: &lt;strong&gt;las extensiones&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Tengo más de 20 …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Tercera y última entrega de la serie sobre &lt;a href="https://pi.dev/"&gt;pi&lt;/a&gt;. En la &lt;a href="https://pablocaro.es/que-es-pi-coding-agent"&gt;primera parte&lt;/a&gt; expliqué qué es pi y lo comparé con otros agents. En la &lt;a href="https://pablocaro.es/pi-de-principiante-a-avanzado"&gt;segunda&lt;/a&gt; hice una guía completa de uso. Ahora toca lo que realmente diferencia a pi de todo lo demás: &lt;strong&gt;las extensiones&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Tengo más de 20 paquetes instalados, skills de varios repositorios, y mis propias extensiones en &lt;a href="https://github.com/pcaro/pcaropi"&gt;pcaropi&lt;/a&gt;. Aquí desgloso lo que uso realmente en el día a día.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Extensiones de pi" src="/images/pi-extensiones.png"&gt;&lt;/p&gt;
&lt;h2 id="subagentes-pi-subagents"&gt;Subagentes — pi-subagents&lt;a class="headerlink" href="#subagentes-pi-subagents" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;El &lt;a href="https://github.com/nicobailon/pi-subagents"&gt;pi-subagents de NicoBailon&lt;/a&gt; es la extensión que más uso. Es un orquestador completo de subagentes con tres modos: single, chain y parallel.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pi&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;npm:pi-subagents
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Viene con 8 agentes incorporados (scout, researcher, planner, worker, reviewer…) y una TUI de preview donde puedes editar los pasos antes de lanzarlos. El modelo fallback en cascada es muy útil: si un modelo falla por quota, prueba el siguiente automáticamente.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://pablocaro.es/extensiones-subagentes-pi"&gt;Ya comparé esta extensión con otras alternativas&lt;/a&gt; (TintinWeb y Taskplane), pero mi elección diaria es NicoBailon sin duda.&lt;/p&gt;
&lt;h2 id="utilidades-del-sistema"&gt;Utilidades del sistema&lt;a class="headerlink" href="#utilidades-del-sistema" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="pi-processes"&gt;pi-processes&lt;a class="headerlink" href="#pi-processes" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pi&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;npm:@aliou/pi-processes
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Gestiona procesos en segundo plano: servidores de desarrollo, watchers de tests, logs. El agente puede arrancar &lt;code&gt;npm run dev&lt;/code&gt; en background, seguir trabajando, y recibir una alerta si el proceso falla. Esencial para cualquier flujo de desarrollo.&lt;/p&gt;
&lt;h3 id="pi-inspect"&gt;pi-inspect&lt;a class="headerlink" href="#pi-inspect" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pi&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;npm:pi-inspect
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Consola de depuración para pi. Muestra el estado interno, las herramientas registradas, los eventos que se están disparando. Útil cuando desarrollas tus propias extensiones o cuando algo no funciona como esperas.&lt;/p&gt;
&lt;h3 id="pi-slim"&gt;pi-slim&lt;a class="headerlink" href="#pi-slim" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pi&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;npm:pi-slim
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Reduce el tamaño del prompt de sistema eliminando descripciones que el modelo no necesita. Cada token cuenta, y esta extensión recorta silenciosamente lo superfluo.&lt;/p&gt;
&lt;h2 id="gestion-de-sesiones"&gt;Gestión de sesiones&lt;a class="headerlink" href="#gestion-de-sesiones" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="pi-smart-sessions-hazat"&gt;pi-smart-sessions (HazAT)&lt;a class="headerlink" href="#pi-smart-sessions-hazat" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pi&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;git:github.com/HazAT/pi-smart-sessions
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Añade gestión inteligente de sesiones: compactación automática, nombrado inteligente, y resumen de ramas abandonadas. Hace que trabajar con sesiones largas sea mucho más llevadero.&lt;/p&gt;
&lt;h3 id="pi-slopchop"&gt;pi-slopchop&lt;a class="headerlink" href="#pi-slopchop" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pi&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;npm:pi-slopchop
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Compresión agresiva de contexto. Reduce el uso de tokens hasta un 98% comprimiendo mensajes antiguos sin perder información crítica. Cuando trabajas con sesiones de días, esto es un salvavidas.&lt;/p&gt;
&lt;h3 id="pi-add-dir-itisbryan"&gt;pi-add-dir (itisbryan)&lt;a class="headerlink" href="#pi-add-dir-itisbryan" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pi&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;https://github.com/itisbryan/pi-add-dir
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Añade un directorio externo a la sesión para que el agente pueda trabajar con archivos fuera del directorio actual. Imprescindible cuando tienes que tocar código en varios repositorios.&lt;/p&gt;
&lt;h2 id="busqueda-y-navegacion-web"&gt;Búsqueda y navegación web&lt;a class="headerlink" href="#busqueda-y-navegacion-web" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="alioupi-linkup"&gt;@aliou/pi-linkup&lt;a class="headerlink" href="#alioupi-linkup" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pi&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;npm:@aliou/pi-linkup
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Tres herramientas de búsqueda web: &lt;code&gt;linkup_web_search&lt;/code&gt; para descubrir fuentes, &lt;code&gt;linkup_web_answer&lt;/code&gt; para respuestas sintetizadas con citas, y &lt;code&gt;linkup_web_fetch&lt;/code&gt; para extraer contenido de URLs concretas. Lo uso constantemente para consultar documentación, investigar APIs, o verificar información durante una sesión de código.&lt;/p&gt;
&lt;h3 id="agent-browser-skill"&gt;agent-browser skill&lt;a class="headerlink" href="#agent-browser-skill" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Instalado como skill desde el &lt;a href="https://github.com/vercel-labs/agent-browser"&gt;repositorio de Vercel Labs&lt;/a&gt;. Permite al agente navegar por páginas web como un humano: hacer clic, rellenar formularios, tomar capturas. &lt;a href="https://pablocaro.es/agent-browser-navegacion-agentes"&gt;Ya escribí sobre ello&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="chrome-cdp-skill"&gt;chrome-cdp skill&lt;a class="headerlink" href="#chrome-cdp-skill" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pi&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;git:github.com/pasky/chrome-cdp-skill@v1.0.1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Alternativa a agent-browser usando Chrome DevTools Protocol directamente. Más ligero y con control más granular sobre el navegador.&lt;/p&gt;
&lt;h2 id="productividad-y-ui"&gt;Productividad y UI&lt;a class="headerlink" href="#productividad-y-ui" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="pi-powerline-footer"&gt;pi-powerline-footer&lt;a class="headerlink" href="#pi-powerline-footer" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pi&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;npm:pi-powerline-footer
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Personaliza la barra de estado de pi con información adicional: estado de CI, rama de git, tiempo de sesión. Lo tengo configurado con ítems personalizados:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;powerline&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;preset&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;default&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;customItems&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ci&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;statusKey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ci&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;position&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;color&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;success&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="tmustierpi-code-actions"&gt;@tmustier/pi-code-actions&lt;a class="headerlink" href="#tmustierpi-code-actions" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pi&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;npm:@tmustier/pi-code-actions
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Añade acciones de código rápidas: formatear archivo, organizar imports, ejecutar linter. El agente puede dispararlas sin tener que escribir comandos Bash manualmente.&lt;/p&gt;
&lt;h3 id="alioupi-guardrails"&gt;@aliou/pi-guardrails&lt;a class="headerlink" href="#alioupi-guardrails" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pi&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;npm:@aliou/pi-guardrails
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Guarda raíles de seguridad: bloquea comandos peligrosos (&lt;code&gt;rm -rf&lt;/code&gt;, &lt;code&gt;sudo&lt;/code&gt;), protege archivos sensibles (&lt;code&gt;.env&lt;/code&gt;, &lt;code&gt;node_modules/&lt;/code&gt;), y confirma antes de operaciones destructivas. Tranquilidad mental cuando el agente trabaja autónomamente.&lt;/p&gt;
&lt;h3 id="pi-ask-user-y-pi-schedule-prompt"&gt;pi-ask-user y pi-schedule-prompt&lt;a class="headerlink" href="#pi-ask-user-y-pi-schedule-prompt" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pi&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;npm:pi-ask-user
pi&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;npm:pi-schedule-prompt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;pi-ask-user&lt;/code&gt; permite al agente hacer preguntas al usuario con opciones estructuradas. &lt;code&gt;pi-schedule-prompt&lt;/code&gt; programa ejecuciones periódicas (cada hora, cada día) para tareas de mantenimiento.&lt;/p&gt;
&lt;h3 id="pi-interactive-shell"&gt;pi-interactive-shell&lt;a class="headerlink" href="#pi-interactive-shell" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pi&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;npm:pi-interactive-shell
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Lanza agentes CLI interactivos (Claude Code, Cursor, Gemini CLI) desde dentro de pi. Modos dispatch, hands-free, monitor. Útil para delegar tareas específicas a otros agentes sin salir de pi.&lt;/p&gt;
&lt;h2 id="skills-que-uso-todos-los-dias"&gt;Skills que uso todos los días&lt;a class="headerlink" href="#skills-que-uso-todos-los-dias" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="del-repositorio-oficial-de-pi"&gt;Del repositorio oficial de pi&lt;a class="headerlink" href="#del-repositorio-oficial-de-pi" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;commit&lt;/strong&gt;: Misión crítica. Cada commit que hago pasa por este skill, que genera mensajes descriptivos y bien estructurados. Lo tengo activado siempre.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;brainstorm&lt;/strong&gt;: Para sesiones de diseño estructurado. Sigue una cadena completa: investigar → clarificar → explorar → validar diseño → escribir plan → crear TODOs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;learn-codebase&lt;/strong&gt;: Cuando entro en un proyecto nuevo, este skill escanea convenciones, agent config files y posibles problemas de seguridad.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;uv, ruff, ty&lt;/strong&gt;: El trío Python. &lt;code&gt;uv&lt;/code&gt; para gestión de paquetes, &lt;code&gt;ruff&lt;/code&gt; para linting+formato, &lt;code&gt;ty&lt;/code&gt; para type checking. Rápidos, modernos, sin fricción.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;github&lt;/strong&gt;: Interactuar con issues, PRs, CI runs y queries avanzadas de GitHub API, todo desde &lt;code&gt;gh&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;frontend-design&lt;/strong&gt;: Para cuando construyo interfaces. Genera componentes con alta calidad de diseño.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="de-mi-repositorio-pcaropi"&gt;De mi repositorio pcaropi&lt;a class="headerlink" href="#de-mi-repositorio-pcaropi" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Todas mis extensiones y skills personalizadas están en &lt;a href="https://github.com/pcaro/pcaropi"&gt;github.com/pcaro/pcaropi&lt;/a&gt;:&lt;/p&gt;
&lt;h4 id="extensiones"&gt;Extensiones&lt;a class="headerlink" href="#extensiones" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;answer.ts&lt;/code&gt; — Q&amp;amp;A interactivo. Extrae preguntas de mensajes del asistente y las responde una a una (&lt;code&gt;/answer&lt;/code&gt; o &lt;code&gt;Ctrl+.&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;context.ts&lt;/code&gt; — Visualización del uso de contexto (tokens, coste, archivos/skills cargados) vía &lt;code&gt;/context&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;files.ts&lt;/code&gt; — Navegador de archivos interactivo (&lt;code&gt;/files&lt;/code&gt; o &lt;code&gt;Ctrl+Shift+o&lt;/code&gt;) con estado git y vista rápida.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;notify.ts&lt;/code&gt; — Notificaciones de escritorio (Linux/notify-send) cuando el agente completa un turno.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;session-breakdown.ts&lt;/code&gt; — Historial de sesiones visualizado en 7/30/90 días (&lt;code&gt;/session-breakdown&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;todos.ts&lt;/code&gt; — Gestor de TODOs en Markdown con TUI, bloqueo de archivos y asignación de tareas (&lt;code&gt;/todos&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uv.ts&lt;/code&gt; — Intercepta comandos Python (&lt;code&gt;pip&lt;/code&gt;, &lt;code&gt;poetry&lt;/code&gt;) y sugiere usar &lt;code&gt;uv&lt;/code&gt; en su lugar.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="skills"&gt;Skills&lt;a class="headerlink" href="#skills" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cli-tools&lt;/code&gt; — Documenta las herramientas CLI disponibles en mi sistema (jq, fx, gh, httpie, etc.)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;google-workspace&lt;/code&gt; — Acceso a APIs de Google (Drive, Docs, Calendar, Gmail) sin MCP.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;summarize&lt;/code&gt; — Convierte URLs o archivos locales a Markdown usando &lt;code&gt;uvx markitdown&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;update-changelog&lt;/code&gt; — Skill para mantener el changelog del repositorio actualizado.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;web-browser&lt;/code&gt; — Interacción con páginas web mediante Chrome DevTools Protocol.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;linkedin-post-formatter&lt;/code&gt; — Formatea posts para LinkedIn con Unicode bold/italic.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="prompt-templates"&gt;Prompt templates&lt;a class="headerlink" href="#prompt-templates" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pr-create.md&lt;/code&gt; — Template para crear PRs con estructura consistente.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pr-fix-checks.md&lt;/code&gt; — Para arreglar checks fallidos en PRs.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;review.md&lt;/code&gt; — Template para revisiones de código estructuradas.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="mcp"&gt;MCP&lt;a class="headerlink" href="#mcp" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="pi-mcp-adapter-nicobailon"&gt;pi-mcp-adapter (NicoBailon)&lt;a class="headerlink" href="#pi-mcp-adapter-nicobailon" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pi&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;git:github.com/nicobailon/pi-mcp-adapter
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Adaptador para conectar servidores MCP estándar. Pi no trae MCP nativo (por diseño), pero con este adaptador puedes usar cualquier servidor MCP del ecosistema.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Fuente original&lt;/em&gt;: &lt;a href="https://pi.dev/"&gt;pi.dev&lt;/a&gt; | &lt;a href="https://github.com/pcaro/pcaropi"&gt;pcaropi&lt;/a&gt;&lt;/p&gt;</content><category term="ia"></category><category term="pi"></category><category term="coding-agent"></category><category term="extensiones"></category><category term="skills"></category><category term="subagentes"></category><category term="cli"></category></entry><entry><title>Pi de novato a avanzado — La guía completa</title><link href="https://pablocaro.es/pi-de-principiante-a-avanzado" rel="alternate"></link><published>2026-05-21T00:00:00+02:00</published><updated>2026-05-21T00:00:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-05-21:/pi-de-principiante-a-avanzado</id><summary type="html">&lt;p&gt;Guía completa de pi: desde la instalación y primeros pasos hasta sesiones en árbol, branching, skills, extensiones, y trucos de power user.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Esta es la segunda entrega de la serie sobre &lt;a href="https://pi.dev/"&gt;pi, el agente de código&lt;/a&gt;. En la &lt;a href="https://pablocaro.es/que-es-pi-coding-agent"&gt;primera parte&lt;/a&gt; comparé pi con otros coding agents. Ahora toca ponerse manos a la obra: desde la instalación hasta trucos de power user, pasando por sesiones, branching, skills y extensiones.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Pi de novato a avanzado" src="/images/pi-aprendizaje.jpg"&gt;&lt;/p&gt;
&lt;h2 id="nivel-1-novato-primeros-pasos"&gt;Nivel 1: Novato — Primeros pasos&lt;a class="headerlink" href="#nivel-1-novato-primeros-pasos" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="instalacion"&gt;Instalación&lt;a class="headerlink" href="#instalacion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Pi es un paquete npm. La forma más rápida:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;npm&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-g&lt;span class="w"&gt; &lt;/span&gt;--ignore-scripts&lt;span class="w"&gt; &lt;/span&gt;@earendil-works/pi-coding-agent
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;O con el instalador oficial (Linux/macOS):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-fsSL&lt;span class="w"&gt; &lt;/span&gt;https://pi.dev/install.sh&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Windows requiere WSL o Git Bash. Más detalles en la &lt;a href="https://pi.dev/docs/latest/windows"&gt;documentación&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="autenticacion"&gt;Autenticación&lt;a class="headerlink" href="#autenticacion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Arranca pi en el directorio de tu proyecto:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/ruta/a/tu/proyecto
pi
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;La primera vez verás la pantalla de bienvenida. Usa &lt;code&gt;/login&lt;/code&gt; para elegir un proveedor:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Suscripciones&lt;/strong&gt;: Claude Pro/Max, ChatGPT Plus/Pro o GitHub Copilot.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API keys&lt;/strong&gt;: Anthropic, OpenAI, DeepSeek, Groq, Mistral, Google, OpenRouter, Azure, Bedrock...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;También puedes pasar la API key como variable de entorno:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sk-ant-...
pi
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Yo, personalmente, tengo todas mis API keys almacenadas en Bitwarden y uso &lt;a href="https://pablocaro.es/rbw-env-exportar-variables"&gt;kitty rbw&lt;/a&gt; para cargarlas automáticamente al abrir el terminal. Así las claves están disponibles sin tener que escribirlas a mano cada vez.&lt;/p&gt;
&lt;h3 id="tu-primer-prompt"&gt;Tu primer prompt&lt;a class="headerlink" href="#tu-primer-prompt" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Ya dentro de pi, escribe algo y pulsa Enter:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Explícame este repositorio y dime cómo ejecutar sus tests.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Por defecto, pi tiene cuatro herramientas: &lt;code&gt;read&lt;/code&gt;, &lt;code&gt;write&lt;/code&gt;, &lt;code&gt;edit&lt;/code&gt; y &lt;code&gt;bash&lt;/code&gt;. Con eso ya puedes hacer prácticamente cualquier cosa.&lt;/p&gt;
&lt;h3 id="archivos-de-contexto-agentsmd"&gt;Archivos de contexto (AGENTS.md)&lt;a class="headerlink" href="#archivos-de-contexto-agentsmd" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Pi carga &lt;code&gt;AGENTS.md&lt;/code&gt; o &lt;code&gt;CLAUDE.md&lt;/code&gt; al arrancar. Es la forma de darle instrucciones globales a tu agente. Crea un archivo en la raíz de tu proyecto:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gh"&gt;# Instrucciones del proyecto&lt;/span&gt;

&lt;span class="k"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Ejecuta &lt;span class="sb"&gt;`npm run check`&lt;/span&gt; después de cada cambio de código.
&lt;span class="k"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;No ejecutes migraciones en producción localmente.
&lt;span class="k"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Mantén las respuestas concisas.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Pi busca estos archivos en:
- &lt;code&gt;~/.pi/agent/AGENTS.md&lt;/code&gt; — instrucciones globales
- Subiendo desde el directorio actual hasta la raíz
- El directorio actual&lt;/p&gt;
&lt;p&gt;Después de modificar un archivo de contexto —o de instalar o cambiar una extensión— ejecuta &lt;code&gt;/reload&lt;/code&gt; para que pi lo recoja sin reiniciar.&lt;/p&gt;
&lt;h3 id="referenciar-archivos-con"&gt;Referenciar archivos con @&lt;a class="headerlink" href="#referenciar-archivos-con" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Usa &lt;code&gt;@&lt;/code&gt; en el editor para buscar archivos con fuzzy-search:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;@README&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;Resume esto&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;@src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;@src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;Revisa estos juntos&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;También puedes pasar imágenes pegadas con Ctrl+V.&lt;/p&gt;
&lt;h2 id="nivel-2-intermedio-navegacion-y-productividad"&gt;Nivel 2: Intermedio — Navegación y productividad&lt;a class="headerlink" href="#nivel-2-intermedio-navegacion-y-productividad" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="atajos-de-teclado-essenciales"&gt;Atajos de teclado essenciales&lt;a class="headerlink" href="#atajos-de-teclado-essenciales" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Atajo&lt;/th&gt;
&lt;th&gt;Acción&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ctrl+L&lt;/td&gt;
&lt;td&gt;Seleccionar modelo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ctrl+P&lt;/td&gt;
&lt;td&gt;Ciclar entre modelos rápido&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shift+Tab&lt;/td&gt;
&lt;td&gt;Cambiar nivel de thinking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ctrl+G&lt;/td&gt;
&lt;td&gt;Abrir editor externo (VS Code, vim...)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ctrl+V&lt;/td&gt;
&lt;td&gt;Pegar imagen en el prompt&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="bash-desde-la-tui"&gt;Bash desde la TUI&lt;a class="headerlink" href="#bash-desde-la-tui" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Pi ejecuta comandos Bash sin salir de la interfaz:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;!comando&lt;/code&gt;&lt;/strong&gt; — Ejecuta y envía la salida al LLM.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;!!comando&lt;/code&gt;&lt;/strong&gt; — Ejecuta de forma local &lt;em&gt;sin&lt;/em&gt; enviar la salida al LLM (útil para comandos silenciosos como &lt;code&gt;git status&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Esta distinción es muy útil: &lt;code&gt;!&lt;/code&gt; para cuando quieres que el agente vea el resultado de un comando y reaccione, &lt;code&gt;!!&lt;/code&gt; para cosas que solo necesitas tú como &lt;code&gt;git status&lt;/code&gt; sin añadir ruido al contexto.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;!npm run lint
!!git status
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="la-barra-de-estado"&gt;La barra de estado&lt;a class="headerlink" href="#la-barra-de-estado" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;En la parte inferior de pi verás:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Directorio de trabajo actual&lt;/li&gt;
&lt;li&gt;Nombre de la sesión&lt;/li&gt;
&lt;li&gt;Uso de tokens y caché&lt;/li&gt;
&lt;li&gt;Coste acumulado&lt;/li&gt;
&lt;li&gt;Porcentaje de contexto usado&lt;/li&gt;
&lt;li&gt;Modelo activo&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="cola-de-mensajes"&gt;Cola de mensajes&lt;a class="headerlink" href="#cola-de-mensajes" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Puedes enviar mensajes mientras el agente está trabajando:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Enter&lt;/strong&gt; — Encola un mensaje de steering (se envía cuando termina la ronda actual de tool calls).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alt+Enter&lt;/strong&gt; — Encola un follow-up (se envía cuando el agente termina todo el trabajo).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Escape&lt;/strong&gt; — Cancela y restaura los mensajes encolados al editor.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="sesiones-y-branching"&gt;Sesiones y branching&lt;a class="headerlink" href="#sesiones-y-branching" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Esta es una de las funcionalidades más potentes y exclusivas de pi.&lt;/p&gt;
&lt;p&gt;Las sesiones se guardan automáticamente en &lt;code&gt;~/.pi/agent/sessions/&lt;/code&gt;. Puedes reanudarlas con:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pi&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="c1"&gt;# Continuar la sesión más reciente&lt;/span&gt;
pi&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="c1"&gt;# Navegar y seleccionar sesiones anteriores&lt;/span&gt;
pi&lt;span class="w"&gt; &lt;/span&gt;--session&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;path&lt;span class="p"&gt;|&lt;/span&gt;id&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# Abrir una sesión específica&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Dentro de pi:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;/session&lt;/code&gt;&lt;/strong&gt; — Muestra info de la sesión actual (archivo, ID, tokens, coste).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;/tree&lt;/code&gt;&lt;/strong&gt; — Navega el árbol de la sesión. Puedes saltar a cualquier punto anterior y continuar desde ahí, creando una rama alternativa sin perder el progreso previo.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;/fork&lt;/code&gt;&lt;/strong&gt; — Crea una nueva sesión desde un mensaje de usuario anterior.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;/clone&lt;/code&gt;&lt;/strong&gt; — Duplica la rama activa en un nuevo archivo de sesión.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;/compact&lt;/code&gt;&lt;/strong&gt; — Resumir mensajes antiguos para liberar contexto.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;/resume&lt;/code&gt;&lt;/strong&gt; — Explorar sesiones anteriores.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;/name &amp;lt;nombre&amp;gt;&lt;/code&gt;&lt;/strong&gt; — Poner nombre a la sesión para encontrarla después.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;El branching con &lt;code&gt;/tree&lt;/code&gt; es una funcionalidad que distingue a pi de cualquier otro agente de código cerrado. Puedes tener varias líneas de exploración en el mismo archivo de sesión, y si abandonas una rama, pi puede resumirla automáticamente para no perder el contexto.&lt;/p&gt;
&lt;h3 id="intercambio-rapido-de-modelos"&gt;Intercambio rápido de modelos&lt;a class="headerlink" href="#intercambio-rapido-de-modelos" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Usa &lt;code&gt;Ctrl+P&lt;/code&gt; para ciclar entre modelos rápidamente. Configura tu selección con &lt;code&gt;--models&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pi&lt;span class="w"&gt; &lt;/span&gt;--models&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;claude-*,gpt-4o,deepseek-*&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;O en &lt;code&gt;settings.json&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;defaultProvider&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;opencode-go&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;defaultModel&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;deepseek-v4-flash&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;defaultThinkingLevel&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;high&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="mensajes-de-sistema"&gt;Mensajes de sistema&lt;a class="headerlink" href="#mensajes-de-sistema" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Puedes reemplazar o extender el prompt de sistema por defecto:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;~/.pi/agent/SYSTEM.md&lt;/code&gt;&lt;/strong&gt; — Reemplaza globalmente.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;.pi/SYSTEM.md&lt;/code&gt;&lt;/strong&gt; — Reemplaza por proyecto.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;~/.pi/agent/APPEND_SYSTEM.md&lt;/code&gt;&lt;/strong&gt; — Añade al final sin reemplazar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;.pi/APPEND_SYSTEM.md&lt;/code&gt;&lt;/strong&gt; — Añade por proyecto.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="nivel-3-avanzado-skills-templates-y-extensiones"&gt;Nivel 3: Avanzado — Skills, templates y extensiones&lt;a class="headerlink" href="#nivel-3-avanzado-skills-templates-y-extensiones" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="skills"&gt;Skills&lt;a class="headerlink" href="#skills" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Los skills son archivos Markdown con frontmatter que se inyectan en el contexto del agente. Son la forma más sencilla de enseñar a pi comportamientos reutilizables.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://pablocaro.es/instalando-skills-de-github"&gt;Ya comparé ambos métodos&lt;/a&gt; en detalle, pero por resumir: puedes instalarlos con &lt;code&gt;npx skills&lt;/code&gt; (mi método preferido) o &lt;code&gt;gh skill&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Instalar skills de Matt Pocock&lt;/span&gt;
npx&lt;span class="w"&gt; &lt;/span&gt;skills&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;mattpocock/skills&lt;span class="w"&gt; &lt;/span&gt;--skill&lt;span class="w"&gt; &lt;/span&gt;grill-me&lt;span class="w"&gt; &lt;/span&gt;--skill&lt;span class="w"&gt; &lt;/span&gt;tdd

&lt;span class="c1"&gt;# Buscar skills disponibles&lt;/span&gt;
npx&lt;span class="w"&gt; &lt;/span&gt;skills&lt;span class="w"&gt; &lt;/span&gt;find
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;También puedes crear tus propios skills. Un skill es un archivo &lt;code&gt;SKILL.md&lt;/code&gt; con frontmatter:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;---
name: my-skill
&lt;span class="gu"&gt;description: Una skill de ejemplo&lt;/span&gt;
&lt;span class="gu"&gt;---&lt;/span&gt;

Esta es mi skill personalizada. Cuando el usuario la invoque con /skill:my-skill,
este contenido se inyecta en el contexto del agente.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Los skills se guardan en &lt;code&gt;~/.pi/agent/skills/&lt;/code&gt; (global) o &lt;code&gt;.pi/skills/&lt;/code&gt; (proyecto). &lt;a href="https://pablocaro.es/instalando-skills-de-github"&gt;Ya escribí sobre cómo instalarlos&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="prompt-templates"&gt;Prompt templates&lt;a class="headerlink" href="#prompt-templates" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Son comandos personalizados con barra &lt;code&gt;/&lt;/code&gt;. Crea un archivo Markdown en &lt;code&gt;.pi/prompts/&lt;/code&gt; o &lt;code&gt;~/.pi/agent/prompts/&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;---
name: review-pr
&lt;span class="gu"&gt;description: Revisa un PR y deja comentarios en GitHub&lt;/span&gt;
&lt;span class="gu"&gt;---&lt;/span&gt;

Review this PR and leave inline comments on GitHub using &lt;span class="sb"&gt;`gh`&lt;/span&gt;. Check for:
&lt;span class="k"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Breaking changes
&lt;span class="k"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Missing error handling
&lt;span class="k"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Test coverage
&lt;span class="k"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Consistency with project conventions
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Después de &lt;code&gt;/reload&lt;/code&gt;, puedes usar &lt;code&gt;/review-pr&lt;/code&gt; y el contenido se expandirá en el editor.&lt;/p&gt;
&lt;h3 id="extensiones-pi-packages"&gt;Extensiones (pi packages)&lt;a class="headerlink" href="#extensiones-pi-packages" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Las extensiones son módulos TypeScript que pueden registrar herramientas, interceptar eventos, mostrar UI personalizada, y mucho más. Es lo que diferencia a pi de cualquier otro agente.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Instalar paquetes&lt;/span&gt;
pi&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;npm:@aliou/pi-linkup&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="c1"&gt;# Búsqueda web&lt;/span&gt;
pi&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;npm:@aliou/pi-processes&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# Procesos en background&lt;/span&gt;
pi&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;npm:pi-subagents&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="c1"&gt;# Subagentes&lt;/span&gt;
pi&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;npm:pi-lens&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="c1"&gt;# AST search + LSP&lt;/span&gt;
pi&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;npm:pi-slopchop&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;# Optimización de contexto&lt;/span&gt;

&lt;span class="c1"&gt;# Listar paquetes instalados&lt;/span&gt;
pi&lt;span class="w"&gt; &lt;/span&gt;list
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Las extensiones se descubren automáticamente desde &lt;code&gt;~/.pi/agent/extensions/&lt;/code&gt; y &lt;code&gt;.pi/extensions/&lt;/code&gt;. Puedes escribir la tuya propia en TypeScript:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ExtensionAPI&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;@earendil-works/pi-coding-agent&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pi&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;ExtensionAPI&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;pi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;saludar&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Saluda a alguien&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;parameters&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Type.Object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;nombre&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Type.String&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Nombre a saludar&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toolCallId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;onUpdate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`¡Hola, &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nombre&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;!`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="compaction"&gt;Compaction&lt;a class="headerlink" href="#compaction" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Cuando la ventana de contexto se llena, puedes compactar:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Automático&lt;/strong&gt;: Pi compacta cuando se acerca al límite de contexto.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Manual&lt;/strong&gt;: &lt;code&gt;/compact [instrucciones]&lt;/code&gt; para compactar con enfoque personalizado.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extensiones&lt;/strong&gt;: &lt;code&gt;pi-slopchop&lt;/code&gt; puede reducir el uso de tokens hasta un 98%.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="temas"&gt;Temas&lt;a class="headerlink" href="#temas" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Pi soporta temas personalizados. Puedes instalar varios, yo uso &lt;a href="https://pablocaro.es/powerline-footer-hidden-gems"&gt;pi-powerline-footer&lt;/a&gt; que personaliza la barra de estado:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pi&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;npm:pi-powerline-footer
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Configura el tema en &lt;code&gt;settings.json&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;powerline&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;preset&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;default&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;customItems&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ci&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;statusKey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ci&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;position&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;color&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;success&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="nivel-4-power-user-configuracion-total"&gt;Nivel 4: Power user — Configuración total&lt;a class="headerlink" href="#nivel-4-power-user-configuracion-total" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="keybindings-personalizados"&gt;Keybindings personalizados&lt;a class="headerlink" href="#keybindings-personalizados" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Pi permite mapear atajos de teclado en &lt;code&gt;keybindings.json&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;ctrl+shift+f&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;files&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;alt+o&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;command&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;command&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ask-user&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="modo-no-interactivo"&gt;Modo no interactivo&lt;a class="headerlink" href="#modo-no-interactivo" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Para prompts de un solo uso desde scripts:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pi&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Resume este código&amp;quot;&lt;/span&gt;
cat&lt;span class="w"&gt; &lt;/span&gt;README.md&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pi&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Resume este texto&amp;quot;&lt;/span&gt;
pi&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;@captura.png&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;¿Qué hay en esta imagen?&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="modos-programaticos"&gt;Modos programáticos&lt;a class="headerlink" href="#modos-programaticos" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;--mode json&lt;/code&gt;&lt;/strong&gt;: Salida estructurada como JSON lines (ideal para pipelines).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;--mode rpc&lt;/code&gt;&lt;/strong&gt;: Comunicación bidireccional sobre stdin/stdout.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SDK&lt;/strong&gt;: Integra pi en aplicaciones Node.js.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="modo-read-only"&gt;Modo read-only&lt;a class="headerlink" href="#modo-read-only" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pi&lt;span class="w"&gt; &lt;/span&gt;--tools&lt;span class="w"&gt; &lt;/span&gt;read,grep,find,ls&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Revisa el código sin modificarlo&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="mi-setup-personal"&gt;Mi setup personal&lt;a class="headerlink" href="#mi-setup-personal" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Este artículo está escrito desde dentro de pi, usando exactamente la configuración que describo. Mi &lt;code&gt;settings.json&lt;/code&gt; incluye más de 20 paquetes, skills personalizadas, y extensiones propias de &lt;a href="https://github.com/pcaro/pcaropi"&gt;mi repositorio pcaropi&lt;/a&gt;. También lanzo pi desde scripts con personalidades concretas: &lt;code&gt;pi -p @prompt.md "Haz X"&lt;/code&gt; para tareas repetitivas sin necesidad de entrar en modo interactivo.&lt;/p&gt;
&lt;p&gt;En la &lt;strong&gt;&lt;a href="https://pablocaro.es/extensiones-pi-que-uso"&gt;tercera y última entrega&lt;/a&gt;&lt;/strong&gt; de esta serie, desgloso todas las extensiones que uso en mi día a día, desde subagentes hasta skills de productividad, pasando por búsqueda web, navegación con agente, y mucho más.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Fuente original&lt;/em&gt;: &lt;a href="https://pi.dev/"&gt;pi.dev&lt;/a&gt;&lt;/p&gt;</content><category term="ia"></category><category term="pi"></category><category term="coding-agent"></category><category term="guia"></category><category term="tutorial"></category><category term="cli"></category><category term="terminal"></category></entry><entry><title>¿Qué es pi? — El agente de código minimalista que se ha vuelto viral</title><link href="https://pablocaro.es/que-es-pi-coding-agent" rel="alternate"></link><published>2026-05-21T00:00:00+02:00</published><updated>2026-05-21T00:00:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-05-21:/que-es-pi-coding-agent</id><summary type="html">&lt;p&gt;Pi es un agente de código minimalista para la terminal. En este artículo explico qué es, cómo se diferencia de Claude Code, Codex, OpenCode y Gemini CLI, y por qué se ha vuelto viral.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Llevo ya varios meses usando &lt;a href="https://pi.dev/"&gt;pi&lt;/a&gt; como mi agente de código principal. Es una de esas herramientas que, una vez que la usas, no entiendes cómo funcionabas antes. En este artículo —primero de una serie de tres— voy a contar qué es pi, en qué se diferencia de otros coding agents y por qué creo que se ha vuelto extremadamente popular en GitHub. No os perdáis &lt;a href="https://pi.dev/"&gt;pi.dev&lt;/a&gt;, la web del proyecto — tiene unas animaciones muy didácticas que explican visualmente los conceptos.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Comparativa de agentes de código" src="/images/comparativa-agentes-pi.png"&gt;&lt;/p&gt;
&lt;h2 id="la-filosofia-menos-es-mas"&gt;La filosofía: menos es más&lt;a class="headerlink" href="#la-filosofia-menos-es-mas" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Pi se define como &lt;em&gt;"a minimal terminal coding harness"&lt;/em&gt; — un arnés de codificación minimalista para la terminal. Y cuando dicen minimalista, lo dicen en serio.&lt;/p&gt;
&lt;p&gt;El núcleo básico de pi tiene exactamente &lt;strong&gt;cinco capacidades nativas&lt;/strong&gt;: leer archivos, ejecutar Bash, editar archivos, escribir archivos y gestionar sesiones. Eso es todo. No trae subagentes, ni modo de planificación, ni pop-ups de permisos, ni MCP incorporado, ni un editor de código.&lt;/p&gt;
&lt;p&gt;Suena a poco, pero es la clave de todo. Vivimos en la era del &lt;em&gt;harness engineering&lt;/em&gt; — la disciplina de construir entornos robustos alrededor de los agentes de IA — y pi es un punto de partida excelente para montar tu propio harness sin pagar el peaje de un ecosistema cerrado.&lt;/p&gt;
&lt;p&gt;La filosofía de pi es que el usuario debe adaptar la herramienta a sus flujos de trabajo, y no al revés. En lugar de venir con 50 funciones que la mayoría no usa, pi te da las primitivas mínimas y un sistema de extensiones TypeScript para que construyas exactamente lo que necesites.&lt;/p&gt;
&lt;p&gt;Si quieres subagentes, instalas una extensión. Si quieres MCP, te montas un adaptador. Si quieres un plan mode, te escribes un prompt template. Y si no quieres nada de eso, pues no lo tienes ocupando espacio ni ralentizando el arranque.&lt;/p&gt;
&lt;h2 id="comparativa-con-otros-coding-agents"&gt;Comparativa con otros coding agents&lt;a class="headerlink" href="#comparativa-con-otros-coding-agents" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Característica&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Pi&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Claude Code&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Codex&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;OpenCode&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Gemini CLI&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Filosofía&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Núcleo mínimo + extensiones&lt;/td&gt;
&lt;td&gt;Todo incluido&lt;/td&gt;
&lt;td&gt;Todo incluido&lt;/td&gt;
&lt;td&gt;Núcleo mínimo&lt;/td&gt;
&lt;td&gt;Todo incluido&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Modelos&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Cualquier proveedor (API key o suscripción)&lt;/td&gt;
&lt;td&gt;Solo Claude&lt;/td&gt;
&lt;td&gt;Solo OpenAI&lt;/td&gt;
&lt;td&gt;Múltiples (OpenAI API)&lt;/td&gt;
&lt;td&gt;Solo Gemini&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Subagentes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Via extensiones&lt;/td&gt;
&lt;td&gt;Nativos&lt;/td&gt;
&lt;td&gt;Nativos&lt;/td&gt;
&lt;td&gt;Nativos&lt;/td&gt;
&lt;td&gt;Nativos&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MCP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Via extensiones&lt;/td&gt;
&lt;td&gt;Nativo&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Nativo&lt;/td&gt;
&lt;td&gt;Nativo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Plan mode&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Via skills/templates&lt;/td&gt;
&lt;td&gt;Nativo&lt;/td&gt;
&lt;td&gt;Nativo&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Extensiones&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;TypeScript SDK&lt;/td&gt;
&lt;td&gt;Plugins JSON&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Precio&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Gratuito (tus claves)&lt;/td&gt;
&lt;td&gt;Suscripción de pago&lt;/td&gt;
&lt;td&gt;Suscripción de pago&lt;/td&gt;
&lt;td&gt;Gratuito (tus claves)&lt;/td&gt;
&lt;td&gt;Gratuito (cuota gratuita)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Plataforma&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Terminal (cualquier emulador)&lt;/td&gt;
&lt;td&gt;Terminal&lt;/td&gt;
&lt;td&gt;Terminal&lt;/td&gt;
&lt;td&gt;Terminal&lt;/td&gt;
&lt;td&gt;Terminal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Sesiones en árbol&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Nativo (JSONL tree)&lt;/td&gt;
&lt;td&gt;Lineal&lt;/td&gt;
&lt;td&gt;Lineal&lt;/td&gt;
&lt;td&gt;Lineal&lt;/td&gt;
&lt;td&gt;Lineal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Skills&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Markdown + frontmatter&lt;/td&gt;
&lt;td&gt;Markdown + frontmatter&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="claude-code-el-todoterreno-de-anthropic"&gt;Claude Code — El todoterreno de Anthropic&lt;a class="headerlink" href="#claude-code-el-todoterreno-de-anthropic" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Claude Code es la apuesta oficial de Anthropic. Viene con todo: subagentes, memoria persistente, sistema de permisos, MCP nativo, y una experiencia muy pulida. Es el más maduro de los que no requieren IDE.&lt;/p&gt;
&lt;p&gt;El problema es que solo funciona con Claude. Si quieres usar DeepSeek, Gemini u OpenAI, no puedes. Y el ecosistema de plugins es limitado comparado con lo que ofrece pi.&lt;/p&gt;
&lt;h3 id="codex-la-integracion-con-chatgpt"&gt;Codex — La integración con ChatGPT&lt;a class="headerlink" href="#codex-la-integracion-con-chatgpt" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Codex es el agente de OpenAI. Se puede usar desde la terminal (&lt;code&gt;codex&lt;/code&gt;) o integrado con ChatGPT. Viene con subagentes y una experiencia similar a Claude Code.&lt;/p&gt;
&lt;p&gt;Pero al igual que Claude Code, estás atado a un solo proveedor. Y las extensiones son prácticamente inexistentes: lo que viene, es lo que tienes.&lt;/p&gt;
&lt;h3 id="gemini-cli-el-cli-de-la-comunidad"&gt;Gemini CLI — El cli de la comunidad&lt;a class="headerlink" href="#gemini-cli-el-cli-de-la-comunidad" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Gemini CLI nace del desarrollo comunitario de google-gemini y se integra con su ecosistema (Gemini API, Google Cloud). Google ha ido dejando de lado el CLI en favor de Antigravity, su cliente privativo para empresas. Viene con skills y subagentes, pero la personalización es limitada y las extensiones de terceros son prácticamente inexistentes.&lt;/p&gt;
&lt;h3 id="opencode-el-alma-gemela"&gt;OpenCode — El alma gemela&lt;a class="headerlink" href="#opencode-el-alma-gemela" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/opencode-go/opencode-go"&gt;OpenCode&lt;/a&gt; es el que más uso después de pi, y en muchos sentidos es su complemento natural. Su filosofía es parecida: núcleo mínimo, terminal, multi-modelo. No tiene el ecosistema de extensiones de pi, pero trae subagentes y MCP nativos. Está escrito en Go, arranca igual de rápido, y su integración con el ecosistema OpenAI es excelente.&lt;/p&gt;
&lt;h2 id="que-hace-unico-a-pi"&gt;¿Qué hace único a pi?&lt;a class="headerlink" href="#que-hace-unico-a-pi" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Después de probarlos todos, lo que hace único a pi es una combinación de factores que ningún otro agente reúne:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Extensibilidad&lt;/strong&gt;: Con el SDK de TypeScript puedes añadir herramientas, comandos, interceptar eventos, crear UI personalizada, y compartirlo todo como pi packages. Nadie más ofrece este nivel de personalización.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sesiones en árbol&lt;/strong&gt;: Las conversaciones se guardan como un árbol de decisión. Usas &lt;code&gt;/tree&lt;/code&gt; para saltar a cualquier punto anterior y crear una rama alternativa sin perder el progreso previo. Esto solo lo tiene pi.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Skills y prompt templates&lt;/strong&gt;: Pequeños archivos markdown que se inyectan en el contexto del agente. Son la forma más simple de enseñar a tu agente nuevas capacidades. &lt;a href="https://pablocaro.es/instalando-skills-de-github"&gt;Ya hablé de cómo instalarlos&lt;/a&gt; y de las &lt;a href="https://pablocaro.es/matt-pocock-skills"&gt;skills de Matt Pocock&lt;/a&gt; que se han vuelto virales.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ecosistema de paquetes&lt;/strong&gt;: En &lt;a href="https://pi.dev/packages"&gt;pi.dev/packages&lt;/a&gt; hay extensiones para casi todo: adaptadores MCP, buscadores web, modos de optimización de contexto, temas, y mucho más. Y si no encuentras lo que buscas, te escribes una extensión en TypeScript en cinco minutos.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No es un IDE&lt;/strong&gt;: Pi es un agente que vive en tu terminal, no un editor. Esto significa que funciona igual en tu máquina local, en un servidor por SSH, dentro de tmux, o en WSL. Usas tu editor favorito y pi se encarga del resto. Profundizaré en este concepto en la &lt;a href="https://pablocaro.es/pi-de-principiante-a-avanzado"&gt;siguiente entrega&lt;/a&gt; de la serie.&lt;/p&gt;
&lt;h2 id="por-que-me-gusta-pi"&gt;Por qué me gusta pi&lt;a class="headerlink" href="#por-que-me-gusta-pi" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A estas alturas seguramente se nota, pero por cerrar: me gusta pi porque vivo en la terminal. Porque quiero probar muchos modelos sin atarme a un proveedor. Porque personalizo todo con mis propias extensiones.&lt;/p&gt;
&lt;p&gt;No es para todos, obviamente. Si prefieres pagar una suscripción y olvidarte, Claude Code o Codex son más directos. Pero si eres un ingeniero que vive en la terminal, pi es probablemente el mejor agente de código que existe hoy.&lt;/p&gt;
&lt;p&gt;En los próximos artículos de esta serie:
- &lt;strong&gt;&lt;a href="https://pablocaro.es/pi-de-principiante-a-avanzado"&gt;Pi de novato a usuario avanzado&lt;/a&gt;&lt;/strong&gt; — Instalación, TUI, sesiones, skills, extensiones y todo lo que necesitas saber para dominarlo.
- &lt;strong&gt;&lt;a href="https://pablocaro.es/extensiones-pi-que-uso"&gt;Las extensiones de pi que uso&lt;/a&gt;&lt;/strong&gt; — Mi setup real con paquetes, skills y extensiones personalizadas.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Fuente original&lt;/em&gt;: &lt;a href="https://pi.dev/"&gt;pi.dev&lt;/a&gt;&lt;/p&gt;</content><category term="ia"></category><category term="pi"></category><category term="coding-agent"></category><category term="agentes-ia"></category><category term="claude-code"></category><category term="codex"></category><category term="opencode"></category><category term="gemini-cli"></category></entry><entry><title>Programming as Theory Building: el artículo de Peter Naur</title><link href="https://pablocaro.es/programming-as-theory-building-naur" rel="alternate"></link><published>2026-05-18T00:00:00+02:00</published><updated>2026-05-18T00:00:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-05-18:/programming-as-theory-building-naur</id><summary type="html">&lt;p&gt;Las ideas clave del artículo de Christian Ekrem sobre el ensayo de Peter Naur y por qué es más relevante que nunca en la era de la IA generativa.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Christian Ekrem ha escrito un artículo fantástico revisitando el ensayo de Peter Naur de 1985, &lt;em&gt;Programming as Theory Building&lt;/em&gt;, y aplicándolo al contexto actual de la IA generativa.&lt;/p&gt;
&lt;p&gt;Las ideas clave:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Un programa no es su código fuente.&lt;/strong&gt; El código es solo una representación imperfecta de la &lt;em&gt;teoría&lt;/em&gt; — el modelo mental compartido que explica cómo funciona el sistema, por qué funciona así y cómo debe evolucionar. Cuando los desarrolladores se van, la teoría se pierde.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;La crisis actual de "código sin teoría"&lt;/strong&gt; se ha disparado por tres factores: el uso reflejo de IA (aceptar código sin entenderlo), el código ciego al dominio generado por LLMs (estadística sin comprensión del negocio), y el problema de integración (importar decisiones arquitectónicas ajenas que contradicen el modelo de dominio).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Los seniors importan más que nunca&lt;/strong&gt; como constructores de teoría, guardianes de la coherencia arquitectónica, colaboradores intencionales con IA (usarla para lo mecánico, preservar el juicio humano para lo importante) y mentores que transfieren comprensión profunda.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;La IA no puede construir teoría.&lt;/strong&gt; Los LLMs generan código sintácticamente correcto pero no entienden contexto de negocio, no hacen trade-offs reflexivos, no mantienen integridad conceptual. La pregunta no es si pueden escribir código, sino si podemos mantener los marcos teóricos humanos que transforman código disperso en programas coherentes.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Programming as Theory Building" src="/images/programming-theory-building-naur.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Fuente original&lt;/em&gt;: &lt;a href="https://cekrem.github.io/posts/programming-as-theory-building-naur/"&gt;Programming as Theory Building&lt;/a&gt;&lt;/p&gt;</content><category term="Programación"></category><category term="programacion"></category><category term="teoria"></category><category term="arquitectura"></category><category term="ia"></category><category term="llm"></category><category term="naur"></category></entry><entry><title>Markdown preview en Kate Editor con markdownpart</title><link href="https://pablocaro.es/markdownpart-kate-preview" rel="alternate"></link><published>2026-05-11T00:00:00+02:00</published><updated>2026-05-11T00:00:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-05-11:/markdownpart-kate-preview</id><summary type="html">&lt;p&gt;Cómo activar la vista previa de Markdown en Kate usando markdownpart.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Resulta que KDE tiene un componente llamado &lt;code&gt;markdownpart&lt;/code&gt; que permite hacer preview de Markdown en tiempo real dentro de Kate.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;markdownpart
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Una vez instalado, en Kate:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Preferencias → Configurar Kate → Complementos&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Activar &lt;strong&gt;Vista previa del documento&lt;/strong&gt; (o similar según la versión)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Aparecerá un botón en la barra lateral con un icono de un documento con una lupa. Al hacer clic, se abre un panel con el Markdown renderizado. Y lo mejor: tiene opciones de actualizar en vivo mientras escribes.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Markdown preview en Kate" src="/images/markdownpart-kate-preview.png"&gt;&lt;/p&gt;</content><category term="Linux"></category><category term="kate"></category><category term="markdown"></category><category term="linux"></category><category term="kde"></category><category term="editor"></category></entry><entry><title>Configurar permisos udev para Vial en Linux</title><link href="https://pablocaro.es/vial-linux-udev" rel="alternate"></link><published>2026-05-11T00:00:00+02:00</published><updated>2026-05-11T00:00:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-05-11:/vial-linux-udev</id><summary type="html">&lt;p&gt;Cómo configurar reglas udev para que Vial detecte tu teclado con firmware Vial en Linux, usando la regla universal basada en el serial mágico.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Al conectar un teclado con firmware Vial (como el Keychron Q10) en Linux, la GUI de Vial no lo detecta porque el usuario no tiene permisos sobre los dispositivos &lt;code&gt;/dev/hidraw*&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;La documentación oficial de Vial recomienda usar una &lt;strong&gt;regla udev universal&lt;/strong&gt; que identifica el teclado por su serial mágico &lt;code&gt;vial:f64c2b3c&lt;/code&gt;. Esto es mejor que usar vendor/product ID porque funciona con cualquier teclado Vial que conectes.&lt;/p&gt;
&lt;p&gt;Primero, identifica tu teclado con &lt;code&gt;lsusb&lt;/code&gt;. El mío aparece como:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Bus 003 Device 015: ID 3434:01a1 Keychron Keychron Q10
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="Vial udev rules on Linux" src="/images/vial-linux-udev.png"&gt;&lt;/p&gt;
&lt;p&gt;El comando para crear la regla, recargar y aplicar:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;USER_GID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;id&lt;span class="w"&gt; &lt;/span&gt;-g&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;--preserve-env&lt;span class="o"&gt;=&lt;/span&gt;USER_GID&lt;span class="w"&gt; &lt;/span&gt;sh&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;echo &amp;quot;KERNEL==\&amp;quot;hidraw*\&amp;quot;, SUBSYSTEM==\&amp;quot;hidraw\&amp;quot;, ATTRS{serial}==\&amp;quot;*vial:f64c2b3c*\&amp;quot;, MODE=\&amp;quot;0660\&amp;quot;, GROUP=\&amp;quot;$USER_GID\&amp;quot;, TAG+=\&amp;quot;uaccess\&amp;quot;, TAG+=\&amp;quot;udev-acl\&amp;quot;&amp;quot; &amp;gt; /etc/udev/rules.d/59-vial.rules &amp;amp;&amp;amp; udevadm control --reload &amp;amp;&amp;amp; udevadm trigger&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Esto crea &lt;code&gt;/etc/udev/rules.d/59-vial.rules&lt;/code&gt;, recarga las reglas y las dispara. Después, desconecta y vuelve a conectar el teclado. Vial debería reconocerlo al instante.&lt;/p&gt;
&lt;p&gt;Si prefieres una regla específica para tu modelo, añade los IDs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;USER_GID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;id&lt;span class="w"&gt; &lt;/span&gt;-g&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;--preserve-env&lt;span class="o"&gt;=&lt;/span&gt;USER_GID&lt;span class="w"&gt; &lt;/span&gt;sh&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;echo &amp;quot;KERNEL==\&amp;quot;hidraw*\&amp;quot;, SUBSYSTEM==\&amp;quot;hidraw\&amp;quot;, ATTRS{serial}==\&amp;quot;*vial:f64c2b3c*\&amp;quot;, ATTRS{idVendor}==\&amp;quot;3434\&amp;quot;, ATTRS{idProduct}==\&amp;quot;01a1\&amp;quot;, MODE=\&amp;quot;0660\&amp;quot;, GROUP=\&amp;quot;$USER_GID\&amp;quot;, TAG+=\&amp;quot;uaccess\&amp;quot;, TAG+=\&amp;quot;udev-acl\&amp;quot;&amp;quot; &amp;gt; /etc/udev/rules.d/59-vial.rules &amp;amp;&amp;amp; udevadm control --reload &amp;amp;&amp;amp; udevadm trigger&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;La regla universal es más simple y cubre cualquier teclado Vial futuro.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Fuente original&lt;/em&gt;: &lt;a href="https://get.vial.today/manual/linux-udev.html"&gt;Vial - Configuring udev on Linux&lt;/a&gt;&lt;/p&gt;</content><category term="Linux"></category><category term="vial"></category><category term="linux"></category><category term="udev"></category><category term="keyboard"></category><category term="keychron"></category><category term="qmk"></category></entry><entry><title>Spec Kit de GitHub — Especificaciones como fuente de verdad</title><link href="https://pablocaro.es/spec-kit-github" rel="alternate"></link><published>2026-05-10T00:00:00+02:00</published><updated>2026-05-10T00:00:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-05-10:/spec-kit-github</id><summary type="html">&lt;p&gt;Sexta entrega de la serie sobre ingeniería de programación con agentes. Hoy toca el proyecto que GitHub ha convertido en el estándar de facto del desarrollo guiado por especificaciones: &lt;strong&gt;Spec Kit&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Hasta ahora hemos visto metodologías para dirigir agentes: compound engineering (Every), skills de disciplina (Addy Osmani), un sistema operativo …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Sexta entrega de la serie sobre ingeniería de programación con agentes. Hoy toca el proyecto que GitHub ha convertido en el estándar de facto del desarrollo guiado por especificaciones: &lt;strong&gt;Spec Kit&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Hasta ahora hemos visto metodologías para dirigir agentes: compound engineering (Every), skills de disciplina (Addy Osmani), un sistema operativo completo para agentes (Superpowers de Jesse Vincent), y skills mínimas y pragmáticas (Matt Pocock). Todas comparten una premisa: defines cómo quieres que el agente trabaje y él ejecuta.&lt;/p&gt;
&lt;p&gt;Spec Kit da la vuelta a esa premisa. No le dice al agente &lt;strong&gt;cómo&lt;/strong&gt; trabajar. Le dice &lt;strong&gt;qué&lt;/strong&gt; construir, y deja que el agente descubra cómo. La diferencia es sutil pero lo cambia todo.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Spec Kit de GitHub — Especificaciones como fuente de verdad" src="/images/spec-kit-github-cover.png"&gt;&lt;/p&gt;
&lt;h2 id="el-problema-que-resuelve"&gt;El problema que resuelve&lt;a class="headerlink" href="#el-problema-que-resuelve" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Todos los frameworks que hemos visto en la serie asumen que sabes lo que quieres construir y que el problema es que el agente no lo construye bien. Tiene razón: sin disciplina, los agentes toman atajos, generan código genérico y producen resultados inconsistentes.&lt;/p&gt;
&lt;p&gt;Pero hay otro problema, más profundo: &lt;strong&gt;¿y si no sabes exactamente lo que quieres?&lt;/strong&gt; ¿Y si tus requisitos son vagos, contradictorios o incompletos?&lt;/p&gt;
&lt;p&gt;Si veinte desarrolladores le piden a un agente "haz un CRUD de usuarios", obtendrán veinte resultados diferentes, y probablemente ninguno sea lo que realmente necesitaban. Porque "CRUD de usuarios" no es una especificación. Es una idea. Y las ideas no se implementan: se refinan.&lt;/p&gt;
&lt;p&gt;Spec Kit aborda este problema desde la raíz. No es un framework para programar con agentes. Es un &lt;strong&gt;framework para especificar con agentes&lt;/strong&gt;. El código sale solo.&lt;/p&gt;
&lt;h2 id="la-inversion-de-poder"&gt;La inversión de poder&lt;a class="headerlink" href="#la-inversion-de-poder" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Spec Kit llama a su idea central &lt;strong&gt;The Power Inversion&lt;/strong&gt; (la inversión de poder). La idea no es nueva: es ingeniería del software de toda la vida. Cuando yo estudiaba ingeniería informática, esto se llamaba DDR (Documento de Definición de Requisitos). Tenías requisitos de usuario ("Como usuario quiero..."), requisitos funcionales y requisitos de sistema. De ahí derivabas una especificación técnica, y luego el código. En Carto era nuestra filosofía principal: PRD, documento de especificación técnica, código.&lt;/p&gt;
&lt;p&gt;Lo que cambia con Spec Kit es que la especificación ya no es un documento que guía la implementación para que un humano la ejecute. Es un documento &lt;strong&gt;ejecutable&lt;/strong&gt; por un agente de IA. El PRD no es contexto para que un desarrollador escriba código. Es la entrada que genera directamente la implementación.&lt;/p&gt;
&lt;p&gt;La diferencia es que los agentes son muy buenos ayudándote a especificar. Pueden hacer preguntas, identificar ambigüedades, explorar el código existente en busca de contexto, y refinar los requisitos contigo antes de escribir una línea. Esa capacidad de iterar sobre la especificación en segundos en lugar de días es lo que hace que el flujo sea práctico.&lt;/p&gt;
&lt;p&gt;Los números lo avalan: 95.000 estrellas en GitHub, 8.200 forks, 30+ agentes de IA soportados, y un ecosistema de extensiones comunitarias que ha crecido de 26 a 83 en solo un mes.&lt;/p&gt;
&lt;h2 id="el-flujo-de-trabajo"&gt;El flujo de trabajo&lt;a class="headerlink" href="#el-flujo-de-trabajo" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Spec Kit define seis comandos principales que cubren todo el ciclo de vida de una feature:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Comando&lt;/th&gt;
&lt;th&gt;Qué hace&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/speckit.constitution&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Define los principios rectores del proyecto&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/speckit.specify&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Describe &lt;strong&gt;qué&lt;/strong&gt; construir (requisitos, user stories)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/speckit.clarify&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Preguntas estructuradas para resolver ambigüedades&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/speckit.plan&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Traduce la especificación a un plan técnico&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/speckit.tasks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Descompone el plan en tareas accionables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/speckit.implement&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ejecuta las tareas y genera el código&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Además hay comandos opcionales: &lt;code&gt;/speckit.analyze&lt;/code&gt; para validación cruzada entre artefactos, y &lt;code&gt;/speckit.checklist&lt;/code&gt; para generar checklists de calidad personalizados.&lt;/p&gt;
&lt;h3 id="1-constitucion"&gt;1. Constitución&lt;a class="headerlink" href="#1-constitucion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Antes de escribir una sola línea de especificación, defines los principios del proyecto. No es un documento de equipo. Es un contrato que el agente debe respetar en cada decisión.&lt;/p&gt;
&lt;p&gt;Spec Kit incluye una constitución de ejemplo con nueve artículos que cubren desde "library-first" (toda feature empieza como librería independiente) hasta "test-first imperative" (no hay código sin tests que fallen primero).&lt;/p&gt;
&lt;p&gt;La constitución no es opcional. Es el sistema inmunológico del proyecto. Sin ella, el agente toma decisiones coherentes pero no necesariamente consistentes. Con ella, cada decisión refuerza la arquitectura.&lt;/p&gt;
&lt;h3 id="2-especificacion"&gt;2. Especificación&lt;a class="headerlink" href="#2-especificacion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Aquí está la clave de todo: en &lt;code&gt;/speckit.specify&lt;/code&gt; solo hablas del &lt;strong&gt;qué&lt;/strong&gt; y el &lt;strong&gt;por qué&lt;/strong&gt;. Nada de stack tecnológico. Nada de APIs. Nada de cómo.&lt;/p&gt;
&lt;p&gt;El template de especificación fuerza esta separación explícitamente:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;- ✅ Focus on WHAT users need and WHY
- ❌ Avoid HOW to implement (no tech stack, APIs, code structure)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Esto es más difícil de lo que parece. Como ingenieros, nuestro instinto es saltar al "cómo" en cuanto entendemos el problema. Spec Kit te obliga a mantenerte en el "qué" el tiempo suficiente para que la especificación sea completa.&lt;/p&gt;
&lt;p&gt;Además, el template incluye marcadores &lt;code&gt;[NEEDS CLARIFICATION]&lt;/code&gt; que el agente debe usar cuando algo no está claro. Esto evita el problema más común con los LLMs: que adivinen en lugar de preguntar.&lt;/p&gt;
&lt;h3 id="3-clarificacion"&gt;3. Clarificación&lt;a class="headerlink" href="#3-clarificacion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Este paso es el que más valoro personalmente. Antes de pasar al plan técnico, &lt;code&gt;/speckit.clarify&lt;/code&gt; inicia un diálogo estructurado donde el agente identifica ambigüedades y te pregunta una por una.&lt;/p&gt;
&lt;p&gt;No es un cuestionario genérico. El agente ha leído la especificación y sabe exactamente qué no entiende. Te pregunta hasta que no quedan ramas del árbol de decisión sin explorar.&lt;/p&gt;
&lt;p&gt;Es el mismo patrón que el &lt;code&gt;/grill-me&lt;/code&gt; de Matt Pocock, pero incrustado en un flujo más amplio y con un formato estructurado que queda registrado en la propia especificación.&lt;/p&gt;
&lt;h3 id="4-plan"&gt;4. Plan&lt;a class="headerlink" href="#4-plan" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Ahora sí, llegó el momento del "cómo". Le dices al agente tu stack tecnológico y tus decisiones arquitectónicas. El agente genera un plan de implementación detallado que incluye:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Modelo de datos&lt;/strong&gt;: esquemas, relaciones, restricciones&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contratos de API&lt;/strong&gt;: endpoints, eventos, formatos&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Investigación&lt;/strong&gt;: comparativa de librerías, benchmarks, decisiones técnicas con su rationale&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Quickstart&lt;/strong&gt;: escenarios de validación clave&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cada elección técnica enlaza con requisitos concretos de la especificación. Nada queda sin trazar.&lt;/p&gt;
&lt;h3 id="5-tareas"&gt;5. Tareas&lt;a class="headerlink" href="#5-tareas" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;El plan se descompone en tareas individuales, cada una con paths exactos, código completo y pasos de verificación. Las tareas independientes se marcan como paralelizables.&lt;/p&gt;
&lt;p&gt;El sistema de tareas de Spec Kit es notable porque distingue entre tareas HITL (requieren decisión humana) y AFK (automatizables de principio a fin). Esto permite que el humano intervenga solo donde realmente aporta valor.&lt;/p&gt;
&lt;h3 id="6-implementacion"&gt;6. Implementación&lt;a class="headerlink" href="#6-implementacion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;El agente ejecuta las tareas en orden, respetando dependencias y siguiendo TDD. Cada tarea produce evidencia: tests que pasan, builds limpios, contratos validados.&lt;/p&gt;
&lt;p&gt;Si algo falla, el agente no sigue adelante. Para, informa y espera instrucciones. Es la materialización del principio que recorre toda la serie: &lt;strong&gt;la verificación no es negociable&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="la-fundacion-constitucional"&gt;La fundación constitucional&lt;a class="headerlink" href="#la-fundacion-constitucional" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Spec Kit impone un gran nivel de detalle en su constitución. No son principios vagos. Son nueve artículos con cláusulas concretas que el agente no puede ignorar.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Artículo I — Library-First&lt;/strong&gt;: Toda feature empieza como librería independiente. Esto fuerza modularidad desde el primer commit. No vale "ya lo refactorizaremos".&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Artículo II — CLI Interface&lt;/strong&gt;: Toda librería debe exponer una interfaz de línea de comandos. Esto garantiza observabilidad: puedes invocar cualquier funcionalidad desde un script, sin tocar la interfaz gráfica.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Artículo III — Test-First Imperative&lt;/strong&gt;: No negociable. Primero los tests. Tests que fallan. Luego la implementación mínima para que pasen.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Artículos VII y VIII — Simplicidad y Anti-Abstracción&lt;/strong&gt;: Máximo 3 proyectos en la implementación inicial. Usa el framework directamente, no lo envuelvas. Nada de "por si acaso".&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Artículo IX — Integration-First Testing&lt;/strong&gt;: Tests en entornos reales. Bases de datos reales. Servicios reales. Nada de mocks si puedes evitarlos.&lt;/p&gt;
&lt;p&gt;El detalle clave es cómo estos artículos se aplican a través de &lt;strong&gt;gates&lt;/strong&gt; en el template del plan de implementación. Antes de que el agente escriba una línea, pasa por una fase de "gates pre-implementación" que verifican cada artículo. Si algo falla, el agente debe justificarlo por escrito en una sección de "Complejidad".&lt;/p&gt;
&lt;p&gt;Esto convierte la constitución de un documento pasivo a un &lt;strong&gt;sistema de enforcement activo&lt;/strong&gt;. No es que el agente "debería" seguir las reglas. Es que no puede saltárselas sin dejar evidencia.&lt;/p&gt;
&lt;h2 id="comparacion-con-el-resto-de-la-serie"&gt;Comparación con el resto de la serie&lt;a class="headerlink" href="#comparacion-con-el-resto-de-la-serie" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Cada proyecto que hemos visto en la serie ocupa un nicho diferente:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Proyecto&lt;/th&gt;
&lt;th&gt;Enfoque&lt;/th&gt;
&lt;th&gt;Fortaleza&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Every&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Compound engineering&lt;/td&gt;
&lt;td&gt;Acumulación de conocimiento en CLAUDE.md&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Addy Osmani&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Harness engineering&lt;/td&gt;
&lt;td&gt;Disciplina de ingeniero senior con anti-racionalización&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Superpowers&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Methodology engineering&lt;/td&gt;
&lt;td&gt;Sistema operativo completo para agentes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Matt Pocock&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Skills engineering&lt;/td&gt;
&lt;td&gt;Skills mínimas que cualquiera puede adoptar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Spec Kit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Spec engineering&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Las especificaciones son la fuente de verdad&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Spec Kit es el único que no empieza por el agente. Empieza por la especificación. El agente es el último paso, no el primero.&lt;/p&gt;
&lt;h2 id="mi-lectura"&gt;Mi lectura&lt;a class="headerlink" href="#mi-lectura" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Spec Kit es el proyecto que más se acerca a la ingeniería del software clásica de los que he cubierto en la serie. No por su complejidad técnica (que es poca: es básicamente un CLI de Python que genera archivos markdown), sino porque formaliza algo que siempre debimos hacer: especificar antes de construir.&lt;/p&gt;
&lt;p&gt;Invertir la relación entre especificación y código es una idea que suena bien en abstracto pero que tiene implicaciones profundas. Si el código es un artefacto generado, ¿qué significa "mantener" el software? Mantener software significa evolucionar especificaciones. Debuggear significa corregir especificaciones que generan código incorrecto. Refactorizar significa reestructurar especificaciones para claridad.&lt;/p&gt;
&lt;p&gt;El perfil del ingeniero de software siempre fue distinto al del programador. Nunca fue alguien que escribe código sin más. Es quien &lt;strong&gt;escribe especificaciones que generan código&lt;/strong&gt;. Algo cercano a lo que ahora se denomina Product Owner en Scrum, pero con capacidad técnica para saber qué es implementable y qué no.&lt;/p&gt;
&lt;p&gt;No sé si esta visión se cumplirá completamente. Pero Spec Kit tiene algo que ninguno de los otros proyectos tiene: el respaldo institucional de GitHub. Con 95.000 estrellas y creciendo, con una tasa de adopción que duplica a Superpowers en meses, Spec Kit no es un experimento. Es un movimiento.&lt;/p&gt;
&lt;p&gt;Y lo más interesante es que GitHub no está construyendo Spec Kit para venderte nada. Es código abierto, es gratuito, y funciona con cualquier agente: Claude Code, Gemini CLI, Cursor, Codex, Copilot, el que uses. Es una apuesta por un estándar abierto de desarrollo con IA, y está funcionando.&lt;/p&gt;
&lt;p&gt;La pregunta que debes hacerte no es "qué agente uso" o "qué methodology uso con mis agentes", sino "qué especificación escribo para que mis agentes construyan lo correcto". Spec Kit da una respuesta: escribe la especificación primero. El resto es implementación.&lt;/p&gt;
&lt;h2 id="recursos"&gt;Recursos&lt;a class="headerlink" href="#recursos" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/github/spec-kit"&gt;GitHub Spec Kit&lt;/a&gt; — El repositorio oficial (95K+ estrellas)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/github/spec-kit/blob/main/spec-driven.md"&gt;Spec-Driven Development — la metodología&lt;/a&gt; — El documento que define SDD&lt;/li&gt;
&lt;li&gt;&lt;a href="https://speckit-community.github.io/extensions/"&gt;Spec Kit Community Extensions&lt;/a&gt; — Catálogo de extensiones comunitarias&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pablocaro.es/matt-pocock-skills"&gt;Matt Pocock — Skills que funcionan&lt;/a&gt; — Artículo anterior de la serie&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pablocaro.es/superpowers-obra-jesse-vincent"&gt;Superpowers — Jesse Vincent&lt;/a&gt; — Artículo anterior de la serie&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pablocaro.es/agent-skills-addy-osmani"&gt;Agent Skills — Addy Osmani&lt;/a&gt; — Artículo anterior de la serie&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pablocaro.es/compound-engineering-every"&gt;Compound Engineering — Every&lt;/a&gt; — Artículo anterior de la serie&lt;/li&gt;
&lt;/ul&gt;</content><category term="ia"></category><category term="agentes"></category><category term="ia"></category><category term="spec-kit"></category><category term="github"></category><category term="spec-driven-development"></category></entry><entry><title>Matt Pocock — Skills sencillas que funcionan</title><link href="https://pablocaro.es/matt-pocock-skills" rel="alternate"></link><published>2026-05-06T00:00:00+02:00</published><updated>2026-05-06T00:00:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-05-06:/matt-pocock-skills</id><summary type="html">&lt;p&gt;Quinta entrega de la serie sobre ingeniería de programación con agentes. Hoy toca el proyecto que está arrasando estos días: las &lt;strong&gt;skills de Matt Pocock&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;He cubierto Superpowers (Jesse Vincent), Agent Skills (Addy Osmani) y Compound Engineering (Every). Cada uno aporta una pieza importante del puzzle. Pero las skills de …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Quinta entrega de la serie sobre ingeniería de programación con agentes. Hoy toca el proyecto que está arrasando estos días: las &lt;strong&gt;skills de Matt Pocock&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;He cubierto Superpowers (Jesse Vincent), Agent Skills (Addy Osmani) y Compound Engineering (Every). Cada uno aporta una pieza importante del puzzle. Pero las skills de Matt Pocock han explotado en popularidad en cuestión de días, y creo que sé por qué: aplican una filosofía KISS llevada al extremo. Skills cortas, directas, sin pretensiones. Y una de ellas, &lt;code&gt;/grill-me&lt;/code&gt;, se ha convertido en un fenómeno por sí sola.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Matt Pocock Skills" src="/images/matt-pocock-skills-cover.png"&gt;&lt;/p&gt;
&lt;h2 id="la-filosofia-skills-que-un-ingeniero-real-usaria"&gt;La filosofía: skills que un ingeniero real usaría&lt;a class="headerlink" href="#la-filosofia-skills-que-un-ingeniero-real-usaria" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Matt es conocido por Total TypeScript y su trabajo en TypeScript. Cuando empezó a publicar skills para Claude Code, no escribió un framework. Escribió archivos markdown de tres líneas que hacían una cosa y la hacían bien.&lt;/p&gt;
&lt;p&gt;El lema del repositorio lo resume: &lt;em&gt;"Skills for Real Engineers"&lt;/em&gt; (skills para ingenieros de verdad). No hay capas de abstracción, ni plugins complejos, ni ciclos de vida sofisticados. Son ficheros markdown con frontmatter que se inyectan en el contexto del agente. Punto.&lt;/p&gt;
&lt;p&gt;El repositorio se organiza en cubos: &lt;code&gt;engineering/&lt;/code&gt; (skills del día a día), &lt;code&gt;productivity/&lt;/code&gt; (herramientas generales), &lt;code&gt;misc/&lt;/code&gt; (cosas que raramente usa), &lt;code&gt;personal/&lt;/code&gt; (atadas a su setup) e &lt;code&gt;in-progress/&lt;/code&gt;. De las ~20 skills totales, solo unas 6 son las que realmente recomienda para el día a día. Esa contención es la clave: no más de media docena de skills, y muy muy cortas.&lt;/p&gt;
&lt;p&gt;El contraste con Superpowers (v5 con 20+ skills, loops adversariales, sistema de fases completo) no podría ser mayor. Matt demuestra que menos puede ser más si eliges bien qué automatizar.&lt;/p&gt;
&lt;h2 id="grill-me-el-skill-que-lo-cambia-todo"&gt;&lt;code&gt;/grill-me&lt;/code&gt;: el skill que lo cambia todo&lt;a class="headerlink" href="#grill-me-el-skill-que-lo-cambia-todo" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Tres frases. Eso es todo el skill:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Interview me relentlessly about every aspect of this plan until we reach a shared understanding. Walk down each branch of the design tree, resolving dependencies between decisions one-by-one. Ask the questions one at a time. If a question can be answered by exploring the codebase, explore the codebase instead."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Tres frases que han generado 62.000 estrellas en GitHub y se han compartido en todas partes. ¿Por qué?&lt;/p&gt;
&lt;p&gt;Porque resuelven el problema número uno de trabajar con agentes: &lt;strong&gt;la alineación&lt;/strong&gt;. El agente no sabe lo que quieres, y tú no sabes lo que el agente ha entendido. El &lt;code&gt;grill-me&lt;/code&gt; fuerza una conversación estructurada donde el agente te pregunta hasta que no quedan ramas del árbol de decisión sin explorar.&lt;/p&gt;
&lt;p&gt;Matt cuenta que en una sesión típica, Claude le hizo 16 preguntas para una feature relativamente simple. En features complejas, ha tenido sesiones de 30-40-50 preguntas que duran casi media hora. Pero al final de esa media hora, el diseño está completamente resuelto. No hay sorpresas cuando el agente empieza a escribir código.&lt;/p&gt;
&lt;p&gt;El concepto de "árbol de decisión" viene de &lt;em&gt;The Design of Design&lt;/em&gt; de Frederick P. Brooks. La idea es que cuando diseñas algo, caminas por ramas: "¿búsqueda avanzada o búsqueda simple? Si avanzada, ¿qué filtros? ¿qué ordenación?" Hasta que has recorrido todas las ramas y el diseño está completamente especificado.&lt;/p&gt;
&lt;h2 id="el-flujo-completo-grill-prd-issues-tdd"&gt;El flujo completo: grill → PRD → issues → TDD&lt;a class="headerlink" href="#el-flujo-completo-grill-prd-issues-tdd" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Matt encadena varias skills en un pipeline que cubre todo el ciclo de vida de una feature, desde la idea hasta el código pasando por tests:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;/grill-me&lt;/code&gt;&lt;/strong&gt;: el agente te entrevista hasta que el diseño está claro. Cada pregunta resuelve una decisión. Si puede responderla explorando el código, lo hace.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;/to-prd&lt;/code&gt;&lt;/strong&gt;: una vez alineados, convierte la conversación en un PRD (Product Requirements Document) con user stories, decisiones de implementación y criterios de aceptación. Se publica como issue en GitHub, Linear o el tracker que uses.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;/to-issues&lt;/code&gt;&lt;/strong&gt;: rompe el PRD en issues independientes usando &lt;em&gt;vertical slices&lt;/em&gt; (tracer bullets). Cada issue atraviesa todas las capas (schema, API, tests) y es demoable por sí mismo. Distingue entre HITL (human-in-the-loop, requiere decisión humana) y AFK (automatizable de principio a fin).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;/tdd&lt;/code&gt;&lt;/strong&gt;: Test-Driven Development con ciclo red-green-refactor. Una skill sustancial que incluye filosofía sobre mocking, módulos profundos y diseño de interfaces. Escribir un test que falla → implementación mínima → refactor. Una iteración a la vez, nunca todas las vistas horizontales juntas.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;/improve-codebase-architecture&lt;/code&gt;&lt;/strong&gt;: para cuando el código se ha convertido en una bola de barro. Explora el codebase buscando módulos poco profundos, acoplamientos ocultos y falta de testabilidad.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;/grill-with-docs&lt;/code&gt;&lt;/strong&gt;: variante de grill-me que además actualiza un &lt;code&gt;CONTEXT.md&lt;/code&gt; con el lenguaje compartido del dominio y escribe ADRs para decisiones difíciles de revertir. Es la skill favorita de Matt — combinación de entrevista con domain-driven design.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;Es notable cómo el flujo entero encaja con lo que hemos visto antes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;El scaffolding de Every&lt;/strong&gt; (CLAUDE.md que acumula conocimiento) aparece aquí como &lt;code&gt;CONTEXT.md&lt;/code&gt; + ADRs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;La disciplina de Addy Osmani&lt;/strong&gt; (tablas anti-racionalización, workflows con checkpoints) aparece en el TDD skill con sus checklist por ciclo.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;La metodología estructurada de Superpowers&lt;/strong&gt; (file structure primero, descomposición en unidades, loops adversariales) aparece aquí como vertical slices, tracer bullets y la separación HITL/AFK.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pero Matt lo envuelve todo en skills tan cortas que caben en un tuit. No hay sobreingeniería. Hay pragmatismo.&lt;/p&gt;
&lt;h2 id="mi-lectura"&gt;Mi lectura&lt;a class="headerlink" href="#mi-lectura" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Matt Pocock ha hecho algo que me parece más difícil que construir un framework: ha destilado su experiencia en piezas tan pequeñas que cualquiera puede adoptarlas sin cambiar su flujo de trabajo.&lt;/p&gt;
&lt;p&gt;El &lt;code&gt;/grill-me&lt;/code&gt; me resulta especialmente familiar porque mi flujo personal con agentes siempre ha incluido una fase de preguntas iterativas — casi una versión manual de lo que Matt ha codificado. Verlo convertido en skill pública, con 62.000 personas dándole estrellas, confirma que el instinto era correcto: la alineación temprana es el paso más infravalorado en el trabajo con agentes.&lt;/p&gt;
&lt;p&gt;El resto de skills son igual de sólidas. El &lt;code&gt;to-prd&lt;/code&gt; y &lt;code&gt;to-issues&lt;/code&gt; formalizan algo que cualquier ingeniero senior hace instintivamente (pensar antes de escribir, dividir el trabajo en trozos revisables) pero que raramente se documenta. El TDD skill es el mejor que he visto para agentes: no solo dice "haz TDD", sino que explica &lt;em&gt;por qué&lt;/em&gt; las vistas horizontales producen tests malos y &lt;em&gt;cómo&lt;/em&gt; hacer ciclos verticales correctamente.&lt;/p&gt;
&lt;p&gt;Lo que diferencia a Matt del resto es la &lt;strong&gt;accesibilidad&lt;/strong&gt;. Superpowers requiere integrar un runtime. Agent Skills requiere instalar un plugin. Las skills de Matt son ficheros markdown que puedes copiar a tu &lt;code&gt;.claude/&lt;/code&gt; en 30 segundos. Y si no quieres instalarlas, leerlas ya te da el framework mental.&lt;/p&gt;
&lt;p&gt;No creo que las skills de Matt reemplacen a Superpowers o Agent Skills. Son herramientas para contextos diferentes. Superpowers es para cuando quieres una metodología completa que el agente no pueda saltarse. Matt es para cuando quieres piezas pequeñas, intercambiables, que entiendes y modificas.&lt;/p&gt;
&lt;p&gt;Las dos tienen sentido. Pero Matt ha demostrado algo importante: a veces la mejor skill es la que cabe en tres líneas.&lt;/p&gt;
&lt;h2 id="recursos"&gt;Recursos&lt;a class="headerlink" href="#recursos" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/mattpocock/skills"&gt;mattpocock/skills en GitHub&lt;/a&gt; — El repositorio (62K+ estrellas)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.aihero.dev/skills"&gt;AI Hero — Skills&lt;/a&gt; — La web oficial con guías de cada skill&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.aihero.dev/5-agent-skills-i-use-every-day"&gt;5 Agent Skills I Use Every Day&lt;/a&gt; — El artículo de Matt presentando su kit&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.aihero.dev/my-grill-me-skill-has-gone-viral"&gt;My Grill Me Skill Has Gone Viral&lt;/a&gt; — Sobre el fenómeno &lt;code&gt;/grill-me&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pablocaro.es/superpowers-obra-jesse-vincent"&gt;Superpowers — Jesse Vincent&lt;/a&gt; — Artículo anterior de la serie&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pablocaro.es/agent-skills-addy-osmani"&gt;Agent Skills — Addy Osmani&lt;/a&gt; — Artículo anterior de la serie&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pablocaro.es/compound-engineering-every"&gt;Compound Engineering — Every&lt;/a&gt; — Artículo anterior de la serie&lt;/li&gt;
&lt;/ul&gt;</content><category term="ia"></category><category term="agentes"></category><category term="ia"></category><category term="skills"></category><category term="matt-pocock"></category><category term="claude-code"></category></entry><entry><title>Superpowers — Jesse Vincent impone metodología a los agentes de codificación</title><link href="https://pablocaro.es/superpowers-obra-jesse-vincent" rel="alternate"></link><published>2026-05-06T00:00:00+02:00</published><updated>2026-05-06T00:00:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-05-06:/superpowers-obra-jesse-vincent</id><summary type="html">&lt;p&gt;Cuarta entrega de la serie sobre ingeniería de programación con agentes. Hoy toca un proyecto que ha cambiado las reglas del juego: &lt;strong&gt;Superpowers&lt;/strong&gt;, de Jesse Vincent.&lt;/p&gt;
&lt;p&gt;Con 180.000 estrellas en GitHub y 16.000 forks, Superpowers es el marco de habilidades para agentes más popular del momento. Pero lo …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Cuarta entrega de la serie sobre ingeniería de programación con agentes. Hoy toca un proyecto que ha cambiado las reglas del juego: &lt;strong&gt;Superpowers&lt;/strong&gt;, de Jesse Vincent.&lt;/p&gt;
&lt;p&gt;Con 180.000 estrellas en GitHub y 16.000 forks, Superpowers es el marco de habilidades para agentes más popular del momento. Pero lo que lo hace especial no es el número de estrellas, sino su obsesión: imponer una metodología de ingeniería completa a los agentes de código, desde el primer prompt hasta el último commit.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Superpowers — Jesse Vincent" src="/images/superpowers-obra-cover.png"&gt;&lt;/p&gt;
&lt;h2 id="quien-es-jesse-vincent"&gt;Quién es Jesse Vincent&lt;a class="headerlink" href="#quien-es-jesse-vincent" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Jesse Vincent es una figura conocida en el mundo del software de código abierto. Es el creador de &lt;strong&gt;Perl 6&lt;/strong&gt; (hoy Raku), cofundó &lt;strong&gt;Best Practical&lt;/strong&gt; y construyó &lt;strong&gt;RT&lt;/strong&gt; (Request Tracker), el sistema de ticketing usado por organizaciones como la Free Software Foundation y el kernel de Linux. Hoy está en &lt;strong&gt;Prime Radiant&lt;/strong&gt;, una empresa que construye herramientas para ingeniería con agentes.&lt;/p&gt;
&lt;p&gt;Su blog es un referente en la comunidad de coding agents desde octubre de 2025, cuando publicó el primer artículo sobre Superpowers. Desde entonces, cada release ha sido un evento.&lt;/p&gt;
&lt;h2 id="la-idea-central"&gt;La idea central&lt;a class="headerlink" href="#la-idea-central" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Superpowers no es un plugin más. Es una &lt;strong&gt;metodología de desarrollo completa&lt;/strong&gt; para agentes de codificación, construida sobre habilidades composables que se activan automáticamente según el contexto.&lt;/p&gt;
&lt;p&gt;El flujo básico es:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Brainstorming&lt;/strong&gt; — El agente NO empieza a programar. Pregunta, explora alternativas y refina la idea antes de tocar una línea.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Using-git-worktrees&lt;/strong&gt; — Crea un workspace aislado en una rama nueva.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Writing-plans&lt;/strong&gt; — Descompone el trabajo en tareas de 2-5 minutos, cada una con paths exactos, código completo y pasos de verificación.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Subagent-driven-development o executing-plans&lt;/strong&gt; — Despacha agentes para implementar o ejecuta en lotes con checkpoints humanos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test-driven-development&lt;/strong&gt; — RED-GREEN-REFACTOR obligatorio. Tests primero, siempre.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Requesting-code-review&lt;/strong&gt; — Review contra el plan, con severidades Critical/Nit/Optional.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Finishing-a-development-branch&lt;/strong&gt; — Tests, merge o PR, cleanup.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;El mantra que resume todo: &lt;strong&gt;si el agente empieza a codificar sin hacer preguntas, Superpowers ha fallado&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="visual-brainstorming"&gt;Visual Brainstorming&lt;a class="headerlink" href="#visual-brainstorming" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;La novedad más llamativa de la versión 5 es el &lt;strong&gt;Visual Brainstorming Companion&lt;/strong&gt;: un servidor web local que el agente levanta para mostrarte mockups, diagramas y comparaciones visuales en el navegador mientras conversáis.&lt;/p&gt;
&lt;p&gt;El origen es curioso. Jesse se encontró pidiendo a Claude una y otra vez: &lt;em&gt;"Oye, ¿por qué no escribes eso como HTML para que pueda ver lo que quieres decir?"&lt;/em&gt; Hasta que recordó el mantra más importante de la programación con agentes: &lt;strong&gt;"¿Por qué lo estoy haciendo yo?"&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;El agente ahora pregunta si quieres probar el compañero visual, y si aceptas, te abre un navegador con contenido interactivo. Detrás, un servidor WebSocket sirve chunks de HTML que el agente escribe a disco, con JavaScript para capturar clics y feedback.&lt;/p&gt;
&lt;p&gt;Jesse lo probó para idear un logo para Prime Radiant con Claude. Dice que imaginar cómo habría sido en ASCII art da escalofríos.&lt;/p&gt;
&lt;h2 id="spec-review-el-bucle-adversarial"&gt;Spec Review: el bucle adversarial&lt;a class="headerlink" href="#spec-review-el-bucle-adversarial" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Otro avance de la v5 es el &lt;strong&gt;spec review loop&lt;/strong&gt;. Jesse detectó que los agentes dejaban secciones "TBD" o "Fill this in later" en los documentos de planificación — y que eso acababa tan mal como cabría esperar.&lt;/p&gt;
&lt;p&gt;La solución: después de planificar, Superpowers lanza un subagente que lee la documentación de diseño buscando lagunas y rarezas. No es un sustituto de leerla uno mismo, pero mejora drásticamente la calidad de los planes.&lt;/p&gt;
&lt;p&gt;Es el mismo patrón que hemos visto en otras herramientas de la serie: &lt;strong&gt;un loop adversarial de revisión&lt;/strong&gt;. El planificador produce, el revisor critica, el planificador corrige. Se repite hasta que no quedan objeciones.&lt;/p&gt;
&lt;h2 id="subagent-driven-development"&gt;Subagent Driven Development&lt;a class="headerlink" href="#subagent-driven-development" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Hasta la v4, Superpowers ofrecía al usuario la opción entre subagentes o ejecución secuencial con checkpoints humanos. Esa ambigüedad venía de una época en que los subagentes eran nuevos y Jesse no se fiaba de ellos.&lt;/p&gt;
&lt;p&gt;A partir de la v5, si tu agente soporta subagentes, &lt;strong&gt;Subagent Driven Development es obligatorio&lt;/strong&gt;. Si no los soporta, Superpowers te advierte que otro harness lo haría mejor, y luego hace lo que puede.&lt;/p&gt;
&lt;p&gt;En harnesses como Claude Code que pueden elegir qué modelo usar para cada subagente, Superpowers instruye: modelo barato para tareas mecánicas, estándar para integración, capaz para arquitectura y revisión. Y los subagentes ahora tienen un protocolo de estado: DONE, DONE_WITH_CONCERNS, BLOCKED, NEEDS_CONTEXT.&lt;/p&gt;
&lt;h2 id="ingenieria-de-software-como-principio"&gt;Ingeniería de software como principio&lt;a class="headerlink" href="#ingenieria-de-software-como-principio" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;El valor de las interfaces comienza aquí:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Descomposición en unidades&lt;/strong&gt;: cada unidad debe tener un propósito claro, interfaces bien definidas y ser testeable de forma independiente.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;"¿Puede alguien entender lo que hace una unidad sin leer su implementación? ¿Puedes cambiar la implementación sin romper a los consumidores?"&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Archivos grandes como smell de diseño&lt;/strong&gt;: no es solo un problema de estilo, es una advertencia de que la descomposición falló.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;File Structure como paso obligatorio&lt;/strong&gt;: antes de descomponer en tareas, primero decides qué archivos existen y qué responsabilidad tiene cada uno.
  Esto es diseño guiado por interfaces llevado desde la especificación hasta la implementación y la revisión.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="otras-decisiones-de-diseno-interesantes"&gt;Otras decisiones de diseño interesantes&lt;a class="headerlink" href="#otras-decisiones-de-diseno-interesantes" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Las skills no son documentación.&lt;/strong&gt; Son &lt;strong&gt;workflows&lt;/strong&gt;: secuencias de pasos que el agente sigue, con checkpoints que producen evidencia, terminando en un criterio de salida definido. Si metes un ensayo de 2000 palabras en el contexto, el agente lo lee y salta la ejecución real. Si metes un workflow, el agente tiene algo que hacer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Prioridad de instrucciones.&lt;/strong&gt; Superpowers establece una jerarquía explícita:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Tus instrucciones directas (CLAUDE.md, AGENTS.md) — prioridad máxima&lt;/li&gt;
&lt;li&gt;Skills de Superpowers — overriding del comportamiento por defecto&lt;/li&gt;
&lt;li&gt;System prompt por defecto — prioridad mínima&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Si tu CLAUDE.md dice "no uses TDD" y un skill de Superpowers dice "usa TDD siempre", gana tu instrucción.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Divulgación progresiva.&lt;/strong&gt; No se cargan las 20 skills en el contexto al inicio. Un meta-skill actúa como router que decide qué skills aplicar según la tarea. Cada skill describe cuándo debe activarse. El agente comprueba si aplica. Si no, sigue.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tablas anti-racionalización.&lt;/strong&gt; Cada skill incluye una tabla de excusas comunes que un agente podría usar para saltarse el workflow, emparejadas con una réplica. Esto es especialmente efectivo porque los LLMs son excelentes produciendo justificaciones plausibles para hacer lo que les apetece.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;El SUBAGENT-STOP gate.&lt;/strong&gt; Los subagentes despachados para tareas específicas se saltan el meta-skill y no activan flujos completos de skills. Esto evita que un subagente que solo debe implementar un cambio empiece a hacer brainstorming.&lt;/p&gt;
&lt;h2 id="en-que-se-diferencia-de-los-otros"&gt;En qué se diferencia de los otros&lt;a class="headerlink" href="#en-que-se-diferencia-de-los-otros" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Every&lt;/strong&gt; se centra en el bucle de aprendizaje: compound engineering acumula conocimiento en CLAUDE.md para que cada ciclo sea más eficiente.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Addy Osmani&lt;/strong&gt; se centra en la disciplina: agent-skills fuerza al agente a pasar por las fases que un ingeniero senior nunca saltaría, con tablas anti-racionalización como arma principal.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Superpowers&lt;/strong&gt; es más ambicioso: es un &lt;strong&gt;sistema operativo completo&lt;/strong&gt; para agentes de código. No solo define workflows, sino que gestiona worktrees de git, decide qué modelo usar para cada subagente, revisa especificaciones con loops adversariales, y tiene un protocolo de comunicación entre subagentes.&lt;/p&gt;
&lt;p&gt;Si Every es compound engineering y Addy es harness engineering, Superpowers es &lt;strong&gt;methodology engineering&lt;/strong&gt;: construir el sistema que obliga al agente a seguir una metodología completa de desarrollo de software.&lt;/p&gt;
&lt;p&gt;Addy pone el foco en la disciplina del ingeniero senior. Superpowers pone el foco en la &lt;strong&gt;disciplina del proceso&lt;/strong&gt;: el workflow es el que manda, no el agente. La diferencia es sutil pero importante.&lt;/p&gt;
&lt;h2 id="lo-que-mas-me-ha-gustado"&gt;Lo que más me ha gustado&lt;a class="headerlink" href="#lo-que-mas-me-ha-gustado" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;El mantra "¿Por qué lo estoy haciendo yo?"&lt;/strong&gt; aplicado al Visual Brainstorming. Es la pregunta que todo el que programa con agentes debería hacerse constantemente. Si el agente puede hacerlo, que lo haga.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;El spec review loop.&lt;/strong&gt; El patrón adversarial para mejorar la calidad de los planes es simple, efectivo, y aplicable a cualquier flujo de trabajo con agentes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;La prioridad de instrucciones.&lt;/strong&gt; Poner CLAUDE.md por encima de las skills de Superpowers es el gesto de diseño más honesto del proyecto. Le dice al usuario: "esto es para ayudarte, no para decirte lo que tienes que hacer".&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;La tasa de rechazo del 94% como señal de calidad.&lt;/strong&gt; Que el creador sea tan agresivo contra el slop de agentes es una declaración de intenciones. Superpowers no quiere más contribuciones, quiere contribuciones excelentes.&lt;/p&gt;
&lt;h2 id="mi-lectura"&gt;Mi lectura&lt;a class="headerlink" href="#mi-lectura" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Superpowers es el proyecto más completo de los que hemos visto en esta serie. Jesse Vincent lleva desde octubre de 2025 iterando sobre la misma idea: cómo hacer que un agente de código se comporte como un ingeniero con experiencia, no como un junior que escribe la primera solución que se le ocurre.&lt;/p&gt;
&lt;p&gt;La diferencia con otras aproximaciones es que Superpowers no confía en el agente. No confía en que "sabrá lo que tiene que hacer". Le da un workflow, le obliga a seguirlo, revisa que lo ha seguido, y si se desvía, le redirige.&lt;/p&gt;
&lt;p&gt;Eso suena restrictivo, y lo es. Pero la experiencia de cientos de miles de usuarios sugiere que es exactamente lo que los agentes necesitan: estructura, no libertad.&lt;/p&gt;
&lt;p&gt;Para mí, lo más valioso de Superpowers no son las skills, sino la &lt;strong&gt;filosofía&lt;/strong&gt;: si quieres que un agente produzca trabajo de calidad, no puedes delegar la metodología al agente. Tienes que codificarla en skills que se activen automáticamente, con checkpoints que produzcan evidencia, y con loops adversariales que verifiquen que no se han saltado nada.&lt;/p&gt;
&lt;p&gt;Este framework es el que más he usado de los tres que he revisado en la serie. Desde sus primeras versiones con Claude Code se mostró muy sólido, y cada release ha sido un salto adelante.&lt;/p&gt;
&lt;p&gt;El próximo artículo de la serie explorará otro enfoque. Pero creo que Superpowers marca un antes y un después en cómo pensamos sobre la ingeniería con agentes.&lt;/p&gt;
&lt;h2 id="recursos"&gt;Recursos&lt;a class="headerlink" href="#recursos" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/obra/superpowers"&gt;Superpowers en GitHub&lt;/a&gt; — El repositorio (180K+ estrellas)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.fsck.com/2026/03/09/superpowers-5/"&gt;Superpowers 5 — el anuncio&lt;/a&gt; — El artículo de Jesse Vincent&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.fsck.com/2025/10/09/superpowers/"&gt;Superpowers: How I'm using coding agents in October 2025&lt;/a&gt; — El artículo original de lanzamiento&lt;/li&gt;
&lt;li&gt;&lt;a href="https://primeradiant.com"&gt;Prime Radiant&lt;/a&gt; — La empresa de Jesse Vincent&lt;/li&gt;
&lt;li&gt;&lt;a href="https://every.to/chain-of-thought/compound-engineering-how-every-codes-with-agents"&gt;Compound Engineering — Every&lt;/a&gt; — Artículo anterior de la serie&lt;/li&gt;
&lt;li&gt;&lt;a href="https://addyosmani.com/blog/agent-skills/"&gt;Agent Skills de Addy Osmani&lt;/a&gt; — Artículo anterior de la serie&lt;/li&gt;
&lt;/ul&gt;</content><category term="ia"></category><category term="agentes"></category><category term="ia"></category><category term="superpowers"></category><category term="jesse-vincent"></category><category term="claude-code"></category><category term="prime-radiant"></category></entry><entry><title>Agent Skills de Addy Osmani — Imponiendo disciplina de ingeniería senior a los agentes</title><link href="https://pablocaro.es/agent-skills-addy-osmani" rel="alternate"></link><published>2026-05-06T00:00:00+02:00</published><updated>2026-05-06T00:00:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-05-06:/agent-skills-addy-osmani</id><summary type="html">&lt;p&gt;En las dos partes anteriores de esta serie hemos visto una visión general de la ingeniería con agentes y luego la metodología de compound engineering de Every. Ahora toca otra aproximación similar: &lt;strong&gt;Agent Skills&lt;/strong&gt;, el proyecto de Addy Osmani en Google que lleva casi 30.000 estrellas en GitHub y …&lt;/p&gt;</summary><content type="html">&lt;p&gt;En las dos partes anteriores de esta serie hemos visto una visión general de la ingeniería con agentes y luego la metodología de compound engineering de Every. Ahora toca otra aproximación similar: &lt;strong&gt;Agent Skills&lt;/strong&gt;, el proyecto de Addy Osmani en Google que lleva casi 30.000 estrellas en GitHub y que aborda el problema desde un ángulo completamente diferente.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Agent Skills de Addy Osmani" src="/images/agent-skills-addy-cover.png"&gt;&lt;/p&gt;
&lt;h2 id="el-problema-que-nadie-quiere-ver"&gt;El problema que nadie quiere ver&lt;a class="headerlink" href="#el-problema-que-nadie-quiere-ver" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Addy plantea algo incómodo: el comportamiento por defecto de cualquier agente de código es tomar el camino más corto hacia "hecho". Le pides una feature y te escribe la feature. No pregunta si tienes una especificación. No escribe un test antes de la implementación. No considera si el cambio cruza un límite de confianza. No verifica cómo se verá el PR para un revisor. Produce código, declara victoria y sigue adelante.&lt;/p&gt;
&lt;p&gt;Esto es exactamente el mismo fallo que todo ingeniero senior ha pasado su carrera aprendiendo a evitar. El trabajo senior no es el que aparece en el diff: es sacar a la luz suposiciones, escribir la especificación, fragmentar el trabajo en chunks revisables, elegir el diseño aburrido, dejar evidencia de que el resultado es correcto, dimensionar el cambio para que un humano pueda revisarlo.&lt;/p&gt;
&lt;p&gt;Los agentes saltan esos pasos por la misma razón que cualquier junior lo haría: son invisibles. La señal de recompensa apunta a "tarea completada", no a "tarea completada y el documento de diseño existe". Así que hay que atornillar el andamiaje de ingeniero senior de nuevo.&lt;/p&gt;
&lt;h2 id="que-es-una-skill"&gt;Qué es una skill&lt;a class="headerlink" href="#que-es-una-skill" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Addy define una skill con precisión: es un archivo markdown con frontmatter que se inyecta en el contexto del agente cuando la situación lo requiere. Algo entre un fragmento de system prompt y un runbook.&lt;/p&gt;
&lt;p&gt;Una skill &lt;strong&gt;no es&lt;/strong&gt; documentación de referencia. No es "todo lo que deberías saber sobre testing". Es un &lt;strong&gt;workflow&lt;/strong&gt;: una secuencia de pasos que el agente sigue, con checkpoints que producen evidencia, terminando en un criterio de salida definido.&lt;/p&gt;
&lt;p&gt;Esa distinción lo cambia todo. Si metes un ensayo de 2000 palabras sobre mejores prácticas de testing en el contexto del agente, el agente lo lee, genera texto plausible, y salta el testing de verdad. Si metes un &lt;strong&gt;workflow&lt;/strong&gt; (escribe el test que falla primero, ejecútalo, míralo fallar, escribe el mínimo código para pasar, míralo pasar, refactoriza), el agente tiene algo que hacer, y tú tienes algo que verificar.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Proceso descrito con claridad. Workflows sobre referencia. Pasos con criterios de salida sobre ensayos sin ellos.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="las-20-skills-y-los-7-comandos"&gt;Las 20 skills y los 7 comandos&lt;a class="headerlink" href="#las-20-skills-y-los-7-comandos" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;El repositorio organiza 20 skills alrededor de 6 fases del ciclo de vida del software, con 7 comandos slash encima:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Fase&lt;/th&gt;
&lt;th&gt;Comando&lt;/th&gt;
&lt;th&gt;Qué hace&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Define&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/spec&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Decide qué estás construyendo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plan&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/plan&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fragmenta el trabajo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/build&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Implementa en slices verticales&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verify&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/test&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Demuestra que funciona&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Review&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/review&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Atrapa lo que se escapó&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ship&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/ship&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Lleva a producción de forma segura&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;También hay &lt;code&gt;/code-simplify&lt;/code&gt; que atraviesa todo el ciclo.&lt;/p&gt;
&lt;p&gt;Una feature compleja podría activar 11 skills en secuencia. Un bug pequeño podría usar 3. Un meta-skill (&lt;code&gt;using-agent-skills&lt;/code&gt;) actúa como router que decide cuáles aplican. El workflow escala al scope real, no al scope asumido.&lt;/p&gt;
&lt;h2 id="cinco-principios-que-sostienen-todo"&gt;Cinco principios que sostienen todo&lt;a class="headerlink" href="#cinco-principios-que-sostienen-todo" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="1-proceso-simple-y-directo"&gt;1. Proceso simple y directo&lt;a class="headerlink" href="#1-proceso-simple-y-directo" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Workflows son accionables para agentes; ensayos no. Lo mismo ocurre en equipos humanos. Si tu manual del equipo tiene 200 páginas, nadie lo lee bajo presión. Si es un conjunto pequeño de workflows con checkpoints, la gente realmente los ejecuta.&lt;/p&gt;
&lt;h3 id="2-tablas-anti-racionalizacion"&gt;2. Tablas anti-racionalización&lt;a class="headerlink" href="#2-tablas-anti-racionalizacion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Esta es la decisión de diseño más distintiva del proyecto. Cada skill incluye una tabla de excusas comunes que un agente (o un ingeniero cansado) podría usar para saltarse el workflow, emparejadas con una réplica.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;"Esta tarea es demasiado simple para necesitar una especificación."&lt;/em&gt; → Los criterios de aceptación siguen aplicando. Cinco líneas están bien. Cero líneas no.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Escribiré los tests después."&lt;/em&gt; → "Después" es la palabra que sostiene todo. No hay después. Escribe el test que falla primero.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Los tests pasan, mándalo."&lt;/em&gt; → Tests que pasan son evidencia, no prueba. ¿Comprobaste el runtime? ¿Verificaste el comportamiento visible al usuario? ¿Un humano leyó el diff?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;La razón por la que esto funciona es que los LLMs son excelentes en la racionalización. Producirán un párrafo plausible explicando por qué &lt;em&gt;esta tarea en particular&lt;/em&gt; no necesita una spec, o por qué &lt;em&gt;este cambio en particular&lt;/em&gt; está bien para mergear sin revisión. &lt;strong&gt;Las tablas anti-racionalización son réplicas pre-escritas a mentiras que el agente aún no ha contado.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;El patrón es igual de bueno para equipos humanos. La mayoría de la decadencia en ingeniería no es que alguien elija hacer mal trabajo. Es que la gente acepta justificaciones plausibles para saltarse las partes que no le apetecen hacer.&lt;/p&gt;
&lt;h3 id="3-la-verificacion-no-es-negociable"&gt;3. La verificación NO es negociable&lt;a class="headerlink" href="#3-la-verificacion-no-es-negociable" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Cada skill termina en evidencia concreta. Tests pasan. La salida del build está limpia. El trace del runtime muestra el comportamiento esperado. Un revisor da el visto bueno. "Parece correcto" nunca es suficiente.&lt;/p&gt;
&lt;p&gt;El agente es un generador. Necesitas una señal separada de que el trabajo está hecho. Las skills hornean esa señal en cada workflow.&lt;/p&gt;
&lt;h3 id="4-divulgacion-progresiva"&gt;4. Divulgación progresiva&lt;a class="headerlink" href="#4-divulgacion-progresiva" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;No cargues las 20 skills en el contexto al inicio de la sesión. Actívalas según la fase.&lt;/p&gt;
&lt;p&gt;Cada token cargado en el contexto degrada el rendimiento en algún lugar, así que cargas lo relevante y dejas el resto en disco. La divulgación progresiva es cómo metes una librería de 20 skills en un slot de 5K tokens sin envenenar el pozo.&lt;/p&gt;
&lt;h3 id="5-disciplina-de-scope"&gt;5. Disciplina de scope&lt;a class="headerlink" href="#5-disciplina-de-scope" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;El meta-skill codifica algo que graparía a cada agente si pudiera: &lt;em&gt;"toca solo lo que te piden tocar"&lt;/em&gt;. No refactorices sistemas adyacentes. No elimines código que no entiendes completamente. No topes con un TODO y decidas reescribir el archivo.&lt;/p&gt;
&lt;p&gt;Esto suena obvio hasta que ves a un agente decidir que arreglar un bug requiere modernizar tres archivos no relacionados. La disciplina de scope es el determinante más importante de si el PR de un agente es mergeable o hay que deshacerlo.&lt;/p&gt;
&lt;h2 id="el-adn-de-google"&gt;El ADN de Google&lt;a class="headerlink" href="#el-adn-de-google" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Addy trabaja en Google, y las skills están saturadas de prácticas de &lt;em&gt;Software Engineering at Google&lt;/em&gt; y la cultura de ingeniería pública de Google. Esto es intencional. La mayoría de lo que hace que el software a escala de Google funcione está documentado y es público, y es &lt;em&gt;exactamente&lt;/em&gt; la parte que los agentes son más propensos a saltar.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Ley de Hyrum&lt;/strong&gt; en &lt;code&gt;api-and-interface-design&lt;/code&gt;. Cualquier comportamiento observable de tu API eventualmente será dependido por alguien.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;La pirámide de tests (~80/15/5) y la Regla de Beyoncé&lt;/strong&gt; en &lt;code&gt;test-driven-development&lt;/code&gt;. &lt;em&gt;"Si te gustó, deberías haberle puesto un test".&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DAMP sobre DRY en tests.&lt;/strong&gt; La filosofía de testing de Google es explícita: el código de test debe leerse como una especificación, incluso a costa de algo de duplicación.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PRs de ~100 líneas, con etiquetas de severidad Critical / Nit / Optional / FYI&lt;/strong&gt; en &lt;code&gt;code-review-and-quality&lt;/code&gt;. Directamente de las normas de code review de Google. PRs grandes no se revisan; se sellan sin mirar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;La Valla de Chesterton&lt;/strong&gt; en &lt;code&gt;code-simplification&lt;/code&gt;. No elimines algo hasta que entiendas por qué fue puesto ahí.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Desarrollo basado en trunk y commits atómicos&lt;/strong&gt; en &lt;code&gt;git-workflow-and-versioning&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shift Left y feature flags&lt;/strong&gt; en &lt;code&gt;ci-cd-and-automation&lt;/code&gt;. Atrapa problemas lo antes posible, desacopla deploy de release.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Código como pasivo&lt;/strong&gt; en &lt;code&gt;deprecation-and-migration&lt;/code&gt;. Cada línea que mantienes es una que tienes que mantener para siempre.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ninguna de estas ideas es nueva. &lt;strong&gt;El punto es que ninguna de ellas está en el agente por defecto.&lt;/strong&gt; Un modelo frontier ha leído la frase "Ley de Hyrum" en su training data, pero no aplica la Ley de Hyrum cuando diseña tu API a las 3 de la mañana mientras duermes. Las skills son cómo te aseguras de que sí lo hace.&lt;/p&gt;
&lt;h2 id="que-puedes-usar-aunque-decidas-no-seguir-la-estrategia-completa"&gt;Qué puedes usar aunque decidas no seguir la estrategia completa&lt;a class="headerlink" href="#que-puedes-usar-aunque-decidas-no-seguir-la-estrategia-completa" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Anti-racionalización como práctica de equipo.&lt;/strong&gt; Escribe las mentiras que tu equipo se cuenta a sí mismo. &lt;em&gt;"Arreglaremos los tests después del lanzamiento."&lt;/em&gt; &lt;em&gt;"Este cambio es demasiado pequeño para un doc de diseño."&lt;/em&gt; &lt;em&gt;"Está bien, tenemos monitorización."&lt;/em&gt; Empareja cada una con la réplica. Ponlo en tu AGENTS.md o en tu wiki de ingeniería. Te ahorrará argumentos y atrapará el próximo atajo del viernes por la tarde.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Proceso claro y sobre papel para cualquier cosa que escribas internamente.&lt;/strong&gt; Si te encuentras escribiendo un documento de 2000 palabras titulado "cómo abordamos X", has escrito material de referencia. Conviértelo a un workflow con checkpoints. El documento se reduce a 400 palabras y la gente realmente lo ejecuta.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Verificación como criterio de salida duro.&lt;/strong&gt; Haz que "producir evidencia" sea el paso de salida de cada tarea. Para agentes, para ingenieros, para ti mismo. La evidencia es lo que demuestra que el trabajo está hecho: una ejecución de tests en verde, una captura de pantalla, un log, una aprobación de review. Sin ella, la tarea no está hecha. "Parece correcto" nunca cierra el bucle.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Divulgación progresiva para cualquier manual.&lt;/strong&gt; No escribas un manual de 50 páginas. Escribe un router pequeño que apunte al capítulo pequeño correcto para la situación. Esto es cierto para AGENTS.md, para runbooks, para playbooks de incidentes, para cualquier cosa que alguien lea bajo presión de tiempo.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cinco no negociables, extraídos del meta-skill, que pondría en cualquier AGENTS.md mañana:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Saca a la luz las suposiciones antes de construir. Suposiciones equivocadas mantenidas en silencio son el modo de fallo más común.&lt;/li&gt;
&lt;li&gt;Para y pregunta cuando los requisitos entren en conflicto. No adivines.&lt;/li&gt;
&lt;li&gt;Empuja hacia atrás cuando sea warranted. El agente (o ingeniero) no es una máquina de decir sí.&lt;/li&gt;
&lt;li&gt;Prefiere la solución aburrida y obvia. La astucia es cara.&lt;/li&gt;
&lt;li&gt;Toca solo lo que te piden tocar.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Esa es una cultura de ingeniería válida en cinco líneas, y no necesitas instalar nada para adoptarla.&lt;/p&gt;
&lt;h2 id="mi-lectura"&gt;Mi lectura&lt;a class="headerlink" href="#mi-lectura" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Lo que propone Addy es distinto a lo de Every, pero complementario. Every se centra en el bucle de aprendizaje: cada iteración hace que el siguiente ciclo sea más fácil. Addy se centra en la disciplina: cada tarea debe pasar por las fases que un ingeniero senior nunca saltaría.&lt;/p&gt;
&lt;p&gt;Every es compound engineering. Addy es &lt;strong&gt;harness engineering&lt;/strong&gt; — construir el arnés que fuerza al agente a comportarse como un senior. El repositorio de Addy no es un framework, es una biblioteca de workflows portables. El mismo archivo SKILL.md funciona en Claude Code, Cursor, Gemini CLI, Codex, y cualquier otro entorno que acepte contenido de system prompt.&lt;/p&gt;
&lt;p&gt;La implicación es clara: no basta con tener un agente potente. Necesitas un sistema que le impida tomar atajos. Las tablas anti-racionalización son la idea más brillante del proyecto porque atacan directamente la debilidad más peligrosa de los LLMs: su capacidad para producir justificaciones plausibles para hacer mal trabajo.&lt;/p&gt;
&lt;p&gt;En mi propio flujo, voy a adoptar dos cosas inmediatamente: las tablas anti-racionalización para las tareas donde más fallo (saltarme tests en cambios "pequeños", asumir que "ya lo revisaré después"), y la regla de los cinco no negociables como checklist mental antes de cualquier sesión con un agente.&lt;/p&gt;
&lt;h2 id="recursos"&gt;Recursos&lt;a class="headerlink" href="#recursos" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/addyosmani/agent-skills"&gt;Agent Skills en GitHub&lt;/a&gt; — El repositorio con las 20 skills (29K+ estrellas)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://addyosmani.com/blog/agent-skills/"&gt;Agent Skills — el artículo&lt;/a&gt; — El post de Addy explicando el diseño&lt;/li&gt;
&lt;li&gt;&lt;a href="https://addyosmani.com/blog/agent-harness-engineering/"&gt;Agent Harness Engineering&lt;/a&gt; — El artículo sobre el arnés de agentes en general&lt;/li&gt;
&lt;li&gt;&lt;a href="https://addyosmani.com/blog/long-running-agents/"&gt;Long-running Agents&lt;/a&gt; — Por qué los skills importan más en sesiones largas&lt;/li&gt;
&lt;/ul&gt;</content><category term="ia"></category><category term="agentes"></category><category term="ia"></category><category term="agent-skills"></category><category term="addy-osmani"></category><category term="google"></category><category term="claude-code"></category></entry><entry><title>Compound Engineering — La visión de Every sobre ingeniería con agentes</title><link href="https://pablocaro.es/compound-engineering-every" rel="alternate"></link><published>2026-05-06T00:00:00+02:00</published><updated>2026-05-06T00:00:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-05-06:/compound-engineering-every</id><summary type="html">&lt;p&gt;En la primera parte de esta serie hablé de cómo los agentes de IA han cambiado mi forma de construir software. Pero eso era solo el calentamiento. Lo que viene ahora es la visión de una empresa que ha llevado esta idea al extremo: &lt;strong&gt;Every&lt;/strong&gt;, la startup de Dan Shipper …&lt;/p&gt;</summary><content type="html">&lt;p&gt;En la primera parte de esta serie hablé de cómo los agentes de IA han cambiado mi forma de construir software. Pero eso era solo el calentamiento. Lo que viene ahora es la visión de una empresa que ha llevado esta idea al extremo: &lt;strong&gt;Every&lt;/strong&gt;, la startup de Dan Shipper y Kieran Klaassen, que ha construido no solo productos con agentes, sino una &lt;strong&gt;metodología completa&lt;/strong&gt; para hacerlo.&lt;/p&gt;
&lt;p&gt;La llaman &lt;strong&gt;compound engineering&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Compound Engineering — How Every Codes With Agents" src="/images/compound-engineering-every-cover.png"&gt;&lt;/p&gt;
&lt;h2 id="el-problema-con-la-ingenieria-tradicional"&gt;El problema con la ingeniería tradicional&lt;a class="headerlink" href="#el-problema-con-la-ingenieria-tradicional" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;En la ingeniería de software clásica, cada nueva feature complica la siguiente. Más código significa más casos límite, más interdependencias, más sorpresas que nadie anticipó. Es una curva de complejidad que solo sube.&lt;/p&gt;
&lt;p&gt;Every se dio cuenta de que con agentes escribiendo el código, esa premisa ya no tenía sento. Si un agente puede generar código en segundos, el cuello de botella deja de ser "escribir código" y pasa a ser "saber qué código escribir". Y si el agente puede aprender de cada iteración, la complejidad deja de ser un peso muerto y se convierte en combustible.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;En compound engineering, cada feature hace más fácil la siguiente. No porque el código desaparezca, sino porque el sistema aprende de él.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="el-bucle-de-compound-engineering"&gt;El bucle de compound engineering&lt;a class="headerlink" href="#el-bucle-de-compound-engineering" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Every define cuatro pasos que se repiten en ciclo. El truco está en que el output de cada ciclo alimenta el siguiente:&lt;/p&gt;
&lt;h3 id="1-plan"&gt;1. Plan&lt;a class="headerlink" href="#1-plan" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Aquí es donde pasa el 40% del tiempo de un compound engineer. No escribir código, no revisar PRs. &lt;strong&gt;Planificar&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;El agente investiga el codebase actual, lee la historia de commits, busca mejores prácticas en internet, y sintetiza todo en un plan de implementación detallado. Cuando empieza a escribir, ya está calentado. Ya conoce las convenciones del proyecto, los patrones que funcionaron antes, y los que fracasaron.&lt;/p&gt;
&lt;p&gt;Este paso es crítico porque un agente sin contexto genera código genérico. Un agente con contexto genera código que encaja en tu arquitectura como si lo hubiera escrito un miembro del equipo.&lt;/p&gt;
&lt;h3 id="2-work"&gt;2. Work&lt;a class="headerlink" href="#2-work" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;El agente escribe código y tests según el plan. Esto es lo que la mayoría de la gente imagina cuando piensa en "programar con IA": el agente genera funciones, clases, tests. Pero en compound engineering, este paso representa apenas el 10% del esfuerzo humano. El código se escribe solo. El valor está en todo lo que rodea a ese código.&lt;/p&gt;
&lt;h3 id="3-review"&gt;3. Review&lt;a class="headerlink" href="#3-review" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;El ingeniero revisa el output. No solo busca bugs, sino que extrae &lt;strong&gt;lecciones&lt;/strong&gt;. ¿Por qué el agente eligió esta abstracción? ¿Hubo un patrón que no reconoció? ¿Un edge case que olvidó?&lt;/p&gt;
&lt;p&gt;En Every usan múltiples agentes revisoras especializadas: una para seguridad, otra para rendimiento, otra para simplicidad, otra para mantenibilidad. Cada una mira el código desde un ángulo diferente. El ingeniero humano orquesta estas revisiones y decide qué feedback incorporar.&lt;/p&gt;
&lt;h3 id="4-compound"&gt;4. Compound&lt;a class="headerlink" href="#4-compound" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Este es el paso que cambia todo. Las lecciones extraídas en la review no se quedan en la cabeza del ingeniero. Se documentan. Se codifican. Se convierten en parte del sistema.&lt;/p&gt;
&lt;p&gt;Every usa archivos como &lt;code&gt;CLAUDE.md&lt;/code&gt; y &lt;code&gt;AGENTS.md&lt;/code&gt; donde se registra todo: patrones de código aprobados, decisiones arquitectónicas, bugs recurrentes y sus soluciones, estilos de nomenclatura, preferencias de diseño. La próxima vez que un agente trabaje en el proyecto, lee estos archivos antes de empezar. Cada ciclo hace que el agente sea más listo.&lt;/p&gt;
&lt;h2 id="un-ejemplo-real-el-detector-de-frustracion"&gt;Un ejemplo real: el detector de frustración&lt;a class="headerlink" href="#un-ejemplo-real-el-detector-de-frustracion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Kieran Klaassen describe en Every cómo construyeron un "detector de frustración" para su producto Cora, un asistente de email con IA. El objetivo: detectar cuando un usuario se está frustrando con la app y generar automáticamente un reporte de mejora.&lt;/p&gt;
&lt;p&gt;El flujo fue así:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Crearon una conversación de ejemplo&lt;/strong&gt; donde un usuario expresa frustración (preguntas repetidas, lenguaje cada vez más conciso).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Le pidieron a Claude que escribiera un test&lt;/strong&gt; que verifique si el sistema detecta esa frustración.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;El test falló&lt;/strong&gt; — como debe ser en TDD.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Le pidieron a Claude que escribiera la lógica de detección&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Funcionó parcialmente&lt;/strong&gt;. Entonces le pidieron que &lt;strong&gt;iterara el prompt&lt;/strong&gt; hasta que el test pasara.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Para evitar la no-determinación de la IA&lt;/strong&gt;, hicieron que Claude ejecutara el test 10 veces.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Analizó los 6 fallos&lt;/strong&gt;, leyó el chain-of-thought de cada uno, y descubrió un patrón: el sistema no detectaba lenguaje atenuado como "Hmm, not quite", que en contexto de repetición sí señala frustración.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Actualizó el prompt&lt;/strong&gt; para buscar específicamente ese lenguaje. En la siguiente iteración, identificaba frustración 9 de 10 veces.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Codificaron todo el flujo&lt;/strong&gt; en &lt;code&gt;CLAUDE.md&lt;/code&gt;. La próxima vez que necesiten detectar una emoción o comportamiento de usuario, no empiezan de cero. Dicen: "usa el workflow del detector de frustración".&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Ese es compound engineering en acción. No es solo resolver el problema de hoy. Es enseñar al sistema a resolver toda una categoría de problemas.&lt;/p&gt;
&lt;h2 id="el-plugin-de-compound-engineering"&gt;El plugin de compound engineering&lt;a class="headerlink" href="#el-plugin-de-compound-engineering" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Every ha open-sourcéado su sistema como un plugin que funciona con Claude Code, Codex CLI, Cursor y otros. Tiene más de 50 agentes especializados y 38 skills accesibles como slash commands.&lt;/p&gt;
&lt;p&gt;Algunos de los skills más interesantes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/ce-strategy&lt;/code&gt; — Crea o mantiene el &lt;code&gt;STRATEGY.md&lt;/code&gt; del producto: problema, approach, persona, métricas clave.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/ce-plan&lt;/code&gt; — Genera planes estructurados para cualquier tarea multi-paso, con verificación de confianza automática.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/ce-brainstorm&lt;/code&gt; — Q&amp;amp;A interactivo para pensar un feature antes de planificarlo.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/ce-work&lt;/code&gt; — Ejecuta items de trabajo de forma sistemática.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/ce-debug&lt;/code&gt; — Encuentra root causes y fixea bugs con hipótesis testeables.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/ce-code-review&lt;/code&gt; — Review estructurada con múltiples agentes revisores en paralelo.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/ce-compound&lt;/code&gt; — Documenta problemas resueltos para que el equipo (y los agentes) aprendan.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/ce-compound-refresh&lt;/code&gt; — Revisa learnings obsoletos y decide si actualizar, reemplazar o archivar.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/ce-product-pulse&lt;/code&gt; — Genera reportes de uso, performance, errores y followups.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/ce-sessions&lt;/code&gt; — Busca en el historial de sesiones de Claude Code, Codex y Cursor.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Y las agentes revisoras son especialistas: hay una para contratos de API, otra para migraciones de datos, otra para carreras en frontend, otra para seguridad, otra para rendimiento. Cada una aplica su lente específico.&lt;/p&gt;
&lt;h2 id="por-que-esto-importa"&gt;Por qué esto importa&lt;a class="headerlink" href="#por-que-esto-importa" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Every corre cinco productos software, cada uno construido y mantenido principalmente por una sola persona. Productos que usan miles de personas todos los días. No son demos. Son negocios reales.&lt;/p&gt;
&lt;p&gt;Su métrica: un solo desarrollador con compound engineering hace el trabajo de cinco desarrolladores tradicionales. No porque escriba más código, sino porque cada línea que escribe construye sobre todo lo aprendido antes.&lt;/p&gt;
&lt;p&gt;La implicación es profunda. Si la ingeniería tradicional asume que escribir código es difícil y los ingenieros son escasos, compound engineering asume que el código es barato y el conocimiento es el activo valioso. La pregunta ya no es "¿cómo escribo esto?", sino "¿cómo enseño al sistema a escribir esto, y todo lo que se le parezca, para siempre?".&lt;/p&gt;
&lt;h2 id="mi-lectura"&gt;Mi lectura&lt;a class="headerlink" href="#mi-lectura" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Lo que propone Every no es una herramienta, es un cambio de paradigma. La ingeniería de software siempre ha tenido un componente de acumulación: bibliotecas, frameworks, patrones. Pero eso era acumulación de código. Compound engineering es acumulación de &lt;strong&gt;conocimiento sobre cómo construir&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;El &lt;code&gt;CLAUDE.md&lt;/code&gt; no es documentación para humanos. Es memoria para agentes. Y eso cambia la naturaleza del trabajo del ingeniero: deja de ser quien escribe código para ser quien diseña sistemas que escriben código, y que además aprenden de cada línea que generan.&lt;/p&gt;
&lt;p&gt;No es futurismo. Every ya lo está haciendo. El plugin está en GitHub, con 16k+ estrellas. La pregunta no es si esto va a llegar a tu equipo, sino cuándo.&lt;/p&gt;
&lt;h2 id="recursos"&gt;Recursos&lt;a class="headerlink" href="#recursos" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/EveryInc/compound-engineering-plugin"&gt;Compound Engineering Plugin&lt;/a&gt; — El plugin open source de Every&lt;/li&gt;
&lt;li&gt;&lt;a href="https://every.to/chain-of-thought/compound-engineering-how-every-codes-with-agents"&gt;Compound Engineering: How Every Codes With Agents&lt;/a&gt; — El artículo original de Dan Shipper y Kieran Klaassen&lt;/li&gt;
&lt;li&gt;&lt;a href="https://every.to/source-code/my-ai-had-already-fixed-the-code-before-i-saw-it"&gt;My AI Had Already Fixed the Code Before I Saw It&lt;/a&gt; — Kieran describe el sistema de compounding en acción&lt;/li&gt;
&lt;/ul&gt;</content><category term="ia"></category><category term="agentes"></category><category term="ia"></category><category term="compound-engineering"></category><category term="every"></category><category term="claude-code"></category></entry><entry><title>Ingeniería de Programación con Agentes</title><link href="https://pablocaro.es/programming-with-agents" rel="alternate"></link><published>2026-05-06T00:00:00+02:00</published><updated>2026-05-06T00:00:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-05-06:/programming-with-agents</id><summary type="html">&lt;p&gt;Después de más de 20 años picando código, la llegada de los agentes de IA ha cambiado cómo trabajo más que cualquier framework o lenguaje nuevo. No se trata de que la IA escriba código por mí, sino de que me permita mantener el foco en lo que realmente importa …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Después de más de 20 años picando código, la llegada de los agentes de IA ha cambiado cómo trabajo más que cualquier framework o lenguaje nuevo. No se trata de que la IA escriba código por mí, sino de que me permita mantener el foco en lo que realmente importa: el diseño, la arquitectura y las decisiones técnicas que un agente no puede tomar. Esta será una serie de anotaciones que recoge el trabajo de varias semanas de prueba con diferentes metodologías y herramientas. Lo dividiré en varias partes, cada una explorando un enfoque distinto.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Ingeniería de Programación con Agentes" src="/images/programming-with-agents-cover.jpg"&gt;&lt;/p&gt;
&lt;h2 id="nuevo-flujo-de-trabajo"&gt;Nuevo flujo de trabajo&lt;a class="headerlink" href="#nuevo-flujo-de-trabajo" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Trabajo con agentes como compañeros de pair programming que nunca se cansan. Les doy contexto, les planteo problemas y reviso su trabajo con el mismo criterio que aplicaría a un pull request de un compañero humano. La diferencia es que puedo iterar en segundos en lugar de horas.&lt;/p&gt;
&lt;p&gt;No uso la IA para generar código que no entiendo. Cada línea que entra en un proyecto mío tiene que pasar por mi cabeza primero. Los agentes son excelentes para:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Escribir tests repetitivos o scaffolding&lt;/li&gt;
&lt;li&gt;Explorar APIs o librerías nuevas&lt;/li&gt;
&lt;li&gt;Refactorizar código siguiendo patrones que yo defino&lt;/li&gt;
&lt;li&gt;Limpiezas y lintering&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="lo-que-no-funciona"&gt;Lo que no funciona&lt;a class="headerlink" href="#lo-que-no-funciona" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Los agentes brillan en tareas bien delimitadas y fallan estrepitosamente cuando les pides que hagan diseño arquitectónico sin supervisión. He visto propuestas de abstracciones innecesarias, dependencias fantasmas y soluciones a problemas que no existían. La regla de oro: si no puedes explicarle el problema a un humano junior, tampoco podrás explicárselo a un agente.&lt;/p&gt;
&lt;h2 id="el-futuro"&gt;El futuro&lt;a class="headerlink" href="#el-futuro" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;La ingeniería de software no va a desaparecer, pero sí va a mutar. Está mutando ya. Y muy, muy rápidamente.&lt;/p&gt;
&lt;p&gt;En esta serie veremos varios procesos de ingeniería aplicados al uso de agentes: Compound Engineering (Every), Agent Skills (Addy Osmani), Superpowers (Jesse Vincent) y Skills para ingenieros reales (Matt Pocock). Cada uno con su filosofía, sus trade-offs y sus casos de uso.&lt;/p&gt;</content><category term="ia"></category><category term="agentes"></category><category term="ia"></category><category term="productividad"></category></entry><entry><title>Winhance: limpiar y optimizar Windows sin reinstallar</title><link href="https://pablocaro.es/winhance-windows-debloater" rel="alternate"></link><published>2026-05-05T00:00:00+02:00</published><updated>2026-05-05T00:00:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-05-05:/winhance-windows-debloater</id><summary type="html">&lt;p&gt;Winhance es una utilidad en C# para debloater, optimizar y personalizar Windows 10/11 sin necesidad de reinstalar el sistema.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="Winhance" src="/images/winhance-windows-debloater.png"&gt;&lt;/p&gt;
&lt;p&gt;Llevo usando Linux como sistema principal desde hace años. Tengo dos máquinas con Windows en casa: el portátil de mi hija y un pequeño Mele que uso para conectar al telescopio. No estoy acostumbrado a manejar el ecosistema de Windows, así que cuando toca reinstalar o limpiar una de esas máquinas siempre termino buscando utilidades que hagan el trabajo por mí.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/memstechtips/Winhance"&gt;Winhance&lt;/a&gt; es exactamente eso: una herramienta de C# con casi 10.000 estrellas en GitHub que permite debloater, optimizar y personalizar Windows 10 y 11 sin tener que reinstalar. Esencialmente hace lo mismo que &lt;a href="https://github.com/memstechtips/UnattendedWinstall"&gt;UnattendedWinstall&lt;/a&gt; pero sin necesidad de una instalación limpia.&lt;/p&gt;
&lt;p&gt;La instalación es ridiculamente simple desde PowerShell:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;irm &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://get.winhance.net&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;iex&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;También hay un instalador descargable desde &lt;a href="https://winhance.net"&gt;winhance.net&lt;/a&gt; o &lt;a href="https://github.com/memstechtips/Winhance/releases"&gt;GitHub Releases&lt;/a&gt;, con opción de instalación normal o portable.&lt;/p&gt;
&lt;p&gt;Lo que me ha gustado:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Software &amp;amp; Apps&lt;/strong&gt;: interfaz para quitar bloatware de Windows (apps preinstaladas, capacidades legacy, optional features) e instalar aplicaciones útiles via WinGet. Tiene categorías organizadas para navegadores, multimedia, visores de documentos, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Optimize&lt;/strong&gt;: todo en un panel searchable. Control del UAC, ajustes de privacidad, optimizaciones para gaming, configuración de Windows Updates, planes de energía, sonido y notificaciones. Cada opción con un toggle switch claro.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Customize&lt;/strong&gt;: selector de tema oscuro/claro, personalización de la taskbar, menú inicio y el explorador de archivos.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Advanced Tools&lt;/strong&gt;: herramienta para crear ISOs de Windows personalizadas con WIMUtil (incluyendo drivers del sistema actual) y generar archivos &lt;code&gt;autounattend.xml&lt;/code&gt; basados en tus selecciones de Winhance.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configuración exportable&lt;/strong&gt;: puedes guardar toda tu configuración en un archivo para importarla después de una reinstalación. Esto es oro puro si manejas varias máquinas.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;La licencia es &lt;a href="https://polyformproject.org/licenses/shield/1.0.0/"&gt;PolyForm Shield 1.0.0&lt;/a&gt;, lo que significa que es gratis para uso personal, empresas y profesionales de IT, pero no puedes hacerle fork y redistribuirlo como producto competidor.&lt;/p&gt;
&lt;p&gt;Me lo dejo aquí apuntado para la próxima vez que tenga que limpiar una de esas dos máquinas Windows. Seguro me ahorra un buen rato de clicks por el panel de configuración.&lt;/p&gt;</content><category term="Herramientas"></category><category term="windows"></category><category term="debloater"></category><category term="herramientas"></category><category term="winhance"></category></entry><entry><title>Extensiones de subagentes para pi: TintinWeb, NicoBailon y Taskplane</title><link href="https://pablocaro.es/extensiones-subagentes-pi" rel="alternate"></link><published>2026-05-03T15:23:00+02:00</published><updated>2026-05-03T15:23:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-05-03:/extensiones-subagentes-pi</id><summary type="html">&lt;p&gt;Comparativa de tres extensiones para ejecutar subagentes en pi — TintinWeb, NicoBailon y Taskplane — cada una con un enfoque distinto.&lt;/p&gt;</summary><content type="html">&lt;p&gt;La mayor ventaja de &lt;a href="https://pi.dev/"&gt;pi&lt;/a&gt; frente a otros coding agents son sus &lt;strong&gt;extensiones&lt;/strong&gt;. Pi no trae subagentes, no trae plan mode, no trae MCP. Te da las primitivas para que montes lo que necesites. Si no quieres montarlo tú, instalas un &lt;a href="https://pi.dev/packages"&gt;paquete de terceros&lt;/a&gt; y listo.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Tres figuras estilizadas corriendo en paralelo, pasándose un testigo brillante" src="/images/pi-subagentes.png"&gt;&lt;/p&gt;
&lt;p&gt;Tengo varias extensiones propias en &lt;a href="https://github.com/pcaro/pcaropi"&gt;mi repo pcaropi&lt;/a&gt;, pero aquí voy a comparar tres enfoques distintos para delegar trabajo a subagentes — que es de lo más útil cuando trabajas con pi en proyectos serios.&lt;/p&gt;
&lt;h2 id="las-tres-alternativas"&gt;Las tres alternativas&lt;a class="headerlink" href="#las-tres-alternativas" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;TintinWeb&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;NicoBailon&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Taskplane (HenryLach)&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Paradigma&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Subagentes individuales estilo Claude Code&lt;/td&gt;
&lt;td&gt;Orquestración genérica (single/chain/parallel)&lt;/td&gt;
&lt;td&gt;Orquestración por batches con waves, lanes y DAG&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Instalación&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pi install npm:@tintinweb/pi-subagents&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pi install npm:pi-subagents&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pi install npm:taskplane&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Agentes built-in&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3 (Explore, Plan, general-purpose)&lt;/td&gt;
&lt;td&gt;8 (scout, researcher, planner, worker, reviewer…)&lt;/td&gt;
&lt;td&gt;4 (supervisor, task-worker, task-reviewer, task-merger)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Chains / Parallel&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ Cadenas + fan-out/fan-in&lt;/td&gt;
&lt;td&gt;✅ Waves con lanes paralelas según DAG&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Git worktrees&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Básico&lt;/td&gt;
&lt;td&gt;✅ Avanzado&lt;/td&gt;
&lt;td&gt;✅ Nucleo del sistema&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Web Dashboard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ Dashboard en vivo con SSE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Madurez&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Early (0.6.3)&lt;/td&gt;
&lt;td&gt;Maduro (0.24.0)&lt;/td&gt;
&lt;td&gt;Initial release&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="tintinweb-subagentes-al-estilo-claude-code"&gt;TintinWeb — Subagentes al estilo Claude Code&lt;a class="headerlink" href="#tintinweb-subagentes-al-estilo-claude-code" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/tintinweb/pi-subagents"&gt;@tintinweb/pi-subagents&lt;/a&gt; replica el modelo de Claude Code: herramientas &lt;code&gt;Agent&lt;/code&gt;, &lt;code&gt;get_subagent_result&lt;/code&gt; y &lt;code&gt;steer_subagent&lt;/code&gt;. Pocos agentes (Explore, Plan, general-purpose), pero experiencia muy pulida: widget animado, conversation viewer interactivo, steering en caliente para redirigir agentes en ejecución.&lt;/p&gt;
&lt;p&gt;Lo más diferencial: &lt;strong&gt;memoria persistente&lt;/strong&gt; entre sesiones (project/local/user) y un &lt;strong&gt;event bus RPC&lt;/strong&gt; para que otras extensiones spawneen subagentes.&lt;/p&gt;
&lt;p&gt;Ideal si vienes de Claude Code y quieres algo familiar.&lt;/p&gt;
&lt;h3 id="nicobailon-el-orquestador-completo"&gt;NicoBailon — El orquestador completo&lt;a class="headerlink" href="#nicobailon-el-orquestador-completo" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/nicobailon/pi-subagents"&gt;pi-subagents de NicoBailon&lt;/a&gt; es el más maduro de los tres (0.24.0, 20K líneas de TypeScript). Una sola herramienta &lt;code&gt;subagent&lt;/code&gt; con modos single, chain y parallel. Viene con 8 agentes built-in, 6 prompt templates predefinidos, y una TUI de preview para editar steps antes de lanzar.&lt;/p&gt;
&lt;p&gt;Cosas que me gustan: &lt;strong&gt;model fallback&lt;/strong&gt; en cascada (si un modelo falla por quota, prueba el siguiente), &lt;strong&gt;agent packages&lt;/strong&gt; con namespacing, e integración con &lt;code&gt;pi-intercom&lt;/code&gt; para comunicación hijo→padre.&lt;/p&gt;
&lt;p&gt;Es el que uso yo en el día a día. Flexible, probado, bien documentado.&lt;/p&gt;
&lt;h3 id="taskplane-orquestracion-por-batches"&gt;Taskplane — Orquestración por batches&lt;a class="headerlink" href="#taskplane-orquestracion-por-batches" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/HenryLach/taskplane"&gt;Taskplane de HenryLach&lt;/a&gt; es otra cosa. No es un framework de subagentes genérico: es un &lt;strong&gt;sistema de orquestración de tareas por lotes&lt;/strong&gt;. Las tareas se definen en archivos &lt;code&gt;PROMPT.md&lt;/code&gt; + &lt;code&gt;STATUS.md&lt;/code&gt; con pasos, dependencias y checkboxes. Un DAG organiza las tareas en waves, cada wave ejecuta lanes en paralelo con worktrees aislados, y un merge agent integra todo al final.&lt;/p&gt;
&lt;p&gt;Tiene cosas brutales: &lt;strong&gt;dashboard web en vivo&lt;/strong&gt; con SSE, &lt;strong&gt;workers multi-paso&lt;/strong&gt; que mantienen contexto entre pasos, &lt;strong&gt;reviews inline&lt;/strong&gt; automáticas, y &lt;strong&gt;auto-recovery&lt;/strong&gt; que reintenta workers caídos. Incluso soporta &lt;strong&gt;polyrepo&lt;/strong&gt; con dependencias cross-repo.&lt;/p&gt;
&lt;p&gt;Para proyectos donde tienes un batch de features bien definidas con dependencias, es imbatible.&lt;/p&gt;
&lt;h2 id="cual-usar"&gt;¿Cuál usar?&lt;a class="headerlink" href="#cual-usar" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Delegar tareas sueltas con una experiencia limpia&lt;/strong&gt; → TintinWeb&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Orquestación flexible con chains, parallel y templates&lt;/strong&gt; → NicoBailon&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ejecutar un batch de features con dependencias y reviews automáticas&lt;/strong&gt; → Taskplane&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Los tres se pueden instalar con un &lt;code&gt;pi install npm:...&lt;/code&gt;. TintinWeb y NicoBailon pueden coexistir porque usan herramientas con nombres distintos. Taskplane tiene su propio paradigma y no debería conflictuar con ninguno.&lt;/p&gt;
&lt;p&gt;Más extensiones y configuraciones de pi en &lt;a href="https://github.com/pcaro/pcaropi"&gt;mi repo pcaropi&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Fuente original&lt;/em&gt;: &lt;a href="https://pi.dev/"&gt;pi.dev&lt;/a&gt;&lt;/p&gt;</content><category term="IA"></category><category term="pi"></category><category term="coding-agent"></category><category term="subagentes"></category><category term="extensiones"></category><category term="cli"></category><category term="herramientas"></category></entry><entry><title>Bloqueo DNS de enlaces de AliExpress con NextDNS</title><link href="https://pablocaro.es/bloqueo-dns-aliexpress-nextdns" rel="alternate"></link><published>2026-05-03T10:01:00+02:00</published><updated>2026-05-03T10:01:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-05-03:/bloqueo-dns-aliexpress-nextdns</id><summary type="html">&lt;p&gt;Al usar una blocklist DNS demasiado agresiva en NextDNS, los enlaces de afiliado de AliExpress dejaban de funcionar. Diagnóstico con dig y solución con HaGeZi Multi PRO.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="DNS blocking illustration" src="/images/dns-blocking-nextdns.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Llevaba unos días con un problema raro: los enlaces de AliExpress que aparecen en descripciones de vídeos de productos no cargaban. Firefox mostraba "Servidor no encontrado". Sabía que tenía pinta de ser un bloqueo DNS, porque al activar la VPN de PIA (Private Internet Access) el problema desaparecía.&lt;/p&gt;
&lt;h2 id="el-diagnostico"&gt;El diagnóstico&lt;a class="headerlink" href="#el-diagnostico" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;El comando &lt;code&gt;dig&lt;/code&gt; lo confirmó enseguida:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Consultando al router (que usa NextDNS)&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;dig&lt;span class="w"&gt; &lt;/span&gt;s.click.aliexpress.com&lt;span class="w"&gt; &lt;/span&gt;@192.168.1.1

&lt;span class="p"&gt;;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ANSWER&lt;span class="w"&gt; &lt;/span&gt;SECTION:
s.click.aliexpress.com.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;300&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;IN&lt;span class="w"&gt;  &lt;/span&gt;A&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0.0.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Consultando a Google DNS&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;dig&lt;span class="w"&gt; &lt;/span&gt;s.click.aliexpress.com&lt;span class="w"&gt; &lt;/span&gt;@8.8.8.8

&lt;span class="p"&gt;;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ANSWER&lt;span class="w"&gt; &lt;/span&gt;SECTION:
s.click.aliexpress.com.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;300&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;IN&lt;span class="w"&gt;  &lt;/span&gt;A&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;23&lt;/span&gt;.40.114.185
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;El router devolvía &lt;code&gt;0.0.0.0&lt;/code&gt; para &lt;code&gt;s.click.aliexpress.com&lt;/code&gt;. Eso no es un NXDOMAIN —dominio inexistente—, es un bloqueo deliberado: el dominio resuelve a una IP nula y el navegador no puede conectar.&lt;/p&gt;
&lt;p&gt;Con la VPN activa, las consultas DNS pasaban por los servidores de PIA, que no aplican blocklists, y los enlaces funcionaban sin problema.&lt;/p&gt;
&lt;h2 id="la-causa"&gt;La causa&lt;a class="headerlink" href="#la-causa" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;El router tenía configurado &lt;strong&gt;NextDNS&lt;/strong&gt; con la lista de bloqueo por defecto. Bloqueaba bien el malware y el phishing, pero también estaba tocando dominios legítimos como los de redirección de afiliados. Demasiado agresiva para mi día a día.&lt;/p&gt;
&lt;h2 id="la-solucion"&gt;La solución&lt;a class="headerlink" href="#la-solucion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;La solución más simple habría sido añadir &lt;code&gt;s.click.aliexpress.com&lt;/code&gt; a la lista blanca de NextDNS y ya está. Pero aproveché para revisar qué lista estaba usando y cambiarla por &lt;strong&gt;HaGeZi - Multi PRO&lt;/strong&gt;, que de paso resolvía el problema.&lt;/p&gt;
&lt;p&gt;Según la &lt;a href="https://github.com/hagezi/dns-blocklists/wiki/FAQ#whatshouldiuse"&gt;guía oficial de HaGeZi&lt;/a&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Lista&lt;/th&gt;
&lt;th&gt;Nivel de bloqueo&lt;/th&gt;
&lt;th&gt;Falsos positivos&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Light&lt;/td&gt;
&lt;td&gt;Relajado&lt;/td&gt;
&lt;td&gt;Ninguno&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Normal&lt;/td&gt;
&lt;td&gt;Relajado/Equilibrado&lt;/td&gt;
&lt;td&gt;Casi ninguno&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pro&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Equilibrado&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Muy raros&lt;/strong&gt; ← recomendada&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pro++&lt;/td&gt;
&lt;td&gt;Equilibrado/Agresivo&lt;/td&gt;
&lt;td&gt;Algunos&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ultimate&lt;/td&gt;
&lt;td&gt;Agresivo&lt;/td&gt;
&lt;td&gt;Frecuentes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Multi PRO (~420k dominios, ~200k comprimidos) es la &lt;strong&gt;recomendación personal del autor&lt;/strong&gt; de HaGeZi para un bloqueo efectivo sin problemas. Bloquea anuncios, trackers, telemetría, phishing, malware, scam, cryptojacking y "crap", con muy pocos falsos positivos.&lt;/p&gt;
&lt;h2 id="el-detalle-clave-dominios-de-referral"&gt;El detalle clave: dominios de referral&lt;a class="headerlink" href="#el-detalle-clave-dominios-de-referral" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;La FAQ de HaGeZi dedica una sección entera a explicar que los &lt;strong&gt;dominios de referral&lt;/strong&gt; —enlaces de afiliado/tracking como &lt;code&gt;s.click.aliexpress.com&lt;/code&gt;— están &lt;strong&gt;permitidos en todas las listas&lt;/strong&gt;, salvo unos pocos que también funcionan como trackers puros y solo se bloquean en Pro++ y Ultimate.&lt;/p&gt;
&lt;p&gt;Estos dominios solo se activan tras un click manual del usuario y no se usan para mostrar publicidad. Bloquearlos rompe enlaces de resultados de búsqueda, ofertas, emails de confirmación, etc.&lt;/p&gt;
&lt;h2 id="resumen"&gt;Resumen&lt;a class="headerlink" href="#resumen" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Una blocklist DNS demasiado agresiva bloquea dominios que no son publicidad ni malware, sino mecanismos de redirección del comercio electrónico. El equilibrio está en &lt;strong&gt;Multi PRO&lt;/strong&gt;: buena protección de privacidad sin romper funcionalidad cotidiana.&lt;/p&gt;
&lt;p&gt;Si en algún momento necesito más agresividad, el precio será tener que desbloquear falsos positivos manualmente. Por ahora, Multi PRO va perfecto.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Más información&lt;/em&gt;: &lt;a href="https://github.com/hagezi/dns-blocklists"&gt;HaGeZi DNS Blocklists&lt;/a&gt;&lt;/p&gt;</content><category term="Infraestructura"></category><category term="dns"></category><category term="nextdns"></category><category term="aliexpress"></category><category term="bloqueo"></category><category term="linux"></category></entry><entry><title>Escaneo a doble cara con la Brother DCP-L3560CDW</title><link href="https://pablocaro.es/escaneo-doble-cara-brother-dcp-l3560cdw" rel="alternate"></link><published>2026-05-03T00:00:00+02:00</published><updated>2026-05-03T00:00:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-05-03:/escaneo-doble-cara-brother-dcp-l3560cdw</id><summary type="html">&lt;p&gt;Workaround para escanear documentos a doble cara con la Brother DCP-L3560CDW y su alimentador simplex.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="Brother DCP-L3560CDW y papeles desordenados" src="/images/brother-escaneo-doble-pagina.png"&gt;&lt;/p&gt;
&lt;p&gt;Mi nueva multifunción &lt;a href="https://www.manua.ls/brother/dcp-l3560cdw/manual"&gt;Brother DCP-L3560CDW&lt;/a&gt; no escanea a doble cara desde el alimentador. El ADF es solo simple cara, aunque la impresora sí imprime a dúplex.&lt;/p&gt;
&lt;p&gt;Para documentos a una cara, el flujo es perfecto: meto las hojas en el alimentador, pulso &lt;em&gt;Scan&lt;/em&gt; en la pantalla táctil de 3.7 pulgadas y el documento aparece directamente en la carpeta Samba de Paperless. Sin tocar el PC.&lt;/p&gt;
&lt;p&gt;El problema llega con documentos a doble cara. La solución que uso:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Escaneo todas las páginas impares con el alimentador&lt;/li&gt;
&lt;li&gt;Doy la vuelta al taco, pongo la última página boca arriba y escaneo las pares&lt;/li&gt;
&lt;li&gt;En &lt;a href="https://gitlab.gnome.org/GNOME/simple-scan"&gt;Document Scanner&lt;/a&gt; (simple-scan): menú hamburguesa → &lt;em&gt;Reorder pages&lt;/em&gt; → &lt;em&gt;Combine sides (reverse)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Guardo el PDF&lt;/li&gt;
&lt;li&gt;Lo muevo a la carpeta de red para que Paperless lo ingiera&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;He mirado alternativas en KDE (Skanpage, Skanlite) pero ninguna tiene la función de intercalado manual que ofrece Document Scanner. Skanlite ni siquiera permite juntar varias páginas en un mismo PDF.&lt;/p&gt;
&lt;p&gt;Seguiré investigando si hay alguna forma más directa. De momento, este flujo funciona.&lt;/p&gt;</content><category term="Infraestructura"></category><category term="brother"></category><category term="impresora"></category><category term="escaner"></category><category term="linux"></category><category term="simple-scan"></category><category term="paperless"></category></entry><entry><title>marksman: LSP para Markdown</title><link href="https://pablocaro.es/marksman-lsp-markdown" rel="alternate"></link><published>2026-04-29T22:46:00+02:00</published><updated>2026-04-29T22:46:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-04-29:/marksman-lsp-markdown</id><summary type="html">&lt;p&gt;marksman es un servidor de protocolo de lenguaje (LSP) para Markdown que añade autocompletado, navegación entre archivos y detección de errores a cualquier editor compatible.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Escribo bastante en Markdown — este blog, notas, documentación — y hasta ahora me las apañaba sin más. Pero tras acostumbrarme a los beneficios del LSP en código, empecé a echar en falta algo similar para mis archivos &lt;code&gt;.md&lt;/code&gt;. Enlaces rotos, referencias a notas inexistentes, falta de autocompletado entre documentos... todo eso se acumula cuando mantienes varios ficheros.&lt;/p&gt;
&lt;p&gt;Así que he instalado &lt;strong&gt;marksman&lt;/strong&gt;, un servidor LSP dedicado exclusivamente a Markdown.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Marksman splash" src="/images/marksman-splash.png"&gt;&lt;/p&gt;
&lt;h2 id="que-es-marksman"&gt;¿Qué es marksman?&lt;a class="headerlink" href="#que-es-marksman" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;marksman es un &lt;a href="https://microsoft.github.io/language-server-protocol/"&gt;servidor de protocolo de lenguaje&lt;/a&gt; (LSP) que entiende la estructura de tus documentos Markdown. No solo resalta sintaxis: analiza enlaces internos, referencias entre archivos, títulos, tablas de contenido y mucho más.&lt;/p&gt;
&lt;p&gt;Algunas de las funciones que ofrece:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Autocompletado&lt;/strong&gt; de enlaces internos entre archivos Markdown.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ir a definición&lt;/strong&gt; (F12) para saltar a la sección o archivo referenciado.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Renombrar símbolos&lt;/strong&gt;: cambia un título y actualiza todos los enlaces que apuntan a él.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Detección de errores&lt;/strong&gt;: alerta sobre enlaces rotos o referencias inexistentes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Información flotante&lt;/strong&gt; (hover) para previsualizar contenido de enlaces.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Búsqueda de referencias&lt;/strong&gt; para ver dónde se usa un título o archivo.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Básicamente, convierte un conjunto de archivos Markdown en algo parecido a un proyecto de código, con todas las ventajas de navegación y refactorización que eso conlleva.&lt;/p&gt;
&lt;h2 id="instalacion"&gt;Instalación&lt;a class="headerlink" href="#instalacion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Lo he instalado con &lt;a href="https://github.com/pablocaro/gah"&gt;gah&lt;/a&gt; (GitHub Asset Helper), una herramienta que uso habitualmente para bajar binarios de releases de GitHub:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;gah&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;artempyanykh/marksman
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Seleccioné la versión &lt;code&gt;marksman-linux-x64&lt;/code&gt; y en cuestión de segundos ya tenía el binario disponible en el PATH.&lt;/p&gt;
&lt;p&gt;También se puede instalar vía Homebrew, Snap, o descargando el binario directamente desde las releases del proyecto.&lt;/p&gt;
&lt;h2 id="configuracion-en-fresh"&gt;Configuración en &lt;code&gt;fresh&lt;/code&gt;&lt;a class="headerlink" href="#configuracion-en-fresh" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Lo uso junto a &lt;a href="https://pablocaro.es/fresh-editor-terminal"&gt;&lt;code&gt;fresh&lt;/code&gt;&lt;/a&gt;, mi editor de terminal preferido. &lt;code&gt;fresh&lt;/code&gt; tiene soporte LSP nativo, así que solo tuve que asegurarme de que &lt;code&gt;marksman&lt;/code&gt; esté en el PATH. Al abrir un archivo Markdown, &lt;code&gt;fresh&lt;/code&gt; detecta automáticamente el servidor y ofrece iniciarlo:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Menú LSP en fresh" src="/images/marksman-fresh-lsp.png"&gt;&lt;/p&gt;
&lt;p&gt;Una vez arrancado, el menú LSP de &lt;code&gt;fresh&lt;/code&gt; muestra todas las opciones disponibles:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Opciones del menú LSP" src="/images/marksman-fresh-menu.png"&gt;&lt;/p&gt;
&lt;h2 id="por-que-me-convence"&gt;¿Por qué me convence?&lt;a class="headerlink" href="#por-que-me-convence" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;No necesito un editor gráfico pesado para escribir Markdown con comodidad. Con &lt;code&gt;marksman&lt;/code&gt; + &lt;code&gt;fresh&lt;/code&gt; tengo autocompletado de enlaces, navegación rápida entre notas, y detección de errores sin salir de la terminal. Es especialmente útil en este blog, donde hay decenas de artículos referenciándose entre sí.&lt;/p&gt;
&lt;p&gt;Si trabajas habitualmente con Markdown y usas un editor con soporte LSP, &lt;code&gt;marksman&lt;/code&gt; es una de esas herramientas que, una vez instaladas, te preguntas cómo has podido vivir sin ellas.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Repositorio oficial&lt;/em&gt;: &lt;a href="https://github.com/artempyanykh/marksman"&gt;marksman en GitHub&lt;/a&gt;&lt;/p&gt;</content><category term="Herramientas"></category><category term="markdown"></category><category term="lsp"></category><category term="fresh"></category><category term="editor"></category><category term="linux"></category></entry><entry><title>La subida de precio de los SBC (Single Board Computers)</title><link href="https://pablocaro.es/muerte-cacharritos" rel="alternate"></link><published>2026-04-28T00:00:00+02:00</published><updated>2026-04-28T00:00:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-04-28:/muerte-cacharritos</id><summary type="html">&lt;p&gt;La subida de los 'cacharritos'&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="Un SBC con una flecha roja de subida de precio y un comprador sudoroso" src="/images/subida-precio-sbc.png"&gt;&lt;/p&gt;
&lt;p&gt;En mi cajón hay una Raspberry Pi 2, una Orange Pi PC, una ODROID-C2 y una Banana Pi M4 Zero. Las compré hace años, cuando 30 euros te daban una placa decente para cacharrear. Servidores DNS, Docker simplón, un poco de automatización en casa... funcionaban sin preguntar demasiado.&lt;/p&gt;
&lt;p&gt;Mirando &lt;a href="https://www.cnx-software.com/2026/04/28/what-a-difference-two-years-make-comparing-sbc-prices-in-2024-and-2026/"&gt;la comparativa de CNX Software&lt;/a&gt;, la Banana Pi BPI-M4 Zero pasó de $29 a $115. Casi un 300% de subida. La Raspberry Pi 5 de 8GB, de $80 a $175. La Orange Pi 5 Ultra de 16GB, de $125 a $309. Y así.&lt;/p&gt;
&lt;p&gt;El culpable es el precio de la RAM. La demanda de memorias para GPUs de IA ha inflado los precios de todos los chips desde finales de 2025. Los fabricantes no pueden competir sin RAM, así que terminan subiendo precios o dejando de fabricar.&lt;/p&gt;
&lt;p&gt;A mí me gustan los clusters de racks de 10 pulgadas. Una docena de placas para montar un mini-datacenter casero. Hoy eso costaría lo mismo que un servidor de segunda mano con más potencia y menos quebraderos de cabeza.&lt;/p&gt;
&lt;p&gt;Los cacharritos han muerto. O mejor dicho: dejaron de ser el chollo que eran.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Fuente original&lt;/em&gt;: &lt;a href="https://www.cnx-software.com/2026/04/28/what-a-difference-two-years-make-comparing-sbc-prices-in-2024-and-2026/"&gt;CNX Software&lt;/a&gt;&lt;/p&gt;</content><category term="Linux"></category><category term="raspberry-pi"></category><category term="sbc"></category><category term="hardware"></category></entry><entry><title>neofetch</title><link href="https://pablocaro.es/neofetch" rel="alternate"></link><published>2026-04-27T18:00:00+02:00</published><updated>2026-04-27T18:00:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-04-27:/neofetch</id><summary type="html">&lt;p&gt;Neofetch, herramienta de línea de comandos para información del sistema.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Me pasa constantemente: quiero saber rápidamente qué distro estoy usando, la versión del kernel, cuánto RAM tengo... y aunque he usado la herramienta mil
veces, el nombre se me escapa de la cabeza.&lt;/p&gt;
&lt;p&gt;Cada vez termino buscando en Google "el programita que te da información de tu distro en la terminal". Ya ni me esfuerzo en recordar.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Salida de neofetch" src="/images/neofetch.png"&gt;&lt;/p&gt;
&lt;p&gt;Quizás algún día se me quede. Pero lo dudo.&lt;/p&gt;</content><category term="Herramientas"></category><category term="linux"></category><category term="terminal"></category></entry><entry><title>Rufus, la mejor forma de instalar un Windows limpio</title><link href="https://pablocaro.es/rufus-instalar-windows-limpio" rel="alternate"></link><published>2026-04-27T12:00:00+02:00</published><updated>2026-04-27T12:00:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-04-27:/rufus-instalar-windows-limpio</id><summary type="html">&lt;p&gt;Rufus, gran herramienta para crear medios de instalación de Windows, permitiendo deshabilitar las integraciones forzosas de IA de Microsoft.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="Captura de Rufus 4.14 en acción" src="/images/rufus-4-14-interfaz.png"&gt;&lt;/p&gt;
&lt;p&gt;Leo en &lt;a href="https://informatica-sinlimites-ii.blogspot.com/2026/04/rufus-414-aumenta-capacidad-contra.html"&gt;informática sin límites&lt;/a&gt; que Rufus 4.14 (en fase beta) mejora sustancialmente su capacidad para instalar un Windows limpio.&lt;/p&gt;
&lt;p&gt;Rufus lleva años siendo la referencia para crear medios de instalación de Windows. La nueva versión va más allá:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Deshabilita Teams, Outlook, Copilot y otros&lt;/strong&gt; programas innecesarios de Microsoft&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Permite usar cuentas locales&lt;/strong&gt; en lugar de obligar a usar cuentas Microsoft&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Elimina BitLocker&lt;/strong&gt; (el cifrado que trae de cabeza a usuarios y empresas)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nuevo modo desatendido&lt;/strong&gt;: automatización completa de la instalación sin intervención del usuario&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Si necesitas un Windows sin toda la basura que Microsoft intenta imponer, Rufus es la herramienta.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Fuente original&lt;/em&gt;: &lt;a href="https://informatica-sinlimites-ii.blogspot.com/2026/04/rufus-414-aumenta-capacidad-contra.html"&gt;Rufus 4.14 - Informática sin límites&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Proyecto&lt;/em&gt;: &lt;a href="https://github.com/pbatard/rufus"&gt;github.com/pbatard/rufus&lt;/a&gt;&lt;/p&gt;</content><category term="Herramientas"></category><category term="rufus"></category><category term="windows"></category><category term="instalacion"></category></entry><entry><title>Probando Aurora: mi primer vistazo al futuro del escritorio Linux</title><link href="https://pablocaro.es/probando-aurora-mi-primer-vistazo-al-futuro-del-escritorio-linux" rel="alternate"></link><published>2026-04-24T09:51:00+02:00</published><updated>2026-04-24T09:51:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-04-24:/probando-aurora-mi-primer-vistazo-al-futuro-del-escritorio-linux</id><summary type="html">&lt;p&gt;Aurora: Image-based, actualizaciones atómicas, devcontainers, homebrew y toda la experiencia DX que estoy probando en una VM.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Siempre he sido de KDE. Con distros Suse o Debian. Desde hace unos años mi escritorio principal es KDE Neon.&lt;/p&gt;
&lt;p&gt;Pero últimamente he estado siguiendo el proyecto &lt;a href="https://getaurora.dev/en/"&gt;Aurora&lt;/a&gt; y decidí darle una oportunidad en una máquina virtual. 
Y tengo que decirlo: tiene muy buena pinta.&lt;/p&gt;
&lt;h2 id="que-es-aurora"&gt;¿Qué es Aurora?&lt;a class="headerlink" href="#que-es-aurora" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Aurora es una distribución Linux basada en Fedora que está replanteando cómo debería funcionar un escritorio moderno. No es solo "otra distro con KDE" — es un cambio de paradigma completo.&lt;/p&gt;
&lt;p&gt;Lo primero que llama la atención es que usa un &lt;strong&gt;modelo image-based&lt;/strong&gt;. En lugar de actualizar paquetes individuales con &lt;code&gt;dnf&lt;/code&gt; o &lt;code&gt;apt&lt;/code&gt;, el sistema operativo completo se actualiza como una imagen atómica. Esto significa:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Actualizaciones en segundo plano&lt;/strong&gt; que se aplican al reiniciar&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rollback instantáneo&lt;/strong&gt; si algo sale mal (puedes arrancar en la versión anterior desde el grub)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sistema inmutable&lt;/strong&gt; que es mucho más difícil de romper&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Para alguien que viene de un sistema tradicional, con muchos años a sus espaldas, esto es un gran gran cambio.&lt;/p&gt;
&lt;h2 id="kde-pero-diferente"&gt;KDE, pero diferente&lt;a class="headerlink" href="#kde-pero-diferente" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Aurora usa KDE Plasma, pero no es el KDE simple original que se solía encontrar en Fedora (nunca fue la mejor distro para este gui).
Está cuidadosamente personalizado para ofrecer una experiencia "out of the box" que simplemente funciona. No hay que configurar nada, no hay que instalar tweaks ni ajustes raros.&lt;/p&gt;
&lt;p&gt;Y aun así, mantiene la esencia de KDE: es flexible, es potente, y se siente familiar desde el primer momento.&lt;/p&gt;
&lt;h2 id="aurora-dx-el-verdadero-atractivo"&gt;Aurora DX: el verdadero atractivo&lt;a class="headerlink" href="#aurora-dx-el-verdadero-atractivo" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Pero donde Aurora realmente brilla es en la &lt;strong&gt;Developer Experience (DX)&lt;/strong&gt;. Llevo días trasteando con esto y cada vez que descubro algo nuevo, parace una buena decisión.
Eso sí, he de tener en cuenta que tengo mucho linux a mis espaldas y es difícil cambiar viejas costumbres.&lt;/p&gt;
&lt;h3 id="devcontainers-por-defecto"&gt;Devcontainers por defecto&lt;a class="headerlink" href="#devcontainers-por-defecto" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;El IDE preinstalado es Visual Studio Code con la extensión de &lt;strong&gt;DevContainers&lt;/strong&gt; ya configurada. Todo el desarrollo ocurre dentro de contenedores, no en el host. Esto significa:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tu sistema operativo no se ensucia con dependencias de proyectos&lt;/li&gt;
&lt;li&gt;Cada proyecto puede tener su propio entorno aislado&lt;/li&gt;
&lt;li&gt;Puedes descartar y recrear entornos en segundos&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="homebrew-integrado"&gt;Homebrew integrado&lt;a class="headerlink" href="#homebrew-integrado" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Aurora incluye &lt;strong&gt;Homebrew&lt;/strong&gt; preinstalado y configurado para no tocar el sistema base. Quieres una herramienta CLI nueva? &lt;code&gt;brew install&lt;/code&gt;. Listo. Sin conflictos con los paquetes del sistema, sin necesidad de sudo, sin romper nada. Ya sabéis que me gusta mantener algunas tools pequeñas muy actualizadas y usar gah para instalar.&lt;/p&gt;
&lt;p&gt;Tienen incluso un &lt;code&gt;ujust bbrew&lt;/code&gt; que te da un menú interactivo para instalar categorías enteras de herramientas: fonts, k8s-tools, ai, cncf...&lt;/p&gt;
&lt;h3 id="ujust-comandos-que-simplifican-la-vida"&gt;ujust: comandos que simplifican la vida&lt;a class="headerlink" href="#ujust-comandos-que-simplifican-la-vida" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ujust&lt;/code&gt; es como un alias con superpoderes. Comandos como:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ujust&lt;span class="w"&gt; &lt;/span&gt;devmode&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# Activa el modo desarrollador&lt;/span&gt;
ujust&lt;span class="w"&gt; &lt;/span&gt;dx-group&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="c1"&gt;# Te añade a los grupos necesarios&lt;/span&gt;
ujust&lt;span class="w"&gt; &lt;/span&gt;jetbrains-toolbox&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# Instala JetBrains Toolbox&lt;/span&gt;
ujust&lt;span class="w"&gt; &lt;/span&gt;cncf&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="c1"&gt;# Navega e instala herramientas CNCF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Cada comando te guía paso a paso. Es como tener un asistente que sabe exactamente qué necesitas.&lt;/p&gt;
&lt;h3 id="tailscale-y-herramientas-cloud-native"&gt;Tailscale y herramientas cloud-native&lt;a class="headerlink" href="#tailscale-y-herramientas-cloud-native" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Tailscale viene preconfigurado para VPN. Cockpit para gestión del sistema. Herramientas de profiling como &lt;code&gt;sysprof&lt;/code&gt;, &lt;code&gt;bcc&lt;/code&gt;, &lt;code&gt;bpftrace&lt;/code&gt;. Todo lo que necesitarías para desarrollo cloud-native está ahí, listo para usar.&lt;/p&gt;
&lt;h3 id="terminal-con-soporte-de-contenedores"&gt;Terminal con soporte de contenedores&lt;a class="headerlink" href="#terminal-con-soporte-de-contenedores" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;El terminal por defecto es &lt;strong&gt;Ptyxis&lt;/strong&gt;, que tiene integración nativa con Distrobox. Puedes tener contenedores "pet" interactivos y cambiar entre ellos y el host con un atajo. Es muy cómodo.&lt;/p&gt;
&lt;h2 id="las-grandes-diferencias"&gt;Las grandes diferencias&lt;a class="headerlink" href="#las-grandes-diferencias" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;En Aurora, el sistema se cuida solo:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;No puedes romperlo fácilmente&lt;/strong&gt; — el sistema base es inmutable&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Las actualizaciones son atómicas&lt;/strong&gt; — o funcionan completas, o no se aplican&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Todo está en contenedores&lt;/strong&gt; — tus herramientas, tus entornos, tus dependencias&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Si algo falla, hay rollback&lt;/strong&gt; — arrancas la versión anterior y listo&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Es como la diferencia entre cuidar un jardín y vivir en un hotel. En un hotel, todo simplemente funciona.&lt;/p&gt;
&lt;h2 id="dejare-kde-neon"&gt;¿Dejaré KDE Neon?&lt;a class="headerlink" href="#dejare-kde-neon" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;No lo sé aún. Llevo años en KDE Neon y tengo mi flujo de trabajo montado. Pero Aurora me está haciendo replantear cosas.&lt;/p&gt;
&lt;p&gt;Lo que más me tira:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;El DX&lt;/strong&gt; es simplemente superior para desarrollo moderno&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Las actualizaciones automáticas&lt;/strong&gt; sin tener que estar pendiente&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;La filosofía&lt;/strong&gt; de "todo en contenedores" tiene mucho sentido&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Lo que me da miedo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;La inmutabilidad&lt;/strong&gt; — al principio se siente restrictivo&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;El cambio de mentalidad&lt;/strong&gt; — dejar de tener control total&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;¿Y si necesito algo específico? (que me a ocurrir seguro)&lt;/strong&gt; — aunque con homebrew y contenedores, probablemente no sea mucho&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Enlaces:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://getaurora.dev/en/"&gt;Aurora Website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.getaurora.dev/dx/aurora-dx-intro/"&gt;Aurora DX Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://universal-blue.org"&gt;Universal Blue (el proyecto detrás)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="Linux"></category><category term="aurora"></category><category term="kde"></category><category term="escritorio"></category><category term="devcontainers"></category><category term="homebrew"></category></entry><entry><title>Ampliar disco de VM Aurora en Unraid y redimensionar particiones</title><link href="https://pablocaro.es/ampliar-disco-vm-aurora-unraid" rel="alternate"></link><published>2026-04-24T00:00:00+02:00</published><updated>2026-04-24T00:00:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-04-24:/ampliar-disco-vm-aurora-unraid</id><summary type="html">&lt;p&gt;Cómo expandir el disco virtual de una VM Aurora ejecutándose en Unraid y redimensionar las particiones Btrfs dentro del sistema operativo.&lt;/p&gt;</summary><content type="html">&lt;h1 id="ampliar-disco-de-vm-aurora-en-unraid-y-redimensionar-particiones"&gt;Ampliar disco de VM Aurora en Unraid y redimensionar particiones&lt;a class="headerlink" href="#ampliar-disco-de-vm-aurora-en-unraid-y-redimensionar-particiones" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Me quedé sin espacio en mi prueba de la distribución &lt;strong&gt;Aurora Linux&lt;/strong&gt;. Como la ejecuto en una VM en mi NAS Unraid, simplemente aumenté el disco virtual y luego redimensioné las particiones dentro del sistema.&lt;/p&gt;
&lt;p&gt;Esta es una distribución basada en &lt;strong&gt;Fedora Kinoite&lt;/strong&gt; (imágenes OSTree/bootc) del proyecto Universal Blue, que usa un sistema de archivos &lt;strong&gt;Btrfs&lt;/strong&gt; con subvolúmenes.&lt;/p&gt;
&lt;h2 id="situacion-inicial-del-disco"&gt;Situación inicial del disco&lt;a class="headerlink" href="#situacion-inicial-del-disco" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Antes de la expansión, el disco tenía aproximadamente &lt;strong&gt;15GB&lt;/strong&gt; y la partición principal estaba al límite:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Diagrama de expansión del disco - antes y después" src="/images/ampliar-disco-vm-aurora-unraid.png"&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;NAME&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;MAJ&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;MIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RM&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;SIZE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TYPE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MOUNTPOINTS&lt;/span&gt;
&lt;span class="n"&gt;vda&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;253&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;disk&lt;/span&gt;
&lt;span class="err"&gt;├─&lt;/span&gt;&lt;span class="n"&gt;vda1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;253&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;boot&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;efi&lt;/span&gt;
&lt;span class="err"&gt;├─&lt;/span&gt;&lt;span class="n"&gt;vda2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;253&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;boot&lt;/span&gt;
&lt;span class="err"&gt;└─&lt;/span&gt;&lt;span class="n"&gt;vda3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;253&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;etc&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;El filesystem Btrfs mostraba:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Device size:         12.41GiB
Device unallocated:    1.00MiB
Free (estimated):    512.12MiB (min: 512.12MiB)
Usage:               93%
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="paso-1-expandir-el-disco-en-unraid"&gt;Paso 1: Expandir el disco en Unraid&lt;a class="headerlink" href="#paso-1-expandir-el-disco-en-unraid" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Desde la terminal de Unraid, con la VM apagada:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Apagar la VM&lt;/span&gt;
virsh&lt;span class="w"&gt; &lt;/span&gt;shutdown&lt;span class="w"&gt; &lt;/span&gt;Aurora

&lt;span class="c1"&gt;# Expandir el disco a 60GB (triplicando el tamaño original)&lt;/span&gt;
qemu-img&lt;span class="w"&gt; &lt;/span&gt;resize&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;raw&lt;span class="w"&gt; &lt;/span&gt;/mnt/user/domains/Aurora/vdisk1.img&lt;span class="w"&gt; &lt;/span&gt;60G

&lt;span class="c1"&gt;# Verificar el nuevo tamaño&lt;/span&gt;
ls&lt;span class="w"&gt; &lt;/span&gt;-lh&lt;span class="w"&gt; &lt;/span&gt;/mnt/user/domains/Aurora/vdisk1.img
qemu-img&lt;span class="w"&gt; &lt;/span&gt;info&lt;span class="w"&gt; &lt;/span&gt;/mnt/user/domains/Aurora/vdisk1.img

&lt;span class="c1"&gt;# Encender la VM&lt;/span&gt;
virsh&lt;span class="w"&gt; &lt;/span&gt;start&lt;span class="w"&gt; &lt;/span&gt;Aurora
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="paso-2-instalar-growpart-en-aurora"&gt;Paso 2: Instalar growpart en Aurora&lt;a class="headerlink" href="#paso-2-instalar-growpart-en-aurora" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Aurora es un sistema inmutable basado en OSTree, así que necesité instalar la herramienta &lt;code&gt;growpart&lt;/code&gt; usando &lt;code&gt;rpm-ostree&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Instalar cloud-utils-growpart&lt;/span&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;rpm-ostree&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;cloud-utils-growpart

&lt;span class="c1"&gt;# Reiniciar (requerido después de rpm-ostree install)&lt;/span&gt;
systemctl&lt;span class="w"&gt; &lt;/span&gt;reboot
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="paso-3-expandir-la-particion"&gt;Paso 3: Expandir la partición&lt;a class="headerlink" href="#paso-3-expandir-la-particion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Una vez reiniciado el sistema, expandí la partición 3 para ocupar todo el espacio del disco:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Expandir la partición 3 del disco /dev/vda&lt;/span&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;growpart&lt;span class="w"&gt; &lt;/span&gt;/dev/vda&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;CHANGED&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;partition&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5425152&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;26030080&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;31455231&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;120403935&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;125829086&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="paso-4-redimensionar-el-filesystem-btrfs"&gt;Paso 4: Redimensionar el filesystem Btrfs&lt;a class="headerlink" href="#paso-4-redimensionar-el-filesystem-btrfs" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Aurora usa una estructura especial con múltiples subvolúmenes Btrfs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;root&lt;/code&gt; (ID 256) - sistema base (montado read-only en /sysroot)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;home&lt;/code&gt; (ID 257) - directorios de usuario&lt;/li&gt;
&lt;li&gt;&lt;code&gt;var&lt;/code&gt; (ID 258) - variables del sistema&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;El truco está en que &lt;code&gt;/sysroot&lt;/code&gt; está montado como &lt;strong&gt;read-only&lt;/strong&gt; (por ser OSTree inmutable), pero puedo hacer el resize desde cualquier mount point con escritura del mismo filesystem Btrfs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Redimensionar desde /var (que tiene escritura)&lt;/span&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;btrfs&lt;span class="w"&gt; &lt;/span&gt;filesystem&lt;span class="w"&gt; &lt;/span&gt;resize&lt;span class="w"&gt; &lt;/span&gt;max&lt;span class="w"&gt; &lt;/span&gt;/var
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Resize device id 1 (/dev/vda3) from 12.41GiB to max
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="resultado-final"&gt;Resultado final&lt;a class="headerlink" href="#resultado-final" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Después del redimensionamiento, el filesystem Btrfs muestra:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Device size:         57.41GiB    ← Antes era 12.41GiB ✓
Device unallocated:  45.00GiB    ← Espacio disponible
Device slack:          3.50KiB   ← Antes era 45.00GiB ✓
Free (estimated):    45.50GiB    ← Espacio libre para usar
Usage:               ~20%        ← Antes estaba al 93%
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Verificando con &lt;code&gt;lsblk&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;NAME&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;MAJ&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;MIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RM&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;SIZE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TYPE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MOUNTPOINTS&lt;/span&gt;
&lt;span class="n"&gt;vda&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;253&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;disk&lt;/span&gt;
&lt;span class="err"&gt;├─&lt;/span&gt;&lt;span class="n"&gt;vda1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;253&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;boot&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;efi&lt;/span&gt;
&lt;span class="err"&gt;├─&lt;/span&gt;&lt;span class="n"&gt;vda2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;253&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;boot&lt;/span&gt;
&lt;span class="err"&gt;└─&lt;/span&gt;&lt;span class="n"&gt;vda3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;253&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;57&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;etc&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="resumen-de-comandos"&gt;Resumen de comandos&lt;a class="headerlink" href="#resumen-de-comandos" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# === EN UNRAID (VM apagada) ===&lt;/span&gt;
qemu-img&lt;span class="w"&gt; &lt;/span&gt;resize&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;raw&lt;span class="w"&gt; &lt;/span&gt;/mnt/user/domains/Aurora/vdisk1.img&lt;span class="w"&gt; &lt;/span&gt;60G

&lt;span class="c1"&gt;# === EN AURORA ===&lt;/span&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;rpm-ostree&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;cloud-utils-growpart
systemctl&lt;span class="w"&gt; &lt;/span&gt;reboot
sudo&lt;span class="w"&gt; &lt;/span&gt;growpart&lt;span class="w"&gt; &lt;/span&gt;/dev/vda&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;btrfs&lt;span class="w"&gt; &lt;/span&gt;filesystem&lt;span class="w"&gt; &lt;/span&gt;resize&lt;span class="w"&gt; &lt;/span&gt;max&lt;span class="w"&gt; &lt;/span&gt;/var

&lt;span class="c1"&gt;# === VERIFICAR ===&lt;/span&gt;
lsblk
df&lt;span class="w"&gt; &lt;/span&gt;-h&lt;span class="w"&gt; &lt;/span&gt;/var
df&lt;span class="w"&gt; &lt;/span&gt;-h&lt;span class="w"&gt; &lt;/span&gt;/var/home
df&lt;span class="w"&gt; &lt;/span&gt;-h&lt;span class="w"&gt; &lt;/span&gt;/etc
sudo&lt;span class="w"&gt; &lt;/span&gt;btrfs&lt;span class="w"&gt; &lt;/span&gt;filesystem&lt;span class="w"&gt; &lt;/span&gt;usage&lt;span class="w"&gt; &lt;/span&gt;/var
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="notas-importantes"&gt;Notas importantes&lt;a class="headerlink" href="#notas-importantes" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Sistemas OSTree&lt;/strong&gt;: En distribuciones inmutables como Aurora, el sistema base es read-only. El resize debe hacerse desde un subvolumen con escritura (&lt;code&gt;/var&lt;/code&gt;, &lt;code&gt;/etc&lt;/code&gt;, o &lt;code&gt;/var/home&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Btrfs dinámico&lt;/strong&gt;: El espacio en Btrfs se asigna dinámicamente a los subvolúmenes según necesidad. No es necesario asignar espacio específico a cada subvolumen.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reinicio requerido&lt;/strong&gt;: Después de &lt;code&gt;rpm-ostree install&lt;/code&gt;, siempre hay que reiniciar para que los cambios surtan efecto.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Backup&lt;/strong&gt;: Aunque el proceso es seguro, siempre es recomendable tener backup antes de operar sobre particiones. En mi caso no hizo falta por no tener nada importante. Es una VM de prueba.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</content><category term="Linux"></category><category term="aurora"></category><category term="unraid"></category><category term="vm"></category><category term="btrfs"></category><category term="disco"></category></entry><entry><title>Rust eBooks Nightly — libros de Rust en EPUB, AZW3, MOBI y PDF</title><link href="https://pablocaro.es/rust-ebooks-nightly" rel="alternate"></link><published>2026-04-21T22:00:00+02:00</published><updated>2026-04-21T22:00:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-04-21:/rust-ebooks-nightly</id><summary type="html">&lt;p&gt;Un proyecto de Artur Sulej que publica automáticamente las versiones más recientes de los mejores libros de Rust en formatos eBook. Se actualiza daily.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://artur-sulej.github.io/rust-ebooks/"&gt;Rust eBooks Nightly&lt;/a&gt; es un proyecto de Artur Sulej que automáticamente construye y publica la última versión de los libros de Rust más importantes en EPUB, AZW3, MOBI y PDF. Se actualiza diariamente via GitHub Actions.&lt;/p&gt;
&lt;p&gt;Los libros disponibles ahora mismo:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The Rust Programming Language&lt;/strong&gt; (el libro oficial de rust-lang.org)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rust By Example&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Rust Reference&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Rustonomicon&lt;/strong&gt; — todo sobre código unsafe en Rust&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Cargo Book&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rust Cookbook&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Asynchronous Programming in Rust&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Learning Rust With Entirely Too Many Linked Lists&lt;/strong&gt; — mi favorito para aprender alienist patterns&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rust Unsafe Code Guidelines&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Embedded Rust Book&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The wasm-bindgen Guide&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Real-Time Interrupt-driven Concurrency&lt;/strong&gt; (RTIC)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Rust and WebAssembly Book&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;La gracia del proyecto es que es nightly — cada día clona el repositorio de cada libro, lo construye con mdBook, lo convierte a los distintos formatos con Calibre, y lo sube a GitHub Pages. Si un libro falla en construirse, se salta y crea un issue automáticamente. Los demás siguen adelante.&lt;/p&gt;
&lt;p&gt;Llevo tiempo usando un Likebook Muses y tener los libros de Rust siempre a mano es bastante útil.&lt;/p&gt;
&lt;p&gt;Estos libros cubren la documentación oficial, así que están bien complementados con otros de pago y gran calidad como &lt;em&gt;Programming Rust&lt;/em&gt; (O'Reilly), &lt;em&gt;Rust for Rustaceans&lt;/em&gt; de Jon Gjengset o &lt;em&gt;Black Hat Rust&lt;/em&gt; de Sylvain Kerkour.&lt;/p&gt;
&lt;p&gt;Enlace: &lt;a href="https://artur-sulej.github.io/rust-ebooks/"&gt;https://artur-sulej.github.io/rust-ebooks/&lt;/a&gt;
Repo: &lt;a href="https://github.com/Artur-Sulej/rust-ebooks"&gt;https://github.com/Artur-Sulej/rust-ebooks&lt;/a&gt;&lt;/p&gt;</content><category term="Programación"></category><category term="rust"></category><category term="ebooks"></category><category term="epub"></category><category term="calibre"></category><category term="github actions"></category></entry><entry><title>Desactivar git autocrlf</title><link href="https://pablocaro.es/desactivar-git-autocrlf" rel="alternate"></link><published>2026-04-21T12:00:00+02:00</published><updated>2026-04-21T12:00:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-04-21:/desactivar-git-autocrlf</id><summary type="html">&lt;p&gt;Si usas Windows (o WSL), Git te está convirtiendo los saltos de línea en silencio. Un solo comando lo arregla.&lt;/p&gt;</summary><content type="html">&lt;p&gt;No soy usuario habitual de Windows, pero si me viese obligado una temporada WSL sería para mí casi una obligación. No está de más tener esta nota a mano (por Ryan Harrison).&lt;/p&gt;
&lt;p&gt;Si usas Windows, Git lleva años modificando tus saltos de línea sin que te des cuenta. El culpable es &lt;code&gt;core.autocrlf&lt;/code&gt;, activado por defecto.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git&lt;span class="w"&gt; &lt;/span&gt;config&lt;span class="w"&gt; &lt;/span&gt;--global&lt;span class="w"&gt; &lt;/span&gt;core.autocrlf&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="que-hace-coreautocrlf"&gt;Qué hace &lt;code&gt;core.autocrlf&lt;/code&gt;&lt;a class="headerlink" href="#que-hace-coreautocrlf" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Windows usa históricamente &lt;code&gt;CRLF&lt;/code&gt; (&lt;code&gt;\r\n&lt;/code&gt;) como salto de línea, mientras que Linux y macOS usan &lt;code&gt;LF&lt;/code&gt; (&lt;code&gt;\n&lt;/code&gt;). Con &lt;code&gt;autocrlf true&lt;/code&gt; (el default en Windows):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;En &lt;strong&gt;checkout&lt;/strong&gt;: Git convierte &lt;code&gt;LF&lt;/code&gt; del repositorio a &lt;code&gt;CRLF&lt;/code&gt; en tu working directory&lt;/li&gt;
&lt;li&gt;En &lt;strong&gt;commit&lt;/strong&gt;: Git convierte &lt;code&gt;CRLF&lt;/code&gt; de vuelta a &lt;code&gt;LF&lt;/code&gt; antes de guardar&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;En teoría, tus ficheros locales quedan en formato Windows y el repositorio se mantiene limpio con &lt;code&gt;LF&lt;/code&gt;. En la práctica, es fuente constante de problemas.&lt;/p&gt;
&lt;h2 id="por-que-es-un-mal-default"&gt;Por qué es un mal default&lt;a class="headerlink" href="#por-que-es-un-mal-default" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;WSL lo agrava.&lt;/strong&gt; Tu entorno Linux espera &lt;code&gt;LF&lt;/code&gt;, pero Git ha convertido todo a &lt;code&gt;CRLF&lt;/code&gt;. Resultado: cada script y config file tiene un &lt;code&gt;\r&lt;/code&gt; fantasma al final de cada línea, provocando fallos silenciosos. Y si no has visto el warning de marras, es que ya te has acostumbrado a ignorarlo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;warning&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;will&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;replaced&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CRLF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;some&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sh&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;will&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;have&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;its&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;endings&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;working&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;directory&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Los editores modernos soportan LF sin problema.&lt;/strong&gt; El argumento original era que el Bloc de notas de Windows no podía manejar &lt;code&gt;LF&lt;/code&gt; — algo que era cierto en 2003. Desde Windows 10 (2018) el Bloc de notas soporta &lt;code&gt;LF&lt;/code&gt;. VS Code, Sublime, Vim, IntelliJ, Notepad++... todos lo manejan perfectamente desde hace años.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;La conversión es pérdida en la práctica.&lt;/strong&gt; Ficheros binarios se pueden corromper si Git los clasifica mal como texto. Y si en un equipo alguien tiene &lt;code&gt;autocrlf true&lt;/code&gt; y otro &lt;code&gt;false&lt;/code&gt;, aparecerán commits que son solo cambios de saltos de línea.&lt;/p&gt;
&lt;h2 id="la-solucion-desactivalo-y-usa-gitattributes"&gt;La solución: desactívalo y usa &lt;code&gt;.gitattributes&lt;/code&gt;&lt;a class="headerlink" href="#la-solucion-desactivalo-y-usa-gitattributes" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git&lt;span class="w"&gt; &lt;/span&gt;config&lt;span class="w"&gt; &lt;/span&gt;--global&lt;span class="w"&gt; &lt;/span&gt;core.autocrlf&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A partir de ahí, Git deja tus saltos de línea tal cual — sin conversiones silenciosas. Para equipos, un fichero &lt;code&gt;.gitattributes&lt;/code&gt; en el repositorio es un enfoque mucho mejor, porque aplica las mismas reglas para todos independientemente de su config local.&lt;/p&gt;
&lt;p&gt;Usa &lt;code&gt;LF&lt;/code&gt; en todas partes, configura tu editor para que guarde con &lt;code&gt;LF&lt;/code&gt; por defecto, y no pienses más en ello.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Fuente original&lt;/em&gt;: &lt;a href="https://ryanharrison.co.uk/2026/04/21/git-disable-autocrlf.html"&gt;Git - Disable autocrlf (Ryan Harrison)&lt;/a&gt;&lt;/p&gt;</content><category term="Programación"></category><category term="git"></category><category term="windows"></category><category term="wsl"></category><category term="line endings"></category></entry><entry><title>Instalando skills de GitHub</title><link href="https://pablocaro.es/instalando-skills-de-github" rel="alternate"></link><published>2026-04-19T12:00:00+02:00</published><updated>2026-04-19T12:00:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-04-19:/instalando-skills-de-github</id><summary type="html">&lt;p&gt;Comparando &lt;code&gt;gh skill&lt;/code&gt; vs &lt;code&gt;npx skills&lt;/code&gt; para instalar y gestionar skills de agentes de IA.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Dos formas principales de instalar skills para agentes de IA (Claude Code, Codex, Cursor, etc.): el nuevo subcomando &lt;code&gt;gh skill&lt;/code&gt; de GitHub CLI y la herramienta &lt;code&gt;npx skills&lt;/code&gt; de Vercel Labs.&lt;/p&gt;
&lt;h2 id="gh-skill-el-enfoque-oficial-de-github"&gt;&lt;code&gt;gh skill&lt;/code&gt; — el enfoque oficial de GitHub&lt;a class="headerlink" href="#gh-skill-el-enfoque-oficial-de-github" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;GitHub CLI ahora tiene un subcomando nativo para gestionar skills:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Buscar skills&lt;/span&gt;
gh&lt;span class="w"&gt; &lt;/span&gt;skill&lt;span class="w"&gt; &lt;/span&gt;search&lt;span class="w"&gt; &lt;/span&gt;terraform

&lt;span class="c1"&gt;# Instalar un skill específico&lt;/span&gt;
gh&lt;span class="w"&gt; &lt;/span&gt;skill&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;github/awesome-copilot&lt;span class="w"&gt; &lt;/span&gt;documentation-writer

&lt;span class="c1"&gt;# Previsualizar antes de instalar&lt;/span&gt;
gh&lt;span class="w"&gt; &lt;/span&gt;skill&lt;span class="w"&gt; &lt;/span&gt;preview&lt;span class="w"&gt; &lt;/span&gt;github/awesome-copilot&lt;span class="w"&gt; &lt;/span&gt;documentation-writer

&lt;span class="c1"&gt;# Actualizar todos los skills instalados&lt;/span&gt;
gh&lt;span class="w"&gt; &lt;/span&gt;skill&lt;span class="w"&gt; &lt;/span&gt;update&lt;span class="w"&gt; &lt;/span&gt;--all

&lt;span class="c1"&gt;# Validar skills antes de publicar&lt;/span&gt;
gh&lt;span class="w"&gt; &lt;/span&gt;skill&lt;span class="w"&gt; &lt;/span&gt;publish&lt;span class="w"&gt; &lt;/span&gt;--dry-run
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Cómo instala&lt;/strong&gt;: &lt;code&gt;gh skill&lt;/code&gt; &lt;strong&gt;copia&lt;/strong&gt; los archivos (no usa symlinks) e inyecta metadata de tracking directamente en el frontmatter del &lt;code&gt;SKILL.md&lt;/code&gt; (repositorio, ref, tree SHA). Esta metadata permite que &lt;code&gt;gh skill update&lt;/code&gt; detecte cambios reales de contenido comparando el tree SHA local vs el remoto.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dónde instala&lt;/strong&gt;: Por defecto en &lt;strong&gt;scope de proyecto&lt;/strong&gt; (dentro del repositorio git actual), pero puedes usar &lt;code&gt;--scope user&lt;/code&gt; para instalar en tu home directory y que esté disponible globalmente. Los directorios por agente son:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agente&lt;/th&gt;
&lt;th&gt;Proyecto&lt;/th&gt;
&lt;th&gt;Usuario&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Copilot&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.agents/skills&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.copilot/skills&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.claude/skills&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.claude/skills&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cursor&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.agents/skills&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.cursor/skills&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codex&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.agents/skills&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.codex/skills&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini CLI&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.agents/skills&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.gemini/skills&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Antigravity&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.agents/skills&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.gemini/antigravity/skills&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Nota: GitHub Copilot, Cursor, Codex, Gemini CLI, Antigravity y Pi comparten &lt;code&gt;.agents/skills&lt;/code&gt; a nivel de proyecto.&lt;/p&gt;
&lt;p&gt;Lo interesante es que está integrado directamente en &lt;code&gt;gh&lt;/code&gt;, así que si ya usas GitHub CLI para todo, no necesitas otra dependencia más. Pero la búsqueda es bastante básica y limitada al ecosistema de GitHub.&lt;/p&gt;
&lt;h2 id="npx-skills-la-herramienta-de-vercel-labs"&gt;&lt;code&gt;npx skills&lt;/code&gt; — la herramienta de Vercel Labs&lt;a class="headerlink" href="#npx-skills-la-herramienta-de-vercel-labs" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Llevo tiempo usando &lt;a href="https://github.com/vercel-labs/skills"&gt;&lt;code&gt;npx skills&lt;/code&gt;&lt;/a&gt; de Vercel Labs y me parece más completa para el día a día:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Listar skills disponibles en un repo&lt;/span&gt;
npx&lt;span class="w"&gt; &lt;/span&gt;skills&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;github/awesome-copilot&lt;span class="w"&gt; &lt;/span&gt;--list

&lt;span class="c1"&gt;# Instalar un skill específico&lt;/span&gt;
npx&lt;span class="w"&gt; &lt;/span&gt;skills&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;github/awesome-copilot&lt;span class="w"&gt; &lt;/span&gt;--skill&lt;span class="w"&gt; &lt;/span&gt;publish-to-pages

&lt;span class="c1"&gt;# Instalar todos los skills de un repo&lt;/span&gt;
npx&lt;span class="w"&gt; &lt;/span&gt;skills&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;vercel-labs/agent-skills&lt;span class="w"&gt; &lt;/span&gt;--all

&lt;span class="c1"&gt;# Actualizar skills&lt;/span&gt;
npx&lt;span class="w"&gt; &lt;/span&gt;skills&lt;span class="w"&gt; &lt;/span&gt;update

&lt;span class="c1"&gt;# Buscar skills de forma interactiva&lt;/span&gt;
npx&lt;span class="w"&gt; &lt;/span&gt;skills&lt;span class="w"&gt; &lt;/span&gt;find
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ventajas que le veo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Soporte multi-agente&lt;/strong&gt;: Funciona con Pi, Claude Code, Codex, Cursor, OpenCode, Gemini CLI, y más de 40 agentes. Cada uno tiene su directorio de skills y &lt;code&gt;npx skills&lt;/code&gt; los gestiona todos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Instalación flexible&lt;/strong&gt;: Puedes instalar skills de forma global (&lt;code&gt;-g&lt;/code&gt;) o por proyecto, y elegir entre symlink (recomendado para actualizar fácil) o copiar archivos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Búsqueda interactiva&lt;/strong&gt;: El comando &lt;code&gt;npx skills find&lt;/code&gt; te da una interfaz tipo &lt;code&gt;fzf&lt;/code&gt; para explorar skills disponibles.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Actualizaciones más granulares&lt;/strong&gt;: Puedes actualizar skills individuales o todos, y elegir si son los globales o los del proyecto.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;El punto débil es que la búsqueda no funciona del todo bien cuando un repo tiene múltiples skills — a veces no los encuentra todos o no los lista como esperas.&lt;/p&gt;
&lt;p&gt;Pero donde &lt;code&gt;npx skills&lt;/code&gt; realmente brilla es con &lt;code&gt;npx skills find&lt;/code&gt; — una interfaz interactiva tipo &lt;code&gt;fzf&lt;/code&gt; para explorar y buscar skills disponibles. Es muchísimo mejor que la búsqueda básica de &lt;code&gt;gh skill&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img alt="npx skills find" src="/images/npx-skills-find.png"&gt;&lt;/p&gt;
&lt;p&gt;Puedes buscar por palabra clave, navegar interactivamente por los resultados, ver el número de instalaciones y lanzar el comando de instalación directamente. Para descubrir nuevas skills, esta es la mejor opción sin duda.&lt;/p&gt;
&lt;h2 id="comparativa-tecnica"&gt;Comparativa técnica&lt;a class="headerlink" href="#comparativa-tecnica" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Característica&lt;/th&gt;
&lt;th&gt;&lt;code&gt;gh skill&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;npx skills&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Método de instalación&lt;/td&gt;
&lt;td&gt;Copia archivos&lt;/td&gt;
&lt;td&gt;Symlink (recomendado) o copia&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scope por defecto&lt;/td&gt;
&lt;td&gt;Proyecto&lt;/td&gt;
&lt;td&gt;Proyecto&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scope global&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--scope user&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-g&lt;/code&gt; / &lt;code&gt;--global&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tracking de versión&lt;/td&gt;
&lt;td&gt;Tree SHA en frontmatter&lt;/td&gt;
&lt;td&gt;Metadata similar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Update&lt;/td&gt;
&lt;td&gt;Compara tree SHA local vs remoto&lt;/td&gt;
&lt;td&gt;Git pull en el repo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agentes soportados&lt;/td&gt;
&lt;td&gt;6 (Copilot, Claude, Cursor, Codex, Gemini, Antigravity)&lt;/td&gt;
&lt;td&gt;40+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Búsqueda interactiva&lt;/td&gt;
&lt;td&gt;No (&lt;code&gt;gh skill search&lt;/code&gt; es básico)&lt;/td&gt;
&lt;td&gt;Sí (&lt;code&gt;npx skills find&lt;/code&gt; con fzf)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integración&lt;/td&gt;
&lt;td&gt;Nativo en gh CLI&lt;/td&gt;
&lt;td&gt;Herramienta independiente&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="skills-lockjson-el-archivo-de-lock-de-npx-skills"&gt;&lt;code&gt;skills-lock.json&lt;/code&gt; — el archivo de lock de &lt;code&gt;npx skills&lt;/code&gt;&lt;a class="headerlink" href="#skills-lockjson-el-archivo-de-lock-de-npx-skills" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;npx skills&lt;/code&gt; mantiene un archivo de lock global en &lt;code&gt;~/.agents/.skill-lock.json&lt;/code&gt; (formato v3) que registra:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Todos los skills instalados globalmente&lt;/li&gt;
&lt;li&gt;Su origen (repo, URL, tipo de fuente)&lt;/li&gt;
&lt;li&gt;Hash de la carpeta para detectar cambios&lt;/li&gt;
&lt;li&gt;Fechas de instalación y última actualización&lt;/li&gt;
&lt;li&gt;Preferencias del usuario (agentes seleccionados)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Actualmente funciona como &lt;strong&gt;registro&lt;/strong&gt; para que &lt;code&gt;npx skills update&lt;/code&gt; sepa qué verificar, pero &lt;strong&gt;no como manifiesto declarativo&lt;/strong&gt; — no hay un comando &lt;code&gt;npx skills install&lt;/code&gt; que restaure skills desde cero leyendo el lock (como haría &lt;code&gt;npm ci&lt;/code&gt;). Es decir, si borras tus skills instalados, el lock file solo sirve como referencia, no para restaurar automáticamente.&lt;/p&gt;
&lt;h2 id="mi-flujo-actual"&gt;Mi flujo actual&lt;a class="headerlink" href="#mi-flujo-actual" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Para instalar skills nuevos:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Ver qué skills tiene un repo&lt;/span&gt;
npx&lt;span class="w"&gt; &lt;/span&gt;skills&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;github/awesome-copilot&lt;span class="w"&gt; &lt;/span&gt;--list

&lt;span class="c1"&gt;# Instalar solo los que me interesan&lt;/span&gt;
npx&lt;span class="w"&gt; &lt;/span&gt;skills&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;github/awesome-copilot&lt;span class="w"&gt; &lt;/span&gt;--skill&lt;span class="w"&gt; &lt;/span&gt;publish-to-pages&lt;span class="w"&gt; &lt;/span&gt;--skill&lt;span class="w"&gt; &lt;/span&gt;documentation-writer
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y para mantenerlos actualizados:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;npx&lt;span class="w"&gt; &lt;/span&gt;skills&lt;span class="w"&gt; &lt;/span&gt;update
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;En resumen: me quedo con &lt;code&gt;npx skills&lt;/code&gt; de Vercel sin duda. GitHub CLI está bien si solo usas Copilot y quieres todo integrado en &lt;code&gt;gh&lt;/code&gt;, pero si trabajas con múltiples agentes (como yo, que uso Pi como principal), &lt;code&gt;npx skills&lt;/code&gt; es muy superior por su soporte multi-agente, flexibilidad y sobre todo por &lt;code&gt;npx skills find&lt;/code&gt; para descubrir nuevas skills.&lt;/p&gt;
&lt;h2 id="enlaces"&gt;Enlaces&lt;a class="headerlink" href="#enlaces" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cli.github.com/manual/gh_skill"&gt;&lt;code&gt;gh skill&lt;/code&gt; documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/vercel-labs/skills"&gt;&lt;code&gt;npx skills&lt;/code&gt; repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://skills.sh"&gt;Skills directory&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="Programación"></category><category term="github"></category><category term="cli"></category><category term="agentes-ia"></category><category term="skills"></category></entry><entry><title>Velvet OS: kernels modernos para hardware ARM antiguo</title><link href="https://pablocaro.es/velvet-os" rel="alternate"></link><published>2026-04-17T00:00:00+02:00</published><updated>2026-04-17T00:00:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-04-17:/velvet-os</id><summary type="html">&lt;p&gt;Nota mental sobre Velvet OS, un proyecto interesante para actualizar kernels en dispositivos ARM antiguos.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="Velvet OS" src="/images/velvet-os.png"&gt;&lt;/p&gt;
&lt;p&gt;Nota mental sobre &lt;a href="https://velvet-os.github.io/"&gt;Velvet OS&lt;/a&gt;, un proyecto que descubrí a través de &lt;a href="https://raspberryparatorpes.net/proyectos/odroid-u3-kernel-6-velvet-os/"&gt;esta entrada en Raspberry Pi para Torpes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Velvet OS es un framework sencillo para construir imágenes de tarjetas SD arrancables con Debian o Ubuntu para diversos dispositivos ARM. El objetivo es proporcionar kernels de Linux modernos para hardware que ya no tiene soporte oficial o que quedó abandonado.&lt;/p&gt;
&lt;p&gt;Lo interesante del proyecto:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Soporta más de 30 sistemas diferentes&lt;/strong&gt;, desde Chromebooks ARM hasta SBCs como Odroid y OrangePi&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kernels actualizados&lt;/strong&gt; (actualmente 6.12) para hardware que se quedó estancado en kernels 3.x o 4.x&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Imágenes listas para usar&lt;/strong&gt;, sin necesidad de compilar nada&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Proyecto mantenido activamente&lt;/strong&gt; por hexdump0815&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;El nombre "Velvet OS" viene de que el autor suele cubrir las tapas rayadas de sus Chromebooks con terciopelo autoadhesivo, haciendo desaparecer el branding comercial. La idea es que usar estas imágenes sea una experiencia tan agradable como tocar terciopelo.&lt;/p&gt;
&lt;p&gt;Tengo varios cacharritos antiguos por casa, incluyendo dos OrangePi. A ver si saco un ratito para actualizarlos con kernels modernos y darles una segunda vida.&lt;/p&gt;
&lt;p&gt;El tutorial de Raspberry Pi para Torpes explica en detalle cómo actualizar una ODROID-U3 de 2013 al kernel 6.12, incluyendo los pasos para configurar el U-Boot, copiar el kernel, generar el initrd y ajustar el &lt;code&gt;boot.ini&lt;/code&gt;. Todo el proceso está muy bien documentado.&lt;/p&gt;
&lt;p&gt;Enlaces:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://velvet-os.github.io/"&gt;Velvet OS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/hexdump0815/linux-mainline-and-mali-generic-stable-kernel"&gt;Kernels precompilados para Exynos4412&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://raspberryparatorpes.net/proyectos/odroid-u3-kernel-6-velvet-os/"&gt;Tutorial: ODROID-U3 con kernel 6.12&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="Linux"></category><category term="kernel"></category><category term="arm"></category><category term="odroid"></category><category term="orangepi"></category><category term="velvet-os"></category><category term="sbc"></category></entry><entry><title>Creación de vídeo con Gemini: personajes de Sevilla</title><link href="https://pablocaro.es/video-gemini-sevilla" rel="alternate"></link><published>2026-04-15T00:00:00+02:00</published><updated>2026-04-15T00:00:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-04-15:/video-gemini-sevilla</id><summary type="html">&lt;p&gt;Probando la generación de vídeo de Gemini con personajes basados en Sevilla.&lt;/p&gt;</summary><content type="html">&lt;p&gt;La creación de vídeo con IA generativa está mejorando a pasos agigantados. El último experimento que he probado es generar vídeos a partir de imágenes con Gemini.&lt;/p&gt;
&lt;p&gt;El proceso es sencillo: creé tres imágenes con personajes ficticios inspirados en Sevilla, y Gemini las convirtió en vídeos con movimiento natural.&lt;/p&gt;
&lt;p&gt;Aquí os dejo las tres imágenes originales:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Sevi" src="/images/Sevi.webp"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Cuento" src="/images/Cuento.jpeg"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Krag" src="/images/Krag.jpeg"&gt;&lt;/p&gt;
&lt;p&gt;Y el vídeo resultante:&lt;/p&gt;
&lt;video controls&gt;
  &lt;source src="/images/El_Secreto_del_Alcázar_de_Sevilla.mp4" type="video/mp4"&gt;
  Tu navegador no soporta vídeo HTML5.
&lt;/video&gt;

&lt;p&gt;Los resultados fueron satisfactorios. Los personajes se mueven de forma coherente, mantienen la consistencia visual y la calidad del movimiento ha mejorado notablemente respecto a versiones anteriores.&lt;/p&gt;
&lt;p&gt;Es impresionante ver cómo la IA puede entender el contexto de una imagen y aplicar animaciones que parecen naturales. Todavía hay trabajo en detalles como las manos y ciertas deformaciones, pero el progreso es evidente.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Fuente original&lt;/em&gt;: &lt;a href="https://x.com/pcaro/status/2044359553412706669"&gt;Tweet en X&lt;/a&gt;&lt;/p&gt;</content><category term="IA"></category><category term="gemini"></category><category term="google"></category><category term="video"></category><category term="sevilla"></category><category term="ia"></category></entry><entry><title>Google Stitch: Generación y prototipado UI con AI</title><link href="https://pablocaro.es/google-stitch" rel="alternate"></link><published>2026-04-13T12:38:00+02:00</published><updated>2026-04-13T12:38:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-04-13:/google-stitch</id><summary type="html">&lt;p&gt;Análisis de Google Stitch como herramienta para generar interfaces de usuario y prototipos interactivos mediante inteligencia artificial.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://stitch.withgoogle.com/"&gt;Google Stitch&lt;/a&gt; es una herramienta de diseño AI-native impulsada por Gemini para generar interfaces de usuario a partir de texto o imágenes. &lt;/p&gt;
&lt;p&gt;&lt;img alt="Google Stitch" src="https://pablocaro.es/images/stitch.png"&gt;&lt;/p&gt;
&lt;h2 id="el-caso-de-uso"&gt;El caso de uso&lt;a class="headerlink" href="#el-caso-de-uso" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Diseñar flujos de UI y prototipos rápidos (dashboards, formularios, CRUDs) es un proceso repetitivo. Herramientas tradicionales como Figma son el estándar para diseño detallado, pero para la ideación rápida o para desarrolladores sin perfil de diseño, la generación mediante AI reduce drásticamente el tiempo.&lt;/p&gt;
&lt;h2 id="caracteristicas-principales"&gt;Características principales&lt;a class="headerlink" href="#caracteristicas-principales" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Tras la gran actualización de marzo de 2026, Stitch incluye:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Canvas infinito AI-native&lt;/strong&gt;: Soporte para texto, imágenes y código simultáneamente.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Generación múltiple&lt;/strong&gt;: Creación de hasta 5 pantallas conectadas a la vez.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prototipado interactivo instantáneo&lt;/strong&gt;: Transición entre pantallas generadas y autocompletado de flujos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Soporte de voz&lt;/strong&gt;: Posibilidad de dictar modificaciones o críticas de diseño en tiempo real.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DESIGN.md&lt;/strong&gt;: Archivo markdown exportable que estandariza las reglas de diseño para ser consumidas por otros agentes o proyectos.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="exportacion-y-codigo"&gt;Exportación y código&lt;a class="headerlink" href="#exportacion-y-codigo" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Una de las ventajas es que Stitch no genera solo imágenes, sino código funcional exportable:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Frameworks soportados&lt;/strong&gt;: HTML/CSS, Tailwind, Vue.js, Angular, Flutter y SwiftUI.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integración con Figma&lt;/strong&gt;: Permite exportar los diseños directamente a Figma para su refinamiento.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MCP y Google AI Studio&lt;/strong&gt;: Facilita la conexión de Stitch con entornos de código y agentes como Antigravity.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="precios-y-limites"&gt;Precios y límites&lt;a class="headerlink" href="#precios-y-limites" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Actualmente Stitch opera dentro de Google Labs y es de uso &lt;strong&gt;gratuito&lt;/strong&gt;, pero con cuotas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;350 generaciones&lt;/strong&gt; mensuales estándar (Gemini 3 Flash)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;200 generaciones&lt;/strong&gt; mensuales experimentales o Pro (Gemini 3 Pro)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="consideraciones-frente-a-figma"&gt;Consideraciones frente a Figma&lt;a class="headerlink" href="#consideraciones-frente-a-figma" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cuándo usar Stitch&lt;/strong&gt;: Para prototipado ultra rápido, MVPs, flujos estándar (CRUDs) y para desarrolladores o product managers que necesiten materializar ideas sin dominar una herramienta de diseño.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cuándo usar Figma&lt;/strong&gt;: Para sistemas de diseño corporativos, identidades visuales complejas, colaboración multijugador masiva en tiempo real y flujos de trabajo fuertemente integrados.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Aunque la irrupción de Stitch amenaza el monopolio del diseño basado en "arrastrar rectángulos" para interfaces estándar, la exportación a Figma permite usarlos conjuntamente.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Fuentes&lt;/em&gt;: 
- &lt;a href="https://blog.google/innovation-and-ai/models-and-research/google-labs/stitch-ai-ui-design/"&gt;Google Blog: Design UI using AI&lt;/a&gt;
- &lt;a href="https://www.nxcode.io/resources/news/google-stitch-vs-figma-ai-design-comparison-2026"&gt;NxCode: Google Stitch vs Figma&lt;/a&gt;&lt;/p&gt;</content><category term="Herramientas"></category><category term="ia"></category><category term="ui"></category><category term="design"></category><category term="gemini"></category><category term="prototipado"></category></entry><entry><title>rbw sync: missing field `access_token`</title><link href="https://pablocaro.es/rbw-sync-access-token" rel="alternate"></link><published>2026-04-11T19:30:00+02:00</published><updated>2026-04-11T19:30:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-04-11:/rbw-sync-access-token</id><summary type="html">&lt;p&gt;Error conocido de rbw al sincronizar con Bitwarden o Vaultwarden: "failed to parse JSON: missing field access_token". La solución es limpiar el caché local.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Hoy me he encontrado con este error al intentar sincronizar mi vault de Bitwarden con &lt;strong&gt;rbw&lt;/strong&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;rbw&lt;span class="w"&gt; &lt;/span&gt;sync
rbw&lt;span class="w"&gt; &lt;/span&gt;sync:&lt;span class="w"&gt; &lt;/span&gt;failed&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;sync&lt;span class="w"&gt; &lt;/span&gt;database&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;server:&lt;span class="w"&gt; &lt;/span&gt;failed&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;parse&lt;span class="w"&gt; &lt;/span&gt;JSON:&lt;span class="w"&gt; &lt;/span&gt;missing&lt;span class="w"&gt; &lt;/span&gt;field&lt;span class="w"&gt; &lt;/span&gt;access_token&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;line&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;column&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;393&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="Issue #202 en el repo de rbw" src="https://pablocaro.es/images/rbw-sync-access-token.png"&gt;&lt;/p&gt;
&lt;h2 id="diagnostico"&gt;Diagnóstico&lt;a class="headerlink" href="#diagnostico" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Es un bug conocido de rbw que existe desde 2020 (&lt;a href="https://github.com/doy/rbw/issues/32"&gt;#32&lt;/a&gt;) y sigue abierto. Ocurre tanto con Bitwarden oficial como con Vaultwarden. No es un problema de la versión de Vaultwarden.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Causa:&lt;/strong&gt; rbw guarda un token de sesión localmente que se corrompe o expira, y al intentar sincronizar, el servidor devuelve una respuesta JSON sin el campo &lt;code&gt;access_token&lt;/code&gt; que rbw espera. El caché local de rbw queda en un estado inválido.&lt;/p&gt;
&lt;h2 id="solucion"&gt;Solución&lt;a class="headerlink" href="#solucion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;rbw&lt;span class="w"&gt; &lt;/span&gt;purge
rbw&lt;span class="w"&gt; &lt;/span&gt;sync
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;rbw purge&lt;/code&gt; limpia el caché local de la vault. Después, &lt;code&gt;rbw sync&lt;/code&gt; te pedirá la master password y volverá a descargar todo desde cero.&lt;/p&gt;
&lt;h2 id="contexto-del-bug"&gt;Contexto del bug&lt;a class="headerlink" href="#contexto-del-bug" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Abierto desde diciembre de 2020 (&lt;a href="https://github.com/doy/rbw/issues/32"&gt;#32&lt;/a&gt;), 19 👍, sin fix&lt;/li&gt;
&lt;li&gt;Reabierto en agosto 2024 (&lt;a href="https://github.com/doy/rbw/issues/202"&gt;#202&lt;/a&gt;) con la misma sintomatía&lt;/li&gt;
&lt;li&gt;El maintainer (doy) no ha podido reproducirlo → sigue sin arreglarse&lt;/li&gt;
&lt;li&gt;Aparece con más frecuencia tras añadir entradas nuevas (TOTP, SSH keys) desde otro cliente (browser extension, web vault) y luego intentar sync desde rbw&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; &lt;code&gt;rbw purge &amp;amp;&amp;amp; rbw sync&lt;/code&gt; y debería funcionar.&lt;/p&gt;</content><category term="Linux"></category><category term="bitwarden"></category><category term="cli"></category><category term="bug"></category><category term="vaultwarden"></category></entry><entry><title>rbw-env: Exportar variables de entorno desde Bitwarden</title><link href="https://pablocaro.es/rbw-env" rel="alternate"></link><published>2026-03-26T20:55:00+01:00</published><updated>2026-03-26T20:55:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-03-26:/rbw-env</id><summary type="html">&lt;p&gt;Cómo usar rbw para exportar variables de entorno desde Bitwarden, útil para establecer API keys de forma segura sin almacenarlas en el disco.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Siguiendo con la saga de &lt;a href="https://pablocaro.es/kitty-rbw-kitten"&gt;kitty-rbw&lt;/a&gt;, hoy presento otro script del mismo proyecto: &lt;strong&gt;rbw-env&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Selección de carpeta en rbw-env" src="https://pablocaro.es/images/rbw-env-example.png"&gt;&lt;/p&gt;
&lt;h2 id="el-problema"&gt;El problema&lt;a class="headerlink" href="#el-problema" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Uso &lt;strong&gt;pi-agent&lt;/strong&gt; con diferentes modelos de IA (Claude, OpenAI, Gemini...) y cada uno requiere su propia &lt;code&gt;API_KEY&lt;/code&gt;. Mantener estas claves en el disco es un riesgo de seguridad, y tener que introducirlas cada vez es tedioso.&lt;/p&gt;
&lt;h2 id="la-solucion-rbw-env"&gt;La solución: rbw-env&lt;a class="headerlink" href="#la-solucion-rbw-env" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;El script permite exportar entradas de Bitwarden como variables de entorno. La idea es sencilla pero efectiva:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Seleccionas una carpeta&lt;/strong&gt; con &lt;code&gt;fzf&lt;/code&gt; (por ejemplo, "ai-providers")&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;rbw-env exporta cada entrada&lt;/strong&gt; como &lt;code&gt;export VARIABLE='valor'&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;El archivo temporal se borra solo&lt;/strong&gt; después de hacer &lt;code&gt;source&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="uso"&gt;Uso&lt;a class="headerlink" href="#uso" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Seleccionar carpeta interactivamente&lt;/span&gt;
kitty&lt;span class="w"&gt; &lt;/span&gt;@&lt;span class="w"&gt; &lt;/span&gt;kitten&lt;span class="w"&gt; &lt;/span&gt;kitty_rbw/rbw_env.py

&lt;span class="c1"&gt;# O pasar la carpeta como argumento&lt;/span&gt;
kitty&lt;span class="w"&gt; &lt;/span&gt;@&lt;span class="w"&gt; &lt;/span&gt;kitten&lt;span class="w"&gt; &lt;/span&gt;kitty_rbw/rbw_env.py&lt;span class="w"&gt; &lt;/span&gt;ai-providers
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;El comando crea un archivo temporal en &lt;code&gt;/tmp/kitty_rbw_env_*&lt;/code&gt; y envía el comando &lt;code&gt;source&lt;/code&gt; a la ventana:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/tmp/kitty_rbw_env_qfdhyh_b
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;La última línea del archivo temporal es el borrado del propio archivo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;rm&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;/tmp/kitty_rbw_env_qfdhyh_b
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;La idea es no hacer los export directamente en la terminal para que no se vean por pantalla (a veces grabo tutoriales por ejemplo)&lt;/p&gt;
&lt;h2 id="estructura-en-bitwarden"&gt;Estructura en Bitwarden&lt;a class="headerlink" href="#estructura-en-bitwarden" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Cada entrada en la carpeta exportada usa:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Usuario&lt;/strong&gt;: nombre de la variable (ej: &lt;code&gt;ANTHROPIC_API_KEY&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contraseña&lt;/strong&gt;: valor de la variable (ej: &lt;code&gt;sk-ant-...&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nombre&lt;/strong&gt;: descripción opcional&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Si alguna entrada tiene usuario o nombre igual a &lt;code&gt;NOEXPORT&lt;/code&gt;, se ignora (útil para notas).&lt;/p&gt;
&lt;h2 id="integracion-con-kitty-rbw"&gt;Integración con kitty-rbw&lt;a class="headerlink" href="#integracion-con-kitty-rbw" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;En la &lt;a href="https://pablocaro.es/kitty-rbw-kitten"&gt;entrada anterior&lt;/a&gt; explico cómo instalar kitty-rbw. Con &lt;code&gt;Ctrl+Alt+Shift+b&lt;/code&gt; tengo el selector de carpetas para exportar las variables de entorno.&lt;/p&gt;</content><category term="Herramientas"></category><category term="bitwarden"></category><category term="cli"></category><category term="entorno"></category><category term="productividad"></category><category term="seguridad"></category></entry><entry><title>Crit: El bucle de revisión para Agentes</title><link href="https://pablocaro.es/crit-review-loop-agents" rel="alternate"></link><published>2026-03-22T14:49:00+01:00</published><updated>2026-03-22T14:49:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-03-22:/crit-review-loop-agents</id><summary type="html">&lt;p&gt;Crit es una herramienta diseñada para cerrar el bucle de revisión de código generado por agentes de IA, proporcionando una interfaz web intuitiva y eficiente.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Revisar lo que un agente de IA escribe directamente en la terminal es, a menudo, una pesadilla. No puedes señalar una línea concreta, dar feedback estructurado y, cuando el agente actualiza el archivo, te toca volver a leerlo todo para ver qué ha cambiado realmente. &lt;/p&gt;
&lt;p&gt;Ahí es donde entra &lt;strong&gt;Crit&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Crit review UI" src="/images/crit-demo.png"&gt;&lt;/p&gt;
&lt;p&gt;Crit abre los archivos generados o modificados por tu agente en el navegador con una interfaz al estilo GitHub. Puedes dejar comentarios en líneas específicas, sugerencias de cambio y, lo mejor de todo, ver los diffs entre las distintas rondas de iteración. &lt;/p&gt;
&lt;p&gt;Lo que más me ha gustado es su enfoque &lt;strong&gt;multi-agente&lt;/strong&gt; y su &lt;strong&gt;usabilidad&lt;/strong&gt;. No importa si usas Claude Code, Cursor, Aider o, como en mi caso, &lt;strong&gt;OpenCode&lt;/strong&gt;. Crit se integra de maravilla.&lt;/p&gt;
&lt;h3 id="instalacion-y-configuracion"&gt;Instalación y configuración&lt;a class="headerlink" href="#instalacion-y-configuracion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;La instalación es tan sencilla como:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;gah&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;tomasz-tomczyk/crit
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Una vez instalado, integrarlo con OpenCode es cuestión de un comando:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;crit&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;opencode
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Esto instala los comandos y las &lt;em&gt;skills&lt;/em&gt; necesarias para que los agentes puedan usar Crit. Al ejecutar &lt;code&gt;/crit&lt;/code&gt; en OpenCode, se inicia el bucle de revisión: el agente propone un plan o cambios, Crit abre la interfaz web para que tú des el visto bueno o corrijas, y el agente recibe tu feedback estructurado en un archivo &lt;code&gt;.crit.json&lt;/code&gt; para actuar en consecuencia.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nota para mi yo del futuro:&lt;/strong&gt; Si usas &lt;a href="https://shittycodingagent.ai/"&gt;pi-agent&lt;/a&gt; en vez de OpenCode, puedes copiar el comando de OpenCode como prompt y copiar el skill de crit tal cual — funciona perfectamente. De hecho, esta misma anotación ha sido revisada usando pi-agent con Crit.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="por-que-usarlo"&gt;Por qué usarlo&lt;a class="headerlink" href="#por-que-usarlo" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Interfaz Web, no TUI&lt;/strong&gt;: Renderizado de Markdown y diffs visuales persistentes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Diffs entre rondas&lt;/strong&gt;: Ves exactamente qué ha cambiado desde tu último comentario.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agnóstico al agente&lt;/strong&gt;: Funciona con cualquier herramienta, permitiendo un flujo de trabajo consistente.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sugerencias directas&lt;/strong&gt;: Puedes insertar sugerencias que el agente puede aplicar tal cual.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Por ejemplo, el propio cambio que subsanó el problema de la coma de esta misma anotación:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Ejemplo de revisión con Crit" src="/images/crit-review-example.png"&gt;&lt;/p&gt;
&lt;p&gt;Es, sin duda, la pieza que faltaba para que el trabajo con agentes sea realmente colaborativo y no una sucesión de "copy-pasted" y revisiones manuales tediosas.&lt;/p&gt;</content><category term="Programación"></category><category term="ia"></category><category term="agents"></category><category term="review"></category><category term="opencode"></category><category term="productividad"></category></entry><entry><title>Joyas ocultas en pi-powerline-footer</title><link href="https://pablocaro.es/powerline-footer-hidden-gems" rel="alternate"></link><published>2026-03-22T00:00:00+01:00</published><updated>2026-03-22T00:00:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-03-22:/powerline-footer-hidden-gems</id><summary type="html">&lt;p&gt;Dos características del plugin pi-powerline-footer que van más allá del típico footer con información del modelo.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="Captura de pantalla del plugin pi-powerline-footer" src="/images/pi-powerline-footer.png"&gt;&lt;/p&gt;
&lt;p&gt;Llevo usando &lt;a href="https://github.com/nicobailon/pi-powerline-footer"&gt;pi-powerline-footer&lt;/a&gt; un tiempo, principalmente por su funcionalidad principal: ese footer con información útil del modelo, contexto, tokens y coste. Pero hace poco descubrí dos features que han cambiado cómo trabajo con pi en el día a día.&lt;/p&gt;
&lt;h2 id="editor-stash-tu-portapapeles-temporal-integrado"&gt;Editor Stash: tu portapapeles temporal integrado&lt;a class="headerlink" href="#editor-stash-tu-portapapeles-temporal-integrado" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Con &lt;code&gt;Alt+S&lt;/code&gt; puedes guardar el contenido actual del editor, limpiarlo, escribir algo rápido (por ejemplo, una pregunta corta a otro agente), y cuando termine, tu texto original vuelve automáticamente.&lt;/p&gt;
&lt;p&gt;Es perfecto cuando estás en medio de una tarea compleja y necesitas hacer una consulta rápida sin perder el contexto. O cuando quieres guardar un bloque de código que vas a reutilizar después.&lt;/p&gt;
&lt;p&gt;Además, tienes atajos para copiar (&lt;code&gt;ctrl+alt+c&lt;/code&gt;) y cortar (&lt;code&gt;ctrl+alt+x&lt;/code&gt;) el contenido completo del editor, y un historial de stashs accesible con &lt;code&gt;ctrl+alt+h&lt;/code&gt; que persiste entre sesiones.&lt;/p&gt;
&lt;h2 id="model-profiles-navegacion-rapida-entre-configuraciones-de-modelo"&gt;Model Profiles: navegación rápida entre configuraciones de modelo&lt;a class="headerlink" href="#model-profiles-navegacion-rapida-entre-configuraciones-de-modelo" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Esta es la feature que más uso ahora mismo. Con varios providers activos —principalmente &lt;a href="https://openrouter.ai/"&gt;OpenRouter&lt;/a&gt; (con sus muchísimos modelos), &lt;a href="https://ai.google.dev/"&gt;Google Gemini&lt;/a&gt; y &lt;a href="https://opencode.ai/"&gt;OpenCode&lt;/a&gt;— cambiar entre modelos era un lío.&lt;/p&gt;
&lt;p&gt;Los Model Profiles guardan combinaciones de modelo + nivel de thinking. Los defino en &lt;code&gt;~/.pi/agent/settings.json&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;modelProfiles&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;model&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;google/gemini-3-pro&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;thinking&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;high&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;label&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Gemini Deep&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;model&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anthropic/claude-opus-4-5&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;thinking&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;xhigh&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;label&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Opus Ultra&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;model&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;openai/codex-5.3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;thinking&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;low&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;label&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Codex Fast&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Con &lt;code&gt;alt+shift+tab&lt;/code&gt; ciclo entre perfiles y con &lt;code&gt;ctrl+alt+m&lt;/code&gt; abro un selector visual. El cambio es instantáneo —no más menúes interminables buscando el modelo exacto.&lt;/p&gt;
&lt;p&gt;Es especialmente útil con OpenRouter, donde la lista de modelos es enorme. En lugar de navegar por todos, tengo mis 3-4 configuraciones preferidas y alterno entre ellas según la tarea.&lt;/p&gt;
&lt;h2 id="el-footer-es-solo-el-principio"&gt;El footer es solo el principio&lt;a class="headerlink" href="#el-footer-es-solo-el-principio" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;La documentación del plugin está llena de pequeñas utilidades: &lt;a href="https://github.com/nicobailon/pi-powerline-footer?tab=readme-ov-file#working-vibes"&gt;Working Vibes&lt;/a&gt; para mensajes de carga temáticos, atajos de teclado configurables, integración con git... Pero estas dos features que he descrito son las que más impacto tienen en mi flujo de trabajo diario.&lt;/p&gt;
&lt;p&gt;Si aún no lo has probado: &lt;code&gt;pi install npm:pi-powerline-footer&lt;/code&gt; y luego &lt;code&gt;/reload&lt;/code&gt;.&lt;/p&gt;</content><category term="IA"></category><category term="pi"></category><category term="terminal"></category><category term="productividad"></category></entry><entry><title>Agent Browser: Navegación para Agentes</title><link href="https://pablocaro.es/agent-browser-navegacion-agentes" rel="alternate"></link><published>2026-03-18T09:00:00+01:00</published><updated>2026-03-18T09:00:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-03-18:/agent-browser-navegacion-agentes</id><summary type="html">&lt;p&gt;&lt;code&gt;agent-browser&lt;/code&gt; es una herramienta de Vercel que permite a los agentes de IA navegar por la web y realizar acciones como lo haría un humano, integrándose fácilmente con frameworks de agentes.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;code&gt;agent-browser&lt;/code&gt; (https://github.com/vercel-labs/agent-browser) me permite que los agentes de IA puedan navegar por la web y realizar acciones complejas como si fueran un humano.&lt;/p&gt;
&lt;p&gt;Lo tengo integrado en una skill y lo uso habitualmente con &lt;code&gt;pi agent&lt;/code&gt; para automatizar tareas que requieren interacción con el navegador. Es brutal lo que se puede conseguir cuando el agente puede "ver" y "actuar" en la web directamente.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Agent Browser Screenshot" src="/images/agent-browser.png"&gt;&lt;/p&gt;</content><category term="IA"></category><category term="agent-browser"></category><category term="ia"></category><category term="agents"></category><category term="automation"></category><category term="llm"></category><category term="pi-agent"></category><category term="pi"></category></entry><entry><title>Tailscale Aperture: Gateway de IA sin distribuir API keys</title><link href="https://pablocaro.es/tailscale-aperture-gateway-ia-seguro" rel="alternate"></link><published>2026-03-16T10:00:00+01:00</published><updated>2026-03-16T10:00:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-03-16:/tailscale-aperture-gateway-ia-seguro</id><summary type="html">&lt;p&gt;Aperture de Tailscale centraliza el acceso a LLMs eliminando la necesidad de distribuir API keys a desarrolladores, usando la identidad de Tailscale para autenticación.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Una de las cosas que me quitó el sueño cuando empecé a usar LLMs en el trabajo fue: &lt;strong&gt;¿dónde guardo las API keys?&lt;/strong&gt; En repositorios privados, en &lt;code&gt;.env&lt;/code&gt; files que se me olvidaban gitignorear, en notas del móvil... Un desastre.&lt;/p&gt;
&lt;p&gt;Tailscale anunció recientemente &lt;a href="https://tailscale.com/docs/features/aperture"&gt;Aperture&lt;/a&gt;, un AI gateway que corre dentro de tu tailnet y que elimina completamente ese problema.&lt;/p&gt;
&lt;h2 id="el-problema-de-las-api-keys-distribuidas"&gt;El problema de las API keys distribuidas&lt;a class="headerlink" href="#el-problema-de-las-api-keys-distribuidas" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Cuando los desarrolladores necesitan acceso a OpenAI, Anthropic, o cualquier otro proveedor de LLM, la práctica habitual ha sido:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Crear una API key&lt;/li&gt;
&lt;li&gt;Compartirla por Slack, email, o pegada en un repo privado&lt;/li&gt;
&lt;li&gt;Rezar porque no se filtrre&lt;/li&gt;
&lt;li&gt;Cuando alguien se va, rotar manualmente todas las keys&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Esto escama mal. Una organización con 50 desarrolladores puede tener cientos de keys flotando en documentos, scripts de CI/CD, y machines de desarrolladores que ya no están en la empresa.&lt;/p&gt;
&lt;h2 id="aperture-centralizacion-con-identidad-real"&gt;Aperture: centralización con identidad real&lt;a class="headerlink" href="#aperture-centralizacion-con-identidad-real" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;La solución de Tailscale es elegante: &lt;strong&gt;usar la identidad del tailnet para autenticar, no keys distribuidas&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Cómo funciona:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Configuras Aperture en tu tailnet&lt;/strong&gt; con las API keys de tus proveedores (OpenAI, Anthropic, Google, OpenRouter, etc.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Los desarrolladores se conectan a Aperture&lt;/strong&gt; usando su identidad de Tailscale&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Aperture inyecta las credenciales&lt;/strong&gt; del proveedor correspondiente y reenvía la petición&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Telemetría completa&lt;/strong&gt;: tokens usados, costos, sesiones, todo en un dashboard&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Los desarrolladores nunca ven las API keys reales. Aperture las mantiene centralizadas y las inyecta server-side.&lt;/p&gt;
&lt;h2 id="integracion-con-herramientas-existentes"&gt;Integración con herramientas existentes&lt;a class="headerlink" href="#integracion-con-herramientas-existentes" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Lo bueno es que funciona con las herramientas que ya usas:&lt;/p&gt;
&lt;p&gt;-&lt;strong&gt;Claude Code&lt;/strong&gt;: Configuras &lt;code&gt;ANTHROPIC_BASE_URL=http://ai&lt;/code&gt; y listo
- &lt;strong&gt;Codex&lt;/strong&gt;: Base URL a Aperture
- &lt;strong&gt;Gemini CLI, Roo Code, Cline&lt;/strong&gt;: Similar, cambias la URL base
- &lt;strong&gt;Custom apps&lt;/strong&gt;: Funciona con cualquier cliente que use OpenAI-compatible APIs&lt;/p&gt;
&lt;p&gt;Desde el punto de vista del cliente, Aperture se ve como el proveedor mismo. Detecta el modelo en el request body y enruta al proveedor correcto.&lt;/p&gt;
&lt;h2 id="pi-ts-aperture-plugin-para-pi"&gt;pi-ts-aperture: Plugin para Pi&lt;a class="headerlink" href="#pi-ts-aperture-plugin-para-pi" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Hay un plugin oficial de Pi para routearte automáticamente a través de Aperture:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pi&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;npm:@aliou/pi-ts-aperture
/aperture:setup
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;El asistente te pide:
1. URL de tu Aperture (ej: &lt;code&gt;ai.your-tailnet.ts.net&lt;/code&gt;)
2. Qué proveedores routear&lt;/p&gt;
&lt;p&gt;Guarda la configuración en &lt;code&gt;~/.pi/agent/extensions/aperture.json&lt;/code&gt; y modifica los providers para usar Aperture como proxy.&lt;/p&gt;
&lt;h2 id="visibilidad-y-control"&gt;Visibilidad y control&lt;a class="headerlink" href="#visibilidad-y-control" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;El dashboardde Aperture te da:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tokens por modelo y usuario&lt;/strong&gt;: ¿Cuánto gasta cada persona?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sesiones agrupadas&lt;/strong&gt;: Una sesión de Claude Code puede tener 50 requests; las ves como una unidad coherente&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tool use&lt;/strong&gt;: Qué herramientas se están invocando y con qué frecuencia&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Adopción&lt;/strong&gt;: Quién está usando qué, y quién probó una vez y no volvió&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Export a S3&lt;/strong&gt;: Para integrar con tu SIEM habitual&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Para equipos de plataforma o seguridad que necesitan auditoria, esto es oro.&lt;/p&gt;
&lt;h2 id="requisitos"&gt;Requisitos&lt;a class="headerlink" href="#requisitos" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Un tailnet de Tailscale (fundamental, toda la autenticación depende de la identidad de Tailscale)&lt;/li&gt;
&lt;li&gt;API keys de los proveedores que quieras usar&lt;/li&gt;
&lt;li&gt;El dispositivo que corre Pi debe estar en el tailnet&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="en-resumen"&gt;En resumen&lt;a class="headerlink" href="#en-resumen" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Si tu organización está adoptando herramientas de IA y no quieres lidiar con API keys circulando por todos lados, Aperture es una solución limpia. Centraliza credenciales, da visibilidad real del uso, y usa la infraestructura de identidad que ya tienes con Tailscale.&lt;/p&gt;
&lt;p&gt;Está en alpha todaví a, pero disponible gratuitodurante el periodo de testing. Vale la pena probarlo si ya usas Tailscale.&lt;/p&gt;</content><category term="Herramientas"></category><category term="tailscale"></category><category term="seguridad"></category><category term="ia"></category><category term="api"></category><category term="gateway"></category></entry><entry><title>sem: Control de versiones semántico</title><link href="https://pablocaro.es/sem-control-de-versiones-semantico" rel="alternate"></link><published>2026-03-15T14:11:00+01:00</published><updated>2026-03-15T14:11:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-03-15:/sem-control-de-versiones-semantico</id><summary type="html">&lt;p&gt;Descubriendo sem, una herramienta CLI que entiende la semántica de tu código y te dice qué entidades cambiaron, no solo qué líneas.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Me encontré con &lt;a href="https://github.com/Ataraxy-Labs/sem"&gt;sem&lt;/a&gt; hace unas semanas y me parece una idea brutal. Es un CLI que va más allá del diff tradicional línea a línea y entiende la estructura real del código.&lt;/p&gt;
&lt;h2 id="la-diferencia-entre-lineas-y-entidades"&gt;La diferencia entre líneas y entidades&lt;a class="headerlink" href="#la-diferencia-entre-lineas-y-entidades" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Cuando haces un &lt;code&gt;git diff&lt;/code&gt; tradicional, ves algo como:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gd"&gt;- const oldAuth = function(user) { ... }&lt;/span&gt;
&lt;span class="gi"&gt;+ const newAuth = async function(user) { ... }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Pero con &lt;code&gt;sem diff&lt;/code&gt; ves:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;⊕&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;validateToken&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;added&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="err"&gt;∆&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;authenticateUser&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;modified&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="err"&gt;⊖&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;legacyAuth&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;deleted&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;No líneas añadidas o eliminadas, sino &lt;strong&gt;entidades&lt;/strong&gt; que cambiaron: funciones, clases, métodos. Mucho más útil para entender qué pasó realmente en el PR.&lt;/p&gt;
&lt;h2 id="comandos-disponibles"&gt;Comandos disponibles&lt;a class="headerlink" href="#comandos-disponibles" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;sem diff&lt;/code&gt;&lt;/strong&gt;: Muestra qué entidades cambiaron&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;sem impact &amp;lt;entidad&amp;gt;&lt;/code&gt;&lt;/strong&gt;: Analiza qué se rompería si cambias esa entidad (dependencias entrantes)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;sem blame&lt;/code&gt;&lt;/strong&gt;: Blame a nivel de entidad — quién cambió cada función/clase&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;sem graph&lt;/code&gt;&lt;/strong&gt;: Muestra el grafo de dependencias entre entidades&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="ejemplo-practico"&gt;Ejemplo práctico&lt;a class="headerlink" href="#ejemplo-practico" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Ver qué entidades cambiaron en working directory&lt;/span&gt;
sem&lt;span class="w"&gt; &lt;/span&gt;diff

&lt;span class="c1"&gt;# Ver solo cambios staged&lt;/span&gt;
sem&lt;span class="w"&gt; &lt;/span&gt;diff&lt;span class="w"&gt; &lt;/span&gt;--staged

&lt;span class="c1"&gt;# Ver cambios de un commit específico&lt;/span&gt;
sem&lt;span class="w"&gt; &lt;/span&gt;diff&lt;span class="w"&gt; &lt;/span&gt;--commit&lt;span class="w"&gt; &lt;/span&gt;abc1234

&lt;span class="c1"&gt;# Ver impacto de cambiar una función&lt;/span&gt;
sem&lt;span class="w"&gt; &lt;/span&gt;impact&lt;span class="w"&gt; &lt;/span&gt;validateToken

&lt;span class="c1"&gt;# Salida JSON para pipelines CI&lt;/span&gt;
sem&lt;span class="w"&gt; &lt;/span&gt;diff&lt;span class="w"&gt; &lt;/span&gt;--format&lt;span class="w"&gt; &lt;/span&gt;json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="instalacion"&gt;Instalación&lt;a class="headerlink" href="#instalacion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# macOS&lt;/span&gt;
brew&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;sem-cli

&lt;span class="c1"&gt;# Linux (mi caso)&lt;/span&gt;
brew&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;sem-cli
&lt;span class="c1"&gt;# o descargar binario de GitHub Releases&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;O build from source si tienes Rust instalado:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://github.com/Ataraxy-Labs/sem
&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sem/crates
cargo&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;--path&lt;span class="w"&gt; &lt;/span&gt;sem-cli
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="why-esto-es-util"&gt;Why esto es útil&lt;a class="headerlink" href="#why-esto-es-util" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Para mí, principalmente dos casos:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Code reviews más rápidas&lt;/strong&gt; — Entiendes el impacto real de un cambio en segundos&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Refactors seguros&lt;/strong&gt; — Antes de renombrar una función crítica, &lt;code&gt;sem impact&lt;/code&gt; te dice qué puede romperse&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Soporta 20 lenguajes (incluyendo Python, Rust, Go, TypeScript, Java, C++, etc.) usando tree-sitter para parsear el código.&lt;/p&gt;
&lt;p&gt;Una herramienta que va directo a mi toolkit diario.&lt;/p&gt;</content><category term="Programación"></category><category term="cli"></category><category term="versionado"></category><category term="rust"></category><category term="herramientas"></category></entry><entry><title>os.click: Central de descarga de ISOs de sistemas operativos</title><link href="https://pablocaro.es/os-click-central-descarga-isos" rel="alternate"></link><published>2026-03-12T10:00:00+01:00</published><updated>2026-03-12T10:00:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-03-12:/os-click-central-descarga-isos</id><summary type="html">&lt;p&gt;Recordatorio sobre os.click para descargar ISOs oficiales rápidamente.&lt;/p&gt;</summary><content type="html">&lt;p&gt;La próxima vez que necesite una ISO de Windows o alguna distro Linux y no quiera estar media hora saltando entre menús de Microsoft o buscando el "mirror" correcto, entrar directamente en &lt;strong&gt;&lt;a href="https://os.click/"&gt;os.click&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Me ha pasado hoy buscando una ISO de Windows 11 para una VM; nunca me acuerdo de la URL exacta y os.click me ha salvado el rato. Lo bueno que tiene:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Captura de os.click" src="https://pablocaro.es/images/os-click.png"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Enlaces directos&lt;/strong&gt;: No alojan nada, te mandan al servidor oficial (Microsoft, Canonical, etc.).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Limpio&lt;/strong&gt;: Sin anuncios raros ni botones de "Download" falsos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Variedad&lt;/strong&gt;: Tienen Windows (10, 11, Server) y casi cualquier distro Linux que use habitualmente.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Básicamente es un índice bien organizado. Para no olvidarlo.&lt;/p&gt;</content><category term="Programación"></category><category term="linux"></category><category term="windows"></category><category term="iso"></category><category term="sistemas operativos"></category><category term="herramientas"></category></entry><entry><title>De net-tools a iproute2: equivalents de comandos</title><link href="https://pablocaro.es/net-tools-to-iproute" rel="alternate"></link><published>2026-03-06T11:30:00+01:00</published><updated>2026-03-06T11:30:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-03-06:/net-tools-to-iproute</id><summary type="html">&lt;p&gt;Guía de equivalencias entre los comandos clásicos de net-tools (ifconfig, route, arp) y los modernos de iproute2 (ip).&lt;/p&gt;</summary><content type="html">&lt;p&gt;Más de 30 años con Linux, y es difícil cambiar a lo que ya estás acostumbrado. En mi caso, siempre he usado &lt;strong&gt;ifconfig&lt;/strong&gt;, &lt;strong&gt;arp&lt;/strong&gt;, &lt;strong&gt;route&lt;/strong&gt; y &lt;strong&gt;netstat&lt;/strong&gt; para configurar y diagnosticar la red. Pero estos comandos pertenecen al paquete &lt;strong&gt;net-tools&lt;/strong&gt;, que lleva años siendo reemplazado por &lt;strong&gt;iproute2&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="net-tools-vs-iproute2"&gt;net-tools vs iproute2&lt;a class="headerlink" href="#net-tools-vs-iproute2" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Tradicionalmente ha habido dos formas de configurar la red en Linux:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;La forma antigua&lt;/strong&gt;: con comandos como &lt;code&gt;ifconfig&lt;/code&gt;, &lt;code&gt;arp&lt;/code&gt;, &lt;code&gt;route&lt;/code&gt; y &lt;code&gt;netstat&lt;/code&gt;, que pertenecen al paquete &lt;em&gt;net-tools&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;La forma nueva&lt;/strong&gt;: mayormente (¡pero no enteramente!) encapsulada en un único comando &lt;code&gt;ip&lt;/code&gt;, que pertenece al paquete &lt;em&gt;iproute2&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Parece que iproute2 fue marcado como "important" en Debian en 2008, lo que significa que cada lanzamiento desde Debian 5 "lenny" (!) ha incluido el comando &lt;code&gt;ip&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;He estado entrenando lentamente a mi cerebro para usar los nuevos comandos, pero a veces olvido algunos. Aquí tienes una tabla de equivalencias:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;net-tools&lt;/th&gt;
&lt;th&gt;iproute2&lt;/th&gt;
&lt;th&gt;Forma corta&lt;/th&gt;
&lt;th&gt;Función&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ifconfig&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ip address&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ip a&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Mostrar/configurar direcciones IP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ifconfig -s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ip link&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ip l&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Mostrar estado de enlaces (up/down, contadores)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;route&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ip route&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ip r&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Mostrar o modificar la tabla de rutas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;arp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ip neigh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ip n&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Mostrar tabla ARP/vecinos&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;netstat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ss&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ss&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Mostrar estadísticas de sockets&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="comparacion-visual"&gt;Comparación visual&lt;a class="headerlink" href="#comparacion-visual" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Aquí puedes ver la diferencia entre usar &lt;code&gt;ip a&lt;/code&gt; y &lt;code&gt;ip -br -c a&lt;/code&gt;:&lt;/p&gt;
&lt;h3 id="ip-a-salida-completa"&gt;&lt;code&gt;ip a&lt;/code&gt; (salida completa)&lt;a class="headerlink" href="#ip-a-salida-completa" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img alt="ip a output" src="/images/net-tools-ip-a.png"&gt;&lt;/p&gt;
&lt;h3 id="ip-br-c-a-salida-resumida-y-coloreada"&gt;&lt;code&gt;ip -br -c a&lt;/code&gt; (salida resumida y coloreada)&lt;a class="headerlink" href="#ip-br-c-a-salida-resumida-y-coloreada" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img alt="ip -br -c a output" src="/images/net-tools-ip-br.png"&gt;&lt;/p&gt;
&lt;h2 id="mi-alias-favorito"&gt;Mi alias favorito&lt;a class="headerlink" href="#mi-alias-favorito" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;También suelo crear un alias para que la salida sea más legible:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ip -br -c&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Esto proporciona una salida mucho más bonita y legible, como puedes ver en la segunda imagen.&lt;/p&gt;
&lt;p&gt;¿Tú sigues usando los comandos antiguos o ya te has pasado a iproute2?&lt;/p&gt;</content><category term="Linux"></category><category term="redes"></category><category term="iproute2"></category><category term="net-tools"></category><category term="terminal"></category></entry><entry><title>Solución al Error de Rodney con Chrome en Wayland</title><link href="https://pablocaro.es/rodney-chrome-wayland-error" rel="alternate"></link><published>2026-03-06T09:00:00+01:00</published><updated>2026-03-06T09:00:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-03-06:/rodney-chrome-wayland-error</id><summary type="html">&lt;p&gt;Cómo resolver el crash de Rodney al iniciar Chrome en sistemas Wayland usando el Chrome del sistema.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://github.com/simonw/rodney/"&gt;Rodney&lt;/a&gt; es una herramienta CLI creada por Simon Willison diseñada específicamente para que agentes de IA puedan controlar una instancia de Chrome mediante el Chrome DevTools Protocol. Es similar a &lt;code&gt;agent-browser&lt;/code&gt; de Vercel, pero usando CDP en lugar de Playwright.&lt;/p&gt;
&lt;h2 id="el-problema"&gt;El Problema&lt;a class="headerlink" href="#el-problema" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Al ejecutar &lt;code&gt;uvx rodney open https://google.es&lt;/code&gt; sin iniciar primero Rodney con &lt;code&gt;rodney start&lt;/code&gt;, el proceso falla porque:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Rodney intenta iniciar Chrome con el Chromium que descarga automáticamente en &lt;code&gt;~/.cache/rod/browser/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Ese Chromium (v128.0.6568.0) tiene un bug conocido con el backend Ozone en sistemas Wayland&lt;/li&gt;
&lt;li&gt;El crash ocurre debido a la combinación de flags &lt;code&gt;--single-process&lt;/code&gt; + &lt;code&gt;--ozone-platform=headless&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;El error en los logs indica: &lt;code&gt;gl_factory_ozone.cc(62): "Expected Mock or Stub, actual:0"&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="la-solucion"&gt;La Solución&lt;a class="headerlink" href="#la-solucion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;La forma más sencilla de solucionar este problema es configurar Rodney para que use el Chrome de tu sistema en lugar del Chromium descargado automáticamente. Esto se hace mediante la variable de entorno &lt;code&gt;ROD_CHROME_BIN&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Añadir a ~/.bashrc o ~/.zshrc&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;export ROD_CHROME_BIN=/usr/bin/google-chrome&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;~/.bashrc
&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/.bashrc
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Después de configurar esto, Rodney funcionará correctamente:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;uvx&lt;span class="w"&gt; &lt;/span&gt;rodney&lt;span class="w"&gt; &lt;/span&gt;start
uvx&lt;span class="w"&gt; &lt;/span&gt;rodney&lt;span class="w"&gt; &lt;/span&gt;open&lt;span class="w"&gt; &lt;/span&gt;https://google.es
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="ejemplo-de-funcionamiento"&gt;Ejemplo de funcionamiento&lt;a class="headerlink" href="#ejemplo-de-funcionamiento" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Una vez configurado, el flujo de trabajo es:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;~/src/anotaciones&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;cv&lt;span class="p"&gt;|&lt;/span&gt;…2&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="m"&gt;09&lt;/span&gt;:39&lt;span class="w"&gt; &lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;uvx&lt;span class="w"&gt; &lt;/span&gt;rodney&lt;span class="w"&gt; &lt;/span&gt;start
Chrome&lt;span class="w"&gt; &lt;/span&gt;started&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;PID&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2981885&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
Debug&lt;span class="w"&gt; &lt;/span&gt;URL:&lt;span class="w"&gt; &lt;/span&gt;ws://127.0.0.1:34373/devtools/browser/319c905a-83a2-47d9-a6e0-8e6a4f2ff830
~/src/anotaciones&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;cv&lt;span class="p"&gt;|&lt;/span&gt;…2&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="m"&gt;09&lt;/span&gt;:39&lt;span class="w"&gt; &lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;uvx&lt;span class="w"&gt; &lt;/span&gt;rodney&lt;span class="w"&gt; &lt;/span&gt;open&lt;span class="w"&gt; &lt;/span&gt;https://google.es
Google
~/src/anotaciones&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;cv&lt;span class="p"&gt;|&lt;/span&gt;…2&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="m"&gt;09&lt;/span&gt;:39&lt;span class="w"&gt; &lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;uvx&lt;span class="w"&gt; &lt;/span&gt;rodney&lt;span class="w"&gt; &lt;/span&gt;screenshot
screenshot.png
~/src/anotaciones&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;cv&lt;span class="p"&gt;|&lt;/span&gt;…3&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="Ejemplo de Rodney funcionando" src="https://pablocaro.es/images/rodney_example.png"&gt;&lt;/p&gt;
&lt;h2 id="por-que-pasa-esto"&gt;¿Por qué pasa esto?&lt;a class="headerlink" href="#por-que-pasa-esto" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;El Chromium que Rodney descarga automáticamente tiene un bug conocido con el backend Ozone en sistemas Linux que usan Wayland (la alternativa a X11). Cuando se usan ciertas combinaciones de flags de Chrome, el proceso falla porque el sistema gráfico no está configurado como "Mock" o "Stub" (que es lo que esperan las pruebas).&lt;/p&gt;
&lt;p&gt;Usar el Chrome del sistema evita este problema porque:
- El Chrome del sistema está compilado con soporte completo para Wayland
- Generalmente es una versión más estable y actualizada
- No tiene los mismos flags de desarrollo que causan conflictos&lt;/p&gt;
&lt;h2 id="enlace"&gt;Enlace&lt;a class="headerlink" href="#enlace" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/simonw/rodney/"&gt;Repositorio de Rodney en GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="Herramientas"></category><category term="rodney"></category><category term="chrome"></category><category term="wayland"></category><category term="linux"></category><category term="debugging"></category></entry><entry><title>Graba y Comparte tus Sesiones de Terminal con asciinema</title><link href="https://pablocaro.es/asciinema-terminal-recorder" rel="alternate"></link><published>2026-02-27T12:00:00+01:00</published><updated>2026-02-27T12:00:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-02-27:/asciinema-terminal-recorder</id><summary type="html">&lt;p&gt;Descubre asciinema, la herramienta perfecta para grabar tus sesiones de terminal de forma ligera, reproducible y fácil de compartir.&lt;/p&gt;</summary><content type="html">&lt;p&gt;A menudo necesitamos compartir lo que sucede en nuestra terminal, ya sea para un tutorial, demostrar un error o simplemente documentar un proceso. Aunque existen herramientas para grabar vídeo tradicional, &lt;strong&gt;asciinema&lt;/strong&gt; ofrece una alternativa mucho más potente y ligera.&lt;/p&gt;
&lt;h2 id="que-es-asciinema"&gt;¿Qué es asciinema?&lt;a class="headerlink" href="#que-es-asciinema" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/asciinema/asciinema"&gt;asciinema&lt;/a&gt; no graba vídeo en el sentido tradicional (píxeles). En su lugar, captura el flujo de texto de la terminal junto con los tiempos exactos. Esto tiene varias ventajas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Archivos diminutos&lt;/strong&gt;: En lugar de megabytes de vídeo, obtienes archivos de texto muy pequeños.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Texto seleccionable&lt;/strong&gt;: Al reproducir una sesión en la web, puedes copiar y pegar el código directamente.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Calidad perfecta&lt;/strong&gt;: No hay pérdida de resolución ni artefactos de compresión.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="instalacion-y-ayuda"&gt;Instalación y Ayuda&lt;a class="headerlink" href="#instalacion-y-ayuda" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Ya tengo &lt;code&gt;asciinema&lt;/code&gt; instalado en mi sistema. Aquí puedes ver el resumen de comandos disponibles:## Ejemplo interactivo&lt;/p&gt;
&lt;p&gt;A continuación puedes ver una pequeña demostración de &lt;code&gt;asciinema&lt;/code&gt; en acción, ejecutando el comando &lt;code&gt;cowsay Muuuuu&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;link rel="stylesheet" type="text/css" href="/theme/asciinema-player/asciinema-player.css" /&gt;&lt;/p&gt;
&lt;div id="demo-player"&gt;&lt;/div&gt;
&lt;script src="/theme/asciinema-player/asciinema-player.js"&gt;&lt;/script&gt;
&lt;script&gt;
    window.addEventListener('load', function() {
        AsciinemaPlayer.create('/images/demo.cast', document.getElementById('demo-player'), {
            cols: 80,
            rows: 10,
            autoPlay: true,
            loop: true
        });
    });
&lt;/script&gt;

&lt;h2 id="comandos-basicos"&gt;Comandos básicos&lt;a class="headerlink" href="#comandos-basicos" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Para empezar a grabar, simplemente ejecuta:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;asciinema&lt;span class="w"&gt; &lt;/span&gt;rec&lt;span class="w"&gt; &lt;/span&gt;demo.cast
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Esto comenzará a grabar todo lo que ocurra en tu sesión actual de terminal. Cuando termines, simplemente presiona &lt;code&gt;Ctrl-D&lt;/code&gt; o escribe &lt;code&gt;exit&lt;/code&gt;. El resultado se guardará en el archivo &lt;code&gt;demo.cast&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Para reproducirlo localmente:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;asciinema&lt;span class="w"&gt; &lt;/span&gt;play&lt;span class="w"&gt; &lt;/span&gt;demo.cast
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y si quieres compartirlo con el mundo, puedes subirlo a su servicio de alojamiento gratuito:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;asciinema&lt;span class="w"&gt; &lt;/span&gt;upload&lt;span class="w"&gt; &lt;/span&gt;demo.cast
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="por-que-usarlo"&gt;¿Por qué usarlo?&lt;a class="headerlink" href="#por-que-usarlo" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Es ideal para desarrolladores que escriben blogs técnicos (como este), ya que permite insertar las grabaciones directamente en el navegador con un reproductor ligero basado en JavaScript. ¡Adiós a los GIFs borrosos de 20MB!&lt;/p&gt;
&lt;p&gt;&lt;img alt="asciinema help" src="https://pablocaro.es/images/asciinema_help.png"&gt;&lt;/p&gt;</content><category term="Herramientas"></category><category term="asciinema"></category><category term="terminal"></category><category term="linux"></category><category term="productividad"></category></entry><entry><title>kitty-rbw: Acceso rápido a Bitwarden desde tu terminal kitty</title><link href="https://pablocaro.es/kitty-rbw-kitten" rel="alternate"></link><published>2026-02-26T14:20:00+01:00</published><updated>2026-02-26T14:20:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-02-26:/kitty-rbw-kitten</id><summary type="html">&lt;p&gt;Presentación de kitty-rbw, un "kitten" para el terminal kitty que permite buscar y usar credenciales de rbw (Bitwarden) de forma instantánea mediante fzf.&lt;/p&gt;</summary><content type="html">&lt;p&gt;En la &lt;a href="https://pablocaro.es/rbw-bitwarden-cli"&gt;entrada anterior&lt;/a&gt; hablamos de &lt;strong&gt;rbw&lt;/strong&gt;, el cliente de Bitwarden para terminal escrito en Rust que destaca por su velocidad y por usar un agente para gestionar el desbloqueo del almacén.&lt;/p&gt;
&lt;p&gt;Hoy doy un paso más allá y presento &lt;strong&gt;&lt;a href="https://github.com/pcaro/kitty_rbw"&gt;kitty-rbw&lt;/a&gt;&lt;/strong&gt;, un &lt;em&gt;kitten&lt;/em&gt; que he desarrollado para el terminal &lt;strong&gt;kitty&lt;/strong&gt;. Su objetivo es permitirte buscar y usar tus credenciales de Bitwarden sin tener que salir del terminal ni escribir comandos complejos, integrándose perfectamente en tu flujo de trabajo.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Interfaz de kitty-rbw" src="https://pablocaro.es/images/kitty-rbw.png"&gt;&lt;/p&gt;
&lt;h2 id="que-es-un-kitten"&gt;¿Qué es un kitten?&lt;a class="headerlink" href="#que-es-un-kitten" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Los &lt;em&gt;kittens&lt;/em&gt; son pequeños programas en Python que extienden las funcionalidades del terminal kitty. Pueden ejecutarse en ventanas superpuestas (overlays), lo que los hace ideales para herramientas interactivas que no queremos que ensucien nuestro historial del terminal.&lt;/p&gt;
&lt;h2 id="caracteristicas-principales"&gt;Características principales&lt;a class="headerlink" href="#caracteristicas-principales" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;kitty-rbw&lt;/code&gt; utiliza &lt;strong&gt;fzf&lt;/strong&gt; para ofrecer una búsqueda difusa (fuzzy search) extremadamente rápida sobre tu almacén de rbw. Estas son sus funciones más destacadas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Búsqueda simultánea&lt;/strong&gt;: Filtra por nombre, usuario y carpeta al mismo tiempo.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Inyección directa&lt;/strong&gt;: Escribe la contraseña o el usuario directamente en la ventana activa del terminal donde lanzaste el kitten. Ideal para prompts de &lt;code&gt;sudo&lt;/code&gt; o inicios de sesión SSH.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Soporte para el portapapeles&lt;/strong&gt;: Copia el usuario, la contraseña o el código TOTP con un solo atajo de teclado.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Priorización de uso&lt;/strong&gt;: Las 10 entradas que más utilizas aparecen al principio de la lista, facilitando el acceso a lo más común.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Atajos por carpeta&lt;/strong&gt;: Puedes configurar el kitten para que se abra pre-filtrado por una carpeta específica (por ejemplo, una para el trabajo y otra personal).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="instalacion"&gt;Instalación&lt;a class="headerlink" href="#instalacion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Para instalarlo, solo tienes que clonar el repositorio en tu directorio de configuración de kitty:```bash
cd ~/.config/kitty
git clone https://github.com/pcaro/kitty_rbw&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Luego, añade un atajo de teclado en tu &lt;span class="sb"&gt;`kitty.conf`&lt;/span&gt;:

```conf
map kitty_mod+b kitten kitty_rbw/rbw.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="uso-y-atajos"&gt;Uso y atajos&lt;a class="headerlink" href="#uso-y-atajos" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Una vez configurado, al pulsar el atajo (&lt;code&gt;Ctrl+Shift+b&lt;/code&gt; por defecto) se abrirá un panel con tus credenciales. &lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tecla&lt;/th&gt;
&lt;th&gt;Acción&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Enter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Escribe la &lt;strong&gt;contraseña&lt;/strong&gt; en el terminal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+u&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Escribe el &lt;strong&gt;nombre de usuario&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Escribe &lt;strong&gt;usuario&lt;/strong&gt;, pulsa &lt;code&gt;Tab&lt;/code&gt; y luego &lt;strong&gt;contraseña&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+t&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Copia el código &lt;strong&gt;TOTP&lt;/strong&gt; al portapapeles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Alt+p&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Copia la &lt;strong&gt;contraseña&lt;/strong&gt; al portapapeles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Alt+u&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Copia el &lt;strong&gt;usuario&lt;/strong&gt; al portapapeles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Alt+s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sincroniza el almacén (&lt;code&gt;rbw sync&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Si quieres saber más o contribuir al proyecto, puedes encontrarlo en GitHub: &lt;a href="https://github.com/pcaro/kitty_rbw"&gt;https://github.com/pcaro/kitty_rbw&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="kitty help" src="https://pablocaro.es/images/kitty_help.png"&gt;&lt;/p&gt;</content><category term="Herramientas"></category><category term="bitwarden"></category><category term="cli"></category><category term="kitty"></category><category term="rust"></category><category term="productividad"></category></entry><entry><title>rbw: El cliente de Bitwarden para terminal que deberías usar</title><link href="https://pablocaro.es/rbw-bitwarden-cli" rel="alternate"></link><published>2026-02-26T11:45:00+01:00</published><updated>2026-02-26T11:45:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-02-26:/rbw-bitwarden-cli</id><summary type="html">&lt;p&gt;Cómo instalar y configurar rbw, una implementación en Rust del cliente de Bitwarden, y cómo integrarlo en tus flujos de trabajo con pi-agent.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Aunque Bitwarden tiene un cliente oficial de línea de comandos, este puede resultar lento al estar basado en Node.js. Para los que buscamos algo más ágil, &lt;a href="https://github.com/doy/rbw"&gt;rbw&lt;/a&gt; es la solución ideal. Es una implementación no oficial en Rust que destaca por ser extremadamente rápida y por gestionar el desbloqueo del almacén mediante un agente, evitando que tengas que introducir tu contraseña maestra en cada comando.&lt;/p&gt;
&lt;h2 id="instalacion"&gt;Instalación&lt;a class="headerlink" href="#instalacion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;La forma más sencilla de instalarlo es bajando el binario directamente desde sus releases de GitHub. Yo utilizo &lt;code&gt;gah&lt;/code&gt; para este propósito:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;gah&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;doy/rbw
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Este comando descargará el binario de &lt;code&gt;rbw&lt;/code&gt; y el de &lt;code&gt;rbw-agent&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="configuracion-inicial"&gt;Configuración inicial&lt;a class="headerlink" href="#configuracion-inicial" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Lo primero es configurar tu cuenta y el servidor:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;rbw&lt;span class="w"&gt; &lt;/span&gt;login
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Te pedirá tu email y la URL del servidor (puedes dejarla en blanco si usas el oficial de Bitwarden o poner la de tu instancia de Vaultwarden).&lt;/p&gt;
&lt;p&gt;Para empezar a usarlo, desbloquea el almacén:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;rbw&lt;span class="w"&gt; &lt;/span&gt;unlock
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A diferencia del cliente oficial, no necesitas exportar variables de entorno con tokens de sesión. El agente de &lt;code&gt;rbw&lt;/code&gt; se encarga de todo en segundo plano.&lt;/p&gt;
&lt;h2 id="uso-basico"&gt;Uso básico&lt;a class="headerlink" href="#uso-basico" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Para obtener una contraseña rápidamente:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;rbw&lt;span class="w"&gt; &lt;/span&gt;get&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Nombre del Elemento&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Si hay varios elementos con el mismo nombre, puedes usar filtros o el ID. También puedes obtener campos personalizados o el nombre de usuario:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;rbw&lt;span class="w"&gt; &lt;/span&gt;get&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;OpenRouter&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--folder&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;APIs&amp;quot;&lt;/span&gt;
rbw&lt;span class="w"&gt; &lt;/span&gt;get&lt;span class="w"&gt; &lt;/span&gt;--username&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Twitter&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="integracion-con-pi-agent"&gt;Integración con pi-agent&lt;a class="headerlink" href="#integracion-con-pi-agent" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Una de las mayores ventajas de tener un cliente CLI rápido y seguro es la capacidad de integrar secretos en tus herramientas de desarrollo.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/mariozechner/pi-agent"&gt;pi-agent&lt;/a&gt; permite ejecutar comandos para obtener claves de API dinámicamente. Esto evita tener que guardar claves en archivos de configuración en texto plano. En tu &lt;code&gt;settings.json&lt;/code&gt;, puedes configurar el acceso a una clave de la siguiente manera:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;api_key&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;key&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;!rbw get &amp;#39;OpenRouter&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;El prefijo &lt;code&gt;!&lt;/code&gt; indica a &lt;code&gt;pi-agent&lt;/code&gt; que debe ejecutar el comando y usar su salida estándar como la clave. Gracias al agente de &lt;code&gt;rbw&lt;/code&gt;, este comando se ejecutará instantáneamente sin pedirte la clave maestra cada vez, siempre que el almacén esté desbloqueado.&lt;/p&gt;
&lt;p&gt;&lt;img alt="rbw help" src="https://pablocaro.es/images/rbw_help.png"&gt;&lt;/p&gt;</content><category term="Herramientas"></category><category term="bitwarden"></category><category term="cli"></category><category term="rust"></category><category term="seguridad"></category><category term="pi-agent"></category></entry><entry><title>DeepDiff: La navaja suiza para comparar datos en Python</title><link href="https://pablocaro.es/deepdiff-comparar-datos-python" rel="alternate"></link><published>2026-02-20T00:00:00+01:00</published><updated>2026-02-20T00:00:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-02-20:/deepdiff-comparar-datos-python</id><summary type="html">&lt;p&gt;Cuando trabajamos con datos estructurados en Python, a menudo necesitamos comparar dos diccionarios, JSONs u objetos complejos para encontrar qué ha cambiado. Si bien la comparación directa (&lt;code&gt;==&lt;/code&gt;) es útil, a veces necesitamos entender &lt;em&gt;exactamente qué&lt;/em&gt; es diferente: qué claves se han añadido, cuáles se han eliminado, o dónde han cambiado …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Cuando trabajamos con datos estructurados en Python, a menudo necesitamos comparar dos diccionarios, JSONs u objetos complejos para encontrar qué ha cambiado. Si bien la comparación directa (&lt;code&gt;==&lt;/code&gt;) es útil, a veces necesitamos entender &lt;em&gt;exactamente qué&lt;/em&gt; es diferente: qué claves se han añadido, cuáles se han eliminado, o dónde han cambiado los valores, incluso en estructuras profundamente anidadas.&lt;/p&gt;
&lt;p&gt;Aquí es donde entra &lt;strong&gt;&lt;a href="https://github.com/seperman/deepdiff"&gt;DeepDiff&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;DeepDiff es una biblioteca de Python increíblemente potente que ofrece mucho más que una simple comparación. Es una herramienta esencial para pruebas, validación de datos y depuración de APIs.&lt;/p&gt;
&lt;h2 id="caracteristicas-principales"&gt;Características principales&lt;a class="headerlink" href="#caracteristicas-principales" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;DeepDiff no es solo una herramienta, es un conjunto de utilidades:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DeepDiff&lt;/strong&gt;: Compara diccionarios, iterables, cadenas y otros objetos recursivamente. Puede ignorar el orden en listas, ignorar tipos específicos, o excluir rutas de la comparación.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DeepSearch&lt;/strong&gt;: Busca objetos dentro de otros objetos, como un &lt;code&gt;grep&lt;/code&gt; para estructuras de datos en memoria.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DeepHash&lt;/strong&gt;: Calcula hashes de objetos basándose en su contenido. Muy útil para deduplicación de datos complejos donde el orden de las claves no importa.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Delta&lt;/strong&gt;: Genera "deltas" (diferencias) que se pueden aplicar a otros objetos, similar a un &lt;code&gt;git patch&lt;/code&gt; pero para objetos Python.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="instalacion"&gt;Instalación&lt;a class="headerlink" href="#instalacion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;La forma moderna y recomendada de instalar herramientas en Python es usando &lt;code&gt;uv&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="como-herramienta-de-linea-de-comandos-cli"&gt;Como herramienta de línea de comandos (CLI)&lt;a class="headerlink" href="#como-herramienta-de-linea-de-comandos-cli" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Si solo quieres usar el comando &lt;code&gt;deep&lt;/code&gt; en tu terminal para comparar archivos JSON o YAML:```bash
uv tool install "deepdiff[cli]"&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;Esto&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;instalará&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;el&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;comando&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n n-Quoted"&gt;`deep`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;en&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tu&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sistema&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;forma&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;aislada&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Asegúrate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;incluir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n n-Quoted"&gt;`[cli]`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;para&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;instalar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;las&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dependencias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;necesarias&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="c1"&gt;### Como biblioteca en tu proyecto&lt;/span&gt;

&lt;span class="n"&gt;Si&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;usarlo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dentro&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scripts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Python&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;

&lt;span class="n n-Quoted"&gt;`&lt;/span&gt;&lt;span class="n n-Quoted n-Quoted-Escape"&gt;``&lt;/span&gt;&lt;span class="n n-Quoted"&gt;bash&lt;/span&gt;
&lt;span class="n n-Quoted"&gt;uv add &amp;quot;deepdiff&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;O si necesitas la CLI también dentro de tu entorno virtual:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;uv&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;deepdiff[cli]&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="ejemplos-de-uso"&gt;Ejemplos de uso&lt;a class="headerlink" href="#ejemplos-de-uso" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="en-python"&gt;En Python&lt;a class="headerlink" href="#en-python" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Imagina que tienes dos respuestas de API ligeramente diferentes y quieres saber qué cambió:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;deepdiff&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DeepDiff&lt;/span&gt;

&lt;span class="n"&gt;t1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Producto A&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;tags&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;nuevo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;oferta&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;details&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;price&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;stock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;t2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Producto A&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;tags&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;oferta&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;nuevo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;# Orden diferente&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;details&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;price&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;stock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Por defecto, el orden importa en listas&lt;/span&gt;
&lt;span class="n"&gt;diff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DeepDiff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Resultado muestra cambios en la lista &amp;#39;tags&amp;#39; y en &amp;#39;details.price&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;# Si ignoramos el orden en iterables&lt;/span&gt;
&lt;span class="n"&gt;diff_ignore_order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DeepDiff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ignore_order&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;diff_ignore_order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Salida:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;values_changed&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;root[&amp;#39;details&amp;#39;][&amp;#39;price&amp;#39;]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;new_value&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;old_value&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Como ves, detectó el cambio de precio pero ignoró el cambio de orden en los tags.&lt;/p&gt;
&lt;h3 id="en-la-terminal-cli"&gt;En la terminal (CLI)&lt;a class="headerlink" href="#en-la-terminal-cli" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;El comando &lt;code&gt;deep&lt;/code&gt; es muy útil para comparar archivos rápidamente:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Comparar dos archivos JSON&lt;/span&gt;
deep&lt;span class="w"&gt; &lt;/span&gt;diff&lt;span class="w"&gt; &lt;/span&gt;production.json&lt;span class="w"&gt; &lt;/span&gt;development.json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;También puedes usarlo para extraer información o buscar dentro de archivos JSON/YAML grandes sin tener que escribir un script.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusión&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;DeepDiff es una de esas librerías que, una vez que conoces, no puedes dejar de usar. Su flexibilidad para ignorar ciertos campos (como timestamps o IDs autogenerados) la hace perfecta para tests de integración y validación de datos.&lt;/p&gt;
&lt;p&gt;Puedes ver la documentación completa en &lt;a href="https://zepworks.com/deepdiff/current/"&gt;zepworks.com/deepdiff&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="deep help" src="https://pablocaro.es/images/deep_help.png"&gt;&lt;/p&gt;</content><category term="Programación"></category><category term="data"></category><category term="cli"></category><category term="herramientas"></category><category term="diff"></category></entry><entry><title>Renderizando Markdown en la terminal con Glow</title><link href="https://pablocaro.es/renderizando-markdown-en-la-terminal-con-glow" rel="alternate"></link><published>2026-02-20T00:00:00+01:00</published><updated>2026-02-20T00:00:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-02-20:/renderizando-markdown-en-la-terminal-con-glow</id><summary type="html">&lt;p&gt;Descubre Glow, una herramienta para renderizar Markdown directamente en la terminal, ideal para leer documentación y READMEs.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://github.com/charmbracelet/glow"&gt;Glow&lt;/a&gt; es un renderizador de Markdown para la línea de comandos. Permite leer archivos Markdown locales o remotos, y renderizarlos con resaltado de sintaxis y estilos, directamente en tu terminal.&lt;/p&gt;
&lt;p&gt;Esta herramienta me resulta especialmente útil ahora que paso más tiempo usando la terminal con &lt;a href="https://pablocaro.es/de-yakuake-a-kitty"&gt;Kitty&lt;/a&gt;, ya que me permite consultar documentación y READMEs sin tener que abrir un navegador o un editor gráfico.&lt;/p&gt;
&lt;h2 id="instalacion"&gt;Instalación&lt;a class="headerlink" href="#instalacion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Para instalar Glow, utilizaremos &lt;a href="https://pablocaro.es/gestionando-instalaciones-desde-github-con-gah"&gt;gah&lt;/a&gt;, una herramienta que facilita la instalación de binarios desde GitHub Releases.```bash
gah install charmbracelet/glow&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;El&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;proceso&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;instalación&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;es&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;muy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;sencillo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="err"&gt;```&lt;/span&gt;&lt;span class="nc"&gt;text&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gah&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;charmbracelet&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;glow&lt;/span&gt;
&lt;span class="n"&gt;Fetching&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;release&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;charmbracelet&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;glow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;latest&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;Found&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;release&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="mf"&gt;.1.1&lt;/span&gt;
&lt;span class="nl"&gt;Downloading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;glow_2&lt;/span&gt;&lt;span class="mf"&gt;.1.1&lt;/span&gt;&lt;span class="n"&gt;_Linux_x86_64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gz&lt;/span&gt;
&lt;span class="err"&gt;###############################################################################################################################################################################&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;100.0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;
&lt;span class="n"&gt;GitHub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;Release&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;did&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;provide&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;glow_2&lt;/span&gt;&lt;span class="mf"&gt;.1.1&lt;/span&gt;&lt;span class="n"&gt;_Linux_x86_64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gz&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Skipping&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;verification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="nl"&gt;Extracting&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;glow_2&lt;/span&gt;&lt;span class="mf"&gt;.1.1&lt;/span&gt;&lt;span class="n"&gt;_Linux_x86_64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gz&lt;/span&gt;
&lt;span class="nl"&gt;Installing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;glow&lt;/span&gt;
&lt;span class="n"&gt;Give&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;keep&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;glow&amp;#39;&lt;/span&gt;&lt;span class="vm"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Leave&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;empty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;keep&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;same&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;New&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="nl"&gt;Installed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;glow&lt;/span&gt;
&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Una vez instalado, simplemente ejecuta &lt;code&gt;glow&lt;/code&gt; seguido del archivo que quieras visualizar, o incluso la URL de un README en GitHub.&lt;/p&gt;
&lt;p&gt;&lt;img alt="glow help" src="https://pablocaro.es/images/glow_help.png"&gt;&lt;/p&gt;</content><category term="Linux"></category><category term="cli"></category><category term="markdown"></category><category term="herramientas"></category><category term="kitty"></category></entry><entry><title>Shot-scraper: Capturas de pantalla automatizadas para la web</title><link href="https://pablocaro.es/shot-scraper-capturas-pantalla-automatizadas" rel="alternate"></link><published>2026-02-20T00:00:00+01:00</published><updated>2026-02-20T00:00:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-02-20:/shot-scraper-capturas-pantalla-automatizadas</id><summary type="html">&lt;p&gt;¿Alguna vez has necesitado tomar capturas de pantalla de una página web de forma programática? Tal vez para documentar tu proyecto, monitorizar cambios en una UI, o generar imágenes para redes sociales. Existen muchas formas de hacerlo, pero &lt;strong&gt;&lt;a href="https://shot-scraper.datasette.io/"&gt;shot-scraper&lt;/a&gt;&lt;/strong&gt; destaca por su facilidad de uso y potencia.&lt;/p&gt;
&lt;p&gt;Creada por Simon …&lt;/p&gt;</summary><content type="html">&lt;p&gt;¿Alguna vez has necesitado tomar capturas de pantalla de una página web de forma programática? Tal vez para documentar tu proyecto, monitorizar cambios en una UI, o generar imágenes para redes sociales. Existen muchas formas de hacerlo, pero &lt;strong&gt;&lt;a href="https://shot-scraper.datasette.io/"&gt;shot-scraper&lt;/a&gt;&lt;/strong&gt; destaca por su facilidad de uso y potencia.&lt;/p&gt;
&lt;p&gt;Creada por Simon Willison, &lt;code&gt;shot-scraper&lt;/code&gt; es una herramienta de línea de comandos (CLI) que envuelve Playwright para hacer que tomar screenshots sea trivial.&lt;/p&gt;
&lt;h2 id="instalacion"&gt;Instalación&lt;a class="headerlink" href="#instalacion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Al igual que con otras herramientas modernas de Python, la mejor forma de instalarla es usando &lt;code&gt;uv&lt;/code&gt;:```bash
uv tool install shot-scraper&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Una vez instalada, necesitarás descargar el navegador que utiliza por debajo (Chromium):

```bash
shot-scraper install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="uso-basico"&gt;Uso básico&lt;a class="headerlink" href="#uso-basico" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;La forma más sencilla de usarlo es darle una URL y un nombre de archivo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;shot-scraper&lt;span class="w"&gt; &lt;/span&gt;https://pablocaro.es/&lt;span class="w"&gt; &lt;/span&gt;blog.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Esto generará una imagen llamada &lt;code&gt;blog.png&lt;/code&gt; con la captura de la página.&lt;/p&gt;
&lt;h3 id="capturando-selectores-especificos"&gt;Capturando selectores específicos&lt;a class="headerlink" href="#capturando-selectores-especificos" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Una de las características más potentes es la capacidad de capturar solo un elemento específico de la página usando selectores CSS:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;shot-scraper&lt;span class="w"&gt; &lt;/span&gt;https://github.com/pcaro&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.js-calendar-graph&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;calendar.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Este comando capturará únicamente el gráfico de contribuciones de GitHub.&lt;/p&gt;
&lt;h3 id="interactuando-con-javascript"&gt;Interactuando con JavaScript&lt;a class="headerlink" href="#interactuando-con-javascript" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A veces necesitas ejecutar algo de código antes de tomar la foto. Por ejemplo, para ocultar un banner de cookies o esperar a que cargue algo.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;shot-scraper&lt;span class="w"&gt; &lt;/span&gt;https://pablocaro.es/&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--javascript&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;document.querySelector(&amp;#39;header&amp;#39;).style.display = &amp;#39;none&amp;#39;;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;sin-header.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="automatizacion-avanzada"&gt;Automatización avanzada&lt;a class="headerlink" href="#automatizacion-avanzada" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Si necesitas tomar muchas capturas, puedes definir un archivo YAML de configuración:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# shots.yml&lt;/span&gt;
&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;https://pablocaro.es/&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;home.png&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;800&lt;/span&gt;
&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;https://pablocaro.es/archives.html&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;archives.png&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;1000&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# Esperar 1 segundo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Y ejecutarlas todas de una vez:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;shot-scraper&lt;span class="w"&gt; &lt;/span&gt;multi&lt;span class="w"&gt; &lt;/span&gt;shots.yml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="mas-alla-de-las-capturas"&gt;Más allá de las capturas&lt;a class="headerlink" href="#mas-alla-de-las-capturas" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;shot-scraper&lt;/code&gt; también incluye utilidades para:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Accesibilidad&lt;/strong&gt;: Volcar el árbol de accesibilidad de una página (&lt;code&gt;shot-scraper accessibility&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PDF&lt;/strong&gt;: Generar PDFs de páginas web (&lt;code&gt;shot-scraper pdf&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HAR&lt;/strong&gt;: Grabar archivos HAR para analizar el tráfico de red (&lt;code&gt;shot-scraper har&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Es una de esas herramientas que, una vez instaladas, encuentras usos para ella constantemente. Perfecta para integrar en pipelines de CI/CD para generar visuales de documentación automáticamente.&lt;/p&gt;
&lt;p&gt;Más info en la &lt;a href="https://shot-scraper.datasette.io/"&gt;documentación oficial&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="shot-scraper help" src="https://pablocaro.es/images/shot_scraper_help.png"&gt;&lt;/p&gt;</content><category term="Programación"></category><category term="cli"></category><category term="herramientas"></category><category term="web"></category><category term="scraping"></category><category term="screenshots"></category></entry><entry><title>Stern: Logs de Kubernetes con esteroides</title><link href="https://pablocaro.es/stern-kubernetes-logs" rel="alternate"></link><published>2026-02-20T00:00:00+01:00</published><updated>2026-02-20T00:00:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-02-20:/stern-kubernetes-logs</id><summary type="html">&lt;p&gt;Si trabajas con Kubernetes, probablemente hayas sufrido el comando &lt;code&gt;kubectl logs&lt;/code&gt;. Es útil para ver los logs de un pod específico, pero se queda corto cuando tienes múltiples réplicas de un servicio o cuando los pods se reinician y cambian de nombre.&lt;/p&gt;
&lt;p&gt;Aquí es donde entra &lt;strong&gt;&lt;a href="https://github.com/stern/stern"&gt;Stern&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Stern te permite …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Si trabajas con Kubernetes, probablemente hayas sufrido el comando &lt;code&gt;kubectl logs&lt;/code&gt;. Es útil para ver los logs de un pod específico, pero se queda corto cuando tienes múltiples réplicas de un servicio o cuando los pods se reinician y cambian de nombre.&lt;/p&gt;
&lt;p&gt;Aquí es donde entra &lt;strong&gt;&lt;a href="https://github.com/stern/stern"&gt;Stern&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Stern te permite hacer "tail" (seguir) logs de múltiples pods y contenedores dentro de Kubernetes simultáneamente. Lo mejor de todo es que utiliza expresiones regulares para seleccionar los pods, por lo que no necesitas copiar y pegar esos IDs aleatorios (&lt;code&gt;pod-1234567890-abcde&lt;/code&gt;).&lt;/p&gt;
&lt;h2 id="instalacion"&gt;Instalación&lt;a class="headerlink" href="#instalacion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Stern es muy popular y está disponible en la mayoría de gestores de paquetes.### Usando Krew (recomendado)
Si ya usas &lt;code&gt;kubectl&lt;/code&gt;, Krew es la forma más natural de instalar plugins:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;krew&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;stern
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="usando-homebrew"&gt;Usando Homebrew&lt;a class="headerlink" href="#usando-homebrew" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;brew&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;stern
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="binario-directo"&gt;Binario directo&lt;a class="headerlink" href="#binario-directo" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;También puedes descargar el binario directamente desde sus &lt;a href="https://github.com/stern/stern/releases"&gt;releases en GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Si utilizas algún helper como &lt;code&gt;gah&lt;/code&gt; (GitHub Asset Helper) o scripts personalizados, la instalación es tan sencilla como:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;gah&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;stern/stern
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="caracteristicas-principales"&gt;Características principales&lt;a class="headerlink" href="#caracteristicas-principales" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="1-tailing-de-multiples-pods"&gt;1. Tailing de múltiples pods&lt;a class="headerlink" href="#1-tailing-de-multiples-pods" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;En lugar de buscar el nombre exacto del pod, puedes usar una regex:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;stern&lt;span class="w"&gt; &lt;/span&gt;backend
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Esto mostrará los logs de todos los pods que contengan "backend" en su nombre (&lt;code&gt;backend-api&lt;/code&gt;, &lt;code&gt;backend-worker&lt;/code&gt;, etc.), intercalados y coloreados para distinguirlos fácilmente.&lt;/p&gt;
&lt;h3 id="2-filtrado-y-exclusion"&gt;2. Filtrado y exclusión&lt;a class="headerlink" href="#2-filtrado-y-exclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Puedes filtrar el contenido de los logs sobre la marcha sin necesidad de &lt;code&gt;grep&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Solo mostrar líneas que contengan &amp;quot;Error&amp;quot;&lt;/span&gt;
stern&lt;span class="w"&gt; &lt;/span&gt;backend&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;Error

&lt;span class="c1"&gt;# Excluir líneas que contengan &amp;quot;Health check&amp;quot;&lt;/span&gt;
stern&lt;span class="w"&gt; &lt;/span&gt;backend&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Health check&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="3-selectores-de-kubernetes"&gt;3. Selectores de Kubernetes&lt;a class="headerlink" href="#3-selectores-de-kubernetes" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Además de regex por nombre, puedes usar selectores de etiquetas, lo cual es mucho más preciso para entornos de producción:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;stern&lt;span class="w"&gt; &lt;/span&gt;-l&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mi-app&lt;span class="w"&gt; &lt;/span&gt;-l&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;tier&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;frontend
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="4-formato-de-salida"&gt;4. Formato de salida&lt;a class="headerlink" href="#4-formato-de-salida" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Si tus logs están en JSON (como es buena práctica), Stern puede pasarlos tal cual para que los proceses con herramientas como &lt;code&gt;jq&lt;/code&gt; o &lt;code&gt;fx&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;stern&lt;span class="w"&gt; &lt;/span&gt;backend&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;raw&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fx
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="conclusion"&gt;Conclusión&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Stern es una de esas herramientas que instalas el primer día y te preguntas cómo podías vivir sin ella. Hace que el debug en entornos distribuidos sea mucho menos doloroso al agregar la información de manera coherente.&lt;/p&gt;
&lt;p&gt;Dale una oportunidad y tus sesiones de debugging te lo agradecerán.&lt;/p&gt;
&lt;p&gt;&lt;img alt="stern help" src="https://pablocaro.es/images/stern_help.png"&gt;&lt;/p&gt;</content><category term="Infraestructura"></category><category term="kubernetes"></category><category term="cli"></category><category term="logs"></category><category term="herramientas"></category></entry><entry><title>Gestionando instalaciones desde GitHub con gah</title><link href="https://pablocaro.es/gestionando-instalaciones-desde-github-con-gah" rel="alternate"></link><published>2026-02-11T20:23:00+01:00</published><updated>2026-02-11T20:23:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-02-11:/gestionando-instalaciones-desde-github-con-gah</id><summary type="html">&lt;p class="first last"&gt;Instala binarios directamente desde releases de GitHub de forma sencilla con gah.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;a class="reference external" href="https://github.com/get-gah/gah"&gt;gah&lt;/a&gt; es una herramienta que facilita la instalación de programas que distribuyen sus binarios a través de releases de GitHub. Es una alternativa ligera cuando no quieres depender del gestor de paquetes de tu distribución o cuando necesitas una versión más reciente.&lt;/p&gt;
&lt;div class="section" id="instalacion"&gt;
&lt;h2&gt;Instalación&lt;/h2&gt;
&lt;p&gt;Para instalar &lt;strong&gt;gah&lt;/strong&gt;, puedes usar el siguiente script oficial:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;bash&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-fsSL&lt;span class="w"&gt; &lt;/span&gt;https://raw.githubusercontent.com/get-gah/gah/refs/heads/master/tools/install.sh&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="uso"&gt;
&lt;h2&gt;Uso&lt;/h2&gt;
&lt;p&gt;Una vez instalado, usarlo es muy directo. Aquí tienes un par de ejemplos que he usado recientemente para instalar herramientas populares escritas en Rust:&lt;/p&gt;
&lt;p&gt;Para instalar &lt;cite&gt;fd&lt;/cite&gt; (una alternativa rápida a &lt;tt class="docutils literal"&gt;find&lt;/tt&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;gah&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;sharkdp/fd
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Para instalar &lt;cite&gt;ripgrep&lt;/cite&gt; (una alternativa rápida a &lt;tt class="docutils literal"&gt;grep&lt;/tt&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;gah&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;BurntSushi/ripgrep
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Gah se encarga de descargar el asset correcto para tu arquitectura, descomprimirlo y colocarlo en tu path.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Linux"></category><category term="herramientas"></category><category term="cli"></category></entry><entry><title>broot, una forma diferente de navegar por tus directorios</title><link href="https://pablocaro.es/broot-una-forma-diferente-de-navegar-directorios" rel="alternate"></link><published>2026-01-02T10:00:00+01:00</published><updated>2026-01-02T10:00:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2026-01-02:/broot-una-forma-diferente-de-navegar-directorios</id><summary type="html">&lt;p&gt;broot es una herramienta para la terminal que permite explorar directorios de forma interactiva, con búsqueda fuzzy, paneles múltiples y previsualización de archivos.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Llevaba tiempo buscando un reemplazo para &lt;code&gt;ls&lt;/code&gt; y &lt;code&gt;tree&lt;/code&gt; que me permitiera navegar por directorios grandes sin perder el contexto. broot es la herramienta que necesitaba.&lt;/p&gt;
&lt;h2 id="instalacion"&gt;Instalación&lt;a class="headerlink" href="#instalacion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Con cargo (Rust)&lt;/span&gt;
cargo&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;broot

&lt;span class="c1"&gt;# O con tu gestor de paquetes&lt;/span&gt;
brew&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;broot&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# macOS&lt;/span&gt;
apt&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;broot&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# Debian/Ubuntu&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="primeros-pasos"&gt;Primeros pasos&lt;a class="headerlink" href="#primeros-pasos" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;El comando básico es simplemente &lt;code&gt;br&lt;/code&gt; (puedes crear un alias para que reemplace a &lt;code&gt;cd&lt;/code&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;br
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Esto abre una vista de árbol interactiva del directorio actual. Las teclas principales:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;↓&lt;/code&gt; / &lt;code&gt;↑&lt;/code&gt; - Navegar entre archivos y directorios&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/&lt;/code&gt; - Búsqueda fuzzy (ej: &lt;code&gt;/config&lt;/code&gt; encuentra todos los archivos con "config" en el nombre)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Enter&lt;/code&gt; - Entrar en un directorio&lt;/li&gt;
&lt;li&gt;&lt;code&gt;alt + Enter&lt;/code&gt; - Hacer &lt;code&gt;cd&lt;/code&gt; al directorio seleccionado y salir de broot&lt;/li&gt;
&lt;li&gt;&lt;code&gt;:e&lt;/code&gt; - Abrir el archivo seleccionado con el editor definido en &lt;code&gt;$EDITOR&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;:q&lt;/code&gt; - Salir&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="caracteristicas-que-uso"&gt;Características que uso&lt;a class="headerlink" href="#caracteristicas-que-uso" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="busqueda-sin-perder-el-contexto"&gt;Búsqueda sin perder el contexto&lt;a class="headerlink" href="#busqueda-sin-perder-el-contexto" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A diferencia de &lt;code&gt;find&lt;/code&gt;, broot muestra dónde está cada resultado dentro del árbol de directorios:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;br
/pytest&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# Encuentra todos los archivos/directorios con &amp;quot;pytest&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Esto es útil cuando sabes el nombre de un archivo pero no recuerdas en qué carpeta está.&lt;/p&gt;
&lt;h3 id="paneles-multiples"&gt;Paneles múltiples&lt;a class="headerlink" href="#paneles-multiples" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Puedes dividir la vista para comparar o mover archivos entre directorios:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;:pp  # Crear panel derecho
:pc  # Crear panel inferior
:pt  # Intercambiar paneles
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;En cada panel puedes navegar independientemente y usar verbos como &lt;code&gt;:copy&lt;/code&gt; o &lt;code&gt;:move&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="previsualizacion-de-archivos"&gt;Previsualización de archivos&lt;a class="headerlink" href="#previsualizacion-de-archivos" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Selecciona un archivo y usa &lt;code&gt;:preview&lt;/code&gt; para ver su contenido sin salir de broot. Para imágenes en terminales que lo soporten (kitty, iterm2):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;:preview
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="ver-solo-archivos-relevantes"&gt;Ver solo archivos relevantes&lt;a class="headerlink" href="#ver-solo-archivos-relevantes" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;broot oculta automáticamente archivos ignorados por Git y directorios comunes como &lt;code&gt;node_modules&lt;/code&gt;. Para verlos:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;:show_git_ignored
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="verbos-personalizados"&gt;Verbos personalizados&lt;a class="headerlink" href="#verbos-personalizados" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;En &lt;code&gt;~/.config/broot/conf.toml&lt;/code&gt; puedes añadir comandos personalizados:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[[verbs]]&lt;/span&gt;
&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;edit&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;invocation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;e&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;execution&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;$EDITOR {file}&amp;quot;&lt;/span&gt;

&lt;span class="k"&gt;[[verbs]]&lt;/span&gt;
&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git status&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;invocation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;gs&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;execution&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git status&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="integracion-con-el-shell"&gt;Integración con el shell&lt;a class="headerlink" href="#integracion-con-el-shell" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Para usar &lt;code&gt;br&lt;/code&gt; como reemplazo de &lt;code&gt;cd&lt;/code&gt;, añade esto a tu &lt;code&gt;.bashrc&lt;/code&gt; o &lt;code&gt;.zshrc&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Esto permite que br cambie el directorio del shell padre&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/usr/share/broot/launcher/bash/br
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ahora &lt;code&gt;br directorio&lt;/code&gt; te deja en ese directorio al salir.&lt;/p&gt;
&lt;h2 id="cuando-usar-broot"&gt;Cuándo usar broot&lt;a class="headerlink" href="#cuando-usar-broot" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Navegar por proyectos grandes con muchas carpetas&lt;/li&gt;
&lt;li&gt;Buscar archivos cuando no recuerdas la ruta exacta&lt;/li&gt;
&lt;li&gt;Mover/copiar archivos entre directorios distantes&lt;/li&gt;
&lt;li&gt;Explorar directorios con muchos archivos ignorados por Git&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="enlaces"&gt;Enlaces&lt;a class="headerlink" href="#enlaces" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dystroy.org/broot/"&gt;Documentación oficial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Canop/broot"&gt;Repositorio GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Fuente original&lt;/em&gt;: &lt;a href="https://dystroy.org/broot/"&gt;Documentación de broot&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="broot terminal interface" src="https://pablocaro.es/images/broot_terminal.png"&gt;&lt;/p&gt;</content><category term="Herramientas"></category><category term="terminal"></category><category term="cli"></category><category term="broot"></category><category term="rust"></category></entry><entry><title>YAML Multilínea: Entendiendo y probando cadenas de texto complejas</title><link href="https://pablocaro.es/yaml-multilinea-cadenas-complejas" rel="alternate"></link><published>2025-12-25T16:10:00+01:00</published><updated>2025-12-25T16:10:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2025-12-25:/yaml-multilinea-cadenas-complejas</id><summary type="html">&lt;p&gt;Explora las opciones de YAML para manejar cadenas de texto multilínea de forma efectiva, con una demo interactiva que te permite probarlas en tiempo real.&lt;/p&gt;</summary><content type="html">&lt;p&gt;YAML se ha convertido en el formato de facto para la configuración en muchos proyectos, desde Kubernetes hasta scripts de CI/CD. Una de sus funcionalidades más potentes, y a veces confusas, es la gestión de &lt;strong&gt;cadenas de texto multilínea&lt;/strong&gt;. Afortunadamente, existe una herramienta interactiva excepcional para dominar este aspecto: &lt;a href="https://yaml-multiline.info/"&gt;yaml-multiline.info&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="formatos-para-cadenas-multilinea-en-yaml"&gt;Formatos para Cadenas Multilínea en YAML&lt;a class="headerlink" href="#formatos-para-cadenas-multilinea-en-yaml" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;YAML ofrece principalmente dos enfoques para definir cadenas que abarcan varias líneas:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Escalares de Flujo (Flow Scalars)&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Son cadenas simples, a menudo entrecomilladas (&lt;code&gt;'...'&lt;/code&gt; o &lt;code&gt;"..."&lt;/code&gt;) o sin comillas.&lt;/li&gt;
&lt;li&gt;Ignoran indentaciones y, por defecto, reemplazan los saltos de línea con espacios.&lt;/li&gt;
&lt;li&gt;Las cadenas con comillas dobles permiten usar secuencias de escape como &lt;code&gt;\n&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Escalares de Bloque (Block Scalars)&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ofrecen un control mucho más preciso sobre los saltos de línea y la indentación.&lt;/li&gt;
&lt;li&gt;Se indican con un carácter especial al inicio de la línea donde comienza la cadena.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="claves-para-cadenas-multilinea-en-bloque"&gt;Claves para Cadenas Multilínea en Bloque&lt;a class="headerlink" href="#claves-para-cadenas-multilinea-en-bloque" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Los dos indicadores de estilo más importantes para escalares de bloque son:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;|&lt;/code&gt; (Estilo Literal)&lt;/strong&gt;: Mantiene todos los saltos de línea y la indentación tal cual. Es ideal para bloques de texto donde el formato es crucial (ej. código, mensajes largos).
    &lt;code&gt;yaml
    literal_string: |
      Esta es una línea.
      Esta es otra.
        Con su propia indentación.&lt;/code&gt;
    Resultado: "Esta es una línea.\nEsta es otra.\n  Con su propia indentación.\n"&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;&amp;gt;&lt;/code&gt; (Estilo Plegado/Folded)&lt;/strong&gt;: Reemplaza los saltos de línea con espacios, uniendo las líneas en una sola. Solo los saltos de línea dobles (líneas en blanco) o las líneas con mayor indentación se conservan como saltos de línea. Es útil para párrafos largos.
    &lt;code&gt;yaml
    folded_string: &amp;gt;
      Esta es una cadena
      plegada en varias líneas.
      Mantendrá el formato de párrafo.&lt;/code&gt;
    Resultado: "Esta es una cadena plegada en varias líneas. Mantendrá el formato de párrafo.\n"&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Además de esto, existen indicadores de "chomping" (&lt;code&gt;-&lt;/code&gt; para eliminar saltos de línea finales, &lt;code&gt;+&lt;/code&gt; para mantenerlos, o el comportamiento por defecto) y de indentación que ofrecen aún más control.&lt;/p&gt;
&lt;h2 id="demo-en-tiempo-real"&gt;¡Demo en Tiempo Real!&lt;a class="headerlink" href="#demo-en-tiempo-real" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Lo que hace a &lt;a href="https://yaml-multiline.info/"&gt;yaml-multiline.info&lt;/a&gt; una herramienta invaluable es su &lt;strong&gt;demo interactiva&lt;/strong&gt;. Puedes probar diferentes sintaxis YAML en tiempo real y ver cómo se interpreta la cadena resultante. Es la mejor manera de entender visualmente cómo funcionan los estilos literal y plegado, y cómo los indicadores de chomping y la indentación afectan el resultado final.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusión&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Entender cómo manejar cadenas multilínea en YAML no solo mejora la legibilidad de tus archivos de configuración, sino que también previene errores inesperados. La demo en tiempo real de &lt;code&gt;yaml-multiline.info&lt;/code&gt; es un recurso fantástico para cualquier persona que trabaje regularmente con YAML y quiera dominar este aspecto.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Sitio web&lt;/em&gt;: &lt;a href="https://yaml-multiline.info/"&gt;&lt;code&gt;yaml-multiline.info&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;</content><category term="Herramientas"></category><category term="yaml"></category><category term="configuracion"></category><category term="desarrollo"></category><category term="multiline"></category><category term="strings"></category></entry><entry><title>Pandas Styler: Mejora la presentación de tus DataFrames</title><link href="https://pablocaro.es/pandas-styler-presentacion-dataframes" rel="alternate"></link><published>2025-12-25T15:55:00+01:00</published><updated>2025-12-25T15:55:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2025-12-25:/pandas-styler-presentacion-dataframes</id><summary type="html">&lt;p&gt;Descubre cómo el objeto &lt;code&gt;Styler&lt;/code&gt; de Pandas te permite aplicar formato condicional y estilos CSS a tus DataFrames para una mejor visualización y presentación de datos.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Cuando trabajamos con datos en Python, los DataFrames de Pandas son nuestra herramienta principal. Sin embargo, su presentación por defecto en consolas o notebooks puede ser muy básica. Aquí es donde el objeto &lt;strong&gt;&lt;code&gt;Styler&lt;/code&gt; de Pandas&lt;/strong&gt; se convierte en un aliado fundamental para transformar DataFrames simples en tablas visualmente atractivas e informativas.&lt;/p&gt;
&lt;h2 id="que-es-pandas-styler"&gt;¿Qué es Pandas &lt;code&gt;Styler&lt;/code&gt;?&lt;a class="headerlink" href="#que-es-pandas-styler" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;El objeto &lt;code&gt;Styler&lt;/code&gt; te permite aplicar formato condicional y estilos CSS directamente a tus DataFrames de Pandas. Con él, puedes resaltar patrones, enfatizar valores importantes y mejorar la legibilidad sin necesidad de exportar los datos a otra herramienta.&lt;/p&gt;
&lt;h2 id="funcionalidades-clave"&gt;Funcionalidades Clave&lt;a class="headerlink" href="#funcionalidades-clave" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Formato Condicional&lt;/strong&gt;: Resalta celdas, filas o columnas basándose en condiciones definidas por ti (e.g., valores por encima de un umbral, duplicados, etc.).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Estilos CSS&lt;/strong&gt;: Aplica estilos CSS como colores de fondo, fuentes, bordes o alineaciones para personalizar la apariencia.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Estilos Predefinidos&lt;/strong&gt;: Incluye métodos como &lt;code&gt;background_gradient()&lt;/code&gt; para aplicar gradientes de color que visualicen la distribución de valores.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integración con Excel&lt;/strong&gt;: Mantiene los estilos al exportar DataFrames a archivos Excel, facilitando reportes profesionales.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="conclusion"&gt;Conclusión&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;El objeto &lt;code&gt;Styler&lt;/code&gt; de Pandas es una herramienta sencilla pero potente para llevar la presentación de tus datos al siguiente nivel. Con unas pocas líneas de código, puedes hacer que tus DataFrames no solo sean una fuente de datos, sino también una herramienta de comunicación efectiva.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Artículo original&lt;/em&gt;: &lt;a href="https://pbpython.com/styling-pandas.html"&gt;Stylin' with Pandas | Practical Business Python&lt;/a&gt;&lt;/p&gt;</content><category term="Programación"></category><category term="pandas"></category><category term="data-science"></category><category term="visualizacion"></category><category term="estilo"></category></entry><entry><title>`fresh`: Mi editor de terminal preferido para el día a día</title><link href="https://pablocaro.es/fresh-editor-terminal" rel="alternate"></link><published>2025-12-25T15:45:00+01:00</published><updated>2025-12-25T15:45:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2025-12-25:/fresh-editor-terminal</id><summary type="html">&lt;p&gt;Descubre &lt;code&gt;fresh&lt;/code&gt;, un editor de texto en terminal rápido, moderno y sin modos, ideal para la edición casual de archivos, superando a &lt;code&gt;nano&lt;/code&gt; y &lt;code&gt;vim&lt;/code&gt; en simplicidad para el uso habitual.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Aunque &lt;code&gt;vim&lt;/code&gt; es una herramienta increíblemente potente y &lt;code&gt;nano&lt;/code&gt; destaca por su simplicidad extrema, a menudo busco un punto intermedio para la edición de archivos en la terminal. Un editor que sea rápido, moderno y que no me obligue a recordar modos o combinaciones de teclas complejas para tareas sencillas. Así es como he descubierto &lt;strong&gt;&lt;code&gt;fresh&lt;/code&gt;&lt;/strong&gt;, y se ha convertido rápidamente en mi editor de terminal preferido para el uso diario.&lt;/p&gt;
&lt;h2 id="que-es-fresh"&gt;¿Qué es &lt;code&gt;fresh&lt;/code&gt;?&lt;a class="headerlink" href="#que-es-fresh" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;fresh&lt;/code&gt; es un editor de texto para terminal diseñado para ser &lt;strong&gt;simple, rápido y eficiente&lt;/strong&gt;. A diferencia de &lt;code&gt;vim&lt;/code&gt;, &lt;code&gt;fresh&lt;/code&gt; es un editor sin modos, lo que significa que puedes empezar a escribir y editar texto inmediatamente, como lo harías en un editor gráfico. A diferencia de &lt;code&gt;nano&lt;/code&gt;, &lt;code&gt;fresh&lt;/code&gt; ofrece una experiencia más moderna con características que esperas de un editor de texto actual.&lt;/p&gt;
&lt;h2 id="por-que-lo-prefiero-a-nano-o-vim-para-mi-uso-habitual"&gt;¿Por qué lo prefiero a &lt;code&gt;nano&lt;/code&gt; o &lt;code&gt;vim&lt;/code&gt; para mi uso habitual?&lt;a class="headerlink" href="#por-que-lo-prefiero-a-nano-o-vim-para-mi-uso-habitual" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Sin Modos&lt;/strong&gt;: La principal ventaja sobre &lt;code&gt;vim&lt;/code&gt;. No necesitas aprender a cambiar entre modos de inserción, normal, visual, etc. Simplemente abres el archivo y editas.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Moderno y Rápido&lt;/strong&gt;: A pesar de ejecutarse en la terminal, &lt;code&gt;fresh&lt;/code&gt; se siente responsivo y ofrece una interfaz limpia.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Resaltado de Sintaxis&lt;/strong&gt;: Soporte para coloreado de sintaxis para una gran variedad de lenguajes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Soporte de Ratón&lt;/strong&gt;: Puedes hacer clic para mover el cursor, seleccionar texto, o incluso usar la rueda para scroll.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multi-Cursor&lt;/strong&gt;: Una característica muy potente que &lt;code&gt;nano&lt;/code&gt; no ofrece y que &lt;code&gt;vim&lt;/code&gt; requiere más complejidad para usar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deshacer/Rehacer&lt;/strong&gt;: Funcionalidad completa de &lt;code&gt;undo&lt;/code&gt;/&lt;code&gt;redo&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Búsqueda y Reemplazo&lt;/strong&gt;: Funciones de búsqueda y reemplazo sencillas e intuitivas.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Mientras que &lt;code&gt;vim&lt;/code&gt; sigue siendo mi elección para ediciones complejas o scripting avanzado, para "abrir un &lt;code&gt;.conf&lt;/code&gt;", modificar un &lt;code&gt;.bashrc&lt;/code&gt;, o escribir una pequeña nota, &lt;code&gt;fresh&lt;/code&gt; es imbatible en velocidad y comodidad. &lt;code&gt;nano&lt;/code&gt; es quizás demasiado básico para mis necesidades, careciendo de la mayoría de las características modernas que &lt;code&gt;fresh&lt;/code&gt; ofrece.&lt;/p&gt;
&lt;h2 id="instalacion"&gt;Instalación&lt;a class="headerlink" href="#instalacion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;fresh&lt;/code&gt; es fácil de instalar en la mayoría de las distribuciones Linux. Por ejemplo, en sistemas basados en Debian/Ubuntu:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;fresh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;O puedes seguir las instrucciones del repositorio oficial para compilarlo desde el código fuente o usar otros gestores de paquetes.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusión&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Si buscas un editor de terminal que sea un híbrido perfecto entre la potencia de &lt;code&gt;vim&lt;/code&gt; y la simplicidad de &lt;code&gt;nano&lt;/code&gt;, pero con un toque moderno y centrado en la usabilidad inmediata, &lt;code&gt;fresh&lt;/code&gt; es una excelente opción. Dale una oportunidad, ¡podría convertirse en tu nuevo favorito!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Repositorio oficial&lt;/em&gt;: &lt;a href="https://sinelaw.github.io/fresh/"&gt;&lt;code&gt;fresh&lt;/code&gt; en GitHub&lt;/a&gt;&lt;/p&gt;</content><category term="Herramientas"></category><category term="fresh"></category><category term="terminal"></category><category term="editor"></category><category term="productividad"></category><category term="linux"></category><category term="cli"></category></entry><entry><title>Cómo ver los archivos de un paquete RPM en Linux</title><link href="https://pablocaro.es/ver-archivos-paquete-rpm" rel="alternate"></link><published>2025-12-25T15:15:00+01:00</published><updated>2025-12-25T15:15:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2025-12-25:/ver-archivos-paquete-rpm</id><summary type="html">&lt;p&gt;Guía rápida para inspeccionar el contenido de paquetes RPM y determinar qué paquetes poseen determinados archivos en tu sistema Linux.&lt;/p&gt;</summary><content type="html">&lt;p&gt;En sistemas Linux basados en RPM (como Red Hat, CentOS, Fedora, openSUSE), es común interactuar con paquetes en formato &lt;code&gt;.rpm&lt;/code&gt;. A menudo, necesitamos saber qué archivos contiene un paquete antes de instalarlo, o qué paquete es el responsable de un archivo específico ya instalado en el sistema. Para ello, la herramienta &lt;code&gt;rpm&lt;/code&gt; nos ofrece varias opciones útiles.&lt;/p&gt;
&lt;h2 id="1-listar-archivos-de-un-paquete-rpm-sin-instalar"&gt;1. Listar Archivos de un Paquete RPM sin Instalar&lt;a class="headerlink" href="#1-listar-archivos-de-un-paquete-rpm-sin-instalar" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Si tienes un archivo &lt;code&gt;.rpm&lt;/code&gt; descargado y quieres ver qué archivos instalará (y dónde) sin necesidad de instalarlo, puedes usar la opción &lt;code&gt;-qlp&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;rpm&lt;span class="w"&gt; &lt;/span&gt;-qlp&lt;span class="w"&gt; &lt;/span&gt;nombre_del_paquete.rpm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-q&lt;/code&gt;: Modo de consulta (query).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-l&lt;/code&gt;: Lista los archivos (list files).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-p&lt;/code&gt;: Consulta un paquete (package file) en lugar de uno instalado.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Ejemplo:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;rpm&lt;span class="w"&gt; &lt;/span&gt;-qlp&lt;span class="w"&gt; &lt;/span&gt;nginx-1.20.1-1.el8.x86_64.rpm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="2-encontrar-el-paquete-dueno-de-un-archivo-instalado"&gt;2. Encontrar el Paquete Dueño de un Archivo Instalado&lt;a class="headerlink" href="#2-encontrar-el-paquete-dueno-de-un-archivo-instalado" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Si encuentras un archivo en tu sistema y quieres saber a qué paquete RPM pertenece (útil para depuración o para saber si puedes borrarlo de forma segura), usa la opción &lt;code&gt;-qf&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;rpm&lt;span class="w"&gt; &lt;/span&gt;-qf&lt;span class="w"&gt; &lt;/span&gt;/ruta/al/archivo
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Ejemplo:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;rpm&lt;span class="w"&gt; &lt;/span&gt;-qf&lt;span class="w"&gt; &lt;/span&gt;/etc/nginx/nginx.conf
&lt;span class="c1"&gt;# Salida esperada: nginx-1.20.1-1.el8.x86_64&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="3-obtener-informacion-detallada-de-un-paquete"&gt;3. Obtener Información Detallada de un Paquete&lt;a class="headerlink" href="#3-obtener-informacion-detallada-de-un-paquete" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Para ver información completa sobre un paquete (versión, descripción, dependencias, etc.), ya sea instalado o un archivo &lt;code&gt;.rpm&lt;/code&gt;, puedes usar &lt;code&gt;-qi&lt;/code&gt; (para paquetes instalados) o &lt;code&gt;-qip&lt;/code&gt; (para archivos de paquete):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;rpm&lt;span class="w"&gt; &lt;/span&gt;-qi&lt;span class="w"&gt; &lt;/span&gt;nombre_del_paquete_instalado
&lt;span class="c1"&gt;# Ejemplo: rpm -qi nginx&lt;/span&gt;

rpm&lt;span class="w"&gt; &lt;/span&gt;-qip&lt;span class="w"&gt; &lt;/span&gt;nombre_del_paquete.rpm
&lt;span class="c1"&gt;# Ejemplo: rpm -qip nginx-1.20.1-1.el8.x86_64.rpm&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="conclusion"&gt;Conclusión&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Dominar estas sencillas opciones del comando &lt;code&gt;rpm&lt;/code&gt; te proporciona un control mucho mayor sobre los paquetes y archivos de tu sistema Linux, facilitando la administración y la depuración. Son herramientas esenciales para cualquier administrador de sistemas o desarrollador que trabaje con distribuciones basadas en RPM.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Artículo original&lt;/em&gt;: &lt;a href="https://comulinux.blogspot.com/2010/04/como-ver-los-archivos-de-un-paquete-rpm.html"&gt;Cómo Ver los Archivos de un Paquete RPM o Deb&lt;/a&gt;&lt;/p&gt;</content><category term="Infraestructura"></category><category term="linux"></category><category term="rpm"></category><category term="paquetes"></category><category term="comandos"></category><category term="administración"></category><category term="dnf"></category><category term="yum"></category></entry><entry><title>Kooha: Captura de pantalla sencilla y elegante en Linux</title><link href="https://pablocaro.es/kooha-captura-pantalla-linux" rel="alternate"></link><published>2025-12-25T15:00:00+01:00</published><updated>2025-12-25T15:00:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2025-12-25:/kooha-captura-pantalla-linux</id><summary type="html">&lt;p&gt;Descubre Kooha, una aplicación de grabación de pantalla minimalista para Linux que combina facilidad de uso con funcionalidades esenciales.&lt;/p&gt;</summary><content type="html">&lt;p&gt;En Linux, la necesidad de grabar la pantalla de forma sencilla y eficiente es común, ya sea para crear tutoriales, presentaciones, reportes de bugs o simplemente compartir una experiencia. Aunque existen muchas herramientas, &lt;strong&gt;Kooha&lt;/strong&gt; destaca por su enfoque minimalista y su interfaz de usuario intuitiva, basada en GTK.&lt;/p&gt;
&lt;h2 id="que-es-kooha"&gt;¿Qué es Kooha?&lt;a class="headerlink" href="#que-es-kooha" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Kooha es una aplicación de grabación de pantalla gratuita y de código abierto, diseñada específicamente para entornos de escritorio Linux. Su objetivo principal es ofrecer una experiencia de usuario sin complicaciones, enfocándose en las funcionalidades esenciales de una grabadora de pantalla.&lt;/p&gt;
&lt;h2 id="por-que-elegir-kooha"&gt;¿Por qué elegir Kooha?&lt;a class="headerlink" href="#por-que-elegir-kooha" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Facilidad de Uso&lt;/strong&gt;: Su interfaz es clara y directa. No hay menús complejos ni opciones abrumadoras, lo que la hace ideal para usuarios de todos los niveles.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Diseño Minimalista&lt;/strong&gt;: Se integra perfectamente con el aspecto de GNOME y otros entornos GTK, ofreciendo una experiencia visual limpia y agradable.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Funcionalidades Clave&lt;/strong&gt;: A pesar de su simplicidad, incluye todas las opciones necesarias para la mayoría de las tareas de grabación.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Código Abierto&lt;/strong&gt;: Al ser open-source, se beneficia de la comunidad y permite a los usuarios inspeccionar y contribuir al código.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="caracteristicas-principales"&gt;Características Principales&lt;a class="headerlink" href="#caracteristicas-principales" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Grabación de Pantalla Completa&lt;/strong&gt;: Captura todo lo que ocurre en tu monitor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Grabación de Ventana Específica&lt;/strong&gt;: Elige una aplicación o ventana en particular para grabar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Grabación de Área Seleccionada&lt;/strong&gt;: Define un área rectangular en tu pantalla para la captura.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Grabación de Audio&lt;/strong&gt;: Incluye audio desde tu micrófono.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Grabación de Webcam&lt;/strong&gt;: Integra la imagen de tu cámara web en la grabación.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Formatos Comunes&lt;/strong&gt;: Soporte para formatos populares como WebM y MP4.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Opciones de Calidad&lt;/strong&gt;: Ajustes para la tasa de fotogramas (FPS) y la calidad del video.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="instalacion"&gt;Instalación&lt;a class="headerlink" href="#instalacion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;La forma más recomendada de instalar Kooha es a través de Flatpak, lo que asegura que obtendrás la última versión y todas las dependencias necesarias:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;flatpak&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;flathub&lt;span class="w"&gt; &lt;/span&gt;com.github.SeaDve.Kooha
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;También puede estar disponible en los repositorios de algunas distribuciones Linux o como paquete Snap.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusión&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Kooha es la herramienta perfecta para quienes buscan una solución de grabación de pantalla en Linux que sea potente sin ser complicada. Su elegancia y sencillez la convierten en una excelente adición al arsenal de productividad de cualquier usuario de Linux.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Repositorio oficial&lt;/em&gt;: &lt;a href="https://github.com/SeaDve/Kooha"&gt;SeaDve/Kooha en GitHub&lt;/a&gt;&lt;/p&gt;</content><category term="Herramientas"></category><category term="linux"></category><category term="kooha"></category><category term="screen-recorder"></category><category term="productividad"></category><category term="open-source"></category></entry><entry><title>OpenEvidence: La plataforma líder en información médica para profesionales</title><link href="https://pablocaro.es/open-evidence-plataforma-medica" rel="alternate"></link><published>2025-12-25T14:45:00+01:00</published><updated>2025-12-25T14:45:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2025-12-25:/open-evidence-plataforma-medica</id><summary type="html">&lt;p&gt;Explorando OpenEvidence, una plataforma líder que consolida información médica, hallazgos clínicos y guías de práctica para profesionales de la salud en EE.UU.&lt;/p&gt;</summary><content type="html">&lt;p&gt;En el ámbito de la salud, el acceso rápido y fiable a la información médica más reciente y relevante es crucial para la toma de decisiones clínicas. &lt;strong&gt;OpenEvidence&lt;/strong&gt; se posiciona como una plataforma líder diseñada para consolidar y presentar esta información de forma accesible a los profesionales sanitarios.&lt;/p&gt;
&lt;h2 id="que-ofrece-openevidence"&gt;¿Qué Ofrece OpenEvidence?&lt;a class="headerlink" href="#que-ofrece-openevidence" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;OpenEvidence actúa como un centro neurálgico para la información médica, firmando acuerdos de contenido con algunas de las publicaciones y organizaciones más prestigiosas del sector. Entre sus colaboradores se incluyen:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The New England Journal of Medicine (NEJM)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JAMA Network&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;National Comprehensive Cancer Network (NCCN)&lt;/strong&gt;, incluyendo sus guías y vías clínicas.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mayo Clinic Platform&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Esto asegura que los profesionales tengan a su alcance una fuente autorizada y actualizada de conocimientos.&lt;/p&gt;
&lt;h2 id="funcionalidades-clave-para-profesionales"&gt;Funcionalidades Clave para Profesionales&lt;a class="headerlink" href="#funcionalidades-clave-para-profesionales" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;La plataforma está diseñada para satisfacer las necesidades específicas de los profesionales de la salud, ofreciendo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Evidencia Primaria&lt;/strong&gt;: Acceso directo a los hallazgos clínicos y la investigación original.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Documentos para Pacientes&lt;/strong&gt;: Herramientas para crear folletos informativos para pacientes, facilitando la comunicación.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Exploración de Capacidades&lt;/strong&gt;: Un sistema intuitivo para navegar por una vasta biblioteca de recursos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Guías Clínicas y Vías&lt;/strong&gt;: Acceso a las últimas recomendaciones para el tratamiento y manejo de diversas condiciones.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="para-quien-es"&gt;¿Para Quién es?&lt;a class="headerlink" href="#para-quien-es" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;OpenEvidence está dirigido principalmente a &lt;strong&gt;profesionales de la salud verificados en EE.UU.&lt;/strong&gt; y se ofrece de forma gratuita para este grupo. Está disponible a través de aplicaciones para iOS y Android, lo que permite un acceso cómodo y móvil a la información.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusión&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;OpenEvidence representa un avance significativo en la democratización del acceso a la información médica de alta calidad. Al consolidar recursos de diversas fuentes autorizadas y ofrecerlos en una plataforma fácil de usar, permite a los profesionales sanitarios tomar decisiones más informadas y, en última instancia, mejorar la atención al paciente.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Página oficial&lt;/em&gt;: &lt;a href="https://www.openevidence.com/"&gt;OpenEvidence&lt;/a&gt;&lt;/p&gt;</content><category term="Linux"></category><category term="open-evidence"></category><category term="medicina"></category><category term="salud"></category><category term="informacion-medica"></category><category term="profesionales-salud"></category><category term="investigacion"></category><category term="clinica"></category></entry><entry><title>`direnv`: Gestiona tus entornos de desarrollo de forma inteligente</title><link href="https://pablocaro.es/direnv-gestion-entornos" rel="alternate"></link><published>2025-12-25T14:30:00+01:00</published><updated>2025-12-25T14:30:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2025-12-25:/direnv-gestion-entornos</id><summary type="html">&lt;p&gt;Descubre &lt;code&gt;direnv&lt;/code&gt;, una extensión de shell que carga y descarga variables de entorno automáticamente al cambiar de directorio, manteniendo tu configuración limpia y organizada.&lt;/p&gt;</summary><content type="html">&lt;p&gt;En el día a día de un desarrollador, es común trabajar con múltiples proyectos, cada uno con sus propias variables de entorno, rutas (&lt;code&gt;PATH&lt;/code&gt;), versiones de herramientas o credenciales. La gestión manual de estas variables puede llevar a configuraciones desordenadas en tu &lt;code&gt;.profile&lt;/code&gt; o &lt;code&gt;.bashrc&lt;/code&gt;, o a errores por usar el entorno incorrecto. Aquí es donde &lt;strong&gt;&lt;code&gt;direnv&lt;/code&gt;&lt;/strong&gt; brilla, ofreciendo una solución elegante y automática.&lt;/p&gt;
&lt;h2 id="que-es-direnv"&gt;¿Qué es &lt;code&gt;direnv&lt;/code&gt;?&lt;a class="headerlink" href="#que-es-direnv" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;direnv&lt;/code&gt; es una extensión para tu shell (Bash, Zsh, Fish, etc.) que detecta automáticamente cuando cambias de directorio. Cuando entras en un directorio que contiene un archivo &lt;code&gt;.envrc&lt;/code&gt;, &lt;code&gt;direnv&lt;/code&gt; lo carga; cuando sales de él, lo descarga. Esto significa que tu entorno de shell se adapta dinámicamente a tu proyecto actual, sin que tengas que hacer nada manualmente.&lt;/p&gt;
&lt;h2 id="como-funciona"&gt;¿Cómo funciona?&lt;a class="headerlink" href="#como-funciona" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;direnv&lt;/code&gt; se integra con tu shell a través de un &lt;em&gt;hook&lt;/em&gt; en el comando &lt;code&gt;cd&lt;/code&gt;. Cada vez que cambias de directorio, &lt;code&gt;direnv&lt;/code&gt; comprueba si existe un archivo &lt;code&gt;.envrc&lt;/code&gt; en el directorio actual o en alguno de sus ancestros. Si encuentra uno (y le has dado permiso para cargarlo), ejecuta los comandos definidos en ese archivo para modificar tu entorno. Al salir del directorio, revierte esos cambios, limpiando tu entorno.&lt;/p&gt;
&lt;h2 id="casos-de-uso-comunes"&gt;Casos de Uso Comunes&lt;a class="headerlink" href="#casos-de-uso-comunes" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Gestión de &lt;code&gt;PATH&lt;/code&gt;&lt;/strong&gt;: Añadir binarios específicos de un proyecto a tu &lt;code&gt;PATH&lt;/code&gt; temporalmente.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Entornos Virtuales de Python&lt;/strong&gt;: Activar automáticamente un &lt;code&gt;virtualenv&lt;/code&gt; o &lt;code&gt;uv venv&lt;/code&gt; al entrar en un proyecto Python.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Credenciales y Claves API&lt;/strong&gt;: Cargar variables de entorno con credenciales de bases de datos, claves API o tokens de forma segura (sin que queden en el historial del shell).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Variables de Entorno Específicas del Proyecto&lt;/strong&gt;: &lt;code&gt;RAILS_ENV&lt;/code&gt;, &lt;code&gt;NODE_ENV&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cambio de Versiones de Herramientas&lt;/strong&gt;: Integrar con herramientas como &lt;code&gt;nvm&lt;/code&gt; o &lt;code&gt;pyenv&lt;/code&gt; para cambiar automáticamente de versión de Node.js o Python.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="instalacion-basica-ejemplo-para-bash"&gt;Instalación Básica (ejemplo para Bash)&lt;a class="headerlink" href="#instalacion-basica-ejemplo-para-bash" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Instalar &lt;code&gt;direnv&lt;/code&gt;&lt;/strong&gt;: Puedes instalarlo desde tu gestor de paquetes (&lt;code&gt;sudo apt install direnv&lt;/code&gt;, &lt;code&gt;brew install direnv&lt;/code&gt;, etc.).&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Activar el hook en tu shell&lt;/strong&gt;: Añade la siguiente línea a tu &lt;code&gt;~/.bashrc&lt;/code&gt; (o &lt;code&gt;~/.zshrc&lt;/code&gt;):&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bash
eval "$(direnv hook bash)"&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Crear un archivo &lt;code&gt;.envrc&lt;/code&gt;&lt;/strong&gt;: En la raíz de tu proyecto, crea un archivo &lt;code&gt;.envrc&lt;/code&gt; con las variables y comandos que quieras.&lt;/p&gt;
&lt;p&gt;```bash&lt;/p&gt;
&lt;h1 id="envrc-en-la-raiz-de-tu-proyecto-python"&gt;.envrc en la raíz de tu proyecto Python&lt;a class="headerlink" href="#envrc-en-la-raiz-de-tu-proyecto-python" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;layout python  # Activa automáticamente un entorno virtual
export MY_API_KEY="super_secreto_del_proyecto"
```&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Permitir la carga&lt;/strong&gt;: La primera vez que entres al directorio con un nuevo &lt;code&gt;.envrc&lt;/code&gt;, &lt;code&gt;direnv&lt;/code&gt; te pedirá confirmación:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bash
direnv: error .envrc is blocked. Run `direnv allow` to approve its contents&lt;/code&gt;
Ejecuta &lt;code&gt;direnv allow&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="la-libreria-estandar-de-direnv"&gt;La Librería Estándar de &lt;code&gt;direnv&lt;/code&gt;&lt;a class="headerlink" href="#la-libreria-estandar-de-direnv" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;direnv&lt;/code&gt; viene con una potente librería estándar que incluye funciones predefinidas para tareas comunes, como &lt;code&gt;layout python&lt;/code&gt; (para gestionar virtualenvs), &lt;code&gt;use nix&lt;/code&gt;, &lt;code&gt;use node&lt;/code&gt;, etc., simplificando enormemente la configuración de tus &lt;code&gt;.envrc&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="beneficios-clave"&gt;Beneficios Clave&lt;a class="headerlink" href="#beneficios-clave" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Entornos limpios&lt;/strong&gt;: Tu &lt;code&gt;.profile&lt;/code&gt; permanece ordenado.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consistencia&lt;/strong&gt;: Cada proyecto tiene exactamente el entorno que necesita.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Productividad&lt;/strong&gt;: No más &lt;code&gt;source venv/bin/activate&lt;/code&gt; manual o recordar configurar variables.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;direnv&lt;/code&gt; es una herramienta pequeña pero poderosa que transforma la forma en que gestionas tus entornos de desarrollo, haciendo tu vida en la terminal mucho más eficiente y libre de errores.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Artículo original&lt;/em&gt;: &lt;a href="https://direnv.net/"&gt;&lt;code&gt;direnv&lt;/code&gt; homepage&lt;/a&gt;&lt;/p&gt;</content><category term="Herramientas"></category><category term="direnv"></category><category term="shell"></category><category term="entorno"></category><category term="productividad"></category><category term="desarrollo"></category><category term="linux"></category></entry><entry><title>`docker-http-https-echo`: Una herramienta esencial para depuración de red</title><link href="https://pablocaro.es/docker-http-https-echo" rel="alternate"></link><published>2025-12-25T14:15:00+01:00</published><updated>2025-12-25T14:15:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2025-12-25:/docker-http-https-echo</id><summary type="html">&lt;p&gt;&lt;code&gt;mendhak/docker-http-https-echo&lt;/code&gt; es un contenedor Docker simple que facilita la depuración de configuraciones de red, proxies y balanceadores de carga.&lt;/p&gt;</summary><content type="html">&lt;p&gt;En el complejo mundo de las aplicaciones distribuidas, la depuración de problemas de red, proxies, balanceadores de carga o reglas de firewall puede ser una tarea ardua. Necesitamos una forma simple de verificar qué está recibiendo un servidor. Aquí es donde una herramienta como &lt;code&gt;mendhak/docker-http-https-echo&lt;/code&gt; se vuelve indispensable.&lt;/p&gt;
&lt;h2 id="que-es-mendhakdocker-http-https-echo"&gt;¿Qué es &lt;code&gt;mendhak/docker-http-https-echo&lt;/code&gt;?&lt;a class="headerlink" href="#que-es-mendhakdocker-http-https-echo" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Es un contenedor Docker extremadamente ligero y simple que funciona como un servidor HTTP y HTTPS de "eco". Su función principal es &lt;strong&gt;responder a cualquier solicitud HTTP o HTTPS con una descripción detallada de la propia solicitud&lt;/strong&gt;. Esto incluye:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cabeceras de la solicitud (headers).&lt;/li&gt;
&lt;li&gt;Método HTTP (GET, POST, PUT, etc.).&lt;/li&gt;
&lt;li&gt;URL y parámetros de la solicitud.&lt;/li&gt;
&lt;li&gt;Cuerpo de la solicitud (body).&lt;/li&gt;
&lt;li&gt;Dirección IP remota del cliente.&lt;/li&gt;
&lt;li&gt;Información del TLS/SSL (si es HTTPS).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="por-que-es-util"&gt;¿Por qué es útil?&lt;a class="headerlink" href="#por-que-es-util" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Este tipo de servidor de eco es invaluable para una variedad de escenarios de depuración:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Depuración de Proxies y Balanceadores de Carga&lt;/strong&gt;: Verifica si un proxy inverso o un balanceador de carga está reescribiendo cabeceras, modificando URLs o pasando el cuerpo de la solicitud como esperas.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reglas de Firewall y Seguridad&lt;/strong&gt;: Confirma que el tráfico está llegando al destino correcto y que no está siendo bloqueado o alterado.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Inspección de Cabeceras HTTP/HTTPS&lt;/strong&gt;: Útil para ver exactamente qué cabeceras envía tu cliente o una aplicación específica.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pruebas de Integración&lt;/strong&gt;: Un objetivo predecible para pruebas automáticas de cómo diferentes servicios se comunican a través de HTTP/HTTPS.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Diagnóstico de Contenido Mixto&lt;/strong&gt;: Puedes usarlo para verificar si los recursos HTTP se están cargando correctamente en un contexto HTTPS.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="como-usarlo"&gt;Cómo Usarlo&lt;a class="headerlink" href="#como-usarlo" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Su uso es directo a través de Docker:&lt;/p&gt;
&lt;h3 id="para-http-puerto-8080"&gt;Para HTTP (puerto 8080)&lt;a class="headerlink" href="#para-http-puerto-8080" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;:8080&lt;span class="w"&gt; &lt;/span&gt;mendhak/http-https-echo
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Luego, puedes acceder a &lt;code&gt;http://localhost:8080&lt;/code&gt; con tu navegador o &lt;code&gt;curl&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;curl&lt;span class="w"&gt; &lt;/span&gt;http://localhost:8080/path?query&lt;span class="o"&gt;=&lt;/span&gt;value&lt;span class="w"&gt; &lt;/span&gt;-H&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;X-Custom-Header: test&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="para-https-puerto-8443"&gt;Para HTTPS (puerto 8443)&lt;a class="headerlink" href="#para-https-puerto-8443" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Para usar HTTPS, debes mapear el puerto HTTPS del contenedor. La imagen ya viene con un certificado autofirmado para pruebas.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8443&lt;/span&gt;:8443&lt;span class="w"&gt; &lt;/span&gt;mendhak/http-https-echo
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Accede a &lt;code&gt;https://localhost:8443&lt;/code&gt; (es posible que tu navegador te advierta sobre el certificado autofirmado).&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusión&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;mendhak/docker-http-https-echo&lt;/code&gt; es una de esas herramientas simples pero increíblemente útiles que todo desarrollador y operador de sistemas debería tener a mano. Facilita la comprensión de los flujos de red y acelera la depuración de problemas de conectividad en cualquier entorno basado en contenedores.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Repositorio original&lt;/em&gt;: &lt;a href="https://github.com/mendhak/docker-http-https-echo"&gt;mendhak/docker-http-https-echo en GitHub&lt;/a&gt;&lt;/p&gt;</content><category term="Herramientas"></category><category term="docker"></category><category term="http"></category><category term="https"></category><category term="depuracion"></category><category term="redes"></category><category term="contenedores"></category></entry><entry><title>Kubegres: Gestión simplificada de PostgreSQL en Kubernetes</title><link href="https://pablocaro.es/kubegres-postgresql-kubernetes" rel="alternate"></link><published>2025-12-25T14:00:00+01:00</published><updated>2025-12-25T14:00:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2025-12-25:/kubegres-postgresql-kubernetes</id><summary type="html">&lt;p&gt;Descubre Kubegres, un operador de Kubernetes que simplifica la implementación y gestión de clusters PostgreSQL altamente disponibles y escalables.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Desplegar y gestionar bases de datos relacionales en Kubernetes puede ser un desafío. Aunque los contenedores y la orquestación ofrecen flexibilidad, la persistencia de datos, la alta disponibilidad y las copias de seguridad de una base de datos como PostgreSQL requieren una consideración cuidadosa. Aquí es donde &lt;strong&gt;Kubegres&lt;/strong&gt; entra en juego.&lt;/p&gt;
&lt;h2 id="que-es-kubegres"&gt;¿Qué es Kubegres?&lt;a class="headerlink" href="#que-es-kubegres" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Kubegres es un &lt;strong&gt;operador de Kubernetes&lt;/strong&gt; que permite implementar y gestionar clusters PostgreSQL en Kubernetes de forma sencilla y eficiente. Un operador es una aplicación que extiende las funcionalidades de Kubernetes, automatizando tareas operacionales complejas que normalmente requieren intervención manual.&lt;/p&gt;
&lt;h2 id="por-que-usar-kubegres"&gt;¿Por qué usar Kubegres?&lt;a class="headerlink" href="#por-que-usar-kubegres" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Kubegres abstrae gran parte de la complejidad de ejecutar PostgreSQL en un entorno orquestado, ofreciendo funcionalidades clave como:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Alta Disponibilidad (HA)&lt;/strong&gt;: Configuración automática de replicas de PostgreSQL para garantizar la continuidad del servicio ante fallos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Replicación&lt;/strong&gt;: Soporte para replicación de streaming (Primary/Standby) para redundancia y escalabilidad de lectura.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Backups y Restauración&lt;/strong&gt;: Facilita la configuración de políticas de backup y la recuperación de datos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Escalabilidad&lt;/strong&gt;: Permite escalar las réplicas de lectura de PostgreSQL para manejar cargas de trabajo crecientes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Actualizaciones sencillas&lt;/strong&gt;: Gestiona las actualizaciones de versiones de PostgreSQL dentro del clúster.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Supervisión&lt;/strong&gt;: Integra mecanismos para monitorizar el estado del clúster.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="como-funciona"&gt;¿Cómo funciona?&lt;a class="headerlink" href="#como-funciona" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Kubegres utiliza los &lt;strong&gt;Custom Resources (CRs)&lt;/strong&gt; de Kubernetes. Esto significa que interactúas con tu clúster PostgreSQL utilizando objetos estándar de Kubernetes (YAML). Defines el estado deseado de tu clúster (número de réplicas, versión de PostgreSQL, políticas de backup) en un archivo YAML, y Kubegres se encarga de que ese estado se mantenga en todo momento.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;kubegres.reactive-tech.io/v1&lt;/span&gt;
&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Kubegres&lt;/span&gt;
&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;my-postgres&lt;/span&gt;
&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;replicas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;3&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;postgres:13.2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;database&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;10Gi&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# ... otras configuraciones para backups, etc.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="beneficios-para-desarrolladores-y-operaciones"&gt;Beneficios para desarrolladores y operaciones&lt;a class="headerlink" href="#beneficios-para-desarrolladores-y-operaciones" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Simplificación&lt;/strong&gt;: Reduce drásticamente la complejidad de operar PostgreSQL en Kubernetes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automatización&lt;/strong&gt;: Tareas como el failover, la replicación y los backups se gestionan automáticamente.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consistencia&lt;/strong&gt;: Asegura que el estado de tu base de datos siempre refleje tu configuración deseada.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DevOps Friendly&lt;/strong&gt;: Permite integrar la gestión de bases de datos en tus pipelines de CI/CD utilizando las mismas herramientas y flujos de trabajo que para el resto de tus aplicaciones en K8s.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Kubegres es una excelente opción para equipos que buscan una solución robusta y automatizada para sus necesidades de PostgreSQL en Kubernetes.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Artículo original&lt;/em&gt;: &lt;a href="https://www.kubegres.io/"&gt;Kubegres - The Kubernetes PostgreSQL operator&lt;/a&gt;&lt;/p&gt;</content><category term="Infraestructura"></category><category term="kubernetes"></category><category term="postgresql"></category><category term="kubegres"></category><category term="operador"></category><category term="base-de-datos"></category></entry><entry><title>Estados de Conexión TCP en GNU/Linux: netstat y ss</title><link href="https://pablocaro.es/estados-conexion-tcp-netstat-ss" rel="alternate"></link><published>2025-12-25T13:30:00+01:00</published><updated>2025-12-25T13:30:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2025-12-25:/estados-conexion-tcp-netstat-ss</id><summary type="html">&lt;p&gt;Comprende los diferentes estados de conexión TCP reportados por netstat y ss, desde el establecimiento hasta la terminación, para un mejor diagnóstico de red.&lt;/p&gt;</summary><content type="html">&lt;p&gt;En el mundo de GNU/Linux, herramientas como &lt;code&gt;netstat&lt;/code&gt; y &lt;code&gt;ss&lt;/code&gt; son indispensables para monitorizar y diagnosticar el estado de las conexiones de red en nuestros sistemas. Para interpretar correctamente su salida, es fundamental entender los diferentes estados por los que atraviesa una conexión TCP. Esta entrada explora esos estados, desde el establecimiento hasta la terminación de una conexión.&lt;/p&gt;
&lt;h2 id="fases-de-una-conexion-tcp"&gt;Fases de una Conexión TCP&lt;a class="headerlink" href="#fases-de-una-conexion-tcp" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Una conexión TCP se gestiona a través de tres fases principales:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Establecimiento de la Conexión&lt;/strong&gt;: Un proceso de reconocimiento (handshake) de varios pasos que inicia la conexión.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transferencia de Datos&lt;/strong&gt;: Una vez establecida, los datos se envían entre los dos puntos finales.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Terminación de la Conexión&lt;/strong&gt;: Cierra la conexión y libera los recursos asociados.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Durante estas fases, el socket de Internet (el punto final local de la comunicación) experimenta una serie de cambios de estado.&lt;/p&gt;
&lt;h2 id="estados-del-socket-tcp-explicados"&gt;Estados del Socket TCP Explicados&lt;a class="headerlink" href="#estados-del-socket-tcp-explicados" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Aquí se detallan los estados más comunes que puedes encontrar al listar conexiones con &lt;code&gt;netstat&lt;/code&gt; o &lt;code&gt;ss&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ESTABLISHED&lt;/strong&gt;: La conexión TCP está establecida y lista para transferir datos. Este es el estado deseado para una conexión activa.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SYN_SENT&lt;/strong&gt;: El socket local ha enviado un paquete SYN al servidor remoto y está esperando una respuesta SYN-ACK. Indica que se está intentando establecer una conexión.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SYN_RECV&lt;/strong&gt;: El servidor ha recibido un paquete SYN de un cliente y ha respondido con un SYN-ACK, esperando ahora un ACK del cliente. Es parte del handshake de tres vías.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FIN_WAIT1&lt;/strong&gt;: El socket local ha cerrado su lado de la conexión y ha enviado un paquete FIN, esperando un ACK del par remoto. La conexión se está cerrando activamente.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FIN_WAIT2&lt;/strong&gt;: El socket local ha recibido el ACK a su FIN, pero aún está esperando un paquete FIN del par remoto para cerrar completamente la conexión.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TIME_WAIT&lt;/strong&gt;: El socket ha esperado después de cerrar la conexión para asegurarse de que todos los paquetes han sido transmitidos y recibidos por ambos lados, y para permitir que los paquetes duplicados se extingan en la red. Es un estado de espera necesario antes de liberar completamente los recursos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CLOSE&lt;/strong&gt;: El socket no está en uso y no hay conexión.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CLOSE_WAIT&lt;/strong&gt;: El par remoto ha cerrado su lado de la conexión (ha enviado un FIN y ha sido ACKed), y el socket local está esperando que la aplicación local cierre su propio lado.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LAST_ACK&lt;/strong&gt;: El socket local ha enviado su FIN al par remoto, y está esperando el ACK final del par remoto. Esto sucede después de un &lt;code&gt;CLOSE_WAIT&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LISTEN&lt;/strong&gt;: El socket está a la espera de conexiones entrantes. Solo se ve con las opciones &lt;code&gt;--listening (-l)&lt;/code&gt; o &lt;code&gt;--all (-a)&lt;/code&gt; de &lt;code&gt;netstat&lt;/code&gt;/&lt;code&gt;ss&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CLOSING&lt;/strong&gt;: Ambos lados de la conexión han intentado cerrarse simultáneamente, y el socket local está en el proceso de enviar/recibir FIN y ACK.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UNKNOWN&lt;/strong&gt;: El estado del socket es desconocido o no se puede determinar.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="herramientas-netstat-y-ss"&gt;Herramientas: &lt;code&gt;netstat&lt;/code&gt; y &lt;code&gt;ss&lt;/code&gt;&lt;a class="headerlink" href="#herramientas-netstat-y-ss" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Puedes ver estos estados utilizando comandos como:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;netstat&lt;span class="w"&gt; &lt;/span&gt;-tn&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# Muestra conexiones TCP, sin resolución de nombres (-n)&lt;/span&gt;
ss&lt;span class="w"&gt; &lt;/span&gt;-tn&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# Versión moderna y más rápida de netstat&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;La opción &lt;code&gt;-t&lt;/code&gt; filtra para conexiones TCP y &lt;code&gt;-n&lt;/code&gt; evita la resolución de nombres, lo que acelera la salida y muestra las IPs directamente.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusión&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Entender los estados de conexión TCP es vital para diagnosticar problemas de red, identificar procesos que no responden o que están atascados, y comprender cómo se comportan las aplicaciones en términos de conectividad. Saber qué significa cada estado te permite depurar problemas de forma más efectiva en tu sistema GNU/Linux.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Artículo original&lt;/em&gt;: &lt;a href="https://www.ochobitshacenunbyte.com/2020/11/07/tipos-de-conexiones-en-netstat-y-ss-explicadas/"&gt;Tipos de Conexiones en netstat y ss Explicadas&lt;/a&gt;&lt;/p&gt;</content><category term="Infraestructura"></category><category term="linux"></category><category term="tcp"></category><category term="netstat"></category><category term="ss"></category></entry><entry><title>Reduce el Ruido en tus Logs de Python: Un enfoque inteligente</title><link href="https://pablocaro.es/reduce-ruido-logs-python" rel="alternate"></link><published>2025-12-25T13:00:00+01:00</published><updated>2025-12-25T13:00:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2025-12-25:/reduce-ruido-logs-python</id><summary type="html">&lt;p&gt;Aprende a configurar el módulo &lt;code&gt;logging&lt;/code&gt; de Python para evitar la sobrecarga de mensajes de librerías de terceros, manteniendo tus logs limpios y útiles.&lt;/p&gt;</summary><content type="html">&lt;p&gt;El módulo &lt;code&gt;logging&lt;/code&gt; de Python es una herramienta poderosa, pero puede convertirse rápidamente en una fuente de "ruido" cuando las librerías de terceros inundan tus logs con mensajes que no son relevantes para la depuración de tu aplicación. Mantener los logs limpios es crucial para identificar problemas de forma eficiente y comprender el comportamiento real de tu código.&lt;/p&gt;
&lt;h2 id="el-problema-con-basicconfig"&gt;El Problema con &lt;code&gt;basicConfig()&lt;/code&gt;&lt;a class="headerlink" href="#el-problema-con-basicconfig" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Muchos desarrolladores Python comienzan a usar &lt;code&gt;logging&lt;/code&gt; con &lt;code&gt;logging.basicConfig()&lt;/code&gt;. Si bien es conveniente, esta función configura el "root logger" (logger raíz) de tu aplicación. El problema es que &lt;strong&gt;todos los loggers de tu aplicación, incluidas las librerías de terceros, son hijos del logger raíz&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Esto significa que si configuras el logger raíz a nivel &lt;code&gt;DEBUG&lt;/code&gt;, empezarás a ver mensajes de depuración de todas las librerías que uses, lo cual puede ser abrumador y ocultar la información verdaderamente importante de tu propio código.&lt;/p&gt;
&lt;h2 id="la-solucion-recomendada-configuracion-granular"&gt;La Solución Recomendada: Configuración Granular&lt;a class="headerlink" href="#la-solucion-recomendada-configuracion-granular" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Para evitar esta sobrecarga, la estrategia consiste en tomar un control más granular de los niveles de logging.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Nombra tus Loggers:&lt;/strong&gt;
    Sigue la buena práctica de crear un logger específico para cada módulo de tu aplicación utilizando &lt;code&gt;logging.getLogger(__name__)&lt;/code&gt;. Esto crea una jerarquía de loggers que puedes controlar individualmente.&lt;/p&gt;
&lt;p&gt;```python&lt;/p&gt;
&lt;h1 id="mi_modulopy"&gt;mi_modulo.py&lt;a class="headerlink" href="#mi_modulopy" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;import logging
logger = logging.getLogger(&lt;strong&gt;name&lt;/strong&gt;)&lt;/p&gt;
&lt;p&gt;def mi_funcion():
    logger.info("Mi función se está ejecutando.")
    logger.debug("Mensaje de depuración en mi función.")
```&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configura Niveles en Loggers Específicos de la Aplicación:&lt;/strong&gt;
    En lugar de modificar el logger raíz, establece el nivel de logging en el logger de nivel más alto de tu propia aplicación. Esto permite que tus módulos registren mensajes a niveles detallados (por ejemplo, &lt;code&gt;DEBUG&lt;/code&gt;), mientras que las librerías de terceros (que seguirán siendo hijos del logger raíz no configurado, o con una configuración por defecto menos verbosa) solo mostrarán mensajes más críticos (por ejemplo, &lt;code&gt;WARNING&lt;/code&gt; o &lt;code&gt;ERROR&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;```python&lt;/p&gt;
&lt;h1 id="mainpy-o-tu-punto-de-entrada-principal"&gt;main.py (o tu punto de entrada principal)&lt;a class="headerlink" href="#mainpy-o-tu-punto-de-entrada-principal" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;import logging
from mi_paquete.mi_modulo import mi_funcion&lt;/p&gt;
&lt;h1 id="crea-un-logger-para-tu-aplicacion-principal"&gt;Crea un logger para tu aplicación principal&lt;a class="headerlink" href="#crea-un-logger-para-tu-aplicacion-principal" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;app_logger = logging.getLogger('mi_paquete') # O el nombre de tu paquete principal
app_logger.setLevel(logging.DEBUG)&lt;/p&gt;
&lt;h1 id="crea-un-handler-para-la-salida-por-ejemplo-a-consola"&gt;Crea un handler para la salida (por ejemplo, a consola)&lt;a class="headerlink" href="#crea-un-handler-para-la-salida-por-ejemplo-a-consola" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;handler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)&lt;/p&gt;
&lt;h1 id="anade-el-handler-al-logger-de-tu-aplicacion"&gt;Añade el handler al logger de tu aplicación&lt;a class="headerlink" href="#anade-el-handler-al-logger-de-tu-aplicacion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;app_logger.addHandler(handler)&lt;/p&gt;
&lt;h1 id="ahora-el-logger-raiz-y-sus-hijos-las-librerias-no-se-veran-afectados-directamente"&gt;Ahora, el logger raíz (y sus hijos, las librerías) no se verán afectados directamente&lt;a class="headerlink" href="#ahora-el-logger-raiz-y-sus-hijos-las-librerias-no-se-veran-afectados-directamente" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;h1 id="puedes-controlar-el-nivel-de-librerias-especificas-si-lo-necesitas"&gt;Puedes controlar el nivel de librerías específicas si lo necesitas&lt;a class="headerlink" href="#puedes-controlar-el-nivel-de-librerias-especificas-si-lo-necesitas" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;logging.getLogger('requests').setLevel(logging.WARNING) # Ejemplo para una librería&lt;/p&gt;
&lt;p&gt;mi_funcion()
```&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="consejo-avanzado-gestion-de-dependencias-propias"&gt;Consejo Avanzado: Gestión de Dependencias Propias&lt;a class="headerlink" href="#consejo-avanzado-gestion-de-dependencias-propias" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Para proyectos con múltiples módulos internos o dependencias de "primera parte" (que tú mismo desarrollas), puedes envolver &lt;code&gt;getLogger()&lt;/code&gt; para que siempre prefije el nombre del logger con el de tu organización o proyecto (por ejemplo, &lt;code&gt;ORG_NAME.mi_modulo&lt;/code&gt;). Esto te permite controlar el nivel de logging de todo un conjunto de loggers internos de forma más sencilla.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusión&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Reducir el ruido en tus logs no solo los hace más legibles, sino también más fiables como herramienta de depuración. Al adoptar un enfoque granular en la configuración de &lt;code&gt;logging&lt;/code&gt; de Python, aseguras que la información crítica de tu aplicación no se pierda entre un mar de mensajes de librerías de terceros.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Artículo original&lt;/em&gt;: &lt;a href="https://sinclairtarget.com/blog/2024/03/how-to-make-your-logs-less-noisy-in-python/"&gt;How to Make Your Logs Less Noisy in Python | Sinclair Target&lt;/a&gt;&lt;/p&gt;</content><category term="Programación"></category><category term="logging"></category><category term="logs"></category><category term="depuracion"></category><category term="desarrollo"></category></entry><entry><title>Kitty como terminal de acceso rápido: La comodidad de un desplegable</title><link href="https://pablocaro.es/kitty-acceso-rapido" rel="alternate"></link><published>2025-12-25T12:30:00+01:00</published><updated>2025-12-25T12:30:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2025-12-25:/kitty-acceso-rapido</id><summary type="html">&lt;p&gt;Configura Kitty para un acceso instantáneo y eficiente, combinando la potencia de un terminal moderno con la conveniencia de un desplegable.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Una de las características más apreciadas de terminales como Yakuake o Guake es su capacidad para aparecer y desaparecer instantáneamente con una sola pulsación de tecla, ofreciendo un acceso rápido sin interrumpir el flujo de trabajo. &lt;strong&gt;Kitty&lt;/strong&gt;, el terminal acelerado por GPU, también puede replicar y mejorar esta experiencia, combinando su potencia con la comodidad de un terminal desplegable.&lt;/p&gt;
&lt;h2 id="el-concepto-del-terminal-desplegable"&gt;El Concepto del Terminal Desplegable&lt;a class="headerlink" href="#el-concepto-del-terminal-desplegable" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;La idea es simple: un terminal que reside en segundo plano y se muestra u oculta a demanda. Esto es especialmente útil para comandos rápidos, monitoreo o cualquier tarea que requiera una interacción breve con la línea de comandos.&lt;/p&gt;
&lt;h2 id="kitty-para-acceso-rapido"&gt;Kitty para Acceso Rápido&lt;a class="headerlink" href="#kitty-para-acceso-rapido" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Kitty no tiene una función de "desplegable" nativa como Yakuake, pero su flexibilidad y capacidades de scripting permiten configurarlo para este propósito. La clave reside en:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Instancias de Kitty dedicadas:&lt;/strong&gt; Puedes lanzar una instancia de Kitty en un "daemon" o en segundo plano, lista para ser activada.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Control remoto:&lt;/strong&gt; Kitty ofrece un protocolo de control remoto (&lt;code&gt;kitty @&lt;/code&gt;) que permite interactuar con instancias ya abiertas.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gestor de ventanas:&lt;/strong&gt; Usar las reglas de tu gestor de ventanas (KDE, Gnome, i3, Sway, etc.) para asignar una tecla de acceso rápido que muestre u oculte la ventana de Kitty.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="configuracion-de-ejemplo"&gt;Configuración de Ejemplo&lt;a class="headerlink" href="#configuracion-de-ejemplo" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Aquí tienes un enfoque conceptual para lograr un terminal de acceso rápido con Kitty y un gestor de ventanas (por ejemplo, con &lt;code&gt;xdotool&lt;/code&gt; para entornos Xorg, o funciones similares en Wayland):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Lanzar Kitty en segundo plano:&lt;/strong&gt;
    Puedes tener una instancia de Kitty que se inicie automáticamente con tu sesión y que esté configurada para ser "flotante" y con un tamaño específico.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Script de alternancia (toggle):&lt;/strong&gt;
    Crear un pequeño script que compruebe si la ventana de Kitty "siempre visible" está activa y, si lo está, la oculte; si no lo está, la muestre.&lt;/p&gt;
&lt;p&gt;```bash&lt;/p&gt;
&lt;h1 id="binbash"&gt;!/bin/bash&lt;a class="headerlink" href="#binbash" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;KITTY_CLASS="kitty-dropdown" # Una clase personalizada para la ventana de Kitty
KITTY_ID=$(xdotool search --class "$KITTY_CLASS" | head -n 1)&lt;/p&gt;
&lt;p&gt;if [ -z "$KITTY_ID" ]; then
    # Si no existe, lanzarla
    kitty --class "$KITTY_CLASS" &amp;amp;
else
    # Si existe, alternar visibilidad
    if xdotool getwindowfocus getwindowpid | grep -q "$(xdotool getwindowpid "$KITTY_ID")"; then
        xdotool windowminimize "$KITTY_ID"
    else
        xdotool windowmap "$KITTY_ID" # Mostrar la ventana
        xdotool windowraise "$KITTY_ID" # Asegurarse de que esté al frente
        xdotool windowfocus "$KITTY_ID" # Darle el foco
    fi
fi
```&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Atajo de teclado en tu gestor de ventanas:&lt;/strong&gt;
    Asigna una tecla global (por ejemplo, &lt;code&gt;F12&lt;/code&gt;) para ejecutar este script.&lt;/p&gt;
&lt;p&gt;```&lt;/p&gt;
&lt;h1 id="ejemplo-en-configi3config-o-similar"&gt;Ejemplo en ~/.config/i3/config o similar&lt;a class="headerlink" href="#ejemplo-en-configi3config-o-similar" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;bindsym $mod+F12 exec --no-startup-id /path/to/your/toggle-kitty-dropdown.sh
```&lt;/p&gt;
&lt;p&gt;Y en tu &lt;code&gt;kitty.conf&lt;/code&gt;, puedes configurar la instancia de Kitty para que tenga la clase específica y una posición/tamaño adecuados:&lt;/p&gt;
&lt;p&gt;```&lt;/p&gt;
&lt;h1 id="kittyconf-configuracion-para-la-instancia-dropdown"&gt;kitty.conf (configuración para la instancia dropdown)&lt;a class="headerlink" href="#kittyconf-configuracion-para-la-instancia-dropdown" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;window_class_members kitty-dropdown
initial_window_width 80%
initial_window_height 40%
window_border_width 0&lt;/p&gt;
&lt;h1 id="otras-configuraciones-de-apariencia"&gt;... otras configuraciones de apariencia ...&lt;a class="headerlink" href="#otras-configuraciones-de-apariencia" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;```&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Este método te proporciona la rapidez y comodidad de un terminal desplegable, pero con toda la potencia y personalización que Kitty ofrece. ¡Es lo mejor de ambos mundos!&lt;/p&gt;</content><category term="Infraestructura"></category><category term="kitty"></category><category term="terminal"></category><category term="acceso-rapido"></category><category term="productividad"></category><category term="linux"></category><category term="atajos"></category></entry><entry><title>Atajos de teclado esenciales en Kitty: Dominando tu terminal</title><link href="https://pablocaro.es/atajos-de-teclado-kitty" rel="alternate"></link><published>2025-12-25T12:15:00+01:00</published><updated>2025-12-25T12:15:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2025-12-25:/atajos-de-teclado-kitty</id><summary type="html">&lt;p&gt;Una guía rápida a los atajos de teclado más útiles de Kitty para optimizar tu flujo de trabajo en la línea de comandos.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;Kitty&lt;/strong&gt; es un terminal que brilla por su rendimiento y su gran capacidad de personalización, pero donde realmente se potencia es en su interfaz controlada por teclado. Dominar sus atajos te permitirá navegar, gestionar y manipular tus sesiones de terminal con una eficiencia asombrosa.&lt;/p&gt;
&lt;h2 id="descubre-tus-atajos-show_shortcuts"&gt;Descubre tus atajos: &lt;code&gt;show_shortcuts&lt;/code&gt;&lt;a class="headerlink" href="#descubre-tus-atajos-show_shortcuts" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;La forma más rápida de conocer todos los atajos de teclado configurados en tu instancia de Kitty es usando su propia funcionalidad:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;kitty&lt;span class="w"&gt; &lt;/span&gt;+kitten&lt;span class="w"&gt; &lt;/span&gt;show_shortcuts
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Esto abrirá una ventana interactiva donde podrás ver todos los atajos, tanto los predeterminados como los personalizados en tu &lt;code&gt;kitty.conf&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="atajos-esenciales-para-el-dia-a-dia"&gt;Atajos Esenciales para el Día a Día&lt;a class="headerlink" href="#atajos-esenciales-para-el-dia-a-dia" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Aquí te presento algunos de los atajos más útiles y que más te ayudarán a optimizar tu flujo de trabajo:&lt;/p&gt;
&lt;h3 id="gestion-de-ventanas-y-splits-paneles"&gt;Gestión de Ventanas y Splits (Paneles)&lt;a class="headerlink" href="#gestion-de-ventanas-y-splits-paneles" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+enter&lt;/code&gt;: Abre una nueva ventana de Kitty.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+w&lt;/code&gt;: Cierra la ventana actual de Kitty.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+o&lt;/code&gt;: Divide la ventana actual verticalmente (nuevo panel a la derecha).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+e&lt;/code&gt;: Divide la ventana actual horizontalmente (nuevo panel abajo).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+f&lt;/code&gt;: Alterna entre paneles divididos (focus).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+[&lt;/code&gt; / &lt;code&gt;ctrl+shift+]&lt;/code&gt;: Navega entre paneles (izquierda/derecha).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+alt+[&lt;/code&gt; / &lt;code&gt;ctrl+shift+alt+]&lt;/code&gt;: Mueve el panel activo (izquierda/derecha).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="gestion-de-pestanas"&gt;Gestión de Pestañas&lt;a class="headerlink" href="#gestion-de-pestanas" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+t&lt;/code&gt;: Abre una nueva pestaña.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+q&lt;/code&gt;: Cierra la pestaña actual.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+.&lt;/code&gt;: Mueve a la siguiente pestaña.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+,&lt;/code&gt;: Mueve a la pestaña anterior.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+alt+.&lt;/code&gt;: Mueve la pestaña actual a la derecha.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+alt+,&lt;/code&gt;: Mueve la pestaña actual a la izquierda.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="copiar-y-pegar"&gt;Copiar y Pegar&lt;a class="headerlink" href="#copiar-y-pegar" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Kitty tiene su propia implementación de copiar/pegar, que puede ser más fiable que la del sistema cuando trabajas en entornos remotos o con multiplexores:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+c&lt;/code&gt;: Copia el texto seleccionado al portapapeles del sistema.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+v&lt;/code&gt;: Pega el texto del portapapeles del sistema.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="control-de-fuentes-y-apariencia"&gt;Control de Fuentes y Apariencia&lt;a class="headerlink" href="#control-de-fuentes-y-apariencia" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+=&lt;/code&gt; (o &lt;code&gt;+&lt;/code&gt;): Aumenta el tamaño de la fuente.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+-&lt;/code&gt;: Disminuye el tamaño de la fuente.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+backspace&lt;/code&gt;: Restablece el tamaño de la fuente al valor predeterminado.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+k&lt;/code&gt;: Entra en modo "scrollback" (para buscar o seleccionar texto histórico).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="personalizando-tus-atajos-en-kittyconf"&gt;Personalizando tus Atajos en &lt;code&gt;kitty.conf&lt;/code&gt;&lt;a class="headerlink" href="#personalizando-tus-atajos-en-kittyconf" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Puedes añadir o modificar atajos de teclado en tu archivo de configuración &lt;code&gt;~/.config/kitty/kitty.conf&lt;/code&gt; usando la directiva &lt;code&gt;map&lt;/code&gt;. Por ejemplo, para mapear &lt;code&gt;F1&lt;/code&gt; para un script personalizado:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;launch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;overlay&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bash&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;my_custom_script.sh&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Dominar estos atajos transformará tu interacción con la terminal, haciéndola más fluida e intuitiva. ¡Experimenta y personaliza Kitty para que se adapte perfectamente a tu estilo de trabajo!&lt;/p&gt;</content><category term="Infraestructura"></category><category term="kitty"></category><category term="terminal"></category><category term="atajos"></category><category term="teclado"></category><category term="productividad"></category></entry><entry><title>De Yakuake a Kitty: Mi transición a un terminal moderno</title><link href="https://pablocaro.es/de-yakuake-a-kitty" rel="alternate"></link><published>2025-12-25T12:00:00+01:00</published><updated>2025-12-25T12:00:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2025-12-25:/de-yakuake-a-kitty</id><summary type="html">&lt;p&gt;Explorando las razones y beneficios de cambiar de Yakuake a Kitty para una experiencia de terminal más potente y personalizable.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Durante años, &lt;strong&gt;Yakuake&lt;/strong&gt; ha sido mi terminal de cabecera en entornos de escritorio KDE. Su comodidad de acceso rápido, desplegándose y ocultándose con una simple pulsación de tecla, es innegable. Sin embargo, en mi búsqueda constante de optimización y rendimiento, decidí explorar alternativas, y &lt;strong&gt;Kitty&lt;/strong&gt; emergió como una opción destacada.&lt;/p&gt;
&lt;h2 id="por-que-el-cambio"&gt;¿Por qué el cambio?&lt;a class="headerlink" href="#por-que-el-cambio" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Aunque Yakuake es excelente en su nicho, Kitty ofrece una serie de ventajas que se alinean mejor con las demandas de un flujo de trabajo moderno y exigente:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Rendimiento Acelerado por GPU&lt;/strong&gt;: Kitty está diseñado para aprovechar la tarjeta gráfica, lo que resulta en un renderizado de texto extremadamente rápido y fluido, especialmente noticeable en terminales con mucho scroll o salida de datos intensa.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multiplexación Integrada&lt;/strong&gt;: A diferencia de Yakuake, que se basa en sesiones de pestañas/ventanas tradicionales, Kitty incorpora su propio sistema de multiplexación (similar a &lt;code&gt;tmux&lt;/code&gt; o &lt;code&gt;screen&lt;/code&gt;) que permite dividir ventanas, crear pestañas y organizar paneles de forma nativa y eficiente.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Configurabilidad Extensa&lt;/strong&gt;: &lt;code&gt;kitty.conf&lt;/code&gt; permite una personalización profunda, desde atajos de teclado hasta la apariencia, temas y comportamiento.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Soporte Moderno&lt;/strong&gt;: Excelente soporte para Wayland (el futuro de Linux en el escritorio), ligaduras de fuentes (como las de Fira Code), y un ecosistema de "kittens" (scripts y funcionalidades extendidas).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-platform&lt;/strong&gt;: Aunque mi uso principal es en Linux, la capacidad de tener una experiencia de terminal consistente en diferentes sistemas operativos es un plus.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="adaptandose-a-kitty"&gt;Adaptándose a Kitty&lt;a class="headerlink" href="#adaptandose-a-kitty" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;La transición requirió ajustar algunas costumbres, principalmente la forma de gestionar las sesiones y el acceso rápido. Sin embargo, las ventajas superan con creces la curva de aprendizaje inicial. La velocidad, la fluidez y la potencia de las características integradas de Kitty lo convierten en una herramienta indispensable para mi día a día.&lt;/p&gt;
&lt;p&gt;Configurar Kitty es un proceso sencillo a través de su archivo &lt;code&gt;kitty.conf&lt;/code&gt;, donde se definen todos los aspectos del terminal. Exploraremos más sobre su configuración y atajos de teclado en próximas entradas.&lt;/p&gt;
&lt;p&gt;Si buscas llevar tu experiencia de terminal al siguiente nivel, la migración a Kitty es un paso que recomiendo encarecidamente.&lt;/p&gt;</content><category term="Infraestructura"></category><category term="kitty"></category><category term="yakuake"></category><category term="terminal"></category><category term="linux"></category><category term="productividad"></category><category term="herramientas"></category></entry><entry><title>Poe the Poet: Automatizando tareas en proyectos Python con uv</title><link href="https://pablocaro.es/poe-the-poet-automatizando-tareas-en-proyectos-python-con-uv" rel="alternate"></link><published>2025-12-25T11:00:00+01:00</published><updated>2025-12-25T11:00:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2025-12-25:/poe-the-poet-automatizando-tareas-en-proyectos-python-con-uv</id><summary type="html">&lt;p&gt;Descubre cómo Poe the Poet te permite definir y ejecutar comandos personalizados directamente desde tu pyproject.toml, optimizando tu flujo de trabajo en entornos uv.&lt;/p&gt;</summary><content type="html">&lt;p&gt;La gestión de proyectos Python a menudo implica la ejecución de diversas tareas personalizadas, desde la construcción de documentación hasta la ejecución de tests o scripts auxiliares. Si bien &lt;code&gt;uv&lt;/code&gt; ha simplificado enormemente la gestión de dependencias, la forma de integrar y ejecutar estos comandos directamente desde &lt;code&gt;pyproject.toml&lt;/code&gt; ha sido un punto de debate. Aquí es donde &lt;strong&gt;Poe the Poet&lt;/strong&gt; emerge como una solución elegante y efectiva.&lt;/p&gt;
&lt;h2 id="que-es-poe-the-poet"&gt;¿Qué es Poe the Poet?&lt;a class="headerlink" href="#que-es-poe-the-poet" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Poe the Poet es una herramienta que permite definir "scripts" o "tareas" personalizadas directamente en tu archivo &lt;code&gt;pyproject.toml&lt;/code&gt;. Estas tareas pueden ser cualquier comando que quieras ejecutar en el contexto de tu entorno Python, haciendo que la automatización del flujo de trabajo sea mucho más limpia y centralizada. Resuelve el problema de tener que recurrir a &lt;code&gt;Makefile&lt;/code&gt; u otros scripts externos para tareas específicas del proyecto.&lt;/p&gt;
&lt;h2 id="como-usar-poe-the-poet-con-uv"&gt;Cómo Usar Poe the Poet con uv&lt;a class="headerlink" href="#como-usar-poe-the-poet-con-uv" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;La integración de Poe the Poet en un proyecto que usa &lt;code&gt;uv&lt;/code&gt; es sencilla:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Añadir &lt;code&gt;poethepoet&lt;/code&gt; a tus dependencias de desarrollo:&lt;/strong&gt;
    Primero, asegúrate de que &lt;code&gt;poethepoet&lt;/code&gt; esté incluido en tu grupo de dependencias de desarrollo (por ejemplo, &lt;code&gt;dev&lt;/code&gt;) en &lt;code&gt;pyproject.toml&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[project]&lt;/span&gt;
&lt;span class="c1"&gt;# ... otras configuraciones del proyecto ...&lt;/span&gt;

&lt;span class="k"&gt;[tool.uv]&lt;/span&gt;
&lt;span class="n"&gt;dev-dependencies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;black&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ruff&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;poethepoet&amp;gt;=0.38.0&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# Añade Poe the Poet aquí&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Definir tareas personalizadas:&lt;/strong&gt;
    Luego, puedes añadir una sección &lt;code&gt;[tool.poe.tasks]&lt;/code&gt; en tu &lt;code&gt;pyproject.toml&lt;/code&gt; para definir tus comandos personalizados.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[tool.poe.tasks]&lt;/span&gt;
&lt;span class="n"&gt;docs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sphinx-build -M html docs docs/_build&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;livehtml&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sphinx-autobuild -b html docs docs/_build&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;cog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cog -r docs/*.md&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="ejecutando-tareas-con-uv-run"&gt;Ejecutando Tareas con &lt;code&gt;uv run&lt;/code&gt;&lt;a class="headerlink" href="#ejecutando-tareas-con-uv-run" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Una vez que Poe the Poet está configurado y tus tareas están definidas, puedes ejecutarlas fácilmente usando &lt;code&gt;uv run&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Para construir la documentación&lt;/span&gt;
uv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;poe&lt;span class="w"&gt; &lt;/span&gt;docs

&lt;span class="c1"&gt;# Para el servidor de previsualización en vivo&lt;/span&gt;
uv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;poe&lt;span class="w"&gt; &lt;/span&gt;livehtml

&lt;span class="c1"&gt;# Para ejecutar el comando &amp;#39;cog&amp;#39;&lt;/span&gt;
uv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;poe&lt;span class="w"&gt; &lt;/span&gt;cog
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="beneficios"&gt;Beneficios&lt;a class="headerlink" href="#beneficios" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Centralización:&lt;/strong&gt; Todas tus tareas personalizadas viven en &lt;code&gt;pyproject.toml&lt;/code&gt;, junto a tus dependencias.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integración con &lt;code&gt;uv&lt;/code&gt;:&lt;/strong&gt; Las tareas se ejecutan en el entorno virtual gestionado por &lt;code&gt;uv&lt;/code&gt;, asegurando que todas las herramientas necesarias estén disponibles.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simplificación:&lt;/strong&gt; Evita la necesidad de &lt;code&gt;Makefile&lt;/code&gt; o scripts bash complejos para tareas comunes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consistencia:&lt;/strong&gt; Promueve un flujo de trabajo más consistente y fácil de compartir entre colaboradores.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Poe the Poet ofrece una forma robusta y elegante de extender las capacidades de &lt;code&gt;uv&lt;/code&gt; para la automatización de tareas en proyectos Python, mejorando la experiencia de desarrollo.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Artículo original&lt;/em&gt;: &lt;a href="https://simonwillison.net/2025/Dec/16/poe-the-poet/#atom-everything"&gt;Poe the Poet - Simon Willison's Weblog&lt;/a&gt;&lt;/p&gt;</content><category term="Programación"></category><category term="uv"></category><category term="poethepoet"></category><category term="automatizacion"></category><category term="pyproject.toml"></category></entry><entry><title>Cómo uso git worktrees para trabajo concurrente</title><link href="https://pablocaro.es/como-uso-git-worktrees" rel="alternate"></link><published>2025-07-23T06:12:00+02:00</published><updated>2025-07-23T06:12:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2025-07-23:/como-uso-git-worktrees</id><summary type="html">&lt;p class="first last"&gt;Un enfoque práctico para usar git worktrees y maximizar la productividad con múltiples contextos de trabajo simultáneos&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Aleksey Kladov comparte su enfoque único para usar git worktrees, que va más allá del simple cambio de ramas y se convierte en una metodología completa para el trabajo concurrente.&lt;/p&gt;
&lt;img alt="Estructura de worktrees" class="align-center" src="https://matklad.github.io/images/worktree-structure.png" /&gt;
&lt;div class="section" id="la-filosofia-central"&gt;
&lt;h2&gt;La filosofía central&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;Git no es un sistema de control de versiones, Git es una caja de herramientas para construir un VCS&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Esta perspectiva cambia completamente cómo pensamos sobre git. En lugar de limitarnos a las funcionalidades básicas, podemos crear nuestro propio flujo de trabajo optimizado.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="los-5-worktrees-especializados"&gt;
&lt;h2&gt;Los 5 worktrees especializados&lt;/h2&gt;
&lt;p&gt;El autor mantiene 5 worktrees, cada uno con un propósito específico:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;strong&gt;main&lt;/strong&gt; - Snapshot de solo lectura
- Reflejo limpio del main remoto
- Referencia para comparaciones
- Nunca se modifica localmente&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;work&lt;/strong&gt; - Espacio de trabajo principal
- Desarrollo activo día a día
- Commits frecuentes con mensajes mínimos
- HEAD desacoplado para máxima flexibilidad&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;review&lt;/strong&gt; - Dedicado a revisiones de código
- Contexto limpio para analizar PRs
- Sin interferir con el trabajo en curso&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;fuzz&lt;/strong&gt; - Pruebas de larga duración
- Fuzzing y tests que duran horas
- Corre en paralelo sin bloquear el desarrollo&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;scratch&lt;/strong&gt; - Tareas rápidas no relacionadas
- Experimentos
- Fixes urgentes
- Investigaciones puntuales&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Estructura típica de directorios&lt;/span&gt;
~/projects/my-project/
├──&lt;span class="w"&gt; &lt;/span&gt;main/&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# worktree principal&lt;/span&gt;
├──&lt;span class="w"&gt; &lt;/span&gt;work/&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# desarrollo activo&lt;/span&gt;
├──&lt;span class="w"&gt; &lt;/span&gt;review/&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;# revisiones&lt;/span&gt;
├──&lt;span class="w"&gt; &lt;/span&gt;fuzz/&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# testing&lt;/span&gt;
└──&lt;span class="w"&gt; &lt;/span&gt;scratch/&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="c1"&gt;# experimentos&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="ventajas-del-enfoque"&gt;
&lt;h2&gt;Ventajas del enfoque&lt;/h2&gt;
&lt;dl class="docutils"&gt;
&lt;dt&gt;&lt;strong&gt;Trabajo verdaderamente concurrente&lt;/strong&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="first last simple"&gt;
&lt;li&gt;Múltiples actividades de codificación simultáneas&lt;/li&gt;
&lt;li&gt;Sin necesidad de stash o branch switching complejo&lt;/li&gt;
&lt;li&gt;Separación limpia de contextos&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;dt&gt;&lt;strong&gt;Flujo de trabajo sin fricción&lt;/strong&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="first last simple"&gt;
&lt;li&gt;Cambio instantáneo entre tareas&lt;/li&gt;
&lt;li&gt;Commits frecuentes sin presión por mensajes perfectos&lt;/li&gt;
&lt;li&gt;Experimentación libre en HEAD desacoplado&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;dt&gt;&lt;strong&gt;Paralelización real&lt;/strong&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="first last simple"&gt;
&lt;li&gt;Fuzzing corriendo mientras desarrollas&lt;/li&gt;
&lt;li&gt;Reviews sin interrumpir el trabajo principal&lt;/li&gt;
&lt;li&gt;Tests en background sin bloqueos&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/div&gt;
&lt;div class="section" id="tecnicas-avanzadas"&gt;
&lt;h2&gt;Técnicas avanzadas&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;HEAD desacoplado estratégico&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# En el worktree &amp;#39;work&amp;#39;, trabajar sin rama&lt;/span&gt;
git&lt;span class="w"&gt; &lt;/span&gt;checkout&lt;span class="w"&gt; &lt;/span&gt;--detach&lt;span class="w"&gt; &lt;/span&gt;HEAD
&lt;span class="c1"&gt;# Commit frecuente, reorganizar después&lt;/span&gt;
git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;wip&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Scripts personalizados para operaciones comunes&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Script para crear/cambiar worktrees rápidamente&lt;/span&gt;
&lt;span class="c1"&gt;#!/bin/bash&lt;/span&gt;
git&lt;span class="w"&gt; &lt;/span&gt;worktree&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;../&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;../&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Flujo de integración&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Desde el worktree &amp;#39;work&amp;#39;&lt;/span&gt;
git&lt;span class="w"&gt; &lt;/span&gt;log&lt;span class="w"&gt; &lt;/span&gt;--oneline&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# revisar commits&lt;/span&gt;
git&lt;span class="w"&gt; &lt;/span&gt;reset&lt;span class="w"&gt; &lt;/span&gt;HEAD~5&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# deshacer commits temporales&lt;/span&gt;
git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="c1"&gt;# seleccionar cambios&lt;/span&gt;
git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="c1"&gt;# commit final limpio&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="lecciones-clave"&gt;
&lt;h2&gt;Lecciones clave&lt;/h2&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;strong&gt;Git es flexible&lt;/strong&gt;: No hay &amp;quot;una forma correcta&amp;quot; de usar git&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automatiza lo repetitivo&lt;/strong&gt;: Scripts personalizados para operaciones comunes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Separa contextos&lt;/strong&gt;: Cada tipo de trabajo merece su propio espacio&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Commits baratos&lt;/strong&gt;: Commitea frecuentemente, organiza después&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Este enfoque transforma git de una herramienta de versionado en una plataforma completa de gestión de flujo de trabajo, maximizando la productividad a través del trabajo verdaderamente concurrente.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Artículo original&lt;/em&gt;: &lt;a class="reference external" href="https://matklad.github.io/2024/07/25/git-worktrees.html"&gt;How I Use Git Worktrees&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
</content><category term="Programación"></category><category term="git"></category><category term="worktrees"></category><category term="flujo-trabajo"></category><category term="productividad"></category></entry><entry><title>Búsqueda híbrida con SQLite: vector + texto completo</title><link href="https://pablocaro.es/busqueda-hibrida-sqlite-vector-texto" rel="alternate"></link><published>2024-10-06T11:43:00+02:00</published><updated>2024-10-06T11:43:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2024-10-06:/busqueda-hibrida-sqlite-vector-texto</id><summary type="html">&lt;p class="first last"&gt;Combinando búsqueda vectorial y texto completo en SQLite usando Reciprocal Rank Fusion para obtener mejores resultados&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Simon Willison presenta una aproximación fascinante para combinar búsqueda vectorial y tradicional búsqueda de texto completo en SQLite, usando una técnica llamada &lt;strong&gt;Reciprocal Rank Fusion (RRF)&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="section" id="el-problema-central"&gt;
&lt;h2&gt;El problema central&lt;/h2&gt;
&lt;p&gt;Cuando tenemos búsquedas vectoriales (basadas en similitud semántica) y búsquedas de texto completo (FTS), cada una devuelve puntuaciones en escalas completamente diferentes:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;FTS devuelve puntuaciones de relevancia&lt;/li&gt;
&lt;li&gt;Búsqueda vectorial devuelve distancias de similitud&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;¿Cómo combinar estos resultados de manera efectiva?&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="la-solucion-reciprocal-rank-fusion"&gt;
&lt;h2&gt;La solución: Reciprocal Rank Fusion&lt;/h2&gt;
&lt;p&gt;La técnica RRF evita comparar puntuaciones incompatibles y en su lugar se basa en el &lt;strong&gt;ranking&lt;/strong&gt; de cada resultado dentro de su respectivo método de búsqueda.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(:&lt;/span&gt;&lt;span class="n"&gt;rrf_k&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;coalesce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fts_rank&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;999&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;fts_weight&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(:&lt;/span&gt;&lt;span class="n"&gt;rrf_k&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;coalesce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vec_rank&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;999&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;vec_weight&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;combined_rank&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;-- Subconsulta FTS con row_number()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;row_number&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OVER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rank&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fts_rank&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fts_search&lt;/span&gt;&lt;span class="p"&gt;(:&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fts&lt;/span&gt;
&lt;span class="k"&gt;FULL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;OUTER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;-- Subconsulta vectorial con row_number()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;row_number&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OVER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vec_rank&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vector_search&lt;/span&gt;&lt;span class="p"&gt;(:&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;combined_rank&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="ventajas-del-enfoque-hibrido"&gt;
&lt;h2&gt;Ventajas del enfoque híbrido&lt;/h2&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;strong&gt;Flexibilidad&lt;/strong&gt;: Permite ajustar pesos entre FTS y búsqueda vectorial&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Robustez&lt;/strong&gt;: Los resultados pueden aparecer en uno o ambos métodos&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Escalabilidad&lt;/strong&gt;: No requiere normalización compleja de puntuaciones&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simplicidad&lt;/strong&gt;: Una sola consulta SQL maneja toda la lógica&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="sqlite-como-plataforma-unificada"&gt;
&lt;h2&gt;SQLite como plataforma unificada&lt;/h2&gt;
&lt;p&gt;Con extensiones como &lt;a class="reference external" href="https://github.com/asg017/sqlite-vec"&gt;sqlite-vec&lt;/a&gt;, SQLite se convierte en una plataforma poderosa para:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Búsqueda de texto completo (FTS5 nativo)&lt;/li&gt;
&lt;li&gt;Búsqueda vectorial de embeddings&lt;/li&gt;
&lt;li&gt;Combinación híbrida de ambas técnicas&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Esta aproximación democratiza las técnicas avanzadas de búsqueda, haciéndolas accesibles sin infraestructura compleja.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Artículo original&lt;/em&gt;: &lt;a class="reference external" href="https://simonwillison.net/2024/Oct/4/hybrid-full-text-search-and-vector-search-with-sqlite/"&gt;Hybrid full-text search and vector search with SQLite&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
</content><category term="Programación"></category><category term="sqlite"></category><category term="búsqueda"></category><category term="vectores"></category><category term="fts"></category><category term="sql"></category></entry><entry><title>Conflación de datos geoespaciales con DuckDB y embeddings</title><link href="https://pablocaro.es/conflacion-datos-geoespaciales-duckdb" rel="alternate"></link><published>2024-10-01T07:12:00+02:00</published><updated>2024-10-01T07:12:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2024-10-01:/conflacion-datos-geoespaciales-duckdb</id><summary type="html">&lt;p class="first last"&gt;Técnicas avanzadas para integrar fuentes de datos geoespaciales usando DuckDB, H3, Ollama y modelos de embeddings&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Drew Breunig presenta un fascinante caso práctico de &lt;strong&gt;conflación de datos geoespaciales&lt;/strong&gt;: el proceso de identificar y unir registros similares de fuentes diferentes.&lt;/p&gt;
&lt;div class="section" id="el-reto-integrar-datos-de-restaurantes"&gt;
&lt;h2&gt;El reto: Integrar datos de restaurantes&lt;/h2&gt;
&lt;p&gt;El objetivo era conectar dos fuentes de datos:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;Inspecciones de restaurantes&lt;/strong&gt; del condado de Alameda&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Datos de lugares&lt;/strong&gt; de Overture Maps Foundation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Dos datasets con información similar pero estructurada de forma diferente.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="herramientas-del-stack-moderno"&gt;
&lt;h2&gt;Herramientas del stack moderno&lt;/h2&gt;
&lt;dl class="docutils"&gt;
&lt;dt&gt;&lt;strong&gt;DuckDB como motor principal&lt;/strong&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="first last simple"&gt;
&lt;li&gt;&lt;strong&gt;Staging y consultas&lt;/strong&gt; de grandes volúmenes de datos&lt;/li&gt;
&lt;li&gt;Soporte nativo para formatos geoespaciales&lt;/li&gt;
&lt;li&gt;Performance excepcional para análisis exploratorio&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;&lt;strong&gt;H3 para agrupación espacial&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;-- Agrupar lugares cercanos usando hexágonos H3&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;h3_cell_to_lat_lng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h3_latlng_to_cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;h3_9&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;places&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;dl class="docutils"&gt;
&lt;dt&gt;&lt;strong&gt;Ollama para embeddings locales&lt;/strong&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="first last simple"&gt;
&lt;li&gt;Framework de ML ejecutándose localmente&lt;/li&gt;
&lt;li&gt;Generación de embeddings contextuales&lt;/li&gt;
&lt;li&gt;Sin dependencia de APIs externas&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/div&gt;
&lt;div class="section" id="tres-enfoques-de-matching"&gt;
&lt;h2&gt;Tres enfoques de matching&lt;/h2&gt;
&lt;dl class="docutils"&gt;
&lt;dt&gt;&lt;strong&gt;1. Matching exacto por nombre&lt;/strong&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="first last simple"&gt;
&lt;li&gt;&lt;strong&gt;Resultado&lt;/strong&gt;: ~31% de coincidencias&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Limitaciones&lt;/strong&gt;: Nombres de cadenas, números de unidad en direcciones&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;&lt;strong&gt;2. Similitud de strings (Jaro-Winkler)&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;-- Comparación combinando nombre y dirección&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;jaro_winkler_similarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;levenshtein&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;address1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;address2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;Resultado&lt;/strong&gt;: ~68% de coincidencias&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Desventaja&lt;/strong&gt;: SQL complejo con muchas reglas condicionales&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. Matching basado en embeddings&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Generar descripción contextual&lt;/span&gt;
&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; at &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;Resultado&lt;/strong&gt;: ~71% de coincidencias&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ventajas&lt;/strong&gt;: Pipeline más simple, mayor flexibilidad&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Desventaja&lt;/strong&gt;: Mayor tiempo de procesamiento&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="insights-clave"&gt;
&lt;h2&gt;Insights clave&lt;/h2&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;strong&gt;No existe la bala de plata&lt;/strong&gt; - cada método tiene fortalezas específicas&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Las herramientas locales son poderosas&lt;/strong&gt; - DuckDB + Ollama permiten análisis sofisticados sin cloud&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Los embeddings son prometedores&lt;/strong&gt; - especialmente para casos con contexto complejo&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;La conflación requiere iteración&lt;/strong&gt; - combinar múltiples técnicas mejora los resultados&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="el-futuro-de-la-integracion-de-datos"&gt;
&lt;h2&gt;El futuro de la integración de datos&lt;/h2&gt;
&lt;p&gt;Este trabajo muestra cómo las herramientas modernas democratizan técnicas que antes requerían infraestructura compleja. La combinación de:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;Bases de datos analíticas&lt;/strong&gt; (DuckDB)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Índices espaciales&lt;/strong&gt; (H3)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ML local&lt;/strong&gt; (Ollama)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;...abre nuevas posibilidades para la integración inteligente de datos.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Artículo original&lt;/em&gt;: &lt;a class="reference external" href="https://www.dbreunig.com/2024/09/27/conflating-overture-points-of-interests-with-duckdb-ollama-and-more.html"&gt;Conflating Overture Places Using DuckDB, Ollama, Embeddings, and More&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
</content><category term="Programación"></category><category term="duckdb"></category><category term="geoespacial"></category><category term="embeddings"></category><category term="ollama"></category><category term="h3"></category></entry><entry><title>HTTPX: Cliente HTTP moderno para Python</title><link href="https://pablocaro.es/httpx-cliente-http-moderno-python" rel="alternate"></link><published>2024-09-23T00:07:00+02:00</published><updated>2024-09-23T00:07:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2024-09-23:/httpx-cliente-http-moderno-python</id><summary type="html">&lt;p class="first last"&gt;HTTPX emerge como el sucesor natural de requests, ofreciendo soporte async, HTTP/2 y una API moderna para aplicaciones Python contemporáneas&lt;/p&gt;
</summary><content type="html">&lt;p&gt;HTTPX se presenta como la evolución natural de la popular biblioteca &lt;tt class="docutils literal"&gt;requests&lt;/tt&gt;, manteniendo su simplicidad pero añadiendo capacidades modernas que las aplicaciones actuales necesitan.&lt;/p&gt;
&lt;div class="section" id="por-que-httpx"&gt;
&lt;h2&gt;¿Por qué HTTPX?&lt;/h2&gt;
&lt;p&gt;Mientras &lt;tt class="docutils literal"&gt;requests&lt;/tt&gt; sigue siendo excelente para uso síncrono, HTTPX aborda las limitaciones que han surgido con el tiempo:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;Sin soporte async nativo&lt;/strong&gt; en requests&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTTP/2 no disponible&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Type hints limitados&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Timeouts menos precisos&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="instalacion-y-configuracion"&gt;
&lt;h2&gt;Instalación y configuración&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Instalación básica&lt;/span&gt;
pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;httpx

&lt;span class="c1"&gt;# Con soporte HTTP/2&lt;/span&gt;
pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;httpx&lt;span class="o"&gt;[&lt;/span&gt;http2&lt;span class="o"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Con compresión avanzada&lt;/span&gt;
pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;httpx&lt;span class="o"&gt;[&lt;/span&gt;brotli,zstd&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="api-familiar-capacidades-modernas"&gt;
&lt;h2&gt;API familiar, capacidades modernas&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Uso síncrono&lt;/strong&gt; - Compatible con requests:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;httpx&lt;/span&gt;

&lt;span class="c1"&gt;# GET básico&lt;/span&gt;
&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://api.github.com/users/octocat&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 200&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;login&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  &lt;span class="c1"&gt;# &amp;#39;octocat&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;# POST con datos JSON&lt;/span&gt;
&lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;key&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;value&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://httpbin.org/post&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Headers personalizados&lt;/span&gt;
&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;User-Agent&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;my-app/1.0&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://api.example.com/data&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="el-poder-del-async-await"&gt;
&lt;h2&gt;El poder del async/await&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Cliente asíncrono&lt;/strong&gt; - La verdadera ventaja:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;httpx&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://api.github.com/users/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AsyncClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;fetch_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;octocat&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;fetch_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;gvanrossum&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;fetch_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;kennethreitz&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;login&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;public_repos&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; repos&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="funcionalidades-avanzadas"&gt;
&lt;h2&gt;Funcionalidades avanzadas&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Timeouts estrictos&lt;/strong&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Timeout granular&lt;/span&gt;
&lt;span class="n"&gt;timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AsyncClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://slow-api.example.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Streaming de datos&lt;/strong&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Descarga streaming&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AsyncClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;GET&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://example.com/bigfile.zip&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aiter_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8192&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="c1"&gt;# Procesar chunk&lt;/span&gt;
            &lt;span class="n"&gt;process_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Pruebas con aplicaciones WSGI/ASGI&lt;/strong&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Testear FastAPI directamente&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;httpx&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_root&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;message&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Hello World&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Test directo sin servidor&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://testserver&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;message&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Hello World&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="http-2-y-conexiones-persistentes"&gt;
&lt;h2&gt;HTTP/2 y conexiones persistentes&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Cliente con HTTP/2 y pool de conexiones&lt;/span&gt;
&lt;span class="n"&gt;limits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Limits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_keepalive_connections&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_connections&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AsyncClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Múltiples requests reutilizan conexiones&lt;/span&gt;
    &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://httpbin.org/get?page=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
             &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

    &lt;span class="n"&gt;responses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Completed &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; requests&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="comparativa-requests-vs-httpx"&gt;
&lt;h2&gt;Comparativa: requests vs HTTPX&lt;/h2&gt;
&lt;table border="1" class="docutils"&gt;
&lt;colgroup&gt;
&lt;col width="33%" /&gt;
&lt;col width="33%" /&gt;
&lt;col width="33%" /&gt;
&lt;/colgroup&gt;
&lt;thead valign="bottom"&gt;
&lt;tr&gt;&lt;th class="head"&gt;Característica&lt;/th&gt;
&lt;th class="head"&gt;requests&lt;/th&gt;
&lt;th class="head"&gt;HTTPX&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;API familiar&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Soporte async&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;HTTP/2&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Type hints&lt;/td&gt;
&lt;td&gt;Parcial&lt;/td&gt;
&lt;td&gt;Completo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Timeouts precisos&lt;/td&gt;
&lt;td&gt;Básico&lt;/td&gt;
&lt;td&gt;Avanzado&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Testing WSGI/ASGI&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Streaming&lt;/td&gt;
&lt;td&gt;Básico&lt;/td&gt;
&lt;td&gt;Avanzado&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class="section" id="casos-de-uso-ideales"&gt;
&lt;h2&gt;Casos de uso ideales&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;HTTPX es perfecto para&lt;/strong&gt;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;APIs concurrentes&lt;/strong&gt; con async/await&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Microservicios&lt;/strong&gt; que necesitan HTTP/2&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Testing de aplicaciones&lt;/strong&gt; FastAPI/Django&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scraping a gran escala&lt;/strong&gt; con streaming&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Aplicaciones modernas&lt;/strong&gt; con type safety&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Mantén requests para&lt;/strong&gt;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;Scripts simples&lt;/strong&gt; sin concurrencia&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Proyectos legacy&lt;/strong&gt; ya establecidos&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Código con dependencias&lt;/strong&gt; que requieren requests&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="el-futuro-de-http-en-python"&gt;
&lt;h2&gt;El futuro de HTTP en Python&lt;/h2&gt;
&lt;p&gt;HTTPX representa el camino hacia adelante para aplicaciones HTTP en Python, ofreciendo:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;Performance moderna&lt;/strong&gt; con async y HTTP/2&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Developer experience&lt;/strong&gt; mejorada con tipos&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ecosistema integrado&lt;/strong&gt; con frameworks actuales&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compatibilidad&lt;/strong&gt; que facilita migración&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Es hora de considerar HTTPX como el cliente HTTP por defecto para nuevos proyectos Python.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Documentación oficial&lt;/em&gt;: &lt;a class="reference external" href="https://www.python-httpx.org/"&gt;python-httpx.org&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
</content><category term="Programación"></category><category term="http"></category><category term="httpx"></category><category term="async"></category><category term="requests"></category></entry><entry><title>Server-Sent Events: Comunicación en tiempo real simplificada</title><link href="https://pablocaro.es/server-sent-events-comunicacion-tiempo-real" rel="alternate"></link><published>2024-09-23T00:01:00+02:00</published><updated>2024-09-23T00:01:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2024-09-23:/server-sent-events-comunicacion-tiempo-real</id><summary type="html">&lt;p&gt;Server-Sent Events ofrece una alternativa simple y eficiente a WebSockets para comunicación unidireccional servidor-cliente en aplicaciones web&lt;/p&gt;</summary><content type="html">&lt;p&gt;Los &lt;strong&gt;Server-Sent Events (SSE)&lt;/strong&gt; representan una solución elegante y simple para implementar comunicación en tiempo real en aplicaciones web cuando solo necesitamos que el servidor envíe datos al cliente.&lt;/p&gt;
&lt;h2 id="que-son-los-server-sent-events"&gt;¿Qué son los Server-Sent Events?&lt;a class="headerlink" href="#que-son-los-server-sent-events" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;SSE es un estándar web que permite que un servidor envíe datos automáticamente a una página web usando una conexión HTTP persistente. A diferencia de WebSockets, la comunicación es &lt;strong&gt;unidireccional&lt;/strong&gt;: solo el servidor puede enviar mensajes al cliente.&lt;/p&gt;
&lt;h2 id="implementacion-basica"&gt;Implementación básica&lt;a class="headerlink" href="#implementacion-basica" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="cliente-javascript"&gt;Cliente (JavaScript)&lt;a class="headerlink" href="#cliente-javascript" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// Crear conexión SSE&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;eventSource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;EventSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/api/events&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Escuchar mensajes genéricos&lt;/span&gt;
&lt;span class="nx"&gt;eventSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Mensaje recibido:&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;updateUI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Escuchar eventos personalizados&lt;/span&gt;
&lt;span class="nx"&gt;eventSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;notification&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;notification&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;showNotification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Manejar errores&lt;/span&gt;
&lt;span class="nx"&gt;eventSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onerror&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Error en SSE:&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Cerrar conexión&lt;/span&gt;
&lt;span class="c1"&gt;// eventSource.close();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="servidor-pythonflask"&gt;Servidor (Python/Flask)&lt;a class="headerlink" href="#servidor-pythonflask" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;threading&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_events&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Generador que produce eventos SSE&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Evento genérico&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;timestamp&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;users_online&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;get_users_count&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;server_status&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;running&amp;#39;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Enviar cada 10 segundos&lt;/span&gt;

&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/api/events&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;stream_events&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Endpoint SSE&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;generate_events&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;mimetype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text/event-stream&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;Cache-Control&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;no-cache&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;Connection&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;keep-alive&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;Access-Control-Allow-Origin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;*&amp;#39;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/api/notify&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_notification&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Enviar notificación específica&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;notification_event&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;notification&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;title&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Nueva actualización&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;message&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;El sistema se actualizó correctamente&amp;#39;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;# Evento con nombre personalizado&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;event: notification&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;notification_event&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;mimetype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text/event-stream&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="formato-del-stream-de-eventos"&gt;Formato del stream de eventos&lt;a class="headerlink" href="#formato-del-stream-de-eventos" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Los eventos SSE siguen un formato específico:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;key&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;value&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;unique&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="n"&gt;retry&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;event&lt;/strong&gt;: Nombre del evento (opcional)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;data&lt;/strong&gt;: Contenido del mensaje&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;id&lt;/strong&gt;: Identificador único para reconexión&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;retry&lt;/strong&gt;: Tiempo de reintento en milisegundos&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="casos-de-uso-ideales"&gt;Casos de uso ideales&lt;a class="headerlink" href="#casos-de-uso-ideales" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="1-feeds-en-tiempo-real"&gt;1. Feeds en tiempo real&lt;a class="headerlink" href="#1-feeds-en-tiempo-real" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newsSource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;EventSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/api/news-feed&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;newsSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;article&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;addArticleToFeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="2-notificaciones-push"&gt;2. Notificaciones push&lt;a class="headerlink" href="#2-notificaciones-push" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;notificationSource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;EventSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/api/notifications&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;notificationSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;notification&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Mostrar notificación del navegador&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;permission&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;granted&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Notification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;icon&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/icon.png&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="3-dashboard-en-tiempo-real"&gt;3. Dashboard en tiempo real&lt;a class="headerlink" href="#3-dashboard-en-tiempo-real" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dashboardSource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;EventSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/api/dashboard&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;dashboardSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;metrics&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;metrics&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;updateCharts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;updateCounters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;dashboardSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;alert&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;showAlert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="ventajas-de-sse"&gt;Ventajas de SSE&lt;a class="headerlink" href="#ventajas-de-sse" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Simplicidad&lt;/strong&gt;: API más simple que WebSockets&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reconexión automática&lt;/strong&gt;: El navegador reintenta automáticamente&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Eficiencia&lt;/strong&gt;: Menor overhead que polling&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compatibilidad&lt;/strong&gt;: Funciona sobre HTTP/HTTPS estándar&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Firewall-friendly&lt;/strong&gt;: No requiere puertos especiales&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="limitaciones-importantes"&gt;Limitaciones importantes&lt;a class="headerlink" href="#limitaciones-importantes" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Unidireccional&lt;/strong&gt;: Solo servidor → cliente&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Límites de conexión&lt;/strong&gt;: Navegadores limitan conexiones simultáneas&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Formato de datos&lt;/strong&gt;: Solo texto (aunque JSON funciona bien)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sin compresión nativa&lt;/strong&gt;: A diferencia de WebSockets&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="sse-vs-websockets-cuando-usar-cada-uno"&gt;SSE vs WebSockets: ¿Cuándo usar cada uno?&lt;a class="headerlink" href="#sse-vs-websockets-cuando-usar-cada-uno" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="usa-sse-cuando"&gt;Usa SSE cuando:&lt;a class="headerlink" href="#usa-sse-cuando" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Solo necesitas servidor → cliente&lt;/li&gt;
&lt;li&gt;Simplicidad es prioritaria  &lt;/li&gt;
&lt;li&gt;Actualizaciones periódicas (feeds, notificaciones)&lt;/li&gt;
&lt;li&gt;Compatibilidad con proxies/firewalls es importante&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="usa-websockets-cuando"&gt;Usa WebSockets cuando:&lt;a class="headerlink" href="#usa-websockets-cuando" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Necesitas comunicación bidireccional&lt;/li&gt;
&lt;li&gt;Latencia ultra-baja es crítica&lt;/li&gt;
&lt;li&gt;Intercambio intensivo de datos&lt;/li&gt;
&lt;li&gt;Control total sobre el protocolo&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="manejo-de-errores-y-reconexion"&gt;Manejo de errores y reconexión&lt;a class="headerlink" href="#manejo-de-errores-y-reconexion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;eventSource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;EventSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/api/events&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;reconnectInterval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;maxReconnectInterval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;30000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;eventSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onopen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Conexión SSE establecida&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;reconnectInterval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Reset interval&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;eventSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onerror&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Error SSE:&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eventSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readyState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;EventSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CLOSED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Reconexión manual con backoff exponencial&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Intentando reconectar...&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;reconnectSSE&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;reconnectInterval&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;reconnectInterval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reconnectInterval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;maxReconnectInterval&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;reconnectSSE&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;eventSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;eventSource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;EventSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/api/events&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="conclusion"&gt;Conclusión&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Server-Sent Events ofrece una solución pragmática para comunicación en tiempo real cuando WebSockets resulta excesivo. Su simplicidad, reconexión automática y compatibilidad lo convierten en una opción excelente para feeds, notificaciones y dashboards en tiempo real.&lt;/p&gt;
&lt;p&gt;Para aplicaciones que requieren actualizaciones del servidor al cliente sin la complejidad de WebSockets, SSE es la herramienta perfecta.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Documentación oficial&lt;/em&gt;: &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events"&gt;Using server-sent events - MDN&lt;/a&gt;&lt;/p&gt;</content><category term="Programación"></category><category term="javascript"></category><category term="sse"></category><category term="tiempo-real"></category><category term="web-apis"></category></entry><entry><title>Una de versiones</title><link href="https://pablocaro.es/una-de-versiones" rel="alternate"></link><published>2017-05-01T18:04:00+02:00</published><updated>2017-05-01T18:04:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2017-05-01:/una-de-versiones</id><summary type="html"></summary><content type="html">&lt;p&gt;¿Quieres saber la forma correcta de nombrar las versiones? Para eso está la &lt;a class="reference external" href="https://www.python.org/dev/peps/pep-0440/"&gt;PEP 440&lt;/a&gt;: Version Identification and Dependency Specification.&lt;/p&gt;
&lt;p&gt;Quizá lo más digno de anotar sea el eso de esos sufijos .dev0, etc. para los eggs en desarrollo.&lt;/p&gt;
</content><category term="Programación"></category></entry><entry><title>MobaXterm. Mucho más que un cliente ssh</title><link href="https://pablocaro.es/mobaxterm-mucho-m%C3%A1s-que-un-cliente-ssh" rel="alternate"></link><published>2017-04-11T14:07:00+02:00</published><updated>2017-04-11T14:07:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2017-04-11:/mobaxterm-mucho-más-que-un-cliente-ssh</id><summary type="html"></summary><content type="html">&lt;p&gt;Como sabéis, no soy de Windows. Linuxero desde hace años, tras pasar por múltiples sabores de Debian, ahora lo mío es &lt;a class="reference external" href="https://es.opensuse.org/Portal:Tumbleweed"&gt;OpenSuse Tumbleweed&lt;/a&gt;. Pero también tengo un equipo de trabajo Windows 7 que uso ocasionalmente.
Para eso viene muy bien &lt;a class="reference external" href="http://mobaxterm.mobatek.net/"&gt;MobaXterm&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://mobaxterm.mobatek.net/"&gt;MobaXterm&lt;/a&gt; es un conjunto de aplicaciones muy interesantes para trabajar como en Linux y conectarte a equipos remotos:
terminal, cliente ssh, servidor X, rdp, vnc, etc.&lt;/p&gt;
</content><category term="Herramientas"></category><category term="linux"></category></entry><entry><title>PosteRazor: Crea pósters gigantes desde cualquier imagen</title><link href="https://pablocaro.es/posterazor-crear-posters-grandes" rel="alternate"></link><published>2017-02-24T20:06:00+01:00</published><updated>2017-02-24T20:06:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2017-02-24:/posterazor-crear-posters-grandes</id><summary type="html">&lt;p&gt;PosteRazor convierte cualquier imagen en un póster gigante dividiendo la imagen en páginas imprimibles que puedes ensamblar&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;PosteRazor&lt;/strong&gt; es una herramienta gratuita y open source que resuelve un problema cotidiano: &lt;strong&gt;¿cómo imprimir una imagen grande cuando solo tienes una impresora doméstica?&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="que-hace-posterazor"&gt;¿Qué hace PosteRazor?&lt;a class="headerlink" href="#que-hace-posterazor" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;PosteRazor toma una imagen raster y la &lt;strong&gt;"corta" en piezas&lt;/strong&gt; que después pueden imprimirse y ensamblarse para formar un póster completo. Convierte una sola imagen en un documento PDF multipágina listo para imprimir.&lt;/p&gt;
&lt;h2 id="caracteristicas-principales"&gt;Características principales&lt;a class="headerlink" href="#caracteristicas-principales" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Interfaz tipo asistente&lt;/strong&gt; con 5 pasos sencillos&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multiplataforma&lt;/strong&gt;: Windows, macOS, Linux&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Versión web&lt;/strong&gt; disponible via WebAssembly&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Completamente gratuito&lt;/strong&gt; y open source&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="casos-de-uso-ideales"&gt;Casos de uso ideales&lt;a class="headerlink" href="#casos-de-uso-ideales" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pósters decorativos&lt;/strong&gt; para casa, escuela u oficina&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Proyectos DIY&lt;/strong&gt; de decoración&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Imprimir imágenes gigantes&lt;/strong&gt; que exceden el tamaño de página estándar&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Presentaciones visuales&lt;/strong&gt; de gran formato&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="como-funciona"&gt;¿Cómo funciona?&lt;a class="headerlink" href="#como-funciona" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Cargas tu imagen&lt;/li&gt;
&lt;li&gt;Seleccionas el tamaño del póster deseado&lt;/li&gt;
&lt;li&gt;Configuras el formato de página de tu impresora&lt;/li&gt;
&lt;li&gt;PosteRazor calcula la división automáticamente&lt;/li&gt;
&lt;li&gt;Generas el PDF e imprimes cada página&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="la-solucion-perfecta"&gt;La solución perfecta&lt;a class="headerlink" href="#la-solucion-perfecta" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;PosteRazor democratiza la creación de pósters grandes. Ya no necesitas servicios de impresión costosos - &lt;strong&gt;con cualquier impresora doméstica puedes crear pósters del tamaño que quieras&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Sitio oficial&lt;/em&gt;: &lt;a href="https://posterazor.sourceforge.io/"&gt;PosteRazor&lt;/a&gt;&lt;/p&gt;</content><category term="Herramientas"></category><category term="posterazor"></category><category term="impresión"></category><category term="posters"></category><category term="diy"></category></entry><entry><title>Correo no leído en pestaña principal de gmail</title><link href="https://pablocaro.es/gmail_correo_no_leido_solo_en_principal" rel="alternate"></link><published>2016-07-28T18:00:00+02:00</published><updated>2016-07-28T18:00:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2016-07-28:/gmail_correo_no_leido_solo_en_principal</id><summary type="html">&lt;p&gt;Lo de siempre, otra nota recordatoria.&lt;/p&gt;
&lt;p&gt;Si filtras el correo en gmail por no leído (&lt;code&gt;is:unread&lt;/code&gt;), obtienes todo el correo no leído,
que en mi caso siempre incluye cientos de notificaciones, social, promociones o foros que no suele ser lo que busco.&lt;/p&gt;
&lt;p&gt;Para buscar los correos no leídos &lt;em&gt;de …&lt;/em&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;Lo de siempre, otra nota recordatoria.&lt;/p&gt;
&lt;p&gt;Si filtras el correo en gmail por no leído (&lt;code&gt;is:unread&lt;/code&gt;), obtienes todo el correo no leído,
que en mi caso siempre incluye cientos de notificaciones, social, promociones o foros que no suele ser lo que busco.&lt;/p&gt;
&lt;p&gt;Para buscar los correos no leídos &lt;em&gt;de la pestaña Principal&lt;/em&gt; la consulta a realizar es esta:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
is:inbox -category:(updates OR promotions OR social OR forums) is:unread
&lt;/pre&gt;
</content><category term="Herramientas"></category><category term="linux"></category></entry><entry><title>Documentación técnica con Sphinx, Paver y Cog</title><link href="https://pablocaro.es/documentacion-tecnica-sphinx-paver-cog" rel="alternate"></link><published>2016-07-14T00:47:00+02:00</published><updated>2016-07-14T00:47:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2016-07-14:/documentacion-tecnica-sphinx-paver-cog</id><summary type="html">&lt;p&gt;Workflow automatizado para crear documentación técnica usando Sphinx, Paver y Cog, eliminando tareas repetitivas y errores manuales en ejemplos de código&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;Doug Hellmann&lt;/strong&gt; presenta un workflow robusto para documentación técnica que elimina el trabajo manual repetitivo y los errores en ejemplos de código.&lt;/p&gt;
&lt;h2 id="el-problema-a-resolver"&gt;El problema a resolver&lt;a class="headerlink" href="#el-problema-a-resolver" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;"Automation is important for my sense of well being. I hate dealing with mundane repetitive tasks."&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Desafíos comunes&lt;/strong&gt;:
- Mantener &lt;strong&gt;ejemplos de código actualizados&lt;/strong&gt;
- &lt;strong&gt;Múltiples formatos&lt;/strong&gt; de salida (HTML, PDF, blog)
- &lt;strong&gt;Tareas repetitivas&lt;/strong&gt; de construcción y publicación
- &lt;strong&gt;Errores de copy-paste&lt;/strong&gt; en output de programas&lt;/p&gt;
&lt;h2 id="la-solucion-trio-de-herramientas"&gt;La solución: Trio de herramientas&lt;a class="headerlink" href="#la-solucion-trio-de-herramientas" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="sphinx-motor-de-documentacion"&gt;Sphinx: Motor de documentación&lt;a class="headerlink" href="#sphinx-motor-de-documentacion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Conversión reStructuredText&lt;/strong&gt; a múltiples formatos&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Temas personalizables&lt;/strong&gt; con Jinja templates&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Generación automática&lt;/strong&gt; de índices y referencias cruzadas&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="paver-automatizacion-de-builds"&gt;Paver: Automatización de builds&lt;a class="headerlink" href="#paver-automatizacion-de-builds" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# pavement.py&lt;/span&gt;
&lt;span class="nd"&gt;@task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;html&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Build HTML documentation&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;call_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cog&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sphinx_build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@task&lt;/span&gt;  
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Build PDF documentation&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;call_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cog&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sphinx_build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;latex&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;make_pdf&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Resuelve&lt;/strong&gt;: Automatización de procesos repetitivos de construcción&lt;/p&gt;
&lt;h3 id="cog-insercion-automatica-de-codigo"&gt;Cog: Inserción automática de código&lt;a class="headerlink" href="#cog-insercion-automatica-de-codigo" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;..&lt;/span&gt; &lt;span class="ow"&gt;cog&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;

   import cog
   import subprocess

   result = subprocess.run([&amp;#39;python&amp;#39;, &amp;#39;ejemplo.py&amp;#39;], 
                          capture_output=True, text=True)
   cog.out(result.stdout)

&lt;span class="p"&gt;..&lt;/span&gt; &lt;span class="ow"&gt;cog&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Ventaja clave&lt;/strong&gt;: El output de programas se &lt;strong&gt;actualiza automáticamente&lt;/strong&gt; al regenerar la documentación.&lt;/p&gt;
&lt;h2 id="workflow-de-produccion"&gt;Workflow de producción&lt;a class="headerlink" href="#workflow-de-produccion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="1-escritura-en-restructuredtext"&gt;1. Escritura en reStructuredText&lt;a class="headerlink" href="#1-escritura-en-restructuredtext" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gh"&gt;Ejemplo de uso&lt;/span&gt;
&lt;span class="gh"&gt;==============&lt;/span&gt;

Ejecutamos el script:

&lt;span class="p"&gt;..&lt;/span&gt; &lt;span class="ow"&gt;cog&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;
   cog.out(&amp;quot;$ python mi_script.py\n&amp;quot;)
   result = subprocess.run([&amp;#39;python&amp;#39;, &amp;#39;mi_script.py&amp;#39;], 
                          capture_output=True, text=True)
   cog.out(result.stdout)
&lt;span class="p"&gt;..&lt;/span&gt; &lt;span class="ow"&gt;cog&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="2-construccion-automatizada"&gt;2. Construcción automatizada&lt;a class="headerlink" href="#2-construccion-automatizada" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Un solo comando para todo&lt;/span&gt;
paver&lt;span class="w"&gt; &lt;/span&gt;html

&lt;span class="c1"&gt;# O para múltiples destinos&lt;/span&gt;
paver&lt;span class="w"&gt; &lt;/span&gt;all_formats
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="3-publicacion-multi-destino"&gt;3. Publicación multi-destino&lt;a class="headerlink" href="#3-publicacion-multi-destino" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Python Module of the Week (PyMOTW)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Blog personal&lt;/strong&gt; &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sitio web del proyecto&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;O'Reilly blog&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Documentación PDF&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="ventajas-del-enfoque"&gt;Ventajas del enfoque&lt;a class="headerlink" href="#ventajas-del-enfoque" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="consistencia-garantizada"&gt;Consistencia garantizada&lt;a class="headerlink" href="#consistencia-garantizada" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Ejemplos siempre actualizados&lt;/strong&gt; con el código real&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Formato uniforme&lt;/strong&gt; across múltiples destinos&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sin errores de transcripción&lt;/strong&gt; manual&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="eficiencia-maximizada"&gt;Eficiencia maximizada&lt;a class="headerlink" href="#eficiencia-maximizada" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Un source, múltiples outputs&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automatización completa&lt;/strong&gt; del pipeline&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Focus en contenido&lt;/strong&gt;, no en proceso&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="mantenibilidad"&gt;Mantenibilidad&lt;a class="headerlink" href="#mantenibilidad" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cambios en una sola fuente&lt;/strong&gt; se propagan automáticamente&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Testing integrado&lt;/strong&gt; de ejemplos de código&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Versionado único&lt;/strong&gt; para toda la documentación&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="implementacion-practica"&gt;Implementación práctica&lt;a class="headerlink" href="#implementacion-practica" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# pavement.py completo&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;paver.easy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;paver.doctools&lt;/span&gt;

&lt;span class="nd"&gt;@task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cog&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Run Cog to update code examples&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;sh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cog -r *.rst&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@task&lt;/span&gt;
&lt;span class="nd"&gt;@needs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cog&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;html&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Build HTML docs&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;paver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctools&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@task&lt;/span&gt;
&lt;span class="nd"&gt;@needs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cog&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;blog_post&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Generate blog post version&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="c1"&gt;# Custom blog formatting&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Este workflow representa la &lt;strong&gt;automatización inteligente&lt;/strong&gt; de documentación técnica: escribes una vez, publicas en todas partes, siempre actualizado.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Fuente original&lt;/em&gt;: &lt;a href="https://doughellmann.com/blog/2009/02/02/writing-technical-documentation-with-sphinx-paver-and-cog/"&gt;Doug Hellmann&lt;/a&gt;&lt;/p&gt;</content><category term="Documentación"></category><category term="sphinx"></category><category term="paver"></category><category term="cog"></category><category term="documentación"></category><category term="rst"></category><category term="automation"></category></entry><entry><title>Determinar slots de RAM en uso en Linux</title><link href="https://pablocaro.es/determinar-slots-ram-uso-linux" rel="alternate"></link><published>2016-07-13T20:28:00+02:00</published><updated>2016-07-13T20:28:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2016-07-13:/determinar-slots-ram-uso-linux</id><summary type="html">&lt;p&gt;Comandos de Linux para determinar cuántos slots de RAM están ocupados y su capacidad usando dmidecode, lshw y técnicas de inspección de hardware&lt;/p&gt;</summary><content type="html">&lt;p&gt;¿Cuántos &lt;strong&gt;slots de RAM tienes ocupados&lt;/strong&gt; y cuántos libres? Linux proporciona herramientas para inspeccionar la configuración de memoria sin abrir el ordenador.&lt;/p&gt;
&lt;h2 id="dmidecode-la-herramienta-principal"&gt;dmidecode: La herramienta principal&lt;a class="headerlink" href="#dmidecode-la-herramienta-principal" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="informacion-basica-de-memoria"&gt;Información básica de memoria&lt;a class="headerlink" href="#informacion-basica-de-memoria" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Ver toda la información de memoria&lt;/span&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;dmidecode&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;memory

&lt;span class="c1"&gt;# Tipo específico de memoria (tabla 16)&lt;/span&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;dmidecode&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;16&lt;/span&gt;

&lt;span class="c1"&gt;# Solo los tamaños instalados&lt;/span&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;dmidecode&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;memory&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;size
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Salida típica&lt;/strong&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;Size&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8192&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MB&lt;/span&gt;
&lt;span class="n"&gt;Size&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8192&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MB&lt;/span&gt;
&lt;span class="n"&gt;Size&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;No&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Installed&lt;/span&gt;
&lt;span class="n"&gt;Size&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;No&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Installed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="informacion-detallada-por-slot"&gt;Información detallada por slot&lt;a class="headerlink" href="#informacion-detallada-por-slot" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Información granular de cada slot (tabla 17)&lt;/span&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;dmidecode&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;

&lt;span class="c1"&gt;# Resumen compacto&lt;/span&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;dmidecode&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;-E&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;(Size|Locator|Speed|Type:)&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Información proporcionada&lt;/strong&gt;:
- &lt;strong&gt;Locator&lt;/strong&gt;: Identificador físico del slot (DIMM_A1, DIMM_B1, etc.)
- &lt;strong&gt;Size&lt;/strong&gt;: Capacidad instalada o "No Module Installed"
- &lt;strong&gt;Type&lt;/strong&gt;: DDR3, DDR4, etc.
- &lt;strong&gt;Speed&lt;/strong&gt;: Velocidad en MT/s&lt;/p&gt;
&lt;h2 id="lshw-alternativa-con-formato-estructurado"&gt;lshw: Alternativa con formato estructurado&lt;a class="headerlink" href="#lshw-alternativa-con-formato-estructurado" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Información de memoria con lshw&lt;/span&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;lshw&lt;span class="w"&gt; &lt;/span&gt;-class&lt;span class="w"&gt; &lt;/span&gt;memory

&lt;span class="c1"&gt;# Solo memoria física (sin caché)&lt;/span&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;lshw&lt;span class="w"&gt; &lt;/span&gt;-class&lt;span class="w"&gt; &lt;/span&gt;memory&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;-A&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;System Memory&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# Formato más compacto&lt;/span&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;lshw&lt;span class="w"&gt; &lt;/span&gt;-short&lt;span class="w"&gt; &lt;/span&gt;-class&lt;span class="w"&gt; &lt;/span&gt;memory
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="scripts-utiles-para-analisis"&gt;Scripts útiles para análisis&lt;a class="headerlink" href="#scripts-utiles-para-analisis" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="contador-de-slots-ocupados"&gt;Contador de slots ocupados&lt;a class="headerlink" href="#contador-de-slots-ocupados" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;=== Análisis de slots de RAM ===&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;TOTAL_SLOTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;dmidecode&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Size:&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;wc&lt;span class="w"&gt; &lt;/span&gt;-l&lt;span class="k"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;OCCUPIED_SLOTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;dmidecode&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Size:&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;No Module&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;wc&lt;span class="w"&gt; &lt;/span&gt;-l&lt;span class="k"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;FREE_SLOTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;&lt;span class="nv"&gt;TOTAL_SLOTS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;OCCUPIED_SLOTS&lt;/span&gt;&lt;span class="k"&gt;))&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Slots totales: &lt;/span&gt;&lt;span class="nv"&gt;$TOTAL_SLOTS&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Slots ocupados: &lt;/span&gt;&lt;span class="nv"&gt;$OCCUPIED_SLOTS&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Slots libres: &lt;/span&gt;&lt;span class="nv"&gt;$FREE_SLOTS&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="resumen-detallado"&gt;Resumen detallado&lt;a class="headerlink" href="#resumen-detallado" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;=== Configuración actual de RAM ===&amp;quot;&lt;/span&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;dmidecode&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;awk&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;
&lt;span class="s1"&gt;/Memory Device/,/^$/ {&lt;/span&gt;
&lt;span class="s1"&gt;    if(/Locator:/) locator=$2&lt;/span&gt;
&lt;span class="s1"&gt;    if(/Size:/ &amp;amp;&amp;amp; !/No Module/) {&lt;/span&gt;
&lt;span class="s1"&gt;        size=$2&amp;quot; &amp;quot;$3&lt;/span&gt;
&lt;span class="s1"&gt;        print locator&amp;quot;: &amp;quot;size&lt;/span&gt;
&lt;span class="s1"&gt;    }&lt;/span&gt;
&lt;span class="s1"&gt;    if(/Size:.*No Module/) print locator&amp;quot;: Vacío&amp;quot;&lt;/span&gt;
&lt;span class="s1"&gt;}&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="informacion-adicional-util"&gt;Información adicional útil&lt;a class="headerlink" href="#informacion-adicional-util" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="capacidad-maxima-soportada"&gt;Capacidad máxima soportada&lt;a class="headerlink" href="#capacidad-maxima-soportada" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Máxima capacidad total&lt;/span&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;dmidecode&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;16&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Maximum Capacity&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# Número máximo de dispositivos&lt;/span&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;dmidecode&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;16&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Number Of Devices&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="verificacion-de-velocidad-y-tipo"&gt;Verificación de velocidad y tipo&lt;a class="headerlink" href="#verificacion-de-velocidad-y-tipo" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Velocidad configurada&lt;/span&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;dmidecode&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;-E&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;(Speed|Configured)&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# Tipo de memoria&lt;/span&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;dmidecode&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Type:&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Error&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="ejemplo-de-salida-interpretada"&gt;Ejemplo de salida interpretada&lt;a class="headerlink" href="#ejemplo-de-salida-interpretada" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;dmidecode&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;-E&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;(Locator|Size|Type:)&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;head&lt;span class="w"&gt; &lt;/span&gt;-12

Locator:&lt;span class="w"&gt; &lt;/span&gt;DIMM_A1
Size:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8192&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;MB&lt;span class="w"&gt;  &lt;/span&gt;
Type:&lt;span class="w"&gt; &lt;/span&gt;DDR4

Locator:&lt;span class="w"&gt; &lt;/span&gt;DIMM_A2
Size:&lt;span class="w"&gt; &lt;/span&gt;No&lt;span class="w"&gt; &lt;/span&gt;Module&lt;span class="w"&gt; &lt;/span&gt;Installed
Type:&lt;span class="w"&gt; &lt;/span&gt;Unknown

Locator:&lt;span class="w"&gt; &lt;/span&gt;DIMM_B1&lt;span class="w"&gt;  &lt;/span&gt;
Size:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8192&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;MB
Type:&lt;span class="w"&gt; &lt;/span&gt;DDR4

Locator:&lt;span class="w"&gt; &lt;/span&gt;DIMM_B2
Size:&lt;span class="w"&gt; &lt;/span&gt;No&lt;span class="w"&gt; &lt;/span&gt;Module&lt;span class="w"&gt; &lt;/span&gt;Installed
Type:&lt;span class="w"&gt; &lt;/span&gt;Unknown
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Interpretación&lt;/strong&gt;: 
- 4 slots totales (DIMM_A1, A2, B1, B2)
- 2 slots ocupados con 8GB DDR4 cada uno
- 2 slots libres disponibles&lt;/p&gt;
&lt;h2 id="limitaciones-importantes"&gt;Limitaciones importantes&lt;a class="headerlink" href="#limitaciones-importantes" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Precisión&lt;/strong&gt;: Las herramientas de línea de comandos pueden no reflejar perfectamente el hardware físico&lt;/p&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Privilegios&lt;/strong&gt;: Se requieren permisos de root para acceder a información DMI&lt;/p&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Compatibilidad&lt;/strong&gt;: Algunos sistemas embebidos pueden no proporcionar información completa&lt;/p&gt;
&lt;p&gt;Para &lt;strong&gt;máxima certeza&lt;/strong&gt;, la inspección física sigue siendo recomendable, pero estas herramientas proporcionan información suficientemente precisa para la mayoría de casos.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Fuente original&lt;/em&gt;: &lt;a href="http://unix.stackexchange.com/questions/33249/how-to-determine-the-amount-of-ram-slots-in-use"&gt;Unix &amp;amp; Linux Stack Exchange&lt;/a&gt;&lt;/p&gt;</content><category term="Linux"></category><category term="ram"></category><category term="dmidecode"></category><category term="lshw"></category><category term="hardware"></category><category term="memoria"></category></entry><entry><title>Introducción a los sistemas de trading de alta frecuencia</title><link href="https://pablocaro.es/introduccion-trading-alta-frecuencia" rel="alternate"></link><published>2016-06-27T11:51:00+02:00</published><updated>2016-06-27T11:51:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2016-06-27:/introduccion-trading-alta-frecuencia</id><summary type="html">&lt;p&gt;Conceptos fundamentales sobre los sistemas de trading de alta frecuencia (HFT), programas que ejecutan miles de operaciones por día en fracciones de segundo&lt;/p&gt;</summary><content type="html">&lt;p&gt;Los &lt;strong&gt;programas de negociación de alta frecuencia&lt;/strong&gt; (High-Frequency Trading o HFT) representan la automatización extrema de las inversiones financieras, operando a velocidades que superan cualquier capacidad humana.&lt;/p&gt;
&lt;h2 id="que-es-el-hft"&gt;¿Qué es el HFT?&lt;a class="headerlink" href="#que-es-el-hft" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Los sistemas HFT son &lt;strong&gt;programas diseñados para invertir en mercados financieros&lt;/strong&gt; con las siguientes características:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Volumen masivo&lt;/strong&gt;: Compran y venden hasta &lt;strong&gt;decenas de miles de acciones u opciones&lt;/strong&gt; al día&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Velocidad extrema&lt;/strong&gt;: Cada operación puede durar &lt;strong&gt;fracciones de segundo&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Márgenes pequeños&lt;/strong&gt;: &lt;strong&gt;Beneficio muy reducido&lt;/strong&gt; por operación individual&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Efecto acumulativo&lt;/strong&gt;: En su &lt;strong&gt;cómputo global se hace muy efectivo&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="principios-fundamentales"&gt;Principios fundamentales&lt;a class="headerlink" href="#principios-fundamentales" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="velocidad-como-ventaja-competitiva"&gt;Velocidad como ventaja competitiva&lt;a class="headerlink" href="#velocidad-como-ventaja-competitiva" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Latencia ultra-baja&lt;/strong&gt;: Cada milisegundo cuenta&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Proximidad física&lt;/strong&gt;: Servidores cerca de las bolsas&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hardware especializado&lt;/strong&gt;: FPGA, cables de fibra óptica optimizados&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="estrategias-algoritmicas"&gt;Estrategias algorítmicas&lt;a class="headerlink" href="#estrategias-algoritmicas" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Market making&lt;/strong&gt;: Proveer liquidez constante&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Arbitraje estadístico&lt;/strong&gt;: Aprovechar discrepancias de precio&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Arbitraje temporal&lt;/strong&gt;: Explotar diferencias entre mercados&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="tecnologias-clave"&gt;Tecnologías clave&lt;a class="headerlink" href="#tecnologias-clave" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="hardware-optimizado"&gt;Hardware optimizado&lt;a class="headerlink" href="#hardware-optimizado" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;FPGA (Field-Programmable Gate Arrays)&lt;/strong&gt; para procesamiento ultrarápido&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Servidores co-ubicados&lt;/strong&gt; en datacenters de intercambios&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Redes de fibra óptica&lt;/strong&gt; de alta velocidad&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="software-especializado"&gt;Software especializado&lt;a class="headerlink" href="#software-especializado" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Algoritmos de baja latencia&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Protocolos de red optimizados&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sistemas de gestión de riesgo&lt;/strong&gt; en tiempo real&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="impacto-en-los-mercados"&gt;Impacto en los mercados&lt;a class="headerlink" href="#impacto-en-los-mercados" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="beneficios"&gt;Beneficios&lt;a class="headerlink" href="#beneficios" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Mayor liquidez&lt;/strong&gt; en los mercados&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Spreads más ajustados&lt;/strong&gt; entre bid/ask&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Eficiencia de precios&lt;/strong&gt; mejorada&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="controversias"&gt;Controversias&lt;a class="headerlink" href="#controversias" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Volatilidad aumentada&lt;/strong&gt; en momentos de estrés&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ventaja injusta&lt;/strong&gt; sobre inversores tradicionales&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Posibles manipulaciones&lt;/strong&gt; de mercado&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="el-futuro-del-hft"&gt;El futuro del HFT&lt;a class="headerlink" href="#el-futuro-del-hft" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;La carrera por la velocidad continúa evolucionando hacia:
- &lt;strong&gt;Inteligencia artificial&lt;/strong&gt; y machine learning
- &lt;strong&gt;Análisis de sentiment&lt;/strong&gt; en tiempo real
- &lt;strong&gt;Computación cuántica&lt;/strong&gt; experimental&lt;/p&gt;
&lt;p&gt;El HFT ejemplifica cómo la &lt;strong&gt;tecnología redefine completamente&lt;/strong&gt; industrias tradicionales, creando nuevas oportunidades y desafíos éticos.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Fuente original&lt;/em&gt;: &lt;a href="http://albertomagallon.es/introduccion-a-los-sistemas-de-trading-de-alta-frecuencia/"&gt;Alberto Magallón&lt;/a&gt;&lt;/p&gt;</content><category term="Finanzas"></category><category term="trading"></category><category term="hft"></category><category term="high-frequency"></category><category term="algoritmos"></category></entry><entry><title>Cómo encontrar hosts vivos en tu red local</title><link href="https://pablocaro.es/encontrar-hosts-vivos-red" rel="alternate"></link><published>2016-06-27T07:59:00+02:00</published><updated>2016-06-27T07:59:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2016-06-27:/encontrar-hosts-vivos-red</id><summary type="html">&lt;p&gt;Métodos y herramientas para descubrir dispositivos activos en redes locales usando nmap, arp-scan, netdiscover y otras técnicas de reconocimiento&lt;/p&gt;</summary><content type="html">&lt;p&gt;Descubrir &lt;strong&gt;qué dispositivos están activos&lt;/strong&gt; en tu red local es una tarea fundamental para administradores de red y profesionales de seguridad. Estas son las técnicas más efectivas.&lt;/p&gt;
&lt;h2 id="nmap-la-herramienta-principal"&gt;Nmap: La herramienta principal&lt;a class="headerlink" href="#nmap-la-herramienta-principal" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="escaneo-basico-de-hosts"&gt;Escaneo básico de hosts&lt;a class="headerlink" href="#escaneo-basico-de-hosts" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Ping scan simple&lt;/span&gt;
nmap&lt;span class="w"&gt; &lt;/span&gt;-sn&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.168.1.1/24

&lt;span class="c1"&gt;# Escaneo con puertos TCP específicos&lt;/span&gt;
nmap&lt;span class="w"&gt; &lt;/span&gt;-sP&lt;span class="w"&gt; &lt;/span&gt;-PS22,3389&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.168.1.1/24

&lt;span class="c1"&gt;# Escaneo UDP para dispositivos especiales&lt;/span&gt;
nmap&lt;span class="w"&gt; &lt;/span&gt;-sP&lt;span class="w"&gt; &lt;/span&gt;-PU161&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.168.1.1/24
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;-sn&lt;/strong&gt;: Solo descubrimiento de hosts, sin escaneo de puertos&lt;br&gt;
&lt;strong&gt;-PS&lt;/strong&gt;: TCP SYN discovery en puertos específicos&lt;br&gt;
&lt;strong&gt;-PU&lt;/strong&gt;: UDP discovery (útil para dispositivos embebidos)&lt;/p&gt;
&lt;h3 id="escaneos-mas-exhaustivos"&gt;Escaneos más exhaustivos&lt;a class="headerlink" href="#escaneos-mas-exhaustivos" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Escaneo completo con detección de OS&lt;/span&gt;
nmap&lt;span class="w"&gt; &lt;/span&gt;-A&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.168.1.1/24

&lt;span class="c1"&gt;# Escaneo TCP Connect (menos intrusivo)&lt;/span&gt;
nmap&lt;span class="w"&gt; &lt;/span&gt;-sT&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.168.1.1/24

&lt;span class="c1"&gt;# Bypass de firewall con fragmentación&lt;/span&gt;
nmap&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;-sS&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.168.1.1/24
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="herramientas-alternativas"&gt;Herramientas alternativas&lt;a class="headerlink" href="#herramientas-alternativas" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="arp-scan-basado-en-arp"&gt;arp-scan: Basado en ARP&lt;a class="headerlink" href="#arp-scan-basado-en-arp" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Escaneo de subred local&lt;/span&gt;
arp-scan&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.168.12.0/24

&lt;span class="c1"&gt;# Con interfaz específica&lt;/span&gt;
arp-scan&lt;span class="w"&gt; &lt;/span&gt;-I&lt;span class="w"&gt; &lt;/span&gt;eth0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;172&lt;/span&gt;.16.17.0/24

&lt;span class="c1"&gt;# Scan local simplificado&lt;/span&gt;
arp-scan&lt;span class="w"&gt; &lt;/span&gt;-l
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Ventaja&lt;/strong&gt;: Funciona incluso con firewalls estrictos&lt;/p&gt;
&lt;h3 id="netdiscover-descubrimiento-pasivoactivo"&gt;netdiscover: Descubrimiento pasivo/activo&lt;a class="headerlink" href="#netdiscover-descubrimiento-pasivoactivo" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Descubrimiento activo&lt;/span&gt;
netdiscover&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;172&lt;/span&gt;.16.17.0/24

&lt;span class="c1"&gt;# Modo pasivo (solo escucha)&lt;/span&gt;
netdiscover&lt;span class="w"&gt; &lt;/span&gt;-p

&lt;span class="c1"&gt;# Fast mode&lt;/span&gt;
netdiscover&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.168.1.0/24
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="fing-scanner-movil"&gt;fing: Scanner móvil&lt;a class="headerlink" href="#fing-scanner-movil" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Escaneo rápido&lt;/span&gt;
fing&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;

&lt;span class="c1"&gt;# Con detalles del fabricante&lt;/span&gt;
fing&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;table,csv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="verificacion-post-escaneo"&gt;Verificación post-escaneo&lt;a class="headerlink" href="#verificacion-post-escaneo" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="revisar-tabla-arp"&gt;Revisar tabla ARP&lt;a class="headerlink" href="#revisar-tabla-arp" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Ver tabla ARP actual&lt;/span&gt;
arp&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;-n

&lt;span class="c1"&gt;# En sistemas modernos&lt;/span&gt;
ip&lt;span class="w"&gt; &lt;/span&gt;neigh&lt;span class="w"&gt; &lt;/span&gt;show

&lt;span class="c1"&gt;# Limpiar y reescanear&lt;/span&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;arp&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;-a
ping&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.168.1.&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;..254&lt;span class="o"&gt;}&lt;/span&gt;
arp&lt;span class="w"&gt; &lt;/span&gt;-a
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="tecnicas-avanzadas"&gt;Técnicas avanzadas&lt;a class="headerlink" href="#tecnicas-avanzadas" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="wireshark-para-analisis-pasivo"&gt;Wireshark para análisis pasivo&lt;a class="headerlink" href="#wireshark-para-analisis-pasivo" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Capturar tráfico en la interfaz de red&lt;/li&gt;
&lt;li&gt;Filtrar por ARP: &lt;code&gt;arp&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Analizar broadcast traffic&lt;/li&gt;
&lt;li&gt;Identificar dispositivos por patrones de tráfico&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="scripts-de-automatizacion"&gt;Scripts de automatización&lt;a class="headerlink" href="#scripts-de-automatizacion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c1"&gt;# Scan completo de red local&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Escaneando red...&amp;quot;&lt;/span&gt;
nmap&lt;span class="w"&gt; &lt;/span&gt;-sn&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;ip&lt;span class="w"&gt; &lt;/span&gt;route&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;-E&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;^192|^10|^172&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;awk&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{print $1}&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;head&lt;span class="w"&gt; &lt;/span&gt;-1&lt;span class="k"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Verificando ARP...&amp;quot;&lt;/span&gt;
arp&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;incomplete&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="consideraciones-importantes"&gt;Consideraciones importantes&lt;a class="headerlink" href="#consideraciones-importantes" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="limitaciones-por-firewall"&gt;Limitaciones por firewall&lt;a class="headerlink" href="#limitaciones-por-firewall" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Algunos dispositivos&lt;/strong&gt; bloquean ping/ICMP&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Firewalls corporativos&lt;/strong&gt; pueden filtrar escaneos&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Diferentes puertos&lt;/strong&gt; pueden revelar dispositivos "ocultos"&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="consideraciones-legales"&gt;Consideraciones legales&lt;a class="headerlink" href="#consideraciones-legales" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Autorización necesaria&lt;/strong&gt;: Solo escanea redes propias&lt;br&gt;
⚠️ &lt;strong&gt;Políticas corporativas&lt;/strong&gt;: Verifica antes de escanear en entornos empresariales&lt;/p&gt;
&lt;h3 id="optimizacion-por-tipo-de-red"&gt;Optimización por tipo de red&lt;a class="headerlink" href="#optimizacion-por-tipo-de-red" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Red doméstica (routers, IoT)&lt;/span&gt;
nmap&lt;span class="w"&gt; &lt;/span&gt;-sn&lt;span class="w"&gt; &lt;/span&gt;--min-rate&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.168.1.1/24

&lt;span class="c1"&gt;# Red corporativa (servidores, estaciones)&lt;/span&gt;
nmap&lt;span class="w"&gt; &lt;/span&gt;-sP&lt;span class="w"&gt; &lt;/span&gt;-PS80,443,22,3389&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.0.0.0/8

&lt;span class="c1"&gt;# Red industrial (dispositivos embebidos)&lt;/span&gt;
nmap&lt;span class="w"&gt; &lt;/span&gt;-sP&lt;span class="w"&gt; &lt;/span&gt;-PU161,502&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;172&lt;/span&gt;.16.0.0/12
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;La &lt;strong&gt;combinación de múltiples técnicas&lt;/strong&gt; proporciona el panorama más completo de dispositivos activos en tu red.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Fuente original&lt;/em&gt;: &lt;a href="http://security.stackexchange.com/questions/36198/how-to-find-live-hosts-on-my-network"&gt;Information Security Stack Exchange&lt;/a&gt;&lt;/p&gt;</content><category term="Infraestructura"></category><category term="nmap"></category><category term="network-discovery"></category><category term="arp-scan"></category><category term="netdiscover"></category></entry><entry><title>Potentes herramientas para desarrolladores desde línea de comandos</title><link href="https://pablocaro.es/herramientas-desarrolladores-linea-comandos" rel="alternate"></link><published>2016-06-07T11:36:00+02:00</published><updated>2016-06-07T11:36:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2016-06-07:/herramientas-desarrolladores-linea-comandos</id><summary type="html">&lt;p&gt;Colección de herramientas de línea de comandos esenciales para desarrolladores: curl, ngrep, netcat, sshuttle, siege y mitmproxy&lt;/p&gt;</summary><content type="html">&lt;p&gt;La &lt;strong&gt;línea de comandos sigue siendo el entorno más poderoso&lt;/strong&gt; para desarrolladores. Estas herramientas especializadas amplían dramáticamente tus capacidades de debugging, testing y análisis.&lt;/p&gt;
&lt;h2 id="curl-el-navajero-suizo-del-http"&gt;Curl: El navajero suizo del HTTP&lt;a class="headerlink" href="#curl-el-navajero-suizo-del-http" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Obtener tu IP pública&lt;/span&gt;
curl&lt;span class="w"&gt; &lt;/span&gt;ifconfig.me

&lt;span class="c1"&gt;# Inspeccionar headers de respuesta&lt;/span&gt;
curl&lt;span class="w"&gt; &lt;/span&gt;-I&lt;span class="w"&gt; &lt;/span&gt;https://ejemplo.com

&lt;span class="c1"&gt;# POST con datos JSON&lt;/span&gt;
curl&lt;span class="w"&gt; &lt;/span&gt;-X&lt;span class="w"&gt; &lt;/span&gt;POST&lt;span class="w"&gt; &lt;/span&gt;-H&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Content-Type: application/json&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{&amp;quot;key&amp;quot;:&amp;quot;value&amp;quot;}&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;https://api.ejemplo.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Uso principal&lt;/strong&gt;: Transferencia de datos de red, testing de APIs, inspección HTTP.&lt;/p&gt;
&lt;h2 id="ngrep-grep-para-trafico-de-red"&gt;Ngrep: Grep para tráfico de red&lt;a class="headerlink" href="#ngrep-grep-para-trafico-de-red" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Filtrar tráfico HTTP por palabra clave&lt;/span&gt;
ngrep&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;password&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;port&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt;

&lt;span class="c1"&gt;# Capturar por IP específica&lt;/span&gt;
ngrep&lt;span class="w"&gt; &lt;/span&gt;host&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.168.1.100

&lt;span class="c1"&gt;# Monitorear tráfico en puerto específico&lt;/span&gt;
ngrep&lt;span class="w"&gt; &lt;/span&gt;port&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;443&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Uso principal&lt;/strong&gt;: Análisis de paquetes de red, debugging de protocolos, monitoreo de tráfico.&lt;/p&gt;
&lt;h2 id="netcat-nc-la-navaja-suiza-del-networking"&gt;Netcat (nc): La navaja suiza del networking&lt;a class="headerlink" href="#netcat-nc-la-navaja-suiza-del-networking" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Escaneo de puertos&lt;/span&gt;
nc&lt;span class="w"&gt; &lt;/span&gt;-zv&lt;span class="w"&gt; &lt;/span&gt;ejemplo.com&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;-1000

&lt;span class="c1"&gt;# Transferir archivo entre servidores&lt;/span&gt;
&lt;span class="c1"&gt;# Receptor: nc -l 1234 &amp;gt; archivo_recibido&lt;/span&gt;
&lt;span class="c1"&gt;# Emisor: nc servidor_destino 1234 &amp;lt; archivo_enviar&lt;/span&gt;

&lt;span class="c1"&gt;# Crear servidor HTTP simple&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;HTTP/1.1 200 OK\n\nHola mundo&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nc&lt;span class="w"&gt; &lt;/span&gt;-l&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Uso principal&lt;/strong&gt;: Escaneo de puertos, transferencia de archivos, testing de conectividad.&lt;/p&gt;
&lt;h2 id="sshuttle-vpn-facil-con-ssh"&gt;Sshuttle: VPN fácil con SSH&lt;a class="headerlink" href="#sshuttle-vpn-facil-con-ssh" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Tunelar todo el tráfico&lt;/span&gt;
sshuttle&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt; &lt;/span&gt;usuario@servidor&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0.0.0/0

&lt;span class="c1"&gt;# Tunelar solo una subred&lt;/span&gt;
sshuttle&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt; &lt;/span&gt;usuario@servidor&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.168.1.0/24

&lt;span class="c1"&gt;# Evitar geobloqueos&lt;/span&gt;
sshuttle&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt; &lt;/span&gt;servidor_remoto&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0.0.0/0&lt;span class="w"&gt; &lt;/span&gt;--dns
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Uso principal&lt;/strong&gt;: Tunneling seguro, bypass de geo-restricciones, protección en WiFi público.&lt;/p&gt;
&lt;h2 id="siege-bombardeo-de-carga-http"&gt;Siege: Bombardeo de carga HTTP&lt;a class="headerlink" href="#siege-bombardeo-de-carga-http" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Test de carga simple&lt;/span&gt;
siege&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;60s&lt;span class="w"&gt; &lt;/span&gt;https://ejemplo.com

&lt;span class="c1"&gt;# Test con múltiples URLs&lt;/span&gt;
siege&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;30s&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;urls.txt

&lt;span class="c1"&gt;# Benchmark específico&lt;/span&gt;
siege&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;https://api.ejemplo.com/endpoint
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Uso principal&lt;/strong&gt;: Testing de rendimiento, benchmarking HTTP, simulación de carga.&lt;/p&gt;
&lt;h2 id="mitmproxy-espia-del-trafico-https"&gt;Mitmproxy: Espía del tráfico HTTP/S&lt;a class="headerlink" href="#mitmproxy-espia-del-trafico-https" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Proxy básico&lt;/span&gt;
mitmproxy&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;

&lt;span class="c1"&gt;# Modo transparent&lt;/span&gt;
mitmproxy&lt;span class="w"&gt; &lt;/span&gt;--mode&lt;span class="w"&gt; &lt;/span&gt;transparent

&lt;span class="c1"&gt;# Guardar tráfico en archivo&lt;/span&gt;
mitmdump&lt;span class="w"&gt; &lt;/span&gt;-w&lt;span class="w"&gt; &lt;/span&gt;captura.flow
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Uso principal&lt;/strong&gt;: Debugging de aplicaciones web, inspección de tráfico HTTPS, análisis de APIs.&lt;/p&gt;
&lt;h2 id="flujo-de-trabajo-tipico"&gt;Flujo de trabajo típico&lt;a class="headerlink" href="#flujo-de-trabajo-tipico" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# 1. Escanear conectividad&lt;/span&gt;
nc&lt;span class="w"&gt; &lt;/span&gt;-zv&lt;span class="w"&gt; &lt;/span&gt;api.ejemplo.com&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;443&lt;/span&gt;

&lt;span class="c1"&gt;# 2. Analizar respuesta&lt;/span&gt;
curl&lt;span class="w"&gt; &lt;/span&gt;-I&lt;span class="w"&gt; &lt;/span&gt;https://api.ejemplo.com

&lt;span class="c1"&gt;# 3. Monitorear tráfico&lt;/span&gt;
ngrep&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;api&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;port&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;443&lt;/span&gt;

&lt;span class="c1"&gt;# 4. Test de carga&lt;/span&gt;
siege&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;30s&lt;span class="w"&gt; &lt;/span&gt;https://api.ejemplo.com

&lt;span class="c1"&gt;# 5. Debugging detallado&lt;/span&gt;
mitmproxy&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="por-que-linea-de-comandos"&gt;¿Por qué línea de comandos?&lt;a class="headerlink" href="#por-que-linea-de-comandos" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Velocidad&lt;/strong&gt;: Sin overhead de GUI&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scriptabilidad&lt;/strong&gt;: Automatización completa&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Flexibilidad&lt;/strong&gt;: Combinación con pipes y redirects&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Universalidad&lt;/strong&gt;: Disponible en cualquier servidor&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Estas herramientas convierten tu terminal en un &lt;strong&gt;laboratorio completo de networking y testing&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Fuente original&lt;/em&gt;: &lt;a href="http://www.xn--apaados-6za.es/tenemos-que-apanar/internet-tutoriales-y-trucos/317-potentes-herramientas-desarrolladores-linea-comandos.html"&gt;Somos Apañados&lt;/a&gt;&lt;/p&gt;</content><category term="Herramientas"></category><category term="cli"></category><category term="desarrollo"></category><category term="redes"></category><category term="testing"></category></entry><entry><title>Sysdig: Plataforma de seguridad cloud-nativa con IA</title><link href="https://pablocaro.es/sysdig-seguridad-cloud-nativa" rel="alternate"></link><published>2016-02-10T14:37:00+01:00</published><updated>2016-02-10T14:37:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2016-02-10:/sysdig-seguridad-cloud-nativa</id><summary type="html">&lt;p&gt;Sysdig ofrece una plataforma de seguridad integral para entornos cloud-nativos que combina IA, visibilidad en tiempo real e innovación open source&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;Sysdig&lt;/strong&gt; es una plataforma de seguridad cloud que proporciona soluciones de seguridad integrales y en tiempo real para entornos cloud-nativos, resolviendo el conflicto entre las necesidades de los equipos de seguridad y desarrollo.&lt;/p&gt;
&lt;h2 id="caracteristicas-principales"&gt;Características principales&lt;a class="headerlink" href="#caracteristicas-principales" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="ia-de-seguridad-inteligente"&gt;IA de seguridad inteligente&lt;a class="headerlink" href="#ia-de-seguridad-inteligente" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;IA contextual&lt;/strong&gt; que comprende entornos cloud y guía insights accionables&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reducción del 76%&lt;/strong&gt; en el tiempo medio de resolución (MTTR)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="visibilidad-en-tiempo-real"&gt;Visibilidad en tiempo real&lt;a class="headerlink" href="#visibilidad-en-tiempo-real" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Monitoreo continuo de actividades cloud y riesgos potenciales&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Detección de amenazas en 2 segundos&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Filtra el ruido para destacar solo problemas críticos&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="innovacion-abierta"&gt;Innovación abierta&lt;a class="headerlink" href="#innovacion-abierta" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Basado en tecnologías open source como &lt;strong&gt;Falco&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Transparencia y personalización en seguridad cloud&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="beneficios-clave"&gt;Beneficios clave&lt;a class="headerlink" href="#beneficios-clave" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;95% de ahorro&lt;/strong&gt; en tiempo de gestión de vulnerabilidades&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;98% de reducción&lt;/strong&gt; en ruido de vulnerabilidades&lt;/li&gt;
&lt;li&gt;Detección ultra-rápida de amenazas&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="propuesta-de-valor"&gt;Propuesta de valor&lt;a class="headerlink" href="#propuesta-de-valor" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Sysdig permite que los equipos de seguridad y desarrollo trabajen de forma más efectiva, &lt;strong&gt;habilitando innovación rápida sin comprometer la seguridad&lt;/strong&gt;. La plataforma es utilizada por grandes empresas como IBM, Booking.com y Neo4j para asegurar sus entornos cloud.&lt;/p&gt;
&lt;p&gt;Esta herramienta representa el futuro de la seguridad cloud-nativa: &lt;strong&gt;inteligente, rápida y sin compromisos&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Sitio oficial&lt;/em&gt;: &lt;a href="https://sysdig.com/"&gt;Sysdig&lt;/a&gt;&lt;/p&gt;</content><category term="Herramientas"></category><category term="sysdig"></category><category term="seguridad"></category><category term="cloud"></category><category term="kubernetes"></category><category term="falco"></category></entry><entry><title>Python: Crear generadores repetibles (repeating generators)</title><link href="https://pablocaro.es/python-generadores-repetibles" rel="alternate"></link><published>2016-02-09T10:10:00+01:00</published><updated>2016-02-09T10:10:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2016-02-09:/python-generadores-repetibles</id><summary type="html">&lt;p&gt;Técnicas en Python para crear generadores que se pueden iterar múltiples veces, superando la limitación de "una sola vez" de los generadores estándar&lt;/p&gt;</summary><content type="html">&lt;p&gt;Los &lt;strong&gt;generadores en Python son "one-shot"&lt;/strong&gt; - solo se pueden iterar una vez. ¿Cómo crear generadores que se puedan reutilizar múltiples veces?&lt;/p&gt;
&lt;h2 id="el-problema"&gt;El problema&lt;a class="headerlink" href="#el-problema" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mi_generador&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;

&lt;span class="n"&gt;gen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mi_generador&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# [1, 2, 3]&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# [] - ¡Vacío!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="solucion-1-enfoque-con-clase-mas-robusto"&gt;Solución 1: Enfoque con clase (Más robusto)&lt;a class="headerlink" href="#solucion-1-enfoque-con-clase-mas-robusto" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;multigen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gen_func&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;_multigen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__kwargs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__iter__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;gen_func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_multigen&lt;/span&gt;

&lt;span class="c1"&gt;# Uso&lt;/span&gt;
&lt;span class="nd"&gt;@multigen&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mi_generador_repetible&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_num&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_num&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;

&lt;span class="n"&gt;gen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mi_generador_repetible&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# [0, 1, 2]&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# [0, 1, 2] - ¡Funciona!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="solucion-2-itertoolscycle-para-secuencias-infinitas"&gt;Solución 2: itertools.cycle() para secuencias infinitas&lt;a class="headerlink" href="#solucion-2-itertoolscycle-para-secuencias-infinitas" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;itertools&lt;/span&gt;

&lt;span class="c1"&gt;# Repetir infinitamente una secuencia&lt;/span&gt;
&lt;span class="n"&gt;repeating_gen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;itertools&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cycle&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Tomar solo los primeros 9 elementos&lt;/span&gt;
&lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;itertools&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;islice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repeating_gen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resultado&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# [1, 2, 3, 1, 2, 3, 1, 2, 3]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="solucion-3-lambda-wrapper-simple"&gt;Solución 3: Lambda wrapper (Simple)&lt;a class="headerlink" href="#solucion-3-lambda-wrapper-simple" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mi_generador&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;

&lt;span class="c1"&gt;# Wrapper para crear nueva instancia cada vez&lt;/span&gt;
&lt;span class="n"&gt;generador_repetible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mi_generador&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;generador_repetible&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;  &lt;span class="c1"&gt;# [1, 2, 3]&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;generador_repetible&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;  &lt;span class="c1"&gt;# [1, 2, 3]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="solucion-4-funcion-factory"&gt;Solución 4: Función factory&lt;a class="headerlink" href="#solucion-4-funcion-factory" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;crear_generador&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datos&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generador&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;datos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;generador&lt;/span&gt;

&lt;span class="c1"&gt;# Uso&lt;/span&gt;
&lt;span class="n"&gt;gen_factory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;crear_generador&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;gen1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gen_factory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;gen2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gen_factory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gen1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# [1, 2, 3]&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gen2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# [1, 2, 3]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="cuando-usar-cada-enfoque"&gt;¿Cuándo usar cada enfoque?&lt;a class="headerlink" href="#cuando-usar-cada-enfoque" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="clase-decorador-multigen"&gt;Clase decorador (&lt;code&gt;@multigen&lt;/code&gt;)&lt;a class="headerlink" href="#clase-decorador-multigen" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Generadores complejos&lt;/strong&gt; con parámetros&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Máxima flexibilidad&lt;/strong&gt; y reutilización&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Preserva metadatos&lt;/strong&gt; del generador original&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="itertoolscycle"&gt;&lt;code&gt;itertools.cycle()&lt;/code&gt;&lt;a class="headerlink" href="#itertoolscycle" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Repetición infinita&lt;/strong&gt; de secuencias pequeñas&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Patrones cíclicos&lt;/strong&gt; conocidos&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Muy eficiente&lt;/strong&gt; en memoria&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="lambda-wrapper"&gt;Lambda wrapper&lt;a class="headerlink" href="#lambda-wrapper" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Casos simples&lt;/strong&gt; sin parámetros&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prototipado rápido&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Generadores sin estado&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="factory-function"&gt;Factory function&lt;a class="headerlink" href="#factory-function" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Balance&lt;/strong&gt; entre simplicidad y flexibilidad&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Generadores con configuración inicial&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Código más explícito&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="consideraciones-de-rendimiento"&gt;Consideraciones de rendimiento&lt;a class="headerlink" href="#consideraciones-de-rendimiento" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Memoria&lt;/strong&gt;: Los generadores repetibles pueden requerir almacenar parámetros iniciales&lt;br&gt;
⚠️ &lt;strong&gt;CPU&lt;/strong&gt;: Cada iteración recalcula desde el inicio&lt;/p&gt;
&lt;p&gt;La elección depende de si necesitas &lt;strong&gt;repetición exacta&lt;/strong&gt; o &lt;strong&gt;reutilización eficiente&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Fuente original&lt;/em&gt;: &lt;a href="http://stackoverflow.com/questions/1376438/how-to-make-a-repeating-generator-in-python/1376531#1376531"&gt;Stack Overflow&lt;/a&gt;&lt;/p&gt;</content><category term="Programación"></category><category term="generadores"></category><category term="itertools"></category><category term="decoradores"></category><category term="yield"></category></entry><entry><title>Botón central en firefox</title><link href="https://pablocaro.es/boton_central_firefox" rel="alternate"></link><published>2016-01-01T07:00:00+01:00</published><updated>2016-01-01T07:00:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2016-01-01:/boton_central_firefox</id><summary type="html">&lt;p class="first last"&gt;Que firefox deje de abrir la url pegada con el botón central del ratón&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Otra nota recordatoria.
Habitualmente abro los enlaces en nueva ventana en firefox usando el botón central.
Me ocurre a menudo que tengo una url en el clipboard y firefox entonces me la abre en lugar del enlace.
Para evitarlo, simplemente  &lt;code&gt;about:config&lt;/code&gt; y establecer la propiedad &lt;code&gt;middlemouse.contentLoadURL&lt;/code&gt; a false.&lt;/p&gt;
</content><category term="Infraestructura"></category><category term="linux"></category></entry><entry><title>Bootstrap: Dos enlaces en la misma fila en dropdown</title><link href="https://pablocaro.es/bootstrap-dropdown-enlaces-horizontal" rel="alternate"></link><published>2015-12-15T10:40:00+01:00</published><updated>2015-12-15T10:40:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2015-12-15:/bootstrap-dropdown-enlaces-horizontal</id><summary type="html">&lt;p&gt;Técnica CSS para colocar dos enlaces en la misma fila horizontal dentro de un menú dropdown de Bootstrap usando flexbox&lt;/p&gt;</summary><content type="html">&lt;p&gt;A veces necesitas &lt;strong&gt;colocar dos enlaces lado a lado&lt;/strong&gt; en la misma fila dentro de un dropdown de Bootstrap. La solución combina HTML estructurado y CSS con flexbox.&lt;/p&gt;
&lt;h2 id="el-problema"&gt;El problema&lt;a class="headerlink" href="#el-problema" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Por defecto, Bootstrap renderiza los enlaces de dropdown como elementos de bloque, ocupando toda la fila:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;&amp;lt;!-- Comportamiento por defecto: cada enlace en su fila --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Enlace 1&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Enlace 2&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="la-solucion"&gt;La solución&lt;a class="headerlink" href="#la-solucion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="html-estructurado"&gt;HTML estructurado&lt;a class="headerlink" href="#html-estructurado" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;hz&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;En la tercera fila&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;También en la tercera fila&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Clave&lt;/strong&gt;: Múltiples enlaces &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; dentro del mismo &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt; con clase personalizada.&lt;/p&gt;
&lt;h3 id="css-con-flexbox"&gt;CSS con flexbox&lt;a class="headerlink" href="#css-con-flexbox" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;open&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;hz&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;inline-flex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Clave&lt;/strong&gt;: &lt;code&gt;inline-flex&lt;/code&gt; override el display block por defecto de Bootstrap.&lt;/p&gt;
&lt;h2 id="implementacion-completa"&gt;Implementación completa&lt;a class="headerlink" href="#implementacion-completa" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;dropdown&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;btn btn-default dropdown-toggle&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;data-toggle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;dropdown&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        Menú &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;caret&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;dropdown-menu&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Primera opción&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Segunda opción&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;hz&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Opción A&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Opción B&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Última opción&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="consideraciones-adicionales"&gt;Consideraciones adicionales&lt;a class="headerlink" href="#consideraciones-adicionales" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="espaciado-entre-enlaces"&gt;Espaciado entre enlaces&lt;a class="headerlink" href="#espaciado-entre-enlaces" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;hz&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;margin-right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;hz&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;last-child&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;margin-right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="responsive"&gt;Responsive&lt;a class="headerlink" href="#responsive" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="k"&gt;media&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;max-width&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;768px&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;open&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;hz&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;block&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="cuando-usarlo"&gt;¿Cuándo usarlo?&lt;a class="headerlink" href="#cuando-usarlo" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Acciones relacionadas&lt;/strong&gt; (Editar/Eliminar)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Opciones binarias&lt;/strong&gt; (Sí/No, Activar/Desactivar)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Navegación compacta&lt;/strong&gt; en espacios reducidos&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Esta técnica mantiene la &lt;strong&gt;semántica HTML correcta&lt;/strong&gt; mientras aprovecha la flexibilidad de flexbox para layouts más complejos.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Fuente original&lt;/em&gt;: &lt;a href="http://stackoverflow.com/questions/15844533/bootstrap-dropdown-menu-two-links-on-same-horizontal-row"&gt;Stack Overflow&lt;/a&gt;&lt;/p&gt;</content><category term="Programación"></category><category term="bootstrap"></category><category term="css"></category><category term="dropdown"></category><category term="flexbox"></category><category term="html"></category></entry><entry><title>Galen Framework: Testing automatizado para diseño responsive</title><link href="https://pablocaro.es/galen-framework-testing-responsive" rel="alternate"></link><published>2015-11-13T18:20:00+01:00</published><updated>2015-11-13T18:20:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2015-11-13:/galen-framework-testing-responsive</id><summary type="html">&lt;p&gt;Galen Framework simplifica el testing automatizado de layouts responsivos verificando la posición y apariencia de elementos across different dispositivos&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;Galen Framework&lt;/strong&gt; es una herramienta de testing automatizado diseñada específicamente para &lt;strong&gt;probar diseños web responsivos&lt;/strong&gt;. Su propósito principal es simplificar las pruebas de layout verificando el posicionamiento y apariencia de elementos web en diferentes tamaños de dispositivo.&lt;/p&gt;
&lt;h2 id="caracteristicas-principales"&gt;Características principales&lt;a class="headerlink" href="#caracteristicas-principales" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="testing-de-layout-inteligente"&gt;Testing de layout inteligente&lt;a class="headerlink" href="#testing-de-layout-inteligente" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;"Layout testing seemed always a complex task. Galen Framework offers a simple solution: test location of objects relatively to each other on page."&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="soporte-responsive-completo"&gt;Soporte responsive completo&lt;a class="headerlink" href="#soporte-responsive-completo" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Redimensionado automático&lt;/strong&gt; del navegador a tamaños definidos&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Testing cross-device&lt;/strong&gt; en múltiples resoluciones&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Verificación de reglas&lt;/strong&gt; específicas por pantalla&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="sintaxis-humana-y-flexible"&gt;Sintaxis humana y flexible&lt;a class="headerlink" href="#sintaxis-humana-y-flexible" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;header
    width: 100% of screen/width
    height: 50px

sidebar
    below: header
    width: 200px
    left-of: content 10px
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="capacidades-avanzadas"&gt;Capacidades avanzadas&lt;a class="headerlink" href="#capacidades-avanzadas" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Compatibilidad con Selenium Grid&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Testing visual&lt;/strong&gt; con comparación de imágenes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Verificación de esquemas de color&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reportes HTML detallados&lt;/strong&gt; con errores destacados&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="soporte-multi-lenguaje"&gt;Soporte multi-lenguaje&lt;a class="headerlink" href="#soporte-multi-lenguaje" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Tests escritos en:
- &lt;strong&gt;JavaScript&lt;/strong&gt;
- &lt;strong&gt;Java&lt;/strong&gt;
- Otros lenguajes con parametrización integrada&lt;/p&gt;
&lt;h2 id="por-que-galen"&gt;¿Por qué Galen?&lt;a class="headerlink" href="#por-que-galen" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Antes de Galen, el testing de layouts era &lt;strong&gt;complejo y propenso a errores&lt;/strong&gt;. Galen introduce:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Sintaxis declarativa&lt;/strong&gt; fácil de leer&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Verificación relacional&lt;/strong&gt; entre elementos&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Testing responsive nativo&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reportes visuales&lt;/strong&gt; comprensibles&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="caso-de-uso-tipico"&gt;Caso de uso típico&lt;a class="headerlink" href="#caso-de-uso-tipico" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// Abrir navegador, redimensionar y probar&lt;/span&gt;
&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Homepage on mobile&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;driver&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;createDriver&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://example.com&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;manage&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;setSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;375&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;667&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;checkLayout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;homepage-mobile.spec&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Galen democratiza el testing de diseño responsive, haciendo que las &lt;strong&gt;pruebas de layout sean tan fáciles como las pruebas funcionales&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Framework open source&lt;/em&gt;: &lt;a href="http://galenframework.com/"&gt;Galen Framework&lt;/a&gt; (Apache License 2.0)&lt;/p&gt;</content><category term="Programación"></category><category term="galen"></category><category term="testing"></category><category term="responsive"></category><category term="layout"></category><category term="selenium"></category></entry><entry><title>La historia detrás de X-Forwarded-For y X-Real-IP</title><link href="https://pablocaro.es/historia-x-forwarded-for-x-real-ip" rel="alternate"></link><published>2015-10-21T23:05:00+02:00</published><updated>2015-10-21T23:05:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2015-10-21:/historia-x-forwarded-for-x-real-ip</id><summary type="html">&lt;p&gt;Análisis de los headers X-Forwarded-For y X-Real-IP, sus orígenes, diferencias y cómo determinar la IP real del cliente en arquitecturas con múltiples proxies&lt;/p&gt;</summary><content type="html">&lt;p&gt;Determinar la &lt;strong&gt;IP real del cliente&lt;/strong&gt; en arquitecturas con proxies es más complejo de lo que parece. Los headers &lt;code&gt;X-Forwarded-For&lt;/code&gt; y &lt;code&gt;X-Real-IP&lt;/code&gt; tienen diferentes propósitos y limitaciones.&lt;/p&gt;
&lt;h2 id="x-forwarded-for-la-cadena-completa"&gt;X-Forwarded-For: La cadena completa&lt;a class="headerlink" href="#x-forwarded-for-la-cadena-completa" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="formato-y-proposito"&gt;Formato y propósito&lt;a class="headerlink" href="#formato-y-proposito" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;X&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;Forwarded&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;For&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;client&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;proxy1&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;proxy2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Rastrea la IP original&lt;/strong&gt; del cliente a través de múltiples saltos&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Acumula IPs&lt;/strong&gt; conforme pasa por proxies&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No es obligatorio&lt;/strong&gt; para todos los proxies&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="limitaciones-criticas"&gt;Limitaciones críticas&lt;a class="headerlink" href="#limitaciones-criticas" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Fácilmente falsificable&lt;/strong&gt;: Un cliente puede añadir IPs falsas&lt;br&gt;
⚠️ &lt;strong&gt;Potencialmente no confiable&lt;/strong&gt; para determinar la IP real&lt;/p&gt;
&lt;h2 id="x-real-ip-el-enfoque-directo"&gt;X-Real-IP: El enfoque directo&lt;a class="headerlink" href="#x-real-ip-el-enfoque-directo" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="caracteristicas"&gt;Características&lt;a class="headerlink" href="#caracteristicas" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Menos documentación&lt;/strong&gt; oficial disponible&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Comúnmente usado&lt;/strong&gt; junto con X-Forwarded-For&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enfoque más simple&lt;/strong&gt; para pasar la IP del cliente&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="limitacion"&gt;Limitación&lt;a class="headerlink" href="#limitacion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;"Not many good info or specs about this one" - No hay estándares definitivos&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="configuracion-nginx-recomendada"&gt;Configuración Nginx recomendada&lt;a class="headerlink" href="#configuracion-nginx-recomendada" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Para extraer la IP más precisa:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Configurar proxies conocidos&lt;/span&gt;
&lt;span class="k"&gt;set_real_ip_from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="s"&gt;.0.0.0/8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;set_real_ip_from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;172&lt;/span&gt;&lt;span class="s"&gt;.16.0.0/12&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;set_real_ip_from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;192&lt;/span&gt;&lt;span class="s"&gt;.168.0.0/16&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;# Activar procesamiento recursivo&lt;/span&gt;
&lt;span class="k"&gt;real_ip_recursive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;# Usar X-Forwarded-For como fuente&lt;/span&gt;
&lt;span class="k"&gt;real_ip_header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;X-Forwarded-For&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="el-desafio-real"&gt;El desafío real&lt;a class="headerlink" href="#el-desafio-real" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Determinar la IP "real" del cliente es inherentemente complejo&lt;/strong&gt; debido a:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Múltiples saltos de red&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Proxies no cooperativos&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Potencial spoofing de IP&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lack de estándares uniformes&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="buenas-practicas"&gt;Buenas prácticas&lt;a class="headerlink" href="#buenas-practicas" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Validar fuentes confiables&lt;/strong&gt; de headers&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Configurar correctamente&lt;/strong&gt; proxies conocidos&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No confiar ciegamente&lt;/strong&gt; en headers de cliente&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Implementar logging&lt;/strong&gt; para debugging&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;La &lt;strong&gt;confiabilidad real&lt;/strong&gt; depende más de tu arquitectura de red que de los headers específicos utilizados.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Fuente original&lt;/em&gt;: &lt;a href="http://distinctplace.com/infrastructure/2014/04/23/story-behind-x-forwarded-for-and-x-real-ip-headers/"&gt;Distinct Place&lt;/a&gt;&lt;/p&gt;</content><category term="Infraestructura"></category><category term="headers"></category><category term="x-forwarded-for"></category><category term="x-real-ip"></category><category term="proxy"></category><category term="nginx"></category></entry><entry><title>Nginx: Configurar X-Forwarded-For en reverse proxy</title><link href="https://pablocaro.es/nginx-x-forwarded-for-reverse-proxy" rel="alternate"></link><published>2015-10-21T23:05:00+02:00</published><updated>2015-10-21T23:05:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2015-10-21:/nginx-x-forwarded-for-reverse-proxy</id><summary type="html">&lt;p&gt;Cómo configurar Nginx para pasar la IP real del cliente a servidores backend usando el header X-Forwarded-For en configuraciones de reverse proxy&lt;/p&gt;</summary><content type="html">&lt;p&gt;Cuando usas &lt;strong&gt;Nginx como reverse proxy&lt;/strong&gt;, necesitas que el servidor backend vea la &lt;strong&gt;IP real del cliente&lt;/strong&gt;, no la IP del proxy. El header &lt;code&gt;X-Forwarded-For&lt;/code&gt; resuelve este problema.&lt;/p&gt;
&lt;h2 id="el-problema"&gt;El problema&lt;a class="headerlink" href="#el-problema" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Sin configuración, el servidor backend solo ve la IP del proxy Nginx, perdiendo información crucial sobre el cliente real para:
- &lt;strong&gt;Logs de acceso&lt;/strong&gt; precisos
- &lt;strong&gt;Geolocalización&lt;/strong&gt; correcta&lt;br&gt;
- &lt;strong&gt;Rate limiting&lt;/strong&gt; por IP real
- &lt;strong&gt;Análisis de tráfico&lt;/strong&gt; fidedignos&lt;/p&gt;
&lt;h2 id="solucion-1-ip-simple"&gt;Solución 1: IP simple&lt;a class="headerlink" href="#solucion-1-ip-simple" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;proxy_set_header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;X-Forwarded-For&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Uso&lt;/strong&gt;: Cuando Nginx es el único proxy en la cadena.&lt;br&gt;
&lt;strong&gt;Resultado&lt;/strong&gt;: Header contiene solo la IP del cliente original.&lt;/p&gt;
&lt;h2 id="solucion-2-cadena-de-ips"&gt;Solución 2: Cadena de IPs&lt;a class="headerlink" href="#solucion-2-cadena-de-ips" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;proxy_set_header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;X-Forwarded-For&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Uso&lt;/strong&gt;: Cuando hay múltiples proxies en la cadena.&lt;br&gt;
&lt;strong&gt;Resultado&lt;/strong&gt;: Conserva IPs existentes y añade la actual.&lt;/p&gt;
&lt;h2 id="configuracion-practica"&gt;Configuración práctica&lt;a class="headerlink" href="#configuracion-practica" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;listen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;server_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_pass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;http://backend-server&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_set_header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_set_header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;X-Real-IP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_set_header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;X-Forwarded-For&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_set_header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;X-Forwarded-Proto&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$scheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="headers-relacionados"&gt;Headers relacionados&lt;a class="headerlink" href="#headers-relacionados" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;X-Real-IP&lt;/strong&gt;: IP del cliente más reciente&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;X-Forwarded-For&lt;/strong&gt;: Cadena completa de IPs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;X-Forwarded-Proto&lt;/strong&gt;: Protocolo original (http/https)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Host&lt;/strong&gt;: Hostname original&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="verificacion"&gt;Verificación&lt;a class="headerlink" href="#verificacion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# En el servidor backend, verifica los headers&lt;/span&gt;
tail&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;/var/log/apache2/access.log
&lt;span class="c1"&gt;# O en tu aplicación&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$_SERVER&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;HTTP_X_FORWARDED_FOR&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="consideraciones-de-seguridad"&gt;Consideraciones de seguridad&lt;a class="headerlink" href="#consideraciones-de-seguridad" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Importante&lt;/strong&gt;: Los headers X-Forwarded-* pueden ser falsificados por clientes. En entornos de producción, asegúrate de que solo tu proxy los establezca.&lt;/p&gt;
&lt;p&gt;Esta configuración es esencial para mantener &lt;strong&gt;trazabilidad completa&lt;/strong&gt; en arquitecturas con reverse proxy.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Fuente original&lt;/em&gt;: &lt;a href="http://www.networkinghowtos.com/howto/set-the-x-forwarded-for-header-on-a-nginx-reverse-proxy-setup/"&gt;Networking How To's&lt;/a&gt;&lt;/p&gt;</content><category term="Infraestructura"></category><category term="nginx"></category><category term="reverse-proxy"></category><category term="x-forwarded-for"></category><category term="ip-real"></category><category term="configuracion"></category></entry><entry><title>SQL: Seleccionar filas con valor máximo por grupo</title><link href="https://pablocaro.es/sql-select-rows-max-value" rel="alternate"></link><published>2015-10-21T23:05:00+02:00</published><updated>2015-10-21T23:05:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2015-10-21:/sql-select-rows-max-value</id><summary type="html">&lt;p&gt;Técnicas SQL para seleccionar filas completas con el valor máximo de una columna dentro de grupos, comparando rendimiento de subqueries, joins y window functions&lt;/p&gt;</summary><content type="html">&lt;p&gt;Un problema común en SQL: &lt;strong&gt;¿cómo seleccionar las filas completas que contienen el valor máximo de una columna para cada grupo?&lt;/strong&gt; No solo el valor máximo, sino la &lt;strong&gt;fila entera&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="el-problema"&gt;El problema&lt;a class="headerlink" href="#el-problema" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Dado una tabla con &lt;code&gt;id&lt;/code&gt; y &lt;code&gt;rev&lt;/code&gt; (revisión), queremos obtener la fila completa con la &lt;code&gt;rev&lt;/code&gt; más alta para cada &lt;code&gt;id&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="solucion-1-subquery-con-in"&gt;Solución 1: Subquery con IN&lt;a class="headerlink" href="#solucion-1-subquery-con-in" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;YourTable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;YourTable&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Ventajas&lt;/strong&gt;: Simple y legible&lt;br&gt;
&lt;strong&gt;Desventajas&lt;/strong&gt;: Puede ser lento en tablas grandes&lt;/p&gt;
&lt;h2 id="solucion-2-join-con-subquery"&gt;Solución 2: Join con subquery&lt;a class="headerlink" href="#solucion-2-join-con-subquery" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;YourTable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;maxrev&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;YourTable&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;maxrev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Ventajas&lt;/strong&gt;: Mejor rendimiento que IN&lt;br&gt;
&lt;strong&gt;Desventajas&lt;/strong&gt;: Más verboso&lt;/p&gt;
&lt;h2 id="solucion-3-window-functions-mysql-8"&gt;Solución 3: Window Functions (MySQL 8+)&lt;a class="headerlink" href="#solucion-3-window-functions-mysql-8" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;ROW_NUMBER&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OVER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PARTITION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ranked_order&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;YourTable&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ranked_order&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Ventajas&lt;/strong&gt;: Más eficiente para datasets grandes&lt;br&gt;
&lt;strong&gt;Desventajas&lt;/strong&gt;: Requiere versiones modernas de SQL&lt;/p&gt;
&lt;h2 id="consideraciones-de-rendimiento"&gt;Consideraciones de rendimiento&lt;a class="headerlink" href="#consideraciones-de-rendimiento" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Window functions&lt;/strong&gt; suelen ser las más eficientes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Joins&lt;/strong&gt; balancean legibilidad y rendimiento  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Subqueries con IN&lt;/strong&gt; son las más simples pero menos escalables&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="cuando-usar-cada-una"&gt;¿Cuándo usar cada una?&lt;a class="headerlink" href="#cuando-usar-cada-una" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Subquery&lt;/strong&gt;: Tablas pequeñas, código simple&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Join&lt;/strong&gt;: Balance entre rendimiento y compatibilidad&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Window functions&lt;/strong&gt;: Datasets grandes, SQL moderno disponible&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;El patrón más robusto y moderno son las &lt;strong&gt;window functions&lt;/strong&gt;, que además permiten manejar empates y casos edge más elegantemente.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Fuente original&lt;/em&gt;: &lt;a href="http://stackoverflow.com/questions/7745609/sql-select-only-rows-with-max-value-on-a-column#7745635"&gt;Stack Overflow&lt;/a&gt;&lt;/p&gt;</content><category term="Programación"></category><category term="sql"></category><category term="mysql"></category><category term="consultas"></category><category term="window-functions"></category><category term="joins"></category></entry><entry><title>Repitiendo una función con hilos</title><link href="https://pablocaro.es/repetiendo-una-funci%C3%B3n-con-hilos" rel="alternate"></link><published>2015-05-03T21:00:00+02:00</published><updated>2015-05-03T21:00:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2015-05-03:/repetiendo-una-función-con-hilos</id><summary type="html">&lt;p class="first last"&gt;Vamos a utilizar hilos en python para ejecutar periodicamente una función&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Tengo un servicio (con &lt;a class="reference external" href="https://github.com/jnrbsn/daemonocle"&gt;daemonocle&lt;/a&gt;  del que ya hablaré otro día) corriendo todo el tiempo en una &lt;a class="reference external" href="http://www.hardkernel.com/main/products/prdt_info.php"&gt;odroid c1&lt;/a&gt;.
Consideré que era mejor controlar con hilos unas tareas periódicas que tengo que repetir (que ya estaba usando)
en lugar de la típica entrada en cron.&lt;/p&gt;
&lt;p&gt;Así puedo contrar mejor el funcionamiento de las mismas. Las ventajas para mí en este caso y para estas tareas concretas:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Ya tengo la configuración de logging realizada por el proceso principal. Estas tareas dejan su info en mis log rotacionales.&lt;/li&gt;
&lt;li&gt;Parando  (arrancando) el servicio lo paro todo y a la vez.&lt;/li&gt;
&lt;li&gt;Mido el consumo de recursos en su conjunto&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Dejo el snippet utilizado.&lt;/p&gt;
&lt;p&gt;En  &lt;code&gt;utils.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# -*- coding: utf-8 -*-&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;unicode_literals&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;threading&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RepeatedTimer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_timer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;function&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_running&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_running&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_running&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_timer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Timer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_run&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_timer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_running&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_timer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_running&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;En &lt;code&gt;main.py&lt;/code&gt; podeís ver su uso, una de las funciones recibe un parámetro:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;setup_logging&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;youtube&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;youtube_api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_authenticated_service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;events_thread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RepeatedTimer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UPDATE_EVENTS_SECONDS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;backend_api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update_events_database&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;streams_thread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RepeatedTimer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UPDATE_STREAM_SECONDS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;youtube_api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update_streams_database&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;youtube&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Deciros que paro estos hilos y limpio recursos en el evento de callback para shutdown que es de lo mejor de &lt;code&gt;daemonocle&lt;/code&gt;.
Ya sabéis que python no es capaz de paralelizar del todo los hilos debido a al GIL, pero para tareas simples como estas con muy poca carga
es simple y funciona a la perfección.&lt;/p&gt;
</content><category term="Programación"></category></entry><entry><title>Instalar InSync en Opensuse</title><link href="https://pablocaro.es/instalar-insync-en-opensuse" rel="alternate"></link><published>2015-04-20T18:18:00+02:00</published><updated>2015-04-20T18:18:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2015-04-20:/instalar-insync-en-opensuse</id><summary type="html"></summary><content type="html">&lt;p&gt;Llevo varios dias probando &lt;a class="reference external" href="https://www.insynchq.com/"&gt;insync&lt;/a&gt; y creo que lo voy a pagar con gusto.&lt;/p&gt;
&lt;p&gt;Me he descargado dos paquetes, uno para el menu contextual de dolphin y otro (el de fedora) para el propio programa en sí:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
20:39 $ rpm -qa | grep insync
insync-1.2.7.35123-fc20.x86_64
insync-dolphin-1.2.7.35123-fc17.noarch
&lt;/pre&gt;
&lt;p&gt;Todo funcionando perfectamente en mi opensuse&lt;/p&gt;
</content><category term="Infraestructura"></category><category term="django"></category><category term="python"></category></entry><entry><title>NetHogs ver ancho de banda consumido</title><link href="https://pablocaro.es/nethogs-ver-ancho-de-banda-consumido" rel="alternate"></link><published>2015-04-16T11:37:00+02:00</published><updated>2015-04-16T11:37:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2015-04-16:/nethogs-ver-ancho-de-banda-consumido</id><summary type="html">&lt;p&gt;Para medir el ancho de banda que estoy utilizando en este momento utilizo NetHogs&lt;/p&gt;
&lt;p&gt;Por defecto busca una interfaz de red llamada &amp;quot;eth0&amp;quot; si no la encuenta se queja así:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
ioctl failed while establishing local IP for selected device eth0. You may specify the device on the command line.
&lt;/pre&gt;
&lt;p&gt;Puedes …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Para medir el ancho de banda que estoy utilizando en este momento utilizo NetHogs&lt;/p&gt;
&lt;p&gt;Por defecto busca una interfaz de red llamada &amp;quot;eth0&amp;quot; si no la encuenta se queja así:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
ioctl failed while establishing local IP for selected device eth0. You may specify the device on the command line.
&lt;/pre&gt;
&lt;p&gt;Puedes ver tu configuración de red con el comando &lt;em&gt;ip&lt;/em&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ ip a
1: lo: &amp;lt;LOOPBACK,UP,LOWER_UP&amp;gt; mtu 65536 qdisc noqueue state UNKNOWN group default
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: wlp3s0: &amp;lt;BROADCAST,MULTICAST&amp;gt; mtu 1500 qdisc mq state DOWN group default qlen 1000
    link/ether 84:3a:4b:50:14:cc brd ff:ff:ff:ff:ff:ff
3: enp0s25: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 3c:97:0e:77:76:d3 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.39/24 brd 192.168.1.255 scope global dynamic enp0s25
       valid_lft 28955sec preferred_lft 28955sec
    inet6 fe80::3e97:eff:fe77:76d3/64 scope link
       valid_lft forever preferred_lft forever
&lt;/pre&gt;
&lt;p&gt;Como podéis ver en mi equipo tengo dos interfaces (aparte de la de loopback) que son enp0s25 (mi tarjeta de red con cable)
y la wifi wlp3s0, en la que estoy conectado ahora que es la que voy a usar (podría listar las dos).&lt;/p&gt;
&lt;p&gt;De forma que ejecutando:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ sudo nethogs enp0s25
&lt;/pre&gt;
&lt;p&gt;Puedo ver los procesos que están consumiendo más ancho de banda.&lt;/p&gt;
&lt;img alt="Nethogs" class="align-center" src="images/nethogs.png" /&gt;
&lt;p&gt;La ayuda del comando es:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
20:02 $ nethogs -h
usage: nethogs [-V] [-b] [-d seconds] [-t] [-p] [device [device [device ...]]]
                -V : prints version.
                -d : delay for update refresh rate in seconds. default is 1.
                -t : tracemode.
                -b : bughunt mode - implies tracemode.
                -p : sniff in promiscious mode (not recommended).
                device : device(s) to monitor. default is eth0

When nethogs is running, press:
 q: quit
 m: switch between total and kb/s mode
&lt;/pre&gt;
</content><category term="Infraestructura"></category><category term="linux"></category></entry><entry><title>Cierra Google Code</title><link href="https://pablocaro.es/cierra-google-code" rel="alternate"></link><published>2015-03-13T17:11:00+01:00</published><updated>2015-03-13T17:11:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2015-03-13:/cierra-google-code</id><summary type="html">&lt;p&gt;Lo dicho, Google anunció el cierre de Google Code.&lt;/p&gt;
&lt;p&gt;Hoy mismo recibí el correo avisandome (tenía un proyecto de prueba alojado allí) de que podía mover
mis proyectos de forma simple a &lt;a class="reference external" href="http://github.com"&gt;github&lt;/a&gt;, el repositorio de facto hoy en día del software libre
(lo siento por &lt;a class="reference external" href="https://bitbucket.org/pcaro"&gt;bitbucket&lt;/a&gt; y sobre todo …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Lo dicho, Google anunció el cierre de Google Code.&lt;/p&gt;
&lt;p&gt;Hoy mismo recibí el correo avisandome (tenía un proyecto de prueba alojado allí) de que podía mover
mis proyectos de forma simple a &lt;a class="reference external" href="http://github.com"&gt;github&lt;/a&gt;, el repositorio de facto hoy en día del software libre
(lo siento por &lt;a class="reference external" href="https://bitbucket.org/pcaro"&gt;bitbucket&lt;/a&gt; y sobre todo por &lt;a class="reference external" href="http://mercurial.selenic.com/"&gt;mercurial&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Su interfaz nunca fue la más amigable, ni tampoco consiguió gran popularidad pero todavía quedan proyectos interesantes
en Google Code que supongo que pasarán a github o morirán para simpre.&lt;/p&gt;
</content><category term="Programación"></category></entry><entry><title>pgcli el descubrimiento de hoy</title><link href="https://pablocaro.es/pgcli-el-descubrimiento-de-hoy" rel="alternate"></link><published>2015-03-02T08:26:00+01:00</published><updated>2015-03-02T08:26:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2015-03-02:/pgcli-el-descubrimiento-de-hoy</id><summary type="html">&lt;p class="first last"&gt;postresql command line&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Leyendo mis feeds RSS acabo de dar con una herramienta muy útil. Se trata de &lt;a class="reference external" href="http://pgcli.com/"&gt;pgcli&lt;/a&gt; que no es más que una
interfaz de línea de comandos para Postgres con autocompletado y resaltado de sintáxis.
Como soy de usar la consola con las bases de datos, y postgres siempre es mi primera elección,
me biene de perlas. with auto-completion and syntax highlighting&lt;/p&gt;
&lt;p&gt;Yo recommiendo instalarla mediante &lt;a class="reference external" href="https://github.com/mitsuhiko/pipsi"&gt;pipsi&lt;/a&gt;.&lt;/p&gt;
</content><category term="Programación"></category><category term="postgresql"></category></entry><entry><title>Reactivando el blog</title><link href="https://pablocaro.es/reactivando-el-blog" rel="alternate"></link><published>2015-02-20T21:40:00+01:00</published><updated>2015-02-20T21:40:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2015-02-20:/reactivando-el-blog</id><summary type="html">&lt;p class="first last"&gt;Ya estoy de nuevo escribiendo.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Después de casi mucho tiempo inactivo (incluso offline) he decido que es el momento de
relanzar este blog para lo de siempre: como bloc de notas.&lt;/p&gt;
&lt;p&gt;He recuperado un fichero dump sql que tenía como copía de seguridad (desfasada eso sí) y con un pequeño
script he creado este blog que ahora pasa a funcionar con &lt;a class="reference external" href="http://getpelican.com"&gt;Pelican&lt;/a&gt; (abandona Wordpress) y a alojarse en github pages.&lt;/p&gt;
</content><category term="Personal"></category><category term="python"></category></entry><entry><title>Decorador vista ajax</title><link href="https://pablocaro.es/decorador-vista-ajax" rel="alternate"></link><published>2014-11-21T11:12:00+01:00</published><updated>2014-11-21T11:12:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2014-11-21:/decorador-vista-ajax</id><summary type="html">&lt;p&gt;Para obligar que una vista &lt;a class="reference external" href="https://www.djangoproject.com/"&gt;django&lt;/a&gt; sea llamada sólo mediante llamamas AJAX.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ajax_required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;    AJAX request required decorator&lt;/span&gt;
&lt;span class="sd"&gt;    use it in your views:&lt;/span&gt;

&lt;span class="sd"&gt;    @ajax_required&lt;/span&gt;
&lt;span class="sd"&gt;    def my_view(request):&lt;/span&gt;
&lt;span class="sd"&gt;        ....&lt;/span&gt;

&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_ajax&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;HttpResponseBadRequest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;wrap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__doc__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__doc__&lt;/span&gt;
    &lt;span class="n"&gt;wrap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__name__ …&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;Para obligar que una vista &lt;a class="reference external" href="https://www.djangoproject.com/"&gt;django&lt;/a&gt; sea llamada sólo mediante llamamas AJAX.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ajax_required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;    AJAX request required decorator&lt;/span&gt;
&lt;span class="sd"&gt;    use it in your views:&lt;/span&gt;

&lt;span class="sd"&gt;    @ajax_required&lt;/span&gt;
&lt;span class="sd"&gt;    def my_view(request):&lt;/span&gt;
&lt;span class="sd"&gt;        ....&lt;/span&gt;

&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_ajax&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;HttpResponseBadRequest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;wrap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__doc__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__doc__&lt;/span&gt;
    &lt;span class="n"&gt;wrap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrap&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Un posible ejemplo de uso:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@login_required&lt;/span&gt;
&lt;span class="nd"&gt;@ajax_required&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_starts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;JsonResponse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;result&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;ok&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
</content><category term="Programación"></category><category term="django"></category></entry><entry><title>Función genérica para usar como upload_to</title><link href="https://pablocaro.es/funcion-generica-para-usar-como-upload_to" rel="alternate"></link><published>2014-10-11T11:02:00+02:00</published><updated>2014-10-11T11:02:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2014-10-11:/funcion-generica-para-usar-como-upload_to</id><summary type="html">&lt;p&gt;Los FileField en django necesitan ana función &lt;a class="reference external" href="https://docs.djangoproject.com/en/1.7/ref/models/fields/#django.db.models.FileField.upload_to"&gt;upload_to&lt;/a&gt; que determine donde se subirá el fichero.
Yo suelo tener en &lt;code&gt;utils.py&lt;/code&gt; una función genérica que los deja en una subcarpeta con el nombre del modelo.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generic_upload_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;    Generic `upload_to` function for models.FileField and models.ImageField …&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;Los FileField en django necesitan ana función &lt;a class="reference external" href="https://docs.djangoproject.com/en/1.7/ref/models/fields/#django.db.models.FileField.upload_to"&gt;upload_to&lt;/a&gt; que determine donde se subirá el fichero.
Yo suelo tener en &lt;code&gt;utils.py&lt;/code&gt; una función genérica que los deja en una subcarpeta con el nombre del modelo.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generic_upload_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;    Generic `upload_to` function for models.FileField and models.ImageField&lt;/span&gt;
&lt;span class="sd"&gt;    which uploads files to `&amp;lt;app_label&amp;gt;/&amp;lt;model_name&amp;gt;/&amp;lt;file_name&amp;gt;`.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app_label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;El uso el previsible:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;featured_image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;upload_to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;generic_upload_to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="n"&gt;verbose_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Imagen Destacada (770x490)&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
</content><category term="Programación"></category><category term="django"></category></entry><entry><title>Django: enlaces en listado de objectos</title><link href="https://pablocaro.es/enlaces-en-listado-de-objectos" rel="alternate"></link><published>2014-05-21T10:42:00+02:00</published><updated>2014-05-21T10:42:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2014-05-21:/enlaces-en-listado-de-objectos</id><summary type="html">&lt;p&gt;En mis proyectos suelo usar enlaces a elementos relacionados en el change_list del admin.&lt;/p&gt;
&lt;div class="contents topic" id="ficheros"&gt;
&lt;p class="topic-title"&gt;Ficheros&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#utils-py" id="toc-entry-1"&gt;utils.py&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#admin-py" id="toc-entry-2"&gt;admin.py&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="utils-py"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;utils.py&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Tengo esto en &lt;code&gt;utils.py&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib.contenttypes.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ContentType&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlresolvers&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.utils.datastructures&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SortedDict&lt;/span&gt;
&lt;span class="kn"&gt;from …&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;En mis proyectos suelo usar enlaces a elementos relacionados en el change_list del admin.&lt;/p&gt;
&lt;div class="contents topic" id="ficheros"&gt;
&lt;p class="topic-title"&gt;Ficheros&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#utils-py" id="toc-entry-1"&gt;utils.py&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#admin-py" id="toc-entry-2"&gt;admin.py&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="utils-py"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;utils.py&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Tengo esto en &lt;code&gt;utils.py&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib.contenttypes.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ContentType&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlresolvers&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.utils.datastructures&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SortedDict&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.utils.functional&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;memoize&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_change_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Utility method&lt;/span&gt;
&lt;span class="sd"&gt;        def server_link(self, obj):&lt;/span&gt;
&lt;span class="sd"&gt;             return self.get_change_url(obj.server)&lt;/span&gt;
&lt;span class="sd"&gt;        server_link.short_description = _(&amp;#39;server&amp;#39;)&lt;/span&gt;
&lt;span class="sd"&gt;        server_link.allow_tags = True&lt;/span&gt;
&lt;span class="sd"&gt;        server_link.admin_order_field = &amp;#39;server__name&amp;#39;&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="n"&gt;content_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_content_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__class__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;change_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urlresolvers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;_change&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app_label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;lt;a href=&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s1"&gt;&amp;quot;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;/a&amp;gt;&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;change_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_get_content_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ContentType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_for_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;get_content_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;memoize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_get_content_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SortedDict&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Hago uso de memoize para aumentar la eficiencia. Creo que es el punto junto entre simplicidad
en el código con facilidad de uso. He tenido también decoradores para repetir menos en el admin.py
pero cuando otros veían el código no lo entendían a la primera.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="admin-py"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;admin.py&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Un ejemplo de uso: en &lt;code&gt;admin.py&lt;/code&gt; (suponiendo que el modelo Link tiene un FK llamado product):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;get_change_url&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LinkAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AdminImageMixin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;list_display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;is_visible&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;product_link&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;updated_at&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;search_fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;product_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;get_change_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;product_link&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;short_description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Product&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;product_link&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allow_tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
    &lt;span class="n"&gt;product_link&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;admin_order_field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;product__name&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
</content><category term="Programación"></category><category term="django"></category></entry><entry><title>Settings vars processor</title><link href="https://pablocaro.es/settings-vars-processor" rel="alternate"></link><published>2014-01-08T11:23:00+01:00</published><updated>2014-01-08T11:23:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2014-01-08:/settings-vars-processor</id><summary type="html">&lt;p&gt;Lo suelo usar durante el principio de los proyectos.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.conf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;django_settings&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.exceptions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ImproperlyConfigured&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;    Adds the settings specified in settings.TEMPLATE_VISIBLE_SETTINGS to&lt;/span&gt;
&lt;span class="sd"&gt;    the request context.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;new_settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;django_settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TEMPLATE_VISIBLE_SETTINGS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;new_settings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;django_settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;AttributeError …&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;Lo suelo usar durante el principio de los proyectos.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.conf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;django_settings&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.exceptions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ImproperlyConfigured&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;    Adds the settings specified in settings.TEMPLATE_VISIBLE_SETTINGS to&lt;/span&gt;
&lt;span class="sd"&gt;    the request context.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;new_settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;django_settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TEMPLATE_VISIBLE_SETTINGS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;new_settings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;django_settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;AttributeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;TEMPLATE_VISIBLE_SETTINGS: &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{0}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39; does not exist&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;ImproperlyConfigured&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;new_settings&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Se usa así:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;TEMPLATE_CONTEXT_PROCESSORS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.auth.context_processors.auth&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;web.context_processors.settings&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;DEBUG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
&lt;span class="n"&gt;CLIENT_SLOGAN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Esto es un ejemplo&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;TEMPLATE_VISIBLE_SETTINGS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;DEBUG&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;CLIENT_SLOGAN&amp;#39;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
</content><category term="Programación"></category><category term="django"></category></entry><entry><title>pyinotify: Ejecutar comando cuando se modifica un fichero</title><link href="https://pablocaro.es/pyinotify-ejecutar-comando-cuando-se-modifica-un-fichero" rel="alternate"></link><published>2013-04-03T05:09:00+02:00</published><updated>2013-04-03T05:09:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2013-04-03:/pyinotify-ejecutar-comando-cuando-se-modifica-un-fichero</id><content type="html">
        Ya he comentado por aquí un comando &lt;em&gt;when-changed&lt;/em&gt;.

Ahora anoto otra alternativa, esta vez python. Se trata de pyinotify.

Yo lo uso para generar la documentación de sphinx mientras estoy escribiendo:

&lt;pre lang="bash"&gt;
python -m pyinotify -r -c "make html" -e IN_MODIFY source
&lt;/pre&gt;
    </content><category term="Infraestructura"></category><category term="python"></category><category term="linux"></category></entry><entry><title>A vueltas con suse</title><link href="https://pablocaro.es/a-vueltas-con-suse" rel="alternate"></link><published>2013-03-25T11:01:00+01:00</published><updated>2013-03-25T11:01:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2013-03-25:/a-vueltas-con-suse</id><content type="html">
        Algunos comandos de zypper para recordar:

Para borrar un paquete y también las dependencias:
&lt;pre lang="bash"&gt;
zypper remove  --clean-deps packate
&lt;/pre&gt;

Listar paquetes huérfanos:
&lt;pre lang="bash"&gt;
LC_ALL=C zypper se -s | fgrep '(System Packages)'
&lt;/pre&gt;

    </content><category term="Programación"></category></entry><entry><title>Paso de ubuntu a suse</title><link href="https://pablocaro.es/paso-de-ubuntu-a-suse" rel="alternate"></link><published>2013-02-01T02:17:00+01:00</published><updated>2013-02-01T02:17:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2013-02-01:/paso-de-ubuntu-a-suse</id><summary type="html">
        En mi nuevo portátil del trabajo he pasado de Ubuntu a OpenSuse. Una distribución muy estable y sobre todo con el mejor soporte de KDE.

Pero hay una cuantas cosas que nunca recuerdo. Como este blog se llama anotaciones...

&lt;table width="100%"&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;Debian&lt;/th&gt;
&lt;th&gt;OpenSuse&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dpkg -S fichero&lt;/td&gt;
&lt;td&gt;rpm -q -f fichero&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dpkg -L …&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;</summary><content type="html">
        En mi nuevo portátil del trabajo he pasado de Ubuntu a OpenSuse. Una distribución muy estable y sobre todo con el mejor soporte de KDE.

Pero hay una cuantas cosas que nunca recuerdo. Como este blog se llama anotaciones...

&lt;table width="100%"&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;Debian&lt;/th&gt;
&lt;th&gt;OpenSuse&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dpkg -S fichero&lt;/td&gt;
&lt;td&gt;rpm -q -f fichero&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dpkg -L paquete&lt;/td&gt;
&lt;td&gt;rpm -q -l paquete&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dpkg –purge –dry-run paquete&lt;/td&gt;
&lt;td&gt;rpm -q –whatrequires paquee&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&amp;nbsp;

&amp;nbsp;
    </content><category term="Infraestructura"></category><category term="linux"></category></entry><entry><title>reducir recursos de KDE</title><link href="https://pablocaro.es/reducir-recursos-de-kde" rel="alternate"></link><published>2012-10-24T03:18:00+02:00</published><updated>2012-10-24T03:18:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2012-10-24:/reducir-recursos-de-kde</id><content type="html">
        Mágnifica entrada que explica como acelerar un poco tu sistema kde:

&lt;a href="http://parduslife.com/2011/02/17/como-acelerar-el-entorno-de-escritorio-plasma-de-la-kde-sc/"&gt;http://parduslife.com/2011/02/17/como-acelerar-el-entorno-de-escritorio-plasma-de-la-kde-sc/&lt;/a&gt;

No uso akonadi y siempre he querido pararlo. Así no tienes que hacer un
&lt;pre lang="bash"&gt;akonadictl stop&lt;/pre&gt;
tras arrancar.

&amp;nbsp;

&lt;a href="http://mschlander.wordpress.com/2011/08/18/disable-akonadi-in-kde-sc-4-7/"&gt;http://mschlander.wordpress.com/2011/08/18/disable-akonadi-in-kde-sc-4-7/&lt;/a&gt;

&amp;nbsp;

&amp;nbsp;
    </content><category term="Infraestructura"></category><category term="linux"></category></entry><entry><title>Cambiar de lugar un virtualenv</title><link href="https://pablocaro.es/cambiar-de-lugar-un-virtualenv" rel="alternate"></link><published>2012-10-24T03:15:00+02:00</published><updated>2012-10-24T03:15:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2012-10-24:/cambiar-de-lugar-un-virtualenv</id><content type="html">
        La opción --relocate no funciona bien.

Lo mejor es usar &lt;a href="https://github.com/fireteam/virtualenv-tools"&gt;virtualenv-tools&lt;/a&gt;.
    </content><category term="Programación"></category><category term="python"></category></entry><entry><title>Depurando Tomcat</title><link href="https://pablocaro.es/depurando-tomcat" rel="alternate"></link><published>2012-10-24T03:13:00+02:00</published><updated>2012-10-24T03:13:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2012-10-24:/depurando-tomcat</id><content type="html">
        Se usa Java Platform Debuging Architecture JPDA

&lt;a href="https://confluence.sakaiproject.org/display/BOOT/Setting+Up+Tomcat+For+Remote+Debugging"&gt;https://confluence.sakaiproject.org/display/BOOT/Setting+Up+Tomcat+For+Remote+Debugging&lt;/a&gt;
    </content><category term="Programación"></category><category term="java"></category></entry><entry><title>Importar clave gpg. kgpg que gran programa</title><link href="https://pablocaro.es/importar-clave-gpg-kgpg-que-gran-programa" rel="alternate"></link><published>2012-10-24T02:22:00+02:00</published><updated>2012-10-24T02:22:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2012-10-24:/importar-clave-gpg-kgpg-que-gran-programa</id><summary type="html">
    &lt;pre lang="bash"&gt;
        gpg --keyserver pgp.rediris.es --search-keys erevilla@yaco.es

gpg: buscando «erevilla@yaco.es» de hkp servidor pgp.rediris.es
(1) Ernesto Revilla Derksen (Yaco)
1024 bit DSA key 3E455F5A, creado: 2009-09-16
Keys 1-1 of 1 for "erevilla@yaco.es". Introduzca número(s), O)tro, o F)in &amp;gt; 1
gpg …&lt;/pre&gt;</summary><content type="html">
    &lt;pre lang="bash"&gt;
        gpg --keyserver pgp.rediris.es --search-keys erevilla@yaco.es

gpg: buscando «erevilla@yaco.es» de hkp servidor pgp.rediris.es
(1) Ernesto Revilla Derksen (Yaco)
1024 bit DSA key 3E455F5A, creado: 2009-09-16
Keys 1-1 of 1 for "erevilla@yaco.es". Introduzca número(s), O)tro, o F)in &amp;gt; 1
gpg: solicitando clave 3E455F5A de hkp servidor pgp.rediris.es
gpg: clave 3E455F5A: clave pública "Ernesto Revilla Derksen (Yaco) " importada
gpg: no se encuentran claves totalmente fiables
gpg: Cantidad total procesada: 1
gpg: importadas: 1

kmail dice:
Mensaje firmado por erevilla@yaco.es (ID Clave: 0x4D76FA323E455F5A).
La firma es válida, pero no se conoce la validez de la clave.

Ver: http://www.gnupg.org/gph/en/manual/x56.html
Tengo que dar validez con una firma mía

pcaro@davinci$ gpg --edit-key 3E455F5A
gpg (GnuPG) 1.4.9; Copyright (C) 2008 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

pub 1024D/3E455F5A creado: 2009-09-16 [caduca: 2011-09-16] uso: SC
confianza: desconocido validez: desconocido
sub 2048g/1AAA03F0 creado: 2009-09-16 [caduca: 2011-09-16] uso: E
desconocido (1). Ernesto Revilla Derksen (Yaco)

Orden&amp;gt; trust
pub 1024D/3E455F5A creado: 2009-09-16 [caduca: 2011-09-16] uso: SC
confianza: desconocido validez: desconocido
sub 2048g/1AAA03F0 creado: 2009-09-16 [caduca: 2011-09-16] uso: E
desconocido (1). Ernesto Revilla Derksen (Yaco)

Por favor, decida su nivel de confianza para que este usuario
verifique las claves de otros usuarios (mirando pasaportes,
comprobando huellas dactilares en diferentes fuentes...)

1 = No lo sé o prefiero no decirlo
2 = NO tengo confianza
3 = Confío un poco
4 = Confío totalmente
5 = confío absolutamente
m = volver al menú principal

¿Su decisión? 4

pub 1024D/3E455F5A creado: 2009-09-16 [caduca: 2011-09-16] uso: SC
confianza: total validez: desconocido
sub 2048g/1AAA03F0 creado: 2009-09-16 [caduca: 2011-09-16] uso: E
desconocido (1). Ernesto Revilla Derksen (Yaco)
Por favor, advierta que la validez de clave mostrada no es necesariamente
correcta a menos de que reinicie el programa.

&lt;/pre&gt;

&lt;a href="http://www.fermu.com/content/view/231/2/lang,es/"&gt;http://www.fermu.com/content/view/231/2/lang,es/&lt;/a&gt;

&lt;a href="http://www.rediris.es/pgp/"&gt;http://www.rediris.es/pgp/&lt;/a&gt;

&lt;pre lang="bash"&gt;

Generando mi propia clave:
pcaro@davinci$ gpg --gen-key
gpg (GnuPG) 1.4.9; Copyright (C) 2008 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Por favor seleccione tipo de clave deseado:
(1) DSA y ElGamal (por defecto)
(2) DSA (sólo firmar)
(5) RSA (sólo firmar)
¿Su selección?: 1
El par de claves DSA tendrá 1024 bits.
las claves ELG-E pueden tener entre 1024 y 4096 bits de longitud.
¿De qué tamaño quiere la clave? (2048)
El tamaño requerido es de 2048 bits
Por favor, especifique el período de validez de la clave.
0 = la clave nunca caduca
= la clave caduca en n días
w = la clave caduca en n semanas
m = la clave caduca en n meses
y = la clave caduca en n años
¿Validez de la clave (0)? 3y
La clave caduca lun 29 oct 2012 12:01:25 CET
¿Es correcto? (s/n) s

Necesita un identificador de usuario para identificar su clave. El programa
construye el identificador a partir del Nombre Real, Comentario y Dirección
de Correo electrónico de esta forma:
"Heinrich Heine (Der Dichter)"

Nombre y apellidos: Pablo Caro Revuelta
Dirección de correo electrónico: pcaro@yaco.es
Comentario:
Ha seleccionado este ID de usuario:
Â«Pablo Caro RevueltaÂ»

¿Cambia (N)ombre, (C)omentario, (D)irección o (V)ale/(S)alir? C
Comentario: pcaro
Ha seleccionado este ID de usuario:
Â«Pablo Caro Revuelta (pcaro)Â»

¿Cambia (N)ombre, (C)omentario, (D)irección o (V)ale/(S)alir? V
Necesita una frase contraseña para proteger su clave secreta.

Es necesario generar muchos bytes aleatorios. Es una buena idea realizar
alguna otra tarea (trabajar en otra ventana/consola, mover el ratón, usar
la red y los discos) durante la generación de números primos. Esto da al
generador de números aleatorios mayor oportunidad de recoger suficiente
entropía.
++++++++++.++++++++++++++++++++.++++++++++...+++++++++++++++..++++++++++++++++++++++++++++++..+++++..++++++++++.+++++++++++++++.++++++++++++++++++++......&amp;gt;.+++++.................................+++++

No hay suficientes bytes aleatorios disponibles. Por favor, haga algún
otro trabajo para que el sistema pueda recolectar más entropía
(se necesitan 280 bytes más).

Es necesario generar muchos bytes aleatorios. Es una buena idea realizar
alguna otra tarea (trabajar en otra ventana/consola, mover el ratón, usar
la red y los discos) durante la generación de números primos. Esto da al
generador de números aleatorios mayor oportunidad de recoger suficiente
entropía.
+++++++++++++++.+++++.+++++++++++++++..+++++.+++++..++++++++++++++++++++..++++++++++++++++++++.+++++.++++++++++++++++++++...+++++.++++++++++++++++++++.+++++++++++++++..&amp;gt;..+++++........&amp;gt;+++++.+++++.+++++....................+++++.....................................................+++++............................+++++^^^
gpg: clave 4A52D5BA marcada como de confianza absoluta
claves pública y secreta creadas y firmadas.

gpg: comprobando base de datos de confianza
gpg: 3 dudosa(s) necesarias, 1 completa(s) necesarias,
modelo de confianza PGP
gpg: nivel: 0 validez: 1 firmada: 0 confianza: 0-, 0q, 0n, 0m, 0f, 1u
gpg: siguiente comprobación de base de datos de confianza el: 2012-10-29
pub 1024D/4A52D5BA 2009-10-30 [[caduca: 2012-10-29]]
Huella de clave = 483C F267 8A4B 0D27 3451 88A9 FE05 5E01 4A52 D5BA
uid Pablo Caro Revuelta (pcaro) sub 2048g/7436FA09 2009-10-30 [[caduca: 2012-10-29]]

&lt;/pre&gt;

&lt;h2&gt;programas graficos&lt;/h2&gt;
&lt;pre lang="bash"&gt;
sudo aptitude install kleopatra kgpg
&lt;/pre&gt;
&lt;p&gt;
Para que puedas enviar los correos firmados en kmail:
&lt;a href="http://randomguy3.wordpress.com/2008/07/24/kmail-trunk-signing/"&gt;http://randomguy3.wordpress.com/2008/07/24/kmail-trunk-signing/&lt;/a&gt;
&lt;/p&gt;
    </content><category term="Infraestructura"></category><category term="linux"></category></entry><entry><title>Traducciones y APT</title><link href="https://pablocaro.es/traducciones-y-apt" rel="alternate"></link><published>2012-10-24T02:20:00+02:00</published><updated>2012-10-24T02:20:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2012-10-24:/traducciones-y-apt</id><content type="html">
    &lt;p&gt;
        &lt;a href="http://mindref.blogspot.com.es/2012/10/apt-translations.html"&gt;http://mindref.blogspot.com.es/2012/10/apt-translations.html&lt;/a&gt;
&lt;a href="http://manpages.ubuntu.com/manpages/natty/man5/apt.conf.5.html"&gt;http://manpages.ubuntu.com/manpages/natty/man5/apt.conf.5.html&lt;/a&gt;
    &lt;/p&gt;
    </content><category term="Infraestructura"></category><category term="linux"></category></entry><entry><title>Mostrar Usuarios y roles existentes en postgres</title><link href="https://pablocaro.es/mostrar-usuarios-y-roles-existentes-en-postgres" rel="alternate"></link><published>2012-10-24T02:13:00+02:00</published><updated>2012-10-24T02:13:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2012-10-24:/mostrar-usuarios-y-roles-existentes-en-postgres</id><content type="html">
        Se utiliza &lt;strong&gt;\du&lt;/strong&gt;
&lt;pre lang="bash"&gt;$ sudo su - postgres
$ psql 
postgres=# \du
                           List of roles
 Role name |                   Attributes                   | Member of 
-----------+------------------------------------------------+-----------
 openerp   | Superuser, Create role, Create DB, Replication | {}
 postgres  | Superuser, Create role, Create DB, Replication | {}

postgres=#&lt;/pre&gt;
    </content><category term="Infraestructura"></category></entry><entry><title>Uso práctico de ngrep</title><link href="https://pablocaro.es/uso-practico-de-ngrep" rel="alternate"></link><published>2012-09-12T03:38:00+02:00</published><updated>2012-09-12T03:38:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2012-09-12:/uso-practico-de-ngrep</id><content type="html">
        Ejemplo de uso común de &lt;strong&gt;ngrep&lt;/strong&gt;:
&lt;pre class="bash" lang="bash"&gt;sudo ngrep -d any port 80 -W byline&lt;/pre&gt;
Esto es para examinar lo que circula por el puerto 80.
    </content><category term="Infraestructura"></category><category term="linux"></category></entry><entry><title>Autorelad en plone</title><link href="https://pablocaro.es/autorelad-en-plone" rel="alternate"></link><published>2012-07-27T04:38:00+02:00</published><updated>2012-07-27T04:38:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2012-07-27:/autorelad-en-plone</id><summary type="html">
        Otro recordatorio.

Para recargar mientras desarrollo una browserview en plone:
&lt;pre lang="bash"&gt;$ when-changed.py *.py -c wget -q -O /dev/null  \
http://admin:admin@localhost:8080/@@reload?action=code&lt;/pre&gt;
Basta pues con usar &lt;a title="when-changed" href="https://github.com/joh/when-changed"&gt;when-changed&lt;/a&gt; y &lt;a href="http://pypi.python.org/pypi/plone.reload/"&gt;plone.reload&lt;/a&gt;

Se que existen extensiones  para el navegador  que recargan una  pestaña cuando cambia un fichero …</summary><content type="html">
        Otro recordatorio.

Para recargar mientras desarrollo una browserview en plone:
&lt;pre lang="bash"&gt;$ when-changed.py *.py -c wget -q -O /dev/null  \
http://admin:admin@localhost:8080/@@reload?action=code&lt;/pre&gt;
Basta pues con usar &lt;a title="when-changed" href="https://github.com/joh/when-changed"&gt;when-changed&lt;/a&gt; y &lt;a href="http://pypi.python.org/pypi/plone.reload/"&gt;plone.reload&lt;/a&gt;

Se que existen extensiones  para el navegador  que recargan una  pestaña cuando cambia un fichero, pero yo soy más de consola.
    </content><category term="Programación"></category><category term="zope"></category></entry><entry><title>virtualbox: Actualizar las Guest Additions en máquinas ubuntu</title><link href="https://pablocaro.es/virtualbox-actualizar-las-guest-additions-en-maquinas-ubuntu" rel="alternate"></link><published>2011-08-24T04:11:00+02:00</published><updated>2011-08-24T04:11:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2011-08-24:/virtualbox-actualizar-las-guest-additions-en-maquinas-ubuntu</id><summary type="html">
        Si usas &lt;a href="http://vagrantup.com/"&gt;vagrant&lt;/a&gt; para gestionar máquinas virtuales con &lt;a href="http://www.virtualbox.org/"&gt;virtualbox&lt;/a&gt;, un buen lugar para obtener boxes es &lt;a href="http://www.vagrantbox.es/"&gt;vagrantboxes&lt;/a&gt;.

Pero a veces la versión que traen de las Guest Additions no coincide con el virtualbox que tienes (en mi caso el último). Vagrant se queja y puede ocasionar problemas.

Lo más simple …</summary><content type="html">
        Si usas &lt;a href="http://vagrantup.com/"&gt;vagrant&lt;/a&gt; para gestionar máquinas virtuales con &lt;a href="http://www.virtualbox.org/"&gt;virtualbox&lt;/a&gt;, un buen lugar para obtener boxes es &lt;a href="http://www.vagrantbox.es/"&gt;vagrantboxes&lt;/a&gt;.

Pero a veces la versión que traen de las Guest Additions no coincide con el virtualbox que tienes (en mi caso el último). Vagrant se queja y puede ocasionar problemas.

Lo más simple es arrancar la máquina en modo gráfico (config.vm.boot_mode = :gui en vagrant) e indicar en el menu de virtualbox en "Dispositivos" -&amp;gt; "CD/DVD" que se ponga VBoxGuestAddions.iso.

Luego vasta con montar el cdrom y ejecutar el comando de instalación:
&lt;pre lang="bash"&gt;$ sudo mount -t auto /dev/cdrom /mnt
$ cd mnt
$ sudo ./VBoxLinuxAdditions.run&lt;/pre&gt;
Puede que tengas de instalar dkms. Si el sistema no tiene sistema gráfico instalado (como en este caso), se quejará pero quedará instalado todo bien.
    </content><category term="Infraestructura"></category><category term="linux"></category></entry><entry><title>Cambiar mapa de teclado en linux</title><link href="https://pablocaro.es/cambiar-mapa-de-teclado-en-linux" rel="alternate"></link><published>2011-08-24T03:59:00+02:00</published><updated>2011-08-24T03:59:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2011-08-24:/cambiar-mapa-de-teclado-en-linux</id><content type="html">
        Siempre olvido esto:
&lt;pre lang="bash"&gt;sudo keymap es&lt;/pre&gt;
    </content><category term="Infraestructura"></category><category term="linux"></category></entry><entry><title>Deshabilitar autocompletado en vim</title><link href="https://pablocaro.es/deshabilitar-autocompletado-en-vim" rel="alternate"></link><published>2011-06-20T04:42:00+02:00</published><updated>2011-06-20T04:42:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2011-06-20:/deshabilitar-autocompletado-en-vim</id><content type="html">
        Uso poco vim, sólo cuando en servidores no tengo jed.

Para pegar texto en el fichero actual sin que te haga un horrible indentado en cascada:
&lt;pre&gt;:setlocal noautoindent
:setlocal nocindent
:setlocal nosmartindent
:setlocal indentexpr=&lt;/pre&gt;
O su equivalente:
&lt;pre&gt;:setl noai nocin nosi inde=&lt;/pre&gt;
Referencia: &lt;a href="http://vim.wikia.com/wiki/How_to_stop_auto_indenting"&gt;http://vim.wikia.com/wiki/How_to_stop_auto_indenting &lt;/a&gt;
    </content><category term="Infraestructura"></category><category term="linux"></category></entry><entry><title>Diccionario de español para komodo edit</title><link href="https://pablocaro.es/diccionario-de-espanol-para-komodo-edit" rel="alternate"></link><published>2011-06-13T07:41:00+02:00</published><updated>2011-06-13T07:41:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2011-06-13:/diccionario-de-espanol-para-komodo-edit</id><content type="html">
        Para instalarlo se siguen &lt;a href="http://community.activestate.com/faq/how-do-i-install-new-dictionaries-spell-checking#comment-4059" target="_blank"&gt;estas instrucciones&lt;/a&gt; descargando previamente &lt;a href="https://addons.mozilla.org/es-ES/firefox/addon/spanish-spain-dictionary/" target="_blank"&gt;el addon&lt;/a&gt;.

No he probado a ver si funciona en el perfil de usuario, pero eso estaría mejor.
    </content><category term="Programación"></category></entry><entry><title>21/3</title><link href="https://pablocaro.es/21-3" rel="alternate"></link><published>2011-03-22T02:35:00+01:00</published><updated>2011-03-22T02:35:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2011-03-22:/21-3</id><summary type="html">
        Han pasado ya&lt;a title="día síndrome down" href="http://www.abc.es/20110321/comunidad-castillaleon/abcp-mundial-sindrome-down-20110321.html"&gt; tres días 21&lt;/a&gt; en lo que llevamos de año. Ayer, fue un día de &lt;a href="http://www.eldiariomontanes.es/v/20110322/sociedad/destacados/jovenes-sindrome-down-exigen-20110322.html"&gt;reivindicaciones&lt;/a&gt;, para exigir la visibilidad necesaria y el respecto que esta sociedad &lt;a href="http://www.elpais.com/articulo/sociedad/Inhabilitado/dueno/pub/echo/jovenes/Down/elpepisoc/20110311elpepisoc_13/Tes"&gt;todavía niega&lt;/a&gt;. Cuando en el día de hoy se habla de &lt;a href="http://www.elpais.com/articulo/internacional/disputas/participacion/OTAN/dividen/aliados/elpepuint/20110322elpepuint_6/Tes"&gt;guerra&lt;/a&gt; y &lt;a href="http://www.elpais.com/articulo/internacional/tecnicos/reanundan/trabajos/Fukushima/elpepuint/20110322elpepuint_5/Tes"&gt;emisiones radiactivas&lt;/a&gt;, yo he aprendido en este último …</summary><content type="html">
        Han pasado ya&lt;a title="día síndrome down" href="http://www.abc.es/20110321/comunidad-castillaleon/abcp-mundial-sindrome-down-20110321.html"&gt; tres días 21&lt;/a&gt; en lo que llevamos de año. Ayer, fue un día de &lt;a href="http://www.eldiariomontanes.es/v/20110322/sociedad/destacados/jovenes-sindrome-down-exigen-20110322.html"&gt;reivindicaciones&lt;/a&gt;, para exigir la visibilidad necesaria y el respecto que esta sociedad &lt;a href="http://www.elpais.com/articulo/sociedad/Inhabilitado/dueno/pub/echo/jovenes/Down/elpepisoc/20110311elpepisoc_13/Tes"&gt;todavía niega&lt;/a&gt;. Cuando en el día de hoy se habla de &lt;a href="http://www.elpais.com/articulo/internacional/disputas/participacion/OTAN/dividen/aliados/elpepuint/20110322elpepuint_6/Tes"&gt;guerra&lt;/a&gt; y &lt;a href="http://www.elpais.com/articulo/internacional/tecnicos/reanundan/trabajos/Fukushima/elpepuint/20110322elpepuint_5/Tes"&gt;emisiones radiactivas&lt;/a&gt;, yo he aprendido en este último año, que si gobernaran ellos no habría guerras, y que la sonrisa de un niño con el síndrome radia felicidad a Tera &lt;a href="http://es.wikipedia.org/wiki/Sievert"&gt;Sieverts&lt;/a&gt; por segundo. No se se afectará a la tiroides, pero os aseguro que se nota en el corazón.
    </content><category term="Programación"></category></entry><entry><title>scp con compresión</title><link href="https://pablocaro.es/scp-con-compresion" rel="alternate"></link><published>2011-03-04T03:49:00+01:00</published><updated>2011-03-04T03:49:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2011-03-04:/scp-con-compresion</id><content type="html">
        La mejor forma de descargar una zodb:
&lt;pre lang="bash"&gt;scp -C -o CompressionLevel=9 user@yoursite.com:~/plonefolder/zinstance/var/filestorage/Data.fs .&lt;/pre&gt;
Leído en: &lt;a href="http://blog.mfabrik.com/2011/03/02/scp-file-copy-with-on-line-compression/"&gt;http://blog.mfabrik.com/2011/03/02/scp-file-copy-with-on-line-compression/&lt;/a&gt;
    </content><category term="Programación"></category><category term="linux"></category><category term="zope"></category></entry><entry><title>Actualizar Mercurial usando ppa en ubuntu</title><link href="https://pablocaro.es/actualizar-mercurial-usando-ppa-en-ubuntu" rel="alternate"></link><published>2011-02-25T05:12:00+01:00</published><updated>2011-02-25T05:12:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2011-02-25:/actualizar-mercurial-usando-ppa-en-ubuntu</id><summary type="html">
        Existen tres PPA para mercurial:
&lt;a rel="nofollow" href="https://launchpad.net/~mercurial-ppa/+archive/releases"&gt;https://launchpad.net/~mercurial-ppa/+archive/releases&lt;/a&gt; (última release, actualmente 1.7.5-0ppa1~maverick1)
&lt;a rel="nofollow" href="https://launchpad.net/~mercurial-ppa/+archive/stable-snapshots"&gt;https://launchpad.net/~mercurial-ppa/+archive/stable-snapshots&lt;/a&gt; (snapshots de la rama estable, actualmente ﻿1.7.5+2-5fc7c84ed9b0-0ppa1~maverick1)
&lt;a rel="nofollow" href="https://launchpad.net/~mercurial-ppa/+archive/snapshots"&gt;https://launchpad.net/~mercurial-ppa/+archive/snapshots&lt;/a&gt; (snapshots de lo último, actualmente 1.8~1.7 …</summary><content type="html">
        Existen tres PPA para mercurial:
&lt;a rel="nofollow" href="https://launchpad.net/~mercurial-ppa/+archive/releases"&gt;https://launchpad.net/~mercurial-ppa/+archive/releases&lt;/a&gt; (última release, actualmente 1.7.5-0ppa1~maverick1)
&lt;a rel="nofollow" href="https://launchpad.net/~mercurial-ppa/+archive/stable-snapshots"&gt;https://launchpad.net/~mercurial-ppa/+archive/stable-snapshots&lt;/a&gt; (snapshots de la rama estable, actualmente ﻿1.7.5+2-5fc7c84ed9b0-0ppa1~maverick1)
&lt;a rel="nofollow" href="https://launchpad.net/~mercurial-ppa/+archive/snapshots"&gt;https://launchpad.net/~mercurial-ppa/+archive/snapshots&lt;/a&gt; (snapshots de lo último, actualmente 1.8~1.7.5+4-8f5c865b7b4a-0ppa1~maverick1)

Como decía en la última anotación para añadir el PPA basta con:
&lt;pre class="bash"&gt;$ sudo add-apt-repository ppa:mercurial-ppa/stable-snapshots
$ sudo aptitude update
$ sudo aptitude  install Mercurial&lt;/pre&gt;
&amp;nbsp;
    </content><category term="Infraestructura"></category><category term="linux"></category></entry><entry><title>Añadiendo un repositio PPA en karmic</title><link href="https://pablocaro.es/anadiendo-un-repositio-ppa-en-karmic" rel="alternate"></link><published>2011-02-23T07:22:00+01:00</published><updated>2011-02-23T07:22:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2011-02-23:/anadiendo-un-repositio-ppa-en-karmic</id><content type="html">
        Este comandito te añade sola la fuente (ojo, en el directorio /etc/apt/sources.list.d) y pide las claves:
&lt;pre lang="bash"&gt;sudo add-apt-repository ppa:chromium-daily&lt;/pre&gt;
    </content><category term="Programación"></category></entry><entry><title>Dos pantallas al inicio de kdm</title><link href="https://pablocaro.es/dos-pantallas-al-inicio-de-kdm" rel="alternate"></link><published>2011-02-01T02:32:00+01:00</published><updated>2011-02-01T02:32:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2011-02-01:/dos-pantallas-al-inicio-de-kdm</id><summary type="html">
        Para que ya el mismo kdm se ejecute con dos pantallas basta con poner la configuración deseada (usando &lt;a title="xrandr" href="http://www.x.org/wiki/Projects/XRandR" target="_blank"&gt;xrandr&lt;/a&gt;) en el fichero ﻿&lt;strong&gt;/etc/kde4/kdm/Xsetup&lt;/strong&gt;. Por ejemplo, en mi caso uso dos pantallas en la oficina de modo que ese fichero contiene:
&lt;pre lang="bash"&gt;INT="LVDS1"
EXT="VGA1"
xrandr -q | grep …&lt;/pre&gt;</summary><content type="html">
        Para que ya el mismo kdm se ejecute con dos pantallas basta con poner la configuración deseada (usando &lt;a title="xrandr" href="http://www.x.org/wiki/Projects/XRandR" target="_blank"&gt;xrandr&lt;/a&gt;) en el fichero ﻿&lt;strong&gt;/etc/kde4/kdm/Xsetup&lt;/strong&gt;. Por ejemplo, en mi caso uso dos pantallas en la oficina de modo que ese fichero contiene:
&lt;pre lang="bash"&gt;INT="LVDS1"
EXT="VGA1"
xrandr -q | grep -q "$EXT connected"   &amp;amp;&amp;amp; {
xrandr --output $EXT --mode 1280x1024
xrandr --output $INT --mode 1280x800
xrandr --output $INT --bellow $EXT
xrandr --output $INT --primary
}&lt;/pre&gt;
Estas lineas hacen que se consulte si tengo el segundo monitor conectado y en se caso se configure de la forma deseada.
    </content><category term="Infraestructura"></category></entry><entry><title>Zen Coding y komodo edit</title><link href="https://pablocaro.es/zen-coding-y-komodo-edit" rel="alternate"></link><published>2010-10-15T14:41:00+02:00</published><updated>2010-10-15T14:41:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2010-10-15:/zen-coding-y-komodo-edit</id><summary type="html">
        Mi editor favorito &lt;a href="http://www.activestate.com/komodo-edit" target="_blank"&gt;Komodo Edit&lt;/a&gt; tiene un plugin de &lt;a href="http://code.google.com/p/zen-coding/" target="_blank"&gt;Zen Coding &lt;/a&gt;y este funciona muy bien también con plantillas zpt. Por ejemplo:

Así una expresión como la siguente
&lt;pre lang="css"&gt;
dl&gt;tal:rep[tal:repeat]&gt;dt+dd
&lt;/pre&gt;
Se convierte en el siguiente trozo de html con los nesesarios tabstops:
&lt;pre lang="css"&gt;
&lt;dl&gt;
    &lt;tal:rep tal:repeat=""&gt;
        &lt;dt&gt;&lt;/dt&gt;
        &lt;dd&gt;&lt;/dd&gt;
    &lt;/tal:rep&gt;
&lt;/dl&gt;&lt;/pre&gt;
Muy pero que …</summary><content type="html">
        Mi editor favorito &lt;a href="http://www.activestate.com/komodo-edit" target="_blank"&gt;Komodo Edit&lt;/a&gt; tiene un plugin de &lt;a href="http://code.google.com/p/zen-coding/" target="_blank"&gt;Zen Coding &lt;/a&gt;y este funciona muy bien también con plantillas zpt. Por ejemplo:

Así una expresión como la siguente
&lt;pre lang="css"&gt;
dl&gt;tal:rep[tal:repeat]&gt;dt+dd
&lt;/pre&gt;
Se convierte en el siguiente trozo de html con los nesesarios tabstops:
&lt;pre lang="css"&gt;
&lt;dl&gt;
    &lt;tal:rep tal:repeat=""&gt;
        &lt;dt&gt;&lt;/dt&gt;
        &lt;dd&gt;&lt;/dd&gt;
    &lt;/tal:rep&gt;
&lt;/dl&gt;&lt;/pre&gt;
Muy pero que muy cómodo
    </content><category term="Programación"></category><category term="zope"></category></entry><entry><title>Progreso en un reindexado de zope</title><link href="https://pablocaro.es/progreso-en-un-reindexado-de-zope" rel="alternate"></link><published>2010-09-18T06:06:00+02:00</published><updated>2010-09-18T06:06:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2010-09-18:/progreso-en-un-reindexado-de-zope</id><content type="html">
        Visto en el producto contentleadimage:
&lt;pre lang="python"&gt;from Products.ZCatalog.ProgressHandler import ZLogHandler
ctool.reindexIndex(['hasContentLeadImage'], portal.REQUEST, pghandler=ZLogHandler())&lt;/pre&gt;
Esto muestra un progreso en la consola si estás ejecutando  en primer plano.
    </content><category term="Programación"></category><category term="zope"></category></entry><entry><title>Zope y noatime</title><link href="https://pablocaro.es/zope-y-noatime" rel="alternate"></link><published>2010-07-25T05:33:00+02:00</published><updated>2010-07-25T05:33:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2010-07-25:/zope-y-noatime</id><content type="html">
        Una fácil mejora para el rendimiento de un zope con problemas de acceso a disco es&lt;a href="http://tldp.org/LDP/solrhe/Securing-Optimizing-Linux-RH-Edition-v1.3/chap6sec73.html"&gt; no actualizar la fecha de último acceso&lt;/a&gt; al fichero de la base de datos Data.fs:

&lt;pre lang="bash"&gt;
chattr -R +A Data.fs
&lt;/pre&gt;

Simple y efectivo.
    </content><category term="Programación"></category><category term="zope"></category></entry><entry><title>Subversion: Añadiendo contenido a un directorio sin descargarlo</title><link href="https://pablocaro.es/subversion-anadiendo-contenido-a-un-directorio-sin-descargarlo" rel="alternate"></link><published>2010-07-16T05:05:00+02:00</published><updated>2010-07-16T05:05:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2010-07-16:/subversion-anadiendo-contenido-a-un-directorio-sin-descargarlo</id><summary type="html">
        Caso práctico. Tengo un directorio &lt;em&gt;dev&lt;/em&gt; dentro del subversión con un montón de código con sus correspondientes ramas de &lt;code&gt;trunk&lt;/code&gt;, &lt;code&gt;branches&lt;/code&gt;, &lt;code&gt;tags&lt;/code&gt;, etc. Quiero añadir un nuevo producto pero no descargar todo ese enorme directorio &lt;em&gt;dev&lt;/em&gt;:
&lt;pre lang="bash"&gt;svn co --depth empty https://repositorio/dev dev
cp loquesea nuevo_producto
svn add loquesea …&lt;/pre&gt;</summary><content type="html">
        Caso práctico. Tengo un directorio &lt;em&gt;dev&lt;/em&gt; dentro del subversión con un montón de código con sus correspondientes ramas de &lt;code&gt;trunk&lt;/code&gt;, &lt;code&gt;branches&lt;/code&gt;, &lt;code&gt;tags&lt;/code&gt;, etc. Quiero añadir un nuevo producto pero no descargar todo ese enorme directorio &lt;em&gt;dev&lt;/em&gt;:
&lt;pre lang="bash"&gt;svn co --depth empty https://repositorio/dev dev
cp loquesea nuevo_producto
svn add loquesea
svn ci&lt;/pre&gt;
Gracias ctabasco por la anotación.
    </content><category term="Programación"></category></entry><entry><title>Restaurando objectos plone borrados</title><link href="https://pablocaro.es/restaurando-objectos-plone-borrados" rel="alternate"></link><published>2010-07-14T01:33:00+02:00</published><updated>2010-07-14T01:33:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2010-07-14:/restaurando-objectos-plone-borrados</id><content type="html">
        Para esas situaciones donde no funciona el undo, la salvación se llama &lt;a href="http://pypi.python.org/pypi/dm.historical"&gt;dm.historical&lt;/a&gt;

Un claro ejemplo en &lt;a href="http://www.zopyx.de/blog/restoring-an-older-state-of-arbitrary-zope-objects" target="_blank"&gt;esta entrada&lt;/a&gt; del Blog de Andreas Jung
    </content><category term="Programación"></category><category term="zope"></category></entry><entry><title>Opciones típicas rsync</title><link href="https://pablocaro.es/opciones-tipicas-rsync" rel="alternate"></link><published>2010-07-08T05:56:00+02:00</published><updated>2010-07-08T05:56:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2010-07-08:/opciones-tipicas-rsync</id><content type="html">
        Para usar un rsync como un scp (pero con las ventajas de rsync como que puedo parar y continuar después):
&lt;pre lang="bash"&gt;rsync -azPrv -e 'ssh' usuario@host:/path/to/folder /path/to/destination&lt;/pre&gt;
    </content><category term="Infraestructura"></category><category term="linux"></category></entry><entry><title>Completar mientras se depura en zope</title><link href="https://pablocaro.es/completar-mientras-se-depura-en-zope" rel="alternate"></link><published>2010-04-28T03:33:00+02:00</published><updated>2010-04-28T03:33:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2010-04-28:/completar-mientras-se-depura-en-zope</id><content type="html">
        Siempre tengo que buscar esto en google, para esto tengo el blog:

&lt;pre lang="bash"&gt;
$ bin/instance debug
&lt;/pre&gt;

&lt;pre lang="python"&gt;
&gt;&gt;&gt; import readline, rlcompleter
&gt;&gt;&gt; readline.parse_and_bind('tab: complete')
&gt;&gt;&gt; app.
Display all 760 possibilities? (y or n)
&gt;&gt;&gt; 
&lt;/pre&gt;
    </content><category term="Programación"></category><category term="web"></category></entry><entry><title>Kbabel es Lokalize en kde4</title><link href="https://pablocaro.es/kbabel-es-lokalize-en-kde4" rel="alternate"></link><published>2010-03-25T10:41:00+01:00</published><updated>2010-03-25T10:41:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2010-03-25:/kbabel-es-lokalize-en-kde4</id><content type="html">
        Ya era la segunda vez que no encontraba el maravilloso &lt;a href="http://kbabel.kde.org/" target="_blank"&gt;kbabel&lt;/a&gt; y es que en kde4 su sucesor se llama &lt;a href="http://userbase.kde.org/Lokalize" target="_blank"&gt;lokalize&lt;/a&gt;
    </content><category term="Programación"></category></entry><entry><title>Descargar una web para visualización local con wget</title><link href="https://pablocaro.es/descargar-una-web-para-visualizacion-local-con-wget" rel="alternate"></link><published>2010-02-22T10:09:00+01:00</published><updated>2010-02-22T10:09:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2010-02-22:/descargar-una-web-para-visualizacion-local-con-wget</id><summary type="html">
        El comando es
&lt;pre lang="bash"&gt;$ wget -E -H -k -K -p  http://www.google.es&lt;/pre&gt;
Con &lt;em&gt;-p&lt;/em&gt; hacemos que se descarguen los enlaces requeridos (css, javascript), con &lt;em&gt;-k&lt;/em&gt; convertimos los enlaces dentro del html para visualización local (&lt;em&gt;-K&lt;/em&gt; copia de seguriad) y con &lt;em&gt;-E&lt;/em&gt; aseguramos que los ficheros descargados tengan extensión html …</summary><content type="html">
        El comando es
&lt;pre lang="bash"&gt;$ wget -E -H -k -K -p  http://www.google.es&lt;/pre&gt;
Con &lt;em&gt;-p&lt;/em&gt; hacemos que se descarguen los enlaces requeridos (css, javascript), con &lt;em&gt;-k&lt;/em&gt; convertimos los enlaces dentro del html para visualización local (&lt;em&gt;-K&lt;/em&gt; copia de seguriad) y con &lt;em&gt;-E&lt;/em&gt; aseguramos que los ficheros descargados tengan extensión html para la visualización en local.
    </content><category term="Programación"></category></entry><entry><title>Plone: Registrar pasos de importación con zcml</title><link href="https://pablocaro.es/plone-registrar-pasos-de-importacion-con-zcml" rel="alternate"></link><published>2010-01-27T04:38:00+01:00</published><updated>2010-01-27T04:38:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2010-01-27:/plone-registrar-pasos-de-importacion-con-zcml</id><summary type="html">
        Registrar un paso de importación con xml el el profile, puede fallar cuando quitas el paquete del buildout.
Un paso roto se que queda dentro de portal_setup_tool.

En algunas versiones de plone esto da un error, haciendo imposible aplicar el perfil nunca más.

En otras versiones (plone 3.2 hacia …</summary><content type="html">
        Registrar un paso de importación con xml el el profile, puede fallar cuando quitas el paquete del buildout.
Un paso roto se que queda dentro de portal_setup_tool.

En algunas versiones de plone esto da un error, haciendo imposible aplicar el perfil nunca más.

En otras versiones (plone 3.2 hacia arriba) sólo da un warning.
&lt;pre&gt;
&lt;configure xmlns="http://namespaces.zope.org/zope" xmlns:gs="http://namespaces.zope.org/genericsetup" xmlns:five="http://namespaces.zope.org/five" i18n_domain="poi"&gt; 
  &lt;gs:importstep name="poi_various" title="Poi various import handlers" description="" handler="Products.Poi.setuphandlers.import_various"&gt; 
    &lt;depends name="catalog" /&gt; 
  &lt;/gs:importstep&gt; 
&lt;/configure&gt; 
&lt;/pre&gt;

Esto es una traducción de un correo de Mauris Van Rees
    </content><category term="Programación"></category></entry><entry><title>Descargar código de un repositorio publicado con gitweb</title><link href="https://pablocaro.es/descargar-codigo-de-un-repositorio-publicado-con-gitweb" rel="alternate"></link><published>2010-01-04T14:31:00+01:00</published><updated>2010-01-04T14:31:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2010-01-04:/descargar-codigo-de-un-repositorio-publicado-con-gitweb</id><summary type="html">
        &lt;p&gt;Quería probar un software disponible en la red (&lt;a href="http://www.perfect-sale.com/"&gt;Perfect Sale&lt;/a&gt;). Sabía que tenía que usar &lt;em&gt;git clone&lt;/em&gt; pero no la url. &lt;/p&gt;
&lt;p&gt;
No queda claro (al menos a mí) &lt;a href="http://git.hforge.org/?p=shop.git;a=tree"&gt;en el repositorio gitweb&lt;/a&gt; la url a usar.&lt;/p&gt;
Al fin, encontré la solución en &lt;a href="http://gitweb.codeendeavor.com/"&gt;un repositorio mejor documentado&lt;/a&gt; (gracias).
&lt;p&gt;El protocolo es …&lt;/p&gt;</summary><content type="html">
        &lt;p&gt;Quería probar un software disponible en la red (&lt;a href="http://www.perfect-sale.com/"&gt;Perfect Sale&lt;/a&gt;). Sabía que tenía que usar &lt;em&gt;git clone&lt;/em&gt; pero no la url. &lt;/p&gt;
&lt;p&gt;
No queda claro (al menos a mí) &lt;a href="http://git.hforge.org/?p=shop.git;a=tree"&gt;en el repositorio gitweb&lt;/a&gt; la url a usar.&lt;/p&gt;
Al fin, encontré la solución en &lt;a href="http://gitweb.codeendeavor.com/"&gt;un repositorio mejor documentado&lt;/a&gt; (gracias).
&lt;p&gt;El protocolo es git y la url no incluye ese "projects" que vemos en el rastro de migas. Lo anoto para no volver a perder el tiempo otra vez.&lt;/p&gt;
&lt;p&gt;Así que para el proyecto citado la orden queda así:&lt;/p&gt;
&lt;pre lang="bash"&gt;
$ git clone git://git.hforge.org/shop.git
&lt;/pre&gt;

    </content><category term="Programación"></category></entry><entry><title>Crear base de datos mysql a partir de un backup</title><link href="https://pablocaro.es/crear-base-de-datos-mysql-a-partir-de-un-backup" rel="alternate"></link><published>2009-12-17T05:06:00+01:00</published><updated>2009-12-17T05:06:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2009-12-17:/crear-base-de-datos-mysql-a-partir-de-un-backup</id><summary type="html">
        Primero crear la base de datos:
&lt;pre lang="bash"&gt;
pcaro@davinci$ mysql --user=root -p
mysql&gt; show databases;
mysql&gt; create database piwik;
mysql&gt; GRANT ALL ON piwik.* TO pcaro@localhost IDENTIFIED BY "piwik"
mysql&gt; show tables;
&lt;/pre&gt;
Luego restaurar el backup:
&lt;pre lang="bash"&gt;
mysql --user=root --pass=*** --host=localhost piwik &lt; piwik_Thursday.sql
&lt;/pre&gt;
Comprobar todo esta bien …</summary><content type="html">
        Primero crear la base de datos:
&lt;pre lang="bash"&gt;
pcaro@davinci$ mysql --user=root -p
mysql&gt; show databases;
mysql&gt; create database piwik;
mysql&gt; GRANT ALL ON piwik.* TO pcaro@localhost IDENTIFIED BY "piwik"
mysql&gt; show tables;
&lt;/pre&gt;
Luego restaurar el backup:
&lt;pre lang="bash"&gt;
mysql --user=root --pass=*** --host=localhost piwik &lt; piwik_Thursday.sql
&lt;/pre&gt;
Comprobar todo esta bien
&lt;pre lang="bash"&gt;
pcaro@davinci$ mysql --user=root -p
mysql&gt; show tables;
&lt;/pre&gt;



    </content><category term="Infraestructura"></category></entry><entry><title>Parcheando, parcheando ...</title><link href="https://pablocaro.es/parcheando-parcheando" rel="alternate"></link><published>2009-12-16T10:18:00+01:00</published><updated>2009-12-16T10:18:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2009-12-16:/parcheando-parcheando</id><summary type="html">
        Siempre tengo que buscar cómo usar patch. Como tengo un blog que se llama anotaciones pues apunto:
&lt;pre lang="bash"&gt;$ patch -p1 &lt; baz.diff&lt;/pre&gt;
Donde el número 1 viene de mirar en el fichero .diff y contar las barras "/" que hay antes que el directorio donde estoy. Por ejemplo si veo &lt;em&gt;/users/stephen …&lt;/em&gt;</summary><content type="html">
        Siempre tengo que buscar cómo usar patch. Como tengo un blog que se llama anotaciones pues apunto:
&lt;pre lang="bash"&gt;$ patch -p1 &lt; baz.diff&lt;/pre&gt;
Donde el número 1 viene de mirar en el fichero .diff y contar las barras "/" que hay antes que el directorio donde estoy. Por ejemplo si veo &lt;em&gt;/users/stephen/package/src/net/http.c&lt;/em&gt; y estoy en un directorio que contiene &lt;em&gt;net/http.c&lt;/em&gt; pues es 5 (ojo, hay que contar la primera).

Para referencia &lt;a href="http://stephenjungels.com/jungels.net/articles/diff-patch-ten-minutes.html" target="_blank"&gt;The Ten Minute Guide to diff and patch&lt;/a&gt;
    </content><category term="Infraestructura"></category><category term="linux"></category></entry><entry><title>Que las aplicaciones GTK+ luzcan mejor en kubuntu karmic</title><link href="https://pablocaro.es/que-las-aplicaciones-gtk-luzcan-mejor-en-kubuntu-karmic" rel="alternate"></link><published>2009-12-10T06:32:00+01:00</published><updated>2009-12-10T06:32:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2009-12-10:/que-las-aplicaciones-gtk-luzcan-mejor-en-kubuntu-karmic</id><content type="html">
        Basta con seguir este &lt;a href="http://marcosaruj.com/archives/401" target="_blank"&gt;tutorial&lt;/a&gt;, que indica como instalar el tema &lt;a href="http://kde-look.org/content/show.php?action=content&amp;amp;content=103741&amp;amp;PHPSESSID=b084f440c2a6cc2a1644514b13fe591a" target="_blank"&gt;Oxygen-Molecule&lt;/a&gt;.
    </content><category term="Infraestructura"></category><category term="linux"></category></entry><entry><title>Añadir PPA desde la consola a ubuntu karmic</title><link href="https://pablocaro.es/anadir-ppa-desde-la-consola-a-ubuntu-karmic" rel="alternate"></link><published>2009-12-09T03:18:00+01:00</published><updated>2009-12-09T03:18:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2009-12-09:/anadir-ppa-desde-la-consola-a-ubuntu-karmic</id><summary type="html">
        Para añadir un PPA a ubuntu karmic desde la consola basta con:

&lt;pre lang="bash"&gt;
 sudo add-apt-repository ppa:kubuntu-ppa/ppa

[sudo] password for pcaro:                                
Executing: gpg --ignore-time-conflict --no-options --no-default-keyring --secret-keyring /etc/apt/secring.gpg --trustdb-name /etc/apt/trustdb.gpg --keyring /etc/apt/trusted.gpg --keyserver keyserver.ubuntu.com --recv E4DFEC907DEDA4B8A670E8042836CB0A8AC93F7A                                                                                  
gpg: solicitando clave …&lt;/pre&gt;</summary><content type="html">
        Para añadir un PPA a ubuntu karmic desde la consola basta con:

&lt;pre lang="bash"&gt;
 sudo add-apt-repository ppa:kubuntu-ppa/ppa

[sudo] password for pcaro:                                
Executing: gpg --ignore-time-conflict --no-options --no-default-keyring --secret-keyring /etc/apt/secring.gpg --trustdb-name /etc/apt/trustdb.gpg --keyring /etc/apt/trusted.gpg --keyserver keyserver.ubuntu.com --recv E4DFEC907DEDA4B8A670E8042836CB0A8AC93F7A                                                                                  
gpg: solicitando clave 8AC93F7A de hkp servidor keyserver.ubuntu.com                                             
gpg: clave 8AC93F7A: clave pública "Launchpad Kubuntu Updates" importada                                         
gpg: no se encuentran claves totalmente fiables                                                                  
gpg: Cantidad total procesada: 1                                                                                 
gpg:  
&lt;/pre&gt;


    </content><category term="Infraestructura"></category><category term="linux"></category></entry><entry><title>Cambiar dpi en kde</title><link href="https://pablocaro.es/cambiar-dpi-en-kde" rel="alternate"></link><published>2009-12-04T05:35:00+01:00</published><updated>2009-12-04T05:35:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2009-12-04:/cambiar-dpi-en-kde</id><summary type="html">
        Siempre que reinstalo me encuentro con el mismo problema. No tengo el dpi estándar de 96, sino uno extraño de 113.
&lt;pre lang="bash"&gt;pcaro@davinci$ xdpyinfo | grep resolutio
  resolution:    113x113 dots per inch&lt;/pre&gt;
Como resultado, muchas páginas webs no se visualizan correctamente.

Para solucionarlo tengo que decirle al servidor X que arranque …</summary><content type="html">
        Siempre que reinstalo me encuentro con el mismo problema. No tengo el dpi estándar de 96, sino uno extraño de 113.
&lt;pre lang="bash"&gt;pcaro@davinci$ xdpyinfo | grep resolutio
  resolution:    113x113 dots per inch&lt;/pre&gt;
Como resultado, muchas páginas webs no se visualizan correctamente.

Para solucionarlo tengo que decirle al servidor X que arranque con un dpi predeterminado. Eso lo hago en los ficheros de configuración de kdm que es el encargado de ejecutar el servidor.

Concretamente en &lt;em&gt;/etc/kde4/kdm/kdmrc&lt;/em&gt;. Basta con indicar al servidor X qué valor de dpi usar. Para ello usamos la opción &lt;em&gt;-dpi&lt;/em&gt;.
&lt;pre lang="ini"&gt;# This string is subject to word splitting.
# Default is ""
ServerArgsLocal=-br -nolisten tcp -dpi 96&lt;/pre&gt;
Eso es todo. Reiniciar las X y listo.

PD: No se el motivo pero el nuevo dpi y mi tarjeta gráfica intel integrada, yo tengo mucho mejor rendimiento en la aceleración gráfica.
    </content><category term="Infraestructura"></category><category term="linux"></category></entry><entry><title>Actualizando postgres en debian y derivados</title><link href="https://pablocaro.es/actualizando-postgres-en-debian-y-derivados" rel="alternate"></link><published>2009-11-26T05:19:00+01:00</published><updated>2009-11-26T05:19:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2009-11-26:/actualizando-postgres-en-debian-y-derivados</id><summary type="html">
        Siempre que se habla de debian, se alaba su paquetería. Y yo estoy de acuerdo de que es uno de los puntos fuertes de la distribución. Hoy me refiero a la facilidad de tener instaladas y conviviendo felices varias versiones de la base de datos &lt;a href="http://www.postgresql.org/" target="_blank"&gt;postgres&lt;/a&gt;. Ya comenté esto en …</summary><content type="html">
        Siempre que se habla de debian, se alaba su paquetería. Y yo estoy de acuerdo de que es uno de los puntos fuertes de la distribución. Hoy me refiero a la facilidad de tener instaladas y conviviendo felices varias versiones de la base de datos &lt;a href="http://www.postgresql.org/" target="_blank"&gt;postgres&lt;/a&gt;. Ya comenté esto en su momento en &lt;a href="/instalar-postgres-82-en-jaunty/" target="_blank"&gt;Instalar postgres 8.2 en jaunty&lt;/a&gt;.

Pues el tener dos servidores instalados a la vez, permite la fácil&lt;a href="http://www.postgresql.org/docs/8.4/interactive/migration.html" target="_blank"&gt; migración de un versión a otra del motor de base de datos&lt;/a&gt;. En este caso detallo como migré las bases de datos de mi equipo ubuntu &lt;a href="http://releases.ubuntu.com/karmic/" target="_blank"&gt;karmic&lt;/a&gt; de postgres 8.3 a 8.4.
&lt;ol&gt;
	&lt;li&gt;Primero tener instalados los dos paquetes: postgresql-8.3 y postgresql-8.4.&lt;/li&gt;
	&lt;li&gt;Determinar en que puerto se ejecuta cada instancia. Esto lo podemos hacer mirando los ficheros &lt;em&gt;/etc/postgresql/8.X/main/postgresql.conf &lt;/em&gt;(variable &lt;em&gt;port&lt;/em&gt;). En mi caso 5482 y 5433 respectivamente. Lo puedes comprobar con:
&lt;pre lang="bash"&gt;$ sudo netstat -putan | grep postgres&lt;/pre&gt;
&lt;/li&gt;
	&lt;li&gt;Después lanzar los dos servidores:
&lt;pre lang="bash"&gt;$ sudo /etc/init.d/postgresql-8.3 start
$ sudo /etc/init.d/postgresql-8.4 start&lt;/pre&gt;
&lt;/li&gt;
	&lt;li&gt;Finalmente copiar los datos de una versión a otra
&lt;pre&gt;$ sudo su - postgres
$ pg_dumpall -p 5432 | psql -d postgres -p 5433&lt;/pre&gt;
&lt;/li&gt;
	&lt;li&gt; Probar que todo ha ido correctamente&lt;/li&gt;
&lt;/ol&gt;
    </content><category term="Infraestructura"></category></entry><entry><title>Añadiendo lenguaje procedural a base de datos postgres: createlang</title><link href="https://pablocaro.es/anadiendo-lenguaje-procedural-a-base-de-datos-postgres-createlang" rel="alternate"></link><published>2009-11-12T05:20:00+01:00</published><updated>2009-11-12T05:20:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2009-11-12:/anadiendo-lenguaje-procedural-a-base-de-datos-postgres-createlang</id><content type="html">
        Postgres 8.3 incluye 4 &lt;a href="http://www.postgresql.org/docs/8.3/static/external-pl.html"&gt;lenguajes procedurales&lt;/a&gt; en la distribución y se pueden instalar más, pero hay que activarlos por base de datos.
Para ello, hay varios métodos (lo clásico es &lt;a href="http://www.postgresql.org/docs/8.3/static/sql-createlanguage.html" target="_blank"&gt;create language&lt;/a&gt;) pero los más cómodo es usar &lt;a href="http://www.postgresql.org/docs/8.3/static/app-createlang.html"&gt;createlang&lt;/a&gt;.

Usa &lt;a href="http://www.postgresql.org/docs/8.3/static/app-droplang.html"&gt;&lt;span class="APPLICATION"&gt;droplang&lt;/span&gt;&lt;/a&gt; para eliminar el lenguaje.
    </content><category term="Infraestructura"></category></entry><entry><title>Colores desde web developer tools</title><link href="https://pablocaro.es/colores-desde-web-developer-tools" rel="alternate"></link><published>2009-11-10T04:55:00+01:00</published><updated>2009-11-10T04:55:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2009-11-10:/colores-desde-web-developer-tools</id><content type="html">
        Últimamente uso tanto &lt;a href="http://getfirebug.com/"&gt;firebug&lt;/a&gt; que ni me acuerdo de las &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/60"&gt;Web Developer Tools&lt;/a&gt;.
Escribo esto para recordar que sigue siendo muy útil esta extensión. 
Por ejemplo, puedo ver los colores (css) usados en una página web a través del menú &lt;em&gt;Información&lt;/em&gt;, seleccionando &lt;em&gt;Ver información del color&lt;/em&gt;.


    </content><category term="Programación"></category><category term="web"></category></entry><entry><title>Que apache sirva el contenido de un fichero .py</title><link href="https://pablocaro.es/que-apache-sirva-el-contenido-de-un-fichero-py" rel="alternate"></link><published>2009-10-13T05:36:00+02:00</published><updated>2009-10-13T05:36:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2009-10-13:/que-apache-sirva-el-contenido-de-un-fichero-py</id><content type="html">
        Para que el servidor apache de &lt;a href="http://www.webfaction.com/" target="_blank"&gt;webfaction&lt;/a&gt; no intentara ejecutar los scripts python que dejo para la descarga en este blog, me creé un fichero &lt;a href="http://httpd.apache.org/docs/1.3/howto/htaccess.html"&gt;&lt;em&gt;.htaccess&lt;/em&gt;&lt;/a&gt; y dentro:
&lt;pre&gt;
removeHandler cgi-script .pl .py .cgi
&lt;/pre&gt;
    </content><category term="Programación"></category></entry><entry><title>Cambiar pantalla anunciadora de kde: Ksplash theme generator</title><link href="https://pablocaro.es/cambiar-pantalla-anunciadora-de-kde-ksplash-theme-generator" rel="alternate"></link><published>2009-09-20T08:22:00+02:00</published><updated>2009-09-20T08:22:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2009-09-20:/cambiar-pantalla-anunciadora-de-kde-ksplash-theme-generator</id><content type="html">
        Lo dicho en la nota anterior sobre &lt;a href="http://www.kde-apps.org/content/show.php/Kdm+theme+generator?content=102760"&gt;KDM Theme Generator&lt;/a&gt; aplica también para su aplicación hermana &lt;a href="http://www.kde-apps.org/content/show.php/Ksplash+theme+generator?content=104456" target="_blank"&gt;Ksplash theme generator&lt;/a&gt;
    </content><category term="Programación"></category></entry><entry><title>Cambiar el aspecto de kdm: kdm theme generator</title><link href="https://pablocaro.es/cambiar-el-aspecto-de-kdm-kdm-theme-generator" rel="alternate"></link><published>2009-09-20T08:07:00+02:00</published><updated>2009-09-20T08:07:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2009-09-20:/cambiar-el-aspecto-de-kdm-kdm-theme-generator</id><summary type="html">
        &lt;a href="http://www.kde-apps.org/content/show.php/Kdm+theme+generator?content=102760"&gt; KDM Theme Generator&lt;/a&gt; es una de esas aplicaciones simples y sencillas (sin interfaz gráfica siquiera), con una utilidad concretísima pero que funciona tan bien que da gloria de usar.

Siguiendo las instrucciones de instalación, te crea un tema para KDM basado en el tema plasma activo y tu fondo de …</summary><content type="html">
        &lt;a href="http://www.kde-apps.org/content/show.php/Kdm+theme+generator?content=102760"&gt; KDM Theme Generator&lt;/a&gt; es una de esas aplicaciones simples y sencillas (sin interfaz gráfica siquiera), con una utilidad concretísima pero que funciona tan bien que da gloria de usar.

Siguiendo las instrucciones de instalación, te crea un tema para KDM basado en el tema plasma activo y tu fondo de escritorio actual. De esta forma tan simple mejorarás visualmente el arranque de tu kde.
    </content><category term="Programación"></category></entry><entry><title>Cambiar la codificación de un fichero de texto</title><link href="https://pablocaro.es/cambiar-la-codificacion-de-un-fichero-de-texto" rel="alternate"></link><published>2009-07-27T11:19:00+02:00</published><updated>2009-07-27T11:19:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2009-07-27:/cambiar-la-codificacion-de-un-fichero-de-texto</id><content type="html">
        Una nota para recordar el comando &lt;a href="http://en.wikipedia.org/wiki/Iconv" target="_blank"&gt;iconv&lt;/a&gt;:
&lt;pre lang="bash"&gt;
iconv -f fromEncoding -t toEncoding inputFile &gt; outputFile
&lt;/pre&gt;
    </content><category term="Infraestructura"></category><category term="linux"></category></entry><entry><title>CSS: Estilos de selección de texto</title><link href="https://pablocaro.es/css-estilos-de-seleccion-de-texto" rel="alternate"></link><published>2009-07-09T03:57:00+02:00</published><updated>2009-07-09T03:57:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2009-07-09:/css-estilos-de-seleccion-de-texto</id><content type="html">
        Una de esas declaraciones de CSS3 que ya aceptan muchos navegadores actuales es &lt;strong&gt;::selection&lt;/strong&gt;.
Se puede utilizar así:
&lt;pre lang="css"&gt;
p::selection {
	background: #ccaacc; /* Safari */
	}
p::-moz-selection {
	background: #ccaacc; /* Firefox */
}
&lt;/pre&gt;
    </content><category term="Programación"></category><category term="web"></category></entry><entry><title>Manejo de árboles en bases de datos</title><link href="https://pablocaro.es/manejo-de-arboles-en-bases-de-datos" rel="alternate"></link><published>2009-07-07T16:59:00+02:00</published><updated>2009-07-07T16:59:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2009-07-07:/manejo-de-arboles-en-bases-de-datos</id><summary type="html">
        Hay cuatro formas de guardar árboles en bases de datos relacionales:
&lt;ol&gt;
	&lt;li&gt;Adjacency List&lt;/li&gt;
	&lt;li&gt;Materialized Paths&lt;/li&gt;
	&lt;li&gt;Nested Sets o Modified Preorder Tree Traversal (MPTT)&lt;/li&gt;
	&lt;li&gt;Nested intervals&lt;/li&gt;
&lt;/ol&gt;
Dejo unos enlaces:
&lt;ul&gt;
	&lt;li&gt;&lt;a href="http://www.dbazine.com/oracle/or-articles/tropashko4"&gt;Trees in SQL: Nested Sets and Materialized Path&lt;/a&gt; El articulo y el hombre de referencia&lt;/li&gt;
	&lt;li&gt;&lt;a href="http://www.sitepoint.com/article/hierarchical-data-database/"&gt;Storing Hierarchical Data in a Database&lt;/a&gt; Todo …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">
        Hay cuatro formas de guardar árboles en bases de datos relacionales:
&lt;ol&gt;
	&lt;li&gt;Adjacency List&lt;/li&gt;
	&lt;li&gt;Materialized Paths&lt;/li&gt;
	&lt;li&gt;Nested Sets o Modified Preorder Tree Traversal (MPTT)&lt;/li&gt;
	&lt;li&gt;Nested intervals&lt;/li&gt;
&lt;/ol&gt;
Dejo unos enlaces:
&lt;ul&gt;
	&lt;li&gt;&lt;a href="http://www.dbazine.com/oracle/or-articles/tropashko4"&gt;Trees in SQL: Nested Sets and Materialized Path&lt;/a&gt; El articulo y el hombre de referencia&lt;/li&gt;
	&lt;li&gt;&lt;a href="http://www.sitepoint.com/article/hierarchical-data-database/"&gt;Storing Hierarchical Data in a Database&lt;/a&gt; Todo muy clarito con muy buenos ejemplos&lt;/li&gt;
	&lt;li&gt;&lt;a href="http://http://dev.mysql.com/tech-resources/articles/hierarchical-data.html"&gt;Mysql: datos jerárquicos&lt;/a&gt; Explicación clara y general&lt;/li&gt;
	&lt;li&gt;&lt;a href="http://www.ibstaff.net/fmartinez/?p=18"&gt; Materialized Path + Nested Sets &lt;/a&gt; Una forma de mezclar estos dos metodos&lt;/li&gt;
&lt;/ul&gt;
Algunas implentacionaciones:
&lt;ul&gt;
	&lt;li&gt;&lt;a href="http://sqlamp.angri.ru/"&gt;sqlamp — Materialized Path for SQLAlchemy&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="http://code.google.com/p/django-treebeard/"&gt;Django Treebeard&lt;/a&gt; Adjacency List, Materialized Path y Nested Sets para django&lt;/li&gt;
	&lt;li&gt;&lt;a href="http://code.google.com/p/django-mptt/"&gt;django-mptt&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
    </content><category term="Programación"></category></entry><entry><title>Mostrar el código de funciones en postgres</title><link href="https://pablocaro.es/mostrar-el-codigo-de-funciones-en-postgres" rel="alternate"></link><published>2009-07-07T11:18:00+02:00</published><updated>2009-07-07T11:18:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2009-07-07:/mostrar-el-codigo-de-funciones-en-postgres</id><summary type="html">
        Puede resultar muy util ver el código de algún procedimiento almacenado en postgres. El código de a continuación tomado de &lt;a href="http://www.varlena.com/GeneralBits/39.php"&gt;Postgresql General Bits&lt;/a&gt; de A. Elein Mustain puede resultar muy útil. 
&lt;pre lang="sql"&gt;
create or replace view showfunctions as
select
l.lanname,
t.typname || ' = ' || n.nspname || '.' ||
    p.proname || '(' || pg_catalog.oidvectortypes(p.proargtypes …&lt;/pre&gt;</summary><content type="html">
        Puede resultar muy util ver el código de algún procedimiento almacenado en postgres. El código de a continuación tomado de &lt;a href="http://www.varlena.com/GeneralBits/39.php"&gt;Postgresql General Bits&lt;/a&gt; de A. Elein Mustain puede resultar muy útil. 
&lt;pre lang="sql"&gt;
create or replace view showfunctions as
select
l.lanname,
t.typname || ' = ' || n.nspname || '.' ||
    p.proname || '(' || pg_catalog.oidvectortypes(p.proargtypes) || ')'
as function_definition,
(select case when lanname &lt;&gt; 'c'
    then '\n\n'||prosrc ||'\n\n'
    else '\n'||prosrc ||'.so\n\n'
end) as source
from pg_proc p, pg_type t, pg_namespace n, pg_language l
where p.prorettype = t.oid and p.pronamespace = n.oid
    and p.prolang = l.oid;

-- Muestra todas las funciones en el schema public ( y cualquier funcion con
-- public en su nombre)
select * from showfunctions where function_definition like '%public%';

-- Muestra todas las funciones que toman o reciben booleanos
select * from showfunctions where function_definition like '%boolean%';
&lt;/pre&gt;
    </content><category term="Infraestructura"></category></entry><entry><title>Instalar postgres 8.2 en jaunty</title><link href="https://pablocaro.es/instalar-postgres-8-2-en-jaunty" rel="alternate"></link><published>2009-06-30T10:52:00+02:00</published><updated>2009-06-30T10:52:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2009-06-30:/instalar-postgres-8-2-en-jaunty</id><summary type="html">
        Postgres 8.3 no me permitía crear una base de datos con un encoding distinto a los instalados en mi maquina, y yo debía recuperar una copia de seguridad de una base de datos para la ampliación de unos trabajos que vienen  de antiguo.

Si intentaba importar los datos fallaba …</summary><content type="html">
        Postgres 8.3 no me permitía crear una base de datos con un encoding distinto a los instalados en mi maquina, y yo debía recuperar una copia de seguridad de una base de datos para la ampliación de unos trabajos que vienen  de antiguo.

Si intentaba importar los datos fallaba por el encoding:
&lt;pre&gt;
# createdb base_datos  -O owner ; 
# pg_restore -F c -d base_datos   viernes-27-feb-2009.dump
pg_restore: [archiver (db)] COPY failed: ERROR:  secuencia de bytes no válida para codificación «UTF8»: 0xa2
&lt;/pre&gt;

Y además no me dejaba crear la base de datos con el encoding correcto:
&lt;pre&gt;
postgres@davinci$ createdb  base_datos -E latin1
createdb: falló la creación de la base de datos:
ERROR:  la codificación LATIN1 no coincide con la configuración regional del servidor es_ES.UTF-8
DETAIL:  La configuración regional LC_CTYPE del servidor requiere la codificación UTF8.
&lt;/pre&gt;

Para poder seguir adelante dos alternativas:&lt;a href="http://softwarelibre.org.bo/wiki/postgresqlutf8tolatin1"&gt; cambiar el encoding de mi base de datos&lt;/a&gt; (no podía porque tengo otras bases de datos) o instalar postgres 8.2 (debian y derivados permiten tener varios postgres a la vez por paquetería)

De esta forma que me dispuse a descargar los paquetes de haunty: &lt;a href="http://packages.ubuntu.com/hardy/i386/postgresql-8.2/download"&gt;postgresql-8.2&lt;/a&gt; y&lt;a href="http://packages.ubuntu.com/hardy/i386/postgresql-client-8.2/download"&gt; postgresql-client-8.2&lt;/a&gt;

Una vez descargados los paquetes bastó:
&lt;pre&gt;
pcaro@davinci$ sudo dpkg -i postgresql-client-8.2_8.2.7-1_i386.deb
pcaro@davinci$ sudo dpkg -i postgresql-8.2_8.2.7-1_i386.deb
sudo aptitude hold postgresql-client-8.2
sudo aptitude hold postgresql-8.2
&lt;/pre&gt;

Luego puedes configurar los postgres para que escuchen en distintos puertos o, si solo lo necesitas de forma temporal como yo, simplemente parar el 8.3 y levantar el 8.2 cuando sea necesario.

PD: Gracias a Antonio por la ayuda.
    </content><category term="Infraestructura"></category><category term="linux"></category></entry><entry><title>Listar las bases de datos postgres que tengo instaladas</title><link href="https://pablocaro.es/listar-las-bases-de-datos-postgres-que-tengo-instaladas" rel="alternate"></link><published>2009-06-29T03:54:00+02:00</published><updated>2009-06-29T03:54:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2009-06-29:/listar-las-bases-de-datos-postgres-que-tengo-instaladas</id><content type="html">
        Para ello:

&lt;pre lang="bash"&gt;
pcaro@davinci$ sudo /etc/init.d/postgresql-8.3 start
pcaro@davinci$ sudo su - postgres
postgres@davinci:~$ psql -l
&lt;/pre&gt;
    </content><category term="Programación"></category></entry><entry><title>Un servidor web de bolsillo gracias a python</title><link href="https://pablocaro.es/un-servidor-web-de-bolsillo-gracias-a-python" rel="alternate"></link><published>2009-06-16T10:31:00+02:00</published><updated>2009-06-16T10:31:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2009-06-16:/un-servidor-web-de-bolsillo-gracias-a-python</id><content type="html">
        Otra cosita para recordar.
La forma más simple de servir unos ficheros vía web:
&lt;pre lang="bash"&gt;python -m SimpleHTTPServer 8080&lt;/pre&gt;
Nota: Antes usaba:
&lt;pre lang="bash"&gt;python -c "import SimpleHTTPServer;SimpleHTTPServer.test()"&lt;/pre&gt;
Pero no permite indicar el puerto y es más tedioso de escribir.
    </content><category term="Programación"></category><category term="python"></category></entry><entry><title>Convertir filtros de kmail a filtros sieve</title><link href="https://pablocaro.es/convertir-filtros-de-kmail-a-filtros-sieve" rel="alternate"></link><published>2009-06-06T14:13:00+02:00</published><updated>2009-06-06T14:13:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2009-06-06:/convertir-filtros-de-kmail-a-filtros-sieve</id><summary type="html">
        No hace mucho que dejé de usar &lt;a title="pop" href="http://en.wikipedia.org/wiki/Post_Office_Protocol" target="_blank"&gt;POP&lt;/a&gt; en mis correos para pasarme al &lt;a title="imap" href="http://en.wikipedia.org/wiki/IMAP" target="_blank"&gt;IMAP&lt;/a&gt; desconectado de kmail con toda las ventaja que ello supone.

Pero todavía seguía utilizando los mismos filtros en kmail que me ordenan los correos por carpeta. Con este sistema el filtrado se realiza una vez …</summary><content type="html">
        No hace mucho que dejé de usar &lt;a title="pop" href="http://en.wikipedia.org/wiki/Post_Office_Protocol" target="_blank"&gt;POP&lt;/a&gt; en mis correos para pasarme al &lt;a title="imap" href="http://en.wikipedia.org/wiki/IMAP" target="_blank"&gt;IMAP&lt;/a&gt; desconectado de kmail con toda las ventaja que ello supone.

Pero todavía seguía utilizando los mismos filtros en kmail que me ordenan los correos por carpeta. Con este sistema el filtrado se realiza una vez descargado el correo, de forma que, ahora con imap, el kmail tiene que pasar los filtros y actualizar las carpetas en el servidor tras el moviento de correos. Desde luego no es lo más conveniente.

Lo más util que el filtrado del correo se realice en el servidor. De esta forma nuestro correo ya se encuentra organizado accedas con el cliente que accedas al correo (webmail, móviles de última generación, etc). Para ello existen los filtros &lt;a title="sieve" href="http://en.wikipedia.org/wiki/Sieve_(mail_filtering_language)" target="_blank"&gt;sieve&lt;/a&gt;.

Pero yo ya tenía mis filtros en kmail, de forma que me hice un pequeño programita python que convirtiera mis filtros de kmail en un fichero con los filtros sieve.

No discrimina entre cuentas y solo esta probado (y funciona) con mis filtros simples de moviento de correo, pero lo dejo aquí por si a alguien pudiera resultarle de utilidad.

Para usarlo:
&lt;ol&gt;
	&lt;li&gt;Guardar los filtros de kmail (Preferencias -&amp;gt; Configurar filtros -&amp;gt; Exportar) en un fichero (por ejemplo filtros_pcaro.ini)&lt;/li&gt;
	&lt;li&gt;Ejecutar &lt;em&gt;python kmail_to_sieve.py  filtros_pcaro.ini &amp;gt; filtros.sieve&lt;/em&gt; Por la salida de error indicará que no ha podido convertir.&lt;/li&gt;
	&lt;li&gt;Examinar filtros&lt;/li&gt;
	&lt;li&gt;Subir y activar desde el mismo kmail (Preferencias -&amp;gt; Gestionar guiones de Sieve.&lt;/li&gt;
&lt;/ol&gt;
Podeís descargarlo desde aquí: &lt;a href="https://github.com/pcaro/little_scripts/blob/master/kmail_to_sieve.py"&gt;kmail_to_sieve&lt;/a&gt;

PD: Arreglado el enlace. Gracias Helder por avisar.
    </content><category term="Infraestructura"></category><category term="python"></category><category term="linux"></category></entry><entry><title>Contenedor de Floats: How to IE &amp; FF</title><link href="https://pablocaro.es/contenedor-de-floats-how-to-ie-ff" rel="alternate"></link><published>2009-05-21T10:41:00+02:00</published><updated>2009-05-21T10:41:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2009-05-21:/contenedor-de-floats-how-to-ie-ff</id><summary type="html">
        Espero no olvidar esto más así que dejo la notita.

Si tienes un elemento (por ejemplo un div#padre), que contiene sólo flotantes (otros divs por ejemplo), no será capaz IE de calcular bien su tamaño (se ve muy claro con un background).

Para que funcione con explorer y firefox …</summary><content type="html">
        Espero no olvidar esto más así que dejo la notita.

Si tienes un elemento (por ejemplo un div#padre), que contiene sólo flotantes (otros divs por ejemplo), no será capaz IE de calcular bien su tamaño (se ve muy claro con un background).

Para que funcione con explorer y firefox la regla es ponerle al elemento contenedor las reglas css:

&lt;pre lang="css"&gt;
div#padre {
  overflow: hidden;
  zoom:1;
}
&lt;/pre&gt;

la regla del zoom es para aplicar el hasLayout en el ie6. No es estándar y es mejor ponerla en un condicional para este navegador.

Nota personal: Pablo olvida el div con el clear:both que en este caso no es solución.

    </content><category term="Programación"></category><category term="web"></category></entry><entry><title>Comandos útiles en la consola de postgres (pgsql)</title><link href="https://pablocaro.es/comandos-utiles-en-la-consola-de-postgres-pgsql" rel="alternate"></link><published>2009-03-30T06:04:00+02:00</published><updated>2009-03-30T06:04:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2009-03-30:/comandos-utiles-en-la-consola-de-postgres-pgsql</id><summary type="html">
        Otra de las cosas que simpre olvido. Así que lo anoto.

Para sacar el resultado de una consulta sql a un fichero desde el pgsql.
&lt;ol&gt;
	&lt;li&gt;\a&lt;/li&gt;
	&lt;li&gt;\f ,&lt;/li&gt;
	&lt;li&gt;\o /tmp/salida.csv&lt;/li&gt;
	&lt;li&gt;select * from ...&lt;/li&gt;
&lt;/ol&gt;
Se trata de:
&lt;ol&gt;
	&lt;li&gt;Cambiar entre modo de salida alineado y sin alinear&lt;/li&gt;
	&lt;li&gt;Definir separador de campos (en …&lt;/li&gt;&lt;/ol&gt;</summary><content type="html">
        Otra de las cosas que simpre olvido. Así que lo anoto.

Para sacar el resultado de una consulta sql a un fichero desde el pgsql.
&lt;ol&gt;
	&lt;li&gt;\a&lt;/li&gt;
	&lt;li&gt;\f ,&lt;/li&gt;
	&lt;li&gt;\o /tmp/salida.csv&lt;/li&gt;
	&lt;li&gt;select * from ...&lt;/li&gt;
&lt;/ol&gt;
Se trata de:
&lt;ol&gt;
	&lt;li&gt;Cambiar entre modo de salida alineado y sin alinear&lt;/li&gt;
	&lt;li&gt;Definir separador de campos (en el ejemplo a una coma)&lt;/li&gt;
	&lt;li&gt;Envíar resultados de consultas a archivo&lt;/li&gt;
	&lt;li&gt;Ejecutar la consulta&lt;/li&gt;
&lt;/ol&gt;
    </content><category term="Programación"></category></entry><entry><title>Cambiar ipython para copiar y pegar luego como doctest</title><link href="https://pablocaro.es/cambiar-ipython-para-copiar-y-pegar-luego-como-doctest" rel="alternate"></link><published>2009-02-02T04:23:00+01:00</published><updated>2009-02-02T04:23:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2009-02-02:/cambiar-ipython-para-copiar-y-pegar-luego-como-doctest</id><content type="html">
        Otra de las ordenes que nunca recuerdo. Estando dentro de &lt;a title="ipython" href="http://ipython.scipy.org/moin/" target="_blank"&gt;ipython&lt;/a&gt; para cambiar el prompt de forma que luego sea fácil crear &lt;a title="doctest" href="http://docs.python.org/library/doctest.html" target="_blank"&gt;doctests&lt;/a&gt;:

&lt;strong&gt;&lt;tt&gt;%doctest_mode&lt;/tt&gt;&lt;/strong&gt;

Esto entra en  el modo doctest de ipython. También puede ayudar &lt;strong&gt;&lt;tt&gt;%history -tn&lt;/tt&gt;&lt;/strong&gt;&lt;tt&gt; para recordar los commandos introducidos previamente.
&lt;/tt&gt;
    </content><category term="Programación"></category><category term="python"></category></entry><entry><title>Importar claves gpg de repositorios ubuntu</title><link href="https://pablocaro.es/importar-claves-gpg-de-repositorios-ubuntu" rel="alternate"></link><published>2009-02-01T13:00:00+01:00</published><updated>2009-02-01T13:00:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2009-02-01:/importar-claves-gpg-de-repositorios-ubuntu</id><summary type="html">
        Ante un error como este:

W: GPG error: http://ppa.launchpad.net intrepid Release: The following signatures couldn't be verified because the public key is not available: NO_PUBKEY 3B81A3FBA47394CE

Hay que importar las claves:
&lt;pre&gt;gpg --keyserver keyserver.ubuntu.com --recv 3B81A3FBA47394CE
gpg --export --armor 3B81A3FBA47394CE | sudo apt-key add -

&lt;em&gt;UPDATE:&lt;/em&gt;
&lt;em&gt;Corregido …&lt;/em&gt;&lt;/pre&gt;</summary><content type="html">
        Ante un error como este:

W: GPG error: http://ppa.launchpad.net intrepid Release: The following signatures couldn't be verified because the public key is not available: NO_PUBKEY 3B81A3FBA47394CE

Hay que importar las claves:
&lt;pre&gt;gpg --keyserver keyserver.ubuntu.com --recv 3B81A3FBA47394CE
gpg --export --armor 3B81A3FBA47394CE | sudo apt-key add -

&lt;em&gt;UPDATE:&lt;/em&gt;
&lt;em&gt;Corregido el tema de los guiones, gracias a miguelbf por la indicación y perdón por la tardanza en arreglarlo&lt;/em&gt;&lt;/pre&gt;
    </content><category term="Infraestructura"></category><category term="linux"></category></entry><entry><title>Formatear de la forma más fácil una tarjeta SD en ubuntu</title><link href="https://pablocaro.es/formatear-de-la-forma-mas-facil-una-tarjeta-sd-en-ubuntu" rel="alternate"></link><published>2009-01-30T13:44:00+01:00</published><updated>2009-01-30T13:44:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2009-01-30:/formatear-de-la-forma-mas-facil-una-tarjeta-sd-en-ubuntu</id><summary type="html">
        Otra anotación para recordar en un futuro. La forma más fácil de formatear una tarjeta SD es:
&lt;ol&gt;
	&lt;li&gt;Identificar el dispositivo (si esta montada la tarjeta lo ves con el comando mount por ejemplo).&lt;/li&gt;
	&lt;li&gt;Si no lo tienes ya, instalar el paquete dosfstools.&lt;/li&gt;
	&lt;li&gt;Ejecutar (con el dispositivo desmontado): &lt;strong&gt;sudo mkdosfs DISPOSITIVO …&lt;/strong&gt;&lt;/li&gt;&lt;/ol&gt;</summary><content type="html">
        Otra anotación para recordar en un futuro. La forma más fácil de formatear una tarjeta SD es:
&lt;ol&gt;
	&lt;li&gt;Identificar el dispositivo (si esta montada la tarjeta lo ves con el comando mount por ejemplo).&lt;/li&gt;
	&lt;li&gt;Si no lo tienes ya, instalar el paquete dosfstools.&lt;/li&gt;
	&lt;li&gt;Ejecutar (con el dispositivo desmontado): &lt;strong&gt;sudo mkdosfs DISPOSITIVO&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
    </content><category term="Programación"></category></entry><entry><title>Siempre el último Dragonfly en Opera</title><link href="https://pablocaro.es/siempre-el-ultimo-dragonfly-en-opera" rel="alternate"></link><published>2009-01-30T08:45:00+01:00</published><updated>2009-01-30T08:45:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2009-01-30:/siempre-el-ultimo-dragonfly-en-opera</id><content type="html">
        Para tener la versión de desarrollo de dragon fly en el opera basta con seguir la ruta:

&lt;code&gt;about:config -&amp;gt; Developer Tools -&amp;gt; Developer Tools url&lt;/code&gt;

Luego, introducir la url siguiente

&lt;a href="http://dragonfly.opera.com/app/cutting-edge/"&gt;http://dragonfly.opera.com/app/cutting-edge/&lt;/a&gt;
    </content><category term="Programación"></category></entry><entry><title>Feliz Navidad</title><link href="https://pablocaro.es/feliz-navidad" rel="alternate"></link><published>2008-12-24T05:38:00+01:00</published><updated>2008-12-24T05:38:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2008-12-24:/feliz-navidad</id><content type="html">
        Ni árboles, ni reyes, ni trineos, simplemente eso,

&lt;strong&gt;Feliz Navidad&lt;/strong&gt; de corazón para todos.
    </content><category term="Programación"></category></entry><entry><title>La potencia de los permisos</title><link href="https://pablocaro.es/la-potencia-de-los-permisos" rel="alternate"></link><published>2008-12-01T14:06:00+01:00</published><updated>2008-12-01T14:06:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2008-12-01:/la-potencia-de-los-permisos</id><summary type="html">
        Recordar los comandos para controlar las listas de control de acceso en linux.

&lt;code&gt;man setfacl
man getfacl&lt;/code&gt;

Tras "setfacl -m user:joe:rwx dir",  verás que ls -d dir muestra un signo + al final que indica que se están utilizado permisos ampliados.

Si quieres saber más, el tío google te …</summary><content type="html">
        Recordar los comandos para controlar las listas de control de acceso en linux.

&lt;code&gt;man setfacl
man getfacl&lt;/code&gt;

Tras "setfacl -m user:joe:rwx dir",  verás que ls -d dir muestra un signo + al final que indica que se están utilizado permisos ampliados.

Si quieres saber más, el tío google te lo dirá.
    </content><category term="Infraestructura"></category><category term="linux"></category></entry><entry><title>Usar find para detectar enlaces simbólicos rotos</title><link href="https://pablocaro.es/usar-find-para-detectar-enlaces-simbolicos-rotos" rel="alternate"></link><published>2008-11-20T11:30:00+01:00</published><updated>2008-11-20T11:30:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2008-11-20:/usar-find-para-detectar-enlaces-simbolicos-rotos</id><content type="html">
        &lt;code&gt;for f in $(find /usr/local/bin -type l); do if [ ! -e "$f" ]; then &lt;a title="Echo command (not yet written)" class="new" href="http://howto.wikia.com/index.php?title=Echo_command&amp;amp;action=edit&amp;amp;redlink=1"&gt;echo&lt;/a&gt; $f; fi; done&lt;/code&gt;
    </content><category term="Infraestructura"></category><category term="linux"></category></entry><entry><title>¿Cómo hacer un "svn export" con git?</title><link href="https://pablocaro.es/como-hacer-un-svn-export-con-git" rel="alternate"></link><published>2008-11-14T05:04:00+01:00</published><updated>2008-11-14T05:04:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2008-11-14:/como-hacer-un-svn-export-con-git</id><content type="html">
        Si tienes que descargarte un árbol git pero sin los metadatos, porque por ejemplo sólo quieres el código para congerlarlo versionado en el subversion de tu proyecto, lo más fácil es:

git clone {clone-url}

rm -rf .gitignore .git
    </content><category term="Programación"></category></entry><entry><title>svn:ignore</title><link href="https://pablocaro.es/svn-ignore" rel="alternate"></link><published>2008-11-13T03:59:00+01:00</published><updated>2008-11-13T03:59:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2008-11-13:/svn-ignore</id><content type="html">
        Esta entrada  es para recordar los dos comandos:

svn propedit svn:ignore ./some_path

svn -R propset svn:ignore -F ../.svnignore .

donde .svnignore contiene un patron por línea como *.pyc
    </content><category term="Programación"></category></entry><entry><title>Reproducir flash con Opera en Ubuntu</title><link href="https://pablocaro.es/reproducir-flash-con-opera-en-ubuntu" rel="alternate"></link><published>2008-11-12T13:47:00+01:00</published><updated>2008-11-12T13:47:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2008-11-12:/reproducir-flash-con-opera-en-ubuntu</id><summary type="html">
        Traducción de un &lt;a href="http://www.pablocaro.es/Often times Opera cannot locate the flashplayer without your help. Here is what you can do. First, make sure that flash is installed and works with firefox. Once that is confirmed, open a terminal and cd into the hidden opera directory:  $ cd ~/.opera  Once in that directory you should see a file named pluginpath.ini...the name says it all. In the default setup there are entries which look likeOpera Preferences version 2.1  -- ; Do not edit this file while Opera is running ; This file is stored in UTF-8 encoding  [Paths] /usr/lib/opera/plugins=1 /usr/lib/mozilla/plugins=1 --  You should heed the warning given and exit Opera if it is still open. You now have a choice to make:  1.) set up the flash libraries in either of the two paths shown above or 2.) add another line to the pluginpath.ini file so that opera also checks the user's local mozilla libraries.    I will take the second approach first. Open a second terminal and move into the mozilla plugin directory.  $ cd ~/.mozilla/plugins/  Once there you should see (at least) two files whose names are flashplayer.xpt and libflashplayer.so. These are the files Opera requires to run flash files.  Suppose your username is qwerty. Add the following line to the end of the pluginpath.ini file in the ~/.opera directory:  /home/qwerty/.mozilla/plugins=1  Start opera again and you should have no trouble playing flash files. This solution works only for you and would need to be repeated for other users on the machine in question.   Note that option 1. will enable all users to use flash/opera. To do this you need to place the 2 flash files mentioned above in either /usr/lib/opera/plugins or /usr/lib/mozilla/plugins. You can do this by copying them directly using cp or by making symbolic links to the actual files stored in your ~/.mozilla/plugins dire" target="_blank"&gt;comentario.&lt;/a&gt;

A menudo Opera no pude encontrar el reproductor de flas sin tu ayuda. Te mostramos lo que puedes hacer.

Primero, asegurate que flash está instalado y funciona en firefox. Una vez confirmado, abre un terminal:
$ cd ~/.opera

En este directorio verás un archivo denominado pluginpath.ini …</summary><content type="html">
        Traducción de un &lt;a href="http://www.pablocaro.es/Often times Opera cannot locate the flashplayer without your help. Here is what you can do. First, make sure that flash is installed and works with firefox. Once that is confirmed, open a terminal and cd into the hidden opera directory:  $ cd ~/.opera  Once in that directory you should see a file named pluginpath.ini...the name says it all. In the default setup there are entries which look likeOpera Preferences version 2.1  -- ; Do not edit this file while Opera is running ; This file is stored in UTF-8 encoding  [Paths] /usr/lib/opera/plugins=1 /usr/lib/mozilla/plugins=1 --  You should heed the warning given and exit Opera if it is still open. You now have a choice to make:  1.) set up the flash libraries in either of the two paths shown above or 2.) add another line to the pluginpath.ini file so that opera also checks the user's local mozilla libraries.    I will take the second approach first. Open a second terminal and move into the mozilla plugin directory.  $ cd ~/.mozilla/plugins/  Once there you should see (at least) two files whose names are flashplayer.xpt and libflashplayer.so. These are the files Opera requires to run flash files.  Suppose your username is qwerty. Add the following line to the end of the pluginpath.ini file in the ~/.opera directory:  /home/qwerty/.mozilla/plugins=1  Start opera again and you should have no trouble playing flash files. This solution works only for you and would need to be repeated for other users on the machine in question.   Note that option 1. will enable all users to use flash/opera. To do this you need to place the 2 flash files mentioned above in either /usr/lib/opera/plugins or /usr/lib/mozilla/plugins. You can do this by copying them directly using cp or by making symbolic links to the actual files stored in your ~/.mozilla/plugins dire" target="_blank"&gt;comentario.&lt;/a&gt;

A menudo Opera no pude encontrar el reproductor de flas sin tu ayuda. Te mostramos lo que puedes hacer.

Primero, asegurate que flash está instalado y funciona en firefox. Una vez confirmado, abre un terminal:
$ cd ~/.opera

En este directorio verás un archivo denominado pluginpath.ini... el nombre lo dice todo.

En la configuración por defecto, están las entradas:

--
; Do not edit this file while Opera is running
; This file is stored in UTF-8 encoding

[Paths]
/usr/lib/opera/plugins=1
/usr/lib/mozilla/plugins=1
--

Deberías tener en cuente el aviso y cerrar tu Opera si todavía lo tienes abierto (usa firefox o imprime para ver esta página) A partir de ahora tienes dos opciones:

Configurar las librerías flash en cualquiera de las dos rutas indicadas arriba o
Añadir otra línea a pluginpath.ini file  de forma que opera también encuentre las librerías del directorio local del usuario que usa mozilla.

Usaremos la segunda aproximación. Abre una segunda consola y cambia al directorio de plugins de mozilla.

$ cd ~/.mozilla/plugins/

Una vez aquí, deberías ver (al menos) dos archivos cuyos nombres son fashplayer.xpt y libflashplayer.so. Son los archivos que Opera necesita para ejecutar flash.

Supongamos que tu nombre de usuario es "qwerty". Añade la siguiente linea al final del archivo pluginpath.ini en el directorio ~/.opera:

/home/qwerty/.mozilla/plugins=1

Arranca opera de nuevo y no deberías de tener problemas con flash. Esta solución funciona sólo para tí y necesetaría ser repetida para todos los usuarios de la máquina en cuestion.

Date cuenta que la opción número 1. habilita a TODOS los usuarios para usar flash en opera. Para hacer esto necesitas colocar los dos archivos mencionados arriba en /usr/lib/opera/plugins o /usr/lib/mozilla/plugins. Pudes hacer esto directamente copiándolos usando cp or creando enlaces simbólicos a losby making symbolic links a los archivos en en tu directorio ~/.mozilla/plugins. En este último caso cuida de los permisos.
    </content><category term="Programación"></category></entry><entry><title>Varios monitores en linux</title><link href="https://pablocaro.es/varios-monitores-en-linux" rel="alternate"></link><published>2008-11-10T03:27:00+01:00</published><updated>2008-11-10T03:27:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2008-11-10:/varios-monitores-en-linux</id><content type="html">
        Esta anotación es sólo para comentar el comando xrandr que uso en kubuntu intrepid para trabajar con dos monitores.

sudo xrandr --output VGA --auto
sudo xrandr --output VGA --above LVDS
    </content><category term="Infraestructura"></category><category term="linux"></category></entry><entry><title>Compartir ficheros gracias a python</title><link href="https://pablocaro.es/compartir-ficheros-gracias-a-python" rel="alternate"></link><published>2008-11-08T16:10:00+01:00</published><updated>2008-11-08T16:10:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2008-11-08:/compartir-ficheros-gracias-a-python</id><content type="html">
        Para compartir de forma rápida unos ficheros vía web graias a python, lo más fácil es:
&lt;pre&gt;python -c "import SimpleHTTPServer;SimpleHTTPServer.test()"&lt;/pre&gt;
    </content><category term="Programación"></category><category term="python"></category></entry><entry><title>Error con setuptools y subversion 1.5</title><link href="https://pablocaro.es/error-con-setuptools-y-subversion-1-5" rel="alternate"></link><published>2008-09-14T13:50:00+02:00</published><updated>2008-09-14T13:50:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2008-09-14:/error-con-setuptools-y-subversion-1-5</id><summary type="html">
        La actual versión de &lt;a target="_blank" href="http://peak.telecommunity.com/DevCenter/setuptools"&gt;setuptools&lt;/a&gt; no se lleva bien el subversion 1.5 (también el actual en mi ubuntu).

Normalmente acostumbro a usar &lt;a target="_blank" href="http://pypi.python.org/pypi/virtualenv"&gt;virtualenv&lt;/a&gt; y me he encontrado con el error:
&lt;pre&gt;NameError: global name 'log' is not defined&lt;/pre&gt;
Ya está arreglado en el desarrollo de setuptools. Una posible solución es …</summary><content type="html">
        La actual versión de &lt;a target="_blank" href="http://peak.telecommunity.com/DevCenter/setuptools"&gt;setuptools&lt;/a&gt; no se lleva bien el subversion 1.5 (también el actual en mi ubuntu).

Normalmente acostumbro a usar &lt;a target="_blank" href="http://pypi.python.org/pypi/virtualenv"&gt;virtualenv&lt;/a&gt; y me he encontrado con el error:
&lt;pre&gt;NameError: global name 'log' is not defined&lt;/pre&gt;
Ya está arreglado en el desarrollo de setuptools. Una posible solución es:
&lt;pre&gt;easy_install setuptools==dev06'&lt;/pre&gt;
&lt;a target="_blank" href="http://groups.google.com/group/linux.debian.bugs.dist/browse_thread/thread/b5d23c96e328b178/796fd556351da9ca?lnk=rao"&gt;Aquí temos más información&lt;/a&gt;.
    </content><category term="Programación"></category><category term="python"></category></entry><entry><title>Editores: Mostrar los números de linea en emacs y en jed</title><link href="https://pablocaro.es/editores-mostrar-los-numeros-de-linea-en-emacs-y-en-jed" rel="alternate"></link><published>2008-09-11T16:24:00+02:00</published><updated>2008-09-11T16:24:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2008-09-11:/editores-mostrar-los-numeros-de-linea-en-emacs-y-en-jed</id><summary type="html">
        Para evitar esos odiosos porcentajes cuandos editas un fichero (¿de verdad le sirven a alguien?) y poner en su lugar el número de la linea en que te encuentras dejo aquí la anotación.
&lt;ol&gt;
	&lt;li&gt;En emacs:&lt;/li&gt;
&lt;pre&gt;;; ========== Enable Line and Column Numbering ==========
;; Show line-number in the mode line
(line-number-mode 1)
;; Show column-number …&lt;/pre&gt;&lt;/ol&gt;</summary><content type="html">
        Para evitar esos odiosos porcentajes cuandos editas un fichero (¿de verdad le sirven a alguien?) y poner en su lugar el número de la linea en que te encuentras dejo aquí la anotación.
&lt;ol&gt;
	&lt;li&gt;En emacs:&lt;/li&gt;
&lt;pre&gt;;; ========== Enable Line and Column Numbering ==========
;; Show line-number in the mode line
(line-number-mode 1)
;; Show column-number in the mode line
(column-number-mode 1)&lt;/pre&gt;
	&lt;li&gt;En jed:&lt;/li&gt;
&lt;pre&gt;LINENUMBERS = 2&lt;/pre&gt;
&lt;/ol&gt;
    </content><category term="Programación"></category></entry><entry><title>Grabar CD de 800MB en linux</title><link href="https://pablocaro.es/grabar-cd-de-800mb-en-linux" rel="alternate"></link><published>2008-07-23T05:07:00+02:00</published><updated>2008-07-23T05:07:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2008-07-23:/grabar-cd-de-800mb-en-linux</id><summary type="html">
        Tenía que grabar unos datos. Eran 730MB aproximadamente, con un CD habitual (700MB) iba a ser imposible, pero tenía por ahí un CD philips de 90 minutos (800MB). Y teniendo ese CD ¿por qué usar un DVD que son más caros?
Mi sorpresa llega cuando el k3b (que uso habitualmente …</summary><content type="html">
        Tenía que grabar unos datos. Eran 730MB aproximadamente, con un CD habitual (700MB) iba a ser imposible, pero tenía por ahí un CD philips de 90 minutos (800MB). Y teniendo ese CD ¿por qué usar un DVD que son más caros?
Mi sorpresa llega cuando el k3b (que uso habitualmente) se niega a grabar, ¡por falta de espacio! a pesar de que le indico (graicias google) que use el modo DAO en la interfaz. Total que a grabar desde la consola :-D

Aquí dejo el comandito como anotación para tenerlo de cerca la próxima vez:
&lt;pre&gt;cdrecord -dev=/dev/scd0 -v -overburn -dao -speed=4 -data -eject /tmp/LaBodademiNovia.iso&lt;/pre&gt;
La opción necesaria además de "DAO" es "overburn". Podéis ver como cdrecord sí reconoce todo el tamaño del disco como indica la línea:
&lt;pre&gt;Total size:      810 MB (80:18.14) = 361361 sectors&lt;/pre&gt;
&lt;em&gt;ACTUALIZACIÓN:  &lt;/em&gt;Ahora veo una opción en las preferencias de k3b para pervitir el sobregrabado. Si es que k3b no me podía fallar :-D
    </content><category term="Infraestructura"></category></entry><entry><title>Buscando una fuente para escribir código</title><link href="https://pablocaro.es/buscando-una-fuente-para-escribir-codigo" rel="alternate"></link><published>2008-07-22T09:43:00+02:00</published><updated>2008-07-22T09:43:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2008-07-22:/buscando-una-fuente-para-escribir-codigo</id><summary type="html">
        Varios enlaces interesantes sobre este tema.
&lt;ul&gt;
	&lt;li&gt;&lt;a target="_blank" href="http://www.lowing.org/fonts/"&gt;Monospace/Fixed Width Programmer's Fonts&lt;/a&gt;. (Actualización de &lt;a target="_blank" href="http://www.kuro5hin.org/story/2004/12/6/11739/5249"&gt;Finding the Best Programmer's Font&lt;/a&gt;).&lt;/li&gt;
	&lt;li&gt;&lt;a target="_blank" href="http://www.quickonlinetips.com/archives/2006/11/best-programming-fonts-for-easier-coding-and-readability/"&gt;Best Programming Fonts for Easy Coding and Readability&lt;/a&gt;.&lt;/li&gt;
	&lt;li&gt;&lt;a target="_blank" href="http://www.briankotek.com/blog/index.cfm/2008/4/3/Development-Tip-Change-Your-Coding-Font"&gt;Development Tip: Change Your Coding Font&lt;/a&gt;.&lt;/li&gt;
	&lt;li&gt;&lt;a target="_blank" href="http://www.codinghorror.com/blog/archives/000969.html"&gt;Revisiting Programming Fonts&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
Personalmente, la Bitstream Vera Sans Mono, me parece una fuente bastante aceptable. Me gusta mucho …</summary><content type="html">
        Varios enlaces interesantes sobre este tema.
&lt;ul&gt;
	&lt;li&gt;&lt;a target="_blank" href="http://www.lowing.org/fonts/"&gt;Monospace/Fixed Width Programmer's Fonts&lt;/a&gt;. (Actualización de &lt;a target="_blank" href="http://www.kuro5hin.org/story/2004/12/6/11739/5249"&gt;Finding the Best Programmer's Font&lt;/a&gt;).&lt;/li&gt;
	&lt;li&gt;&lt;a target="_blank" href="http://www.quickonlinetips.com/archives/2006/11/best-programming-fonts-for-easier-coding-and-readability/"&gt;Best Programming Fonts for Easy Coding and Readability&lt;/a&gt;.&lt;/li&gt;
	&lt;li&gt;&lt;a target="_blank" href="http://www.briankotek.com/blog/index.cfm/2008/4/3/Development-Tip-Change-Your-Coding-Font"&gt;Development Tip: Change Your Coding Font&lt;/a&gt;.&lt;/li&gt;
	&lt;li&gt;&lt;a target="_blank" href="http://www.codinghorror.com/blog/archives/000969.html"&gt;Revisiting Programming Fonts&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
Personalmente, la Bitstream Vera Sans Mono, me parece una fuente bastante aceptable. Me gusta mucho también Envy Code R.

¿Qué tipo de letra es vuestra preferida para codificar?
    </content><category term="Programación"></category></entry><entry><title>Mi wordle</title><link href="https://pablocaro.es/mi-wordle" rel="alternate"></link><published>2008-06-13T02:17:00+02:00</published><updated>2008-06-13T02:17:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2008-06-13:/mi-wordle</id><content type="html">
        Siguiendo una cadena,aquí tenéis mi wordle a partir de mis enlaces de del.icio.us:
&lt;a title="Wordle: pcaro del.icio.us" href="http://wordle.net/gallery/pcaro_del_icio_us"&gt;&lt;img style="border: 1px solid #dddddd; padding: 4px" src="http://wordle.net/thumb/pcaro_del_icio_us" /&gt;&lt;/a&gt;

¿Cómo es el tuyo?
    </content><category term="Personal"></category></entry><entry><title>Convertir disco vmware</title><link href="https://pablocaro.es/convertir-disco-vmware" rel="alternate"></link><published>2008-05-11T12:07:00+02:00</published><updated>2008-05-11T12:07:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2008-05-11:/convertir-disco-vmware</id><content type="html">
    &lt;pre lang="bash"&gt;
pcaro@davinci$ vmware-vdiskmanager -r Windows\ XP\ Professional.vmdk -t 0 WindowsXPProfessionalUnico.vmdk
Creating disk 'WindowsXPProfessionalUnico.vmdk'
Convert: 100% done.
Virtual disk conversion successful.
    &lt;/pre&gt;
    </content><category term="Infraestructura"></category></entry><entry><title>¿Qué ejecutas tu desde la consola?</title><link href="https://pablocaro.es/que-ejecutas-tu-desde-la-consola" rel="alternate"></link><published>2008-04-13T14:00:00+02:00</published><updated>2008-04-13T14:00:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2008-04-13:/que-ejecutas-tu-desde-la-consola</id><summary type="html">
&lt;p&gt;
        Un meme para ver que ejecutas desde la consola:
&lt;/p&gt;
&lt;pre&gt;
pcaro@davinci$ history|awk '{a[$2]++} END{for(i in a){printf "%5d\t%s\n",a[i],i}}'|sort -rn|head
95   sudo
71   ls
67   cd
33   svn
29   dentro
24   clear
18   ssh
15   i2e-cli
14   ps
13 …&lt;/pre&gt;</summary><content type="html">
&lt;p&gt;
        Un meme para ver que ejecutas desde la consola:
&lt;/p&gt;
&lt;pre&gt;
pcaro@davinci$ history|awk '{a[$2]++} END{for(i in a){printf "%5d\t%s\n",a[i],i}}'|sort -rn|head
95   sudo
71   ls
67   cd
33   svn
29   dentro
24   clear
18   ssh
15   i2e-cli
14   ps
13   man
&lt;/pre&gt;
&lt;p&gt;
A notar que dentro es un script que tengo para hacer un grep -r filtrando algunos resultados.i2e-cli es un diccionario.
&lt;/p&gt;
    </content><category term="Programación"></category></entry><entry><title>Más comandos a recordar</title><link href="https://pablocaro.es/mas-comandos-a-recordar" rel="alternate"></link><published>2008-04-09T11:54:00+02:00</published><updated>2008-04-09T11:54:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2008-04-09:/mas-comandos-a-recordar</id><summary type="html">
        Otra nota para que no se me vuelvan a olvidar algunos de los super-mini-comandos unix.
&lt;ul&gt;
	&lt;li&gt;Para desconectar un programa de la consola desde la que se está ejecutando y no pare por ejemplo si muere la conexión ssh, hay que usar &lt;code&gt;disown&lt;/code&gt;.  Es una de esas &lt;a target="_blank" href="http://www.faqs.org/docs/bashman/bashref_79.html"&gt;herramientas para control de …&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">
        Otra nota para que no se me vuelvan a olvidar algunos de los super-mini-comandos unix.
&lt;ul&gt;
	&lt;li&gt;Para desconectar un programa de la consola desde la que se está ejecutando y no pare por ejemplo si muere la conexión ssh, hay que usar &lt;code&gt;disown&lt;/code&gt;.  Es una de esas &lt;a target="_blank" href="http://www.faqs.org/docs/bashman/bashref_79.html"&gt;herramientas para control de trabajos&lt;/a&gt; como fg. Los pasos son:&lt;/li&gt;
&lt;ul&gt;
&lt;ol&gt;
	&lt;li&gt;Ejecutar la tarea en segudo plano "ejecutable.sh &amp;", en la consola te saldrá el número de trabajo [1]&lt;/li&gt;
	&lt;li&gt;luego disown %1&lt;/li&gt;
&lt;/ol&gt;
&lt;/ul&gt;
	&lt;li&gt;&lt;a target="_blank" href="http://www.linuxmanpages.com/man1/watch.1.php"&gt;&lt;code&gt;watch&lt;/code&gt;&lt;/a&gt;: para ejecutar un programa periódicamente, mostrando su salida a pantalla completa y pudiendo señalar las diferencias. Lo uso con un ls -l por ejemplo para ver como va cambiando el tamaño de un fichero, etc.&lt;/li&gt;
&lt;/ul&gt;
PD: Gracias a mis compañeros de Yaco por enseñarme un poco todos los días.

Nota post-post:

Me comentan que es muy útil &lt;a target="_blank" href="http://www.linuxmanpages.com/man1/nohup.1.php"&gt;nohup&lt;/a&gt;.
&lt;ul&gt;
&lt;ul&gt;
&lt;ul /&gt;&lt;/ul&gt;
&lt;/ul&gt;
    </content><category term="Infraestructura"></category></entry><entry><title>Timing en "psql" la consola de postgres</title><link href="https://pablocaro.es/timing-en-psql-la-consola-de-postgres" rel="alternate"></link><published>2008-03-31T03:25:00+02:00</published><updated>2008-03-31T03:25:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2008-03-31:/timing-en-psql-la-consola-de-postgres</id><content type="html">
        Recordar:
&lt;pre&gt;timing&lt;/pre&gt;
También podemos utilizar para más información "explain analyze":
&lt;pre&gt;EXPLAIN ANALYZE select ...&lt;/pre&gt;
    </content><category term="Programación"></category></entry><entry><title>Ejecutar comandos en python</title><link href="https://pablocaro.es/ejecutar-comandos-en-python" rel="alternate"></link><published>2008-03-04T06:18:00+01:00</published><updated>2008-03-04T06:18:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2008-03-04:/ejecutar-comandos-en-python</id><summary type="html">
        Hago a menudo pequeños scripts en python. Y muchas de las veces necesito ejecutar algún comando del sistema.

Habitualmente importaba el módulo &lt;code&gt;os&lt;/code&gt; y llamaba a alguna de sus funciones (las popen).

Pero en general no hace falta importar os. Existe un módulo llamado &lt;a target="_blank" title="commands" href="http://docs.python.org/lib/module-commands.html"&gt;&lt;code&gt;commands&lt;/code&gt;&lt;/a&gt;&lt;code&gt; &lt;/code&gt;(sólo para unix) que permite …</summary><content type="html">
        Hago a menudo pequeños scripts en python. Y muchas de las veces necesito ejecutar algún comando del sistema.

Habitualmente importaba el módulo &lt;code&gt;os&lt;/code&gt; y llamaba a alguna de sus funciones (las popen).

Pero en general no hace falta importar os. Existe un módulo llamado &lt;a target="_blank" title="commands" href="http://docs.python.org/lib/module-commands.html"&gt;&lt;code&gt;commands&lt;/code&gt;&lt;/a&gt;&lt;code&gt; &lt;/code&gt;(sólo para unix) que permite realizar esta tarea con suma facilidad y mejor lectura del código.

Dejar claro que para cosas más avanzadas es mejor usar &lt;code&gt;&lt;a target="_blank" href="http://docs.python.org/lib/module-subprocess.html"&gt;subprocess&lt;/a&gt; &lt;/code&gt; que pretende reemplazarlo.
    </content><category term="Programación"></category><category term="python"></category></entry><entry><title>Rendimiento y Monitorización de procesos en windows</title><link href="https://pablocaro.es/rendimiento-y-monitorizacion-de-procesos-en-windows" rel="alternate"></link><published>2008-02-12T07:21:00+01:00</published><updated>2008-02-12T07:21:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2008-02-12:/rendimiento-y-monitorizacion-de-procesos-en-windows</id><summary type="html">
        Prácticamente nunca uso windows, y por trabajo, cuando tengo que hacerlo es para ver errores. Resulta útil ver los procesos. Para no olvidarme como simpre dejo mi nota aquí:
&lt;ul&gt;
	&lt;li&gt;Para ver el rendimiento del sistema, Monitor de rendimiento: Incio, ejecutar: &lt;strong&gt;perfmon&lt;/strong&gt;&lt;/li&gt;
	&lt;li&gt;Un buen&lt;a href="http://technet.microsoft.com/es-es/sysinternals/bb896645(en-us).aspx" target="_blank" title="monitor procesos"&gt; monitor de procesos&lt;/a&gt; se encuentra entres las …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">
        Prácticamente nunca uso windows, y por trabajo, cuando tengo que hacerlo es para ver errores. Resulta útil ver los procesos. Para no olvidarme como simpre dejo mi nota aquí:
&lt;ul&gt;
	&lt;li&gt;Para ver el rendimiento del sistema, Monitor de rendimiento: Incio, ejecutar: &lt;strong&gt;perfmon&lt;/strong&gt;&lt;/li&gt;
	&lt;li&gt;Un buen&lt;a href="http://technet.microsoft.com/es-es/sysinternals/bb896645(en-us).aspx" target="_blank" title="monitor procesos"&gt; monitor de procesos&lt;/a&gt; se encuentra entres las utilidades que da microsoft&lt;/li&gt;
&lt;/ul&gt;
    </content><category term="Infraestructura"></category></entry><entry><title>Continuar con una descarga</title><link href="https://pablocaro.es/continuar-con-una-descarga" rel="alternate"></link><published>2008-01-27T08:37:00+01:00</published><updated>2008-01-27T08:37:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2008-01-27:/continuar-con-una-descarga</id><content type="html">
        &lt;code&gt;scp&lt;/code&gt; no permite continuar con copias que se cortan, pero lo permite rsync. El comando para recordar es:

&lt;code&gt;alias scpresume="rsync --partial --progress --rsh=ssh"&lt;/code&gt;Eso claro, siempre que tengas  &lt;code&gt;rsync&lt;/code&gt;
    </content><category term="Programación"></category></entry><entry><title>ubuntu, wordpress, php5 y mysql</title><link href="https://pablocaro.es/ubuntu-wordpress-php5-y-mysql" rel="alternate"></link><published>2007-11-25T06:03:00+01:00</published><updated>2007-11-25T06:03:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2007-11-25:/ubuntu-wordpress-php5-y-mysql</id><summary type="html">
        Configurando un wordpress local para pruebas (con lighttpd), me encuentro con el siguiente error:

&lt;code&gt;Your PHP installation appears to be missing the MySQL which is required for WordPress.&lt;/code&gt;

Sabía que php5 no tiene incluido el soporte para mysql pero para mi sorpresa en mi ubuntu yo sí tenía instalado el …</summary><content type="html">
        Configurando un wordpress local para pruebas (con lighttpd), me encuentro con el siguiente error:

&lt;code&gt;Your PHP installation appears to be missing the MySQL which is required for WordPress.&lt;/code&gt;

Sabía que php5 no tiene incluido el soporte para mysql pero para mi sorpresa en mi ubuntu yo sí tenía instalado el paquete adecuado.

El problema es que no estaba activado. Por si les pasa a otros y para recordarlo en un futuro, la cosa se arregló con:

&lt;code&gt;sudo dpkg-reconfigure php5-mysql&lt;/code&gt;

y el pertinente reinicio del servidor:

&lt;code&gt;sudo /etc/init.d/lighttpd restart&lt;/code&gt;
    </content><category term="Programación"></category></entry><entry><title>Para aprender javascript</title><link href="https://pablocaro.es/para-aprender-javascript" rel="alternate"></link><published>2007-11-01T14:01:00+01:00</published><updated>2007-11-01T14:01:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2007-11-01:/para-aprender-javascript</id><summary type="html">
        Para aprender una tecnología todos conocemos &lt;a target="_blank" href="http://www.pablocaro.es/www.google.com"&gt;nuestro punto de referencia.&lt;/a&gt; Pero "El Buscador" no es punto de partida más recomendable para aprender javascript. Existen cientos, miles, millones, de tutoriales donde aprender mal javascript. No hablo de aprender las palabras clave y las sentencias de control. Para eso te vale cualquiera …</summary><content type="html">
        Para aprender una tecnología todos conocemos &lt;a target="_blank" href="http://www.pablocaro.es/www.google.com"&gt;nuestro punto de referencia.&lt;/a&gt; Pero "El Buscador" no es punto de partida más recomendable para aprender javascript. Existen cientos, miles, millones, de tutoriales donde aprender mal javascript. No hablo de aprender las palabras clave y las sentencias de control. Para eso te vale cualquiera. Hablo de  aprender los conceptos clave detrás del lenguaje y su implicación explicada de forma práctica.

Para no perder el tiempo y comprender de verdad el lenguaje incomprendido hay que acudir a:
&lt;ul&gt;
	&lt;li&gt;&lt;a title="Mastering JavaScript — concept and resource guide" rel="bookmark" target="_blank" href="http://blog.reindel.com/2007/10/16/mastering-javascript-concept-and-resource-guide/"&gt;Mastering JavaScript — concept and resource guide&lt;/a&gt; gracias a Brian Reindel&lt;/li&gt;
	&lt;li&gt;Las presentaciones de &lt;a target="_blank" href="http://javascript.crockford.com/"&gt;Douglas Crockford&lt;/a&gt;. Los vídeos están recopilados &lt;a target="_blank" href="http://101out.com/jss.php"&gt;en este enlace&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
    </content><category term="Programación"></category><category term="javascript"></category></entry><entry><title>Knetworkmanager</title><link href="https://pablocaro.es/knetworkmanager" rel="alternate"></link><published>2007-10-06T05:28:00+02:00</published><updated>2007-10-06T05:28:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2007-10-06:/knetworkmanager</id><summary type="html">
        Ahora una de esas entradas para que me sirva de recordatorio.

Habitualmente estoy acostumbrado, cuando voy a un lugar y me dan conexión wifi, a configurarla a mano en /etc/network/interfaces.

Esto hace que deje de funcionar el knetwormanager. Indica "configuración manual de red". Para que vuelva a tomar …</summary><content type="html">
        Ahora una de esas entradas para que me sirva de recordatorio.

Habitualmente estoy acostumbrado, cuando voy a un lugar y me dan conexión wifi, a configurarla a mano en /etc/network/interfaces.

Esto hace que deje de funcionar el knetwormanager. Indica "configuración manual de red". Para que vuelva a tomar el control de las interaces de red tengo que comentar todas las líneas en ese fichero como bien se indica &lt;a target="_blank" title="documentación de NetworkManager" href="https://help.ubuntu.com/community/WifiDocs/NetworkManager"&gt;en la documentación de ubuntu&lt;/a&gt;.

Para manejar el demonio NetWorkmanger tengo que usar:

&lt;code&gt; /etc/dbus-1/event.d/25NetworkManager {start|stop|restart|force-reload} &lt;/code&gt;

&lt;code&gt;/etc/dbus-1/event.d/26NetworkManagerDispatcher {start|stop|restart|force-reload}&lt;/code&gt;
    </content><category term="Infraestructura"></category><category term="linux"></category></entry><entry><title>Mi nueva cámara</title><link href="https://pablocaro.es/mi-nueva-camara" rel="alternate"></link><published>2007-08-25T10:43:00+02:00</published><updated>2007-08-25T10:43:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2007-08-25:/mi-nueva-camara</id><summary type="html">
        Me acabo de compar una cámara digital compacta. No soy muy dado a fotos pero ya hora de tener una cámara digital.

La elegida ha sido una &lt;a target="_blank" title="Panasonic DMC-FX10" href="http://www.pablocaro.es/Panasonic%20DMC-FX10"&gt;Panasonic DMC-FX10&lt;/a&gt; de 6 Mpx y lentes Leica.

La prueba de fuego la ha tenido en Granada. Es el peor sito para una …</summary><content type="html">
        Me acabo de compar una cámara digital compacta. No soy muy dado a fotos pero ya hora de tener una cámara digital.

La elegida ha sido una &lt;a target="_blank" title="Panasonic DMC-FX10" href="http://www.pablocaro.es/Panasonic%20DMC-FX10"&gt;Panasonic DMC-FX10&lt;/a&gt; de 6 Mpx y lentes Leica.

La prueba de fuego la ha tenido en Granada. Es el peor sito para una cámara compacta (uno echa de menos un zoom potente y todas las características de una buena reflex con esas vista de la Alhambra al atardecer) pero me ha gustado el aparato y creo que ha sido una buena elección.

Ya pondré unas fotos.
    </content><category term="Personal"></category></entry><entry><title>¡Que práctico es python!: svnrevisions.py</title><link href="https://pablocaro.es/que-practico-es-python-svnrevisions-py" rel="alternate"></link><published>2007-04-10T09:55:00+02:00</published><updated>2007-04-10T09:55:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2007-04-10:/que-practico-es-python-svnrevisions-py</id><summary type="html">
        Pongo a continuación un script rápido que hice hace ya un tiempo. En ese momento estaba realizando un backport de código y de forma sucia realicé varios
&lt;pre&gt;svn up -rTAL&lt;/pre&gt;
De esa forma y con unos pequeños cambios obtuve el cambio que necesitaba. Pero después de un rato no recordaba …</summary><content type="html">
        Pongo a continuación un script rápido que hice hace ya un tiempo. En ese momento estaba realizando un backport de código y de forma sucia realicé varios
&lt;pre&gt;svn up -rTAL&lt;/pre&gt;
De esa forma y con unos pequeños cambios obtuve el cambio que necesitaba. Pero después de un rato no recordaba que fichero cambié exactamente y a qué revisiones. Un poquito de &lt;a title="ipython" href="http://ipython.scipy.org/moin/About"&gt;terminal&lt;/a&gt; y listo.

Se que es sucio, que podría usar las librerías de python para subversion, etc. Pero se hizo de forma rápida y funciona.

El funcionamiento se puede ver en el código fuente, pero pongo una ayudita:
&lt;pre lang="bash"&gt;
$ python svnrevisions.py
$ python svnrevisions.py 123
$ python svnrevisions.py ALL
&lt;/pre&gt;
Aquí dejo el código: &lt;a href="https://github.com/pcaro/little_scripts/blob/master/svnrevisions.py"&gt;svnrevisions&lt;/a&gt;
    </content><category term="Programación"></category><category term="python"></category></entry><entry><title>Error al grabar CDs con k3b</title><link href="https://pablocaro.es/error-al-grabar-cds-con-k3b" rel="alternate"></link><published>2006-12-18T05:18:00+01:00</published><updated>2006-12-18T05:18:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2006-12-18:/error-al-grabar-cds-con-k3b</id><summary type="html">
        Tengo un  Dell latitude D610 y kubuntu edgy.

Desde dapper siempre me ha ocurrido un error al copiar cds con k3b. El problema es que no tengo acceso al dispositivo /dev/sg0

con &lt;strike&gt;sudo chown /dev/sg0 root:cdrom&lt;/strike&gt; sudo root:cdrom /dev/sg0 lo soluciono (doy grupo cdrom a …</summary><content type="html">
        Tengo un  Dell latitude D610 y kubuntu edgy.

Desde dapper siempre me ha ocurrido un error al copiar cds con k3b. El problema es que no tengo acceso al dispositivo /dev/sg0

con &lt;strike&gt;sudo chown /dev/sg0 root:cdrom&lt;/strike&gt; sudo root:cdrom /dev/sg0 lo soluciono (doy grupo cdrom a quién quiera que use la grabadora).

El problema es que al reiniciar se vuelve a colocar el grupo a root y no se como cambiar esto en el udev. ¿Alguna idea?
    </content><category term="Infraestructura"></category><category term="linux"></category></entry><entry><title>Siempre la coma como símbolo decimal en un formulario .net</title><link href="https://pablocaro.es/siempre-la-coma-como-simbolo-decimal-en-un-formulario-net" rel="alternate"></link><published>2006-12-15T14:23:00+01:00</published><updated>2006-12-15T14:23:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2006-12-15:/siempre-la-coma-como-simbolo-decimal-en-un-formulario-net</id><summary type="html">
        Entre los muchos lenguajes que me ha tocado codificar (únicamente no me arrepiento de hacerlo en &lt;a title="Python" href="http://www.python.org"&gt;python&lt;/a&gt;) se encuentra .net.

Es una chusquería que me solicitó un cliente. Quería que en un formulario al pulsar el punto del teclado numérico la entrada fuera una coma (no dependiera de la configuración …</summary><content type="html">
        Entre los muchos lenguajes que me ha tocado codificar (únicamente no me arrepiento de hacerlo en &lt;a title="Python" href="http://www.python.org"&gt;python&lt;/a&gt;) se encuentra .net.

Es una chusquería que me solicitó un cliente. Quería que en un formulario al pulsar el punto del teclado numérico la entrada fuera una coma (no dependiera de la configuración de los locales en el windows). Puede llegar a ser muy útil.
&lt;pre class="wiki"&gt;/**
* En este formulario el simbolo decimal del teclado numerico siempre la comma
*/
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (keyData == Keys.Decimal)
{
SendKeys.Send(",");
return true;
}
return base.ProcessCmdKey(ref msg, keyData);&lt;/pre&gt;
&lt;pre class="wiki"&gt;}&lt;/pre&gt;
    </content><category term="Programación"></category><category term=".net"></category></entry><entry><title>Corrector ortográfico de firefox 2.0 en castellano</title><link href="https://pablocaro.es/corrector-ortografico-de-firefox-2-0-en-castellano" rel="alternate"></link><published>2006-11-14T07:18:00+01:00</published><updated>2006-11-14T07:18:00+01:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2006-11-14:/corrector-ortografico-de-firefox-2-0-en-castellano</id><content type="html">
        En la url escribir:

about:config

Filtras las propiedades por "dic".

Y estableces el valor de la propiedad "spellchecker.dictionary" a es_ES.
    </content><category term="Programación"></category></entry><entry><title>Primera anotación de prueba</title><link href="https://pablocaro.es/primera-anotacion-de-prueba" rel="alternate"></link><published>2006-10-10T09:55:00+02:00</published><updated>2006-10-10T09:55:00+02:00</updated><author><name>Pablo Caro</name></author><id>tag:pablocaro.es,2006-10-10:/primera-anotacion-de-prueba</id><summary type="html">
        Como todo blog que comienza, es de obligado cumplimiento una primera entrada "sólo para probar".
En la primera entrada además del maravilloso lorem ipsum aprovecho para felicitar al autor del tema
que estoy probando a priori: Orange Dream de Buddha.
Por cierto que luego veré si puedo traducir esto :-D …</summary><content type="html">
        Como todo blog que comienza, es de obligado cumplimiento una primera entrada "sólo para probar".
En la primera entrada además del maravilloso lorem ipsum aprovecho para felicitar al autor del tema
que estoy probando a priori: Orange Dream de Buddha.
Por cierto que luego veré si puedo traducir esto :-D

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Quisque vel quam. Morbi quis dolor. Pellentesque accumsan imperdiet tellus. Suspendisse eleifend, ante eu ullamcorper interdum, magna quam mattis tellus, ullamcorper pellentesque massa nibh laoreet tellus. Ut posuere risus eget massa rhoncus tincidunt. Integer commodo malesuada metus. Sed lacus urna, feugiat ut, lobortis id, pharetra quis, sem. Morbi vehicula, orci sed imperdiet viverra, magna tortor tincidunt erat, nec ultrices erat diam non nulla. Vivamus nulla. Cras ligula turpis, lacinia eu, elementum quis, facilisis vel, tortor. Vivamus venenatis tempor elit. Mauris in dui. Curabitur libero libero, ultrices id, lacinia nec, pellentesque eget, risus.

Curabitur suscipit. Proin iaculis nisi ac ligula. Donec semper. Aliquam mauris nunc, tincidunt a, rhoncus consequat, fringilla quis, nunc. Duis facilisis pede eu orci. Praesent auctor nunc ut metus porta dignissim. Pellentesque posuere lectus. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Suspendisse vestibulum quam non lectus. Maecenas odio. Curabitur mollis vehicula erat. Sed pulvinar, lorem id malesuada ultricies, velit tortor volutpat orci, eu porta dolor lacus eu nunc. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Mauris ultricies magna mollis turpis. Proin ac ante nec mauris pulvinar accumsan. Donec euismod iaculis orci. Mauris tempor porttitor urna. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;
    </content><category term="Programación"></category></entry></feed>