<?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-03-06T11:30:00+01:00</updated><subtitle>Anotaciones</subtitle><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="linux"></category><category term="networking"></category><category term="iproute2"></category><category term="net-tools"></category><category term="terminal"></category></entry><entry><title>From net-tools to iproute2: command equivalents</title><link href="https://pablocaro.es/en/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:/en/net-tools-to-iproute</id><summary type="html">&lt;p&gt;A guide to equivalents between classic net-tools commands (ifconfig, route, arp) and modern iproute2 commands (ip).&lt;/p&gt;</summary><content type="html">&lt;p&gt;Over 30 years with Linux, and it's hard to change what you're already used to. In my case, I've always used &lt;strong&gt;ifconfig&lt;/strong&gt;, &lt;strong&gt;arp&lt;/strong&gt;, &lt;strong&gt;route&lt;/strong&gt;, and &lt;strong&gt;netstat&lt;/strong&gt; to configure and diagnose the network. But these commands belong to the &lt;strong&gt;net-tools&lt;/strong&gt; package, which has been replaced by &lt;strong&gt;iproute2&lt;/strong&gt; years ago.&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;There are traditionally two ways of configuring the network in Linux:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The old way&lt;/strong&gt;: with commands like &lt;code&gt;ifconfig&lt;/code&gt;, &lt;code&gt;arp&lt;/code&gt;, &lt;code&gt;route&lt;/code&gt; and &lt;code&gt;netstat&lt;/code&gt;, which are part of the &lt;em&gt;net-tools&lt;/em&gt; package&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The new way&lt;/strong&gt;: mostly (but not entirely!) wrapped in a single &lt;code&gt;ip&lt;/code&gt; command, which is part of the &lt;em&gt;iproute2&lt;/em&gt; package&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It seems like the latter was made "important" in Debian in 2008, which means every release since Debian 5 "lenny" (!) has featured the &lt;code&gt;ip&lt;/code&gt; command.&lt;/p&gt;
&lt;p&gt;I have been slowly training my brain to use the new commands but I sometimes forget some. So, here's a couple of equivalences from the old net-tools package to the new iproute2:&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;Shorter form&lt;/th&gt;
&lt;th&gt;What it does&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;Show/configure IP addresses&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;Show link stats (up/down, packet counts)&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;Show or modify routing table&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;Show ARP/neighbors table&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;Show socket statistics&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="visual-comparison"&gt;Visual comparison&lt;a class="headerlink" href="#visual-comparison" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here's the difference between using &lt;code&gt;ip a&lt;/code&gt; and &lt;code&gt;ip -br -c a&lt;/code&gt;:&lt;/p&gt;
&lt;h3 id="ip-a-full-output"&gt;&lt;code&gt;ip a&lt;/code&gt; (full output)&lt;a class="headerlink" href="#ip-a-full-output" 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-brief-and-colored-output"&gt;&lt;code&gt;ip -br -c a&lt;/code&gt; (brief and colored output)&lt;a class="headerlink" href="#ip-br-c-a-brief-and-colored-output" 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="my-favorite-alias"&gt;My favorite alias&lt;a class="headerlink" href="#my-favorite-alias" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I often alias &lt;code&gt;ip&lt;/code&gt; to provide much prettier output:&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;This provides a much nicer and readable output, as you can see in the second image.&lt;/p&gt;
&lt;p&gt;Do you still use the old commands or have you already switched to iproute2?&lt;/p&gt;</content><category term="Linux"></category><category term="linux"></category><category term="networking"></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="Problemas"></category><category term="rodney"></category><category term="chrome"></category><category term="wayland"></category><category term="linux"></category><category term="debugging"></category></entry><entry><title>Fixing Rodney Chrome Error on Wayland</title><link href="https://pablocaro.es/en/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:/en/rodney-chrome-wayland-error</id><summary type="html">&lt;p&gt;How to fix Rodney crash when starting Chrome on Wayland systems by using the system Chrome.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://github.com/simonw/rodney/"&gt;Rodney&lt;/a&gt; is a CLI tool created by Simon Willison designed specifically for AI agents to control a Chrome instance using the Chrome DevTools Protocol. It's similar to Vercel's &lt;code&gt;agent-browser&lt;/code&gt;, but uses CDP instead of Playwright.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;a class="headerlink" href="#the-problem" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Running &lt;code&gt;uvx rodney open https://google.es&lt;/code&gt; without first starting Rodney with &lt;code&gt;rodney start&lt;/code&gt; fails because:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Rodney tries to start Chrome with the Chromium it automatically downloads to &lt;code&gt;~/.cache/rod/browser/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;That Chromium (v128.0.6568.0) has a known bug with the Ozone backend on Wayland systems&lt;/li&gt;
&lt;li&gt;The crash occurs due to the combination of flags &lt;code&gt;--single-process&lt;/code&gt; + &lt;code&gt;--ozone-platform=headless&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The error in the logs shows: &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="the-solution"&gt;The Solution&lt;a class="headerlink" href="#the-solution" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The easiest way to fix this is to configure Rodney to use your system's Chrome instead of the automatically downloaded Chromium. This is done via the &lt;code&gt;ROD_CHROME_BIN&lt;/code&gt; environment variable:&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;# Add to ~/.bashrc or ~/.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;After setting this up, Rodney will work correctly:&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="working-example"&gt;Working example&lt;a class="headerlink" href="#working-example" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Once configured, the workflow looks like this:&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="Rodney working example" src="https://pablocaro.es/images/rodney_example.png"&gt;&lt;/p&gt;
&lt;h2 id="why-does-this-happen"&gt;Why does this happen?&lt;a class="headerlink" href="#why-does-this-happen" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Chromium that Rodney downloads automatically has a known bug with the Ozone backend on Linux systems that use Wayland (the alternative to X11). When certain Chrome flag combinations are used, the process fails because the graphics system isn't configured as "Mock" or "Stub" (which is what the development builds expect).&lt;/p&gt;
&lt;p&gt;Using the system Chrome avoids this problem because:
- System Chrome is compiled with full Wayland support
- It's generally a more stable and updated version
- It doesn't have the same development flags that cause conflicts&lt;/p&gt;
&lt;h2 id="link"&gt;Link&lt;a class="headerlink" href="#link" 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;Rodney GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="Problems"></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="productivity"></category></entry><entry><title>Record and Share your Terminal Sessions with asciinema</title><link href="https://pablocaro.es/en/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:/en/asciinema-terminal-recorder</id><summary type="html">&lt;p&gt;Discover asciinema, the perfect tool to record your terminal sessions in a lightweight, reproducible, and easy-to-share way.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Often we need to share what's happening in our terminal, whether for a tutorial, demonstrating a bug, or simply documenting a process. While traditional video recording tools exist, &lt;strong&gt;asciinema&lt;/strong&gt; offers a much more powerful and lightweight alternative.&lt;/p&gt;
&lt;h2 id="what-is-asciinema"&gt;What is asciinema?&lt;a class="headerlink" href="#what-is-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; doesn't record video in the traditional sense (pixels). Instead, it captures the terminal's text stream along with exact timings. This has several advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tiny files&lt;/strong&gt;: Instead of megabytes of video, you get very small text files.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Selectable text&lt;/strong&gt;: When playing back a session on the web, you can copy and paste the code directly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Perfect quality&lt;/strong&gt;: No loss of resolution or compression artifacts.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="installation-and-help"&gt;Installation and Help&lt;a class="headerlink" href="#installation-and-help" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I already have &lt;code&gt;asciinema&lt;/code&gt; installed on my system. Here you can see the summary of available commands:## Interactive example&lt;/p&gt;
&lt;p&gt;Below you can see a small demonstration of &lt;code&gt;asciinema&lt;/code&gt; in action, running the command &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="basic-commands"&gt;Basic commands&lt;a class="headerlink" href="#basic-commands" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To start recording, simply run:&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;This will begin recording everything that happens in your current terminal session. When you're finished, simply press &lt;code&gt;Ctrl-D&lt;/code&gt; or type &lt;code&gt;exit&lt;/code&gt;. The result will be saved in the &lt;code&gt;demo.cast&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;To play it back locally:&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;And if you want to share it with the world, you can upload it to their free hosting service:&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="why-use-it"&gt;Why use it?&lt;a class="headerlink" href="#why-use-it" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It's ideal for developers writing technical blogs (like this one), as it allows you to embed the recordings directly into the browser with a lightweight JavaScript-based player. Goodbye, blurry 20MB GIFs!&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="Tools"></category><category term="asciinema"></category><category term="terminal"></category><category term="linux"></category><category term="productivity"></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>kitty-rbw: Fast Bitwarden access from your kitty terminal</title><link href="https://pablocaro.es/en/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:/en/kitty-rbw-kitten</id><summary type="html">&lt;p&gt;Introducing kitty-rbw, a "kitten" for the kitty terminal that allows you to search and use rbw (Bitwarden) credentials instantly using fzf.&lt;/p&gt;</summary><content type="html">&lt;p&gt;In the &lt;a href="https://pablocaro.es/en/rbw-bitwarden-cli"&gt;previous post&lt;/a&gt; we talked about &lt;strong&gt;rbw&lt;/strong&gt;, the Bitwarden CLI client written in Rust that stands out for its speed and for using an agent to manage vault unlocking.&lt;/p&gt;
&lt;p&gt;Today I'm going a step further and introducing &lt;strong&gt;&lt;a href="https://github.com/pcaro/kitty_rbw"&gt;kitty-rbw&lt;/a&gt;&lt;/strong&gt;, a &lt;em&gt;kitten&lt;/em&gt; I've developed for the &lt;strong&gt;kitty&lt;/strong&gt; terminal. Its goal is to let you search and use your Bitwarden credentials without leaving the terminal or typing complex commands, integrating perfectly into your workflow.&lt;/p&gt;
&lt;p&gt;&lt;img alt="kitty-rbw interface" src="https://pablocaro.es/images/kitty-rbw.png"&gt;&lt;/p&gt;
&lt;h2 id="what-is-a-kitten"&gt;What is a kitten?&lt;a class="headerlink" href="#what-is-a-kitten" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Kittens&lt;/em&gt; are small Python programs that extend the functionality of the kitty terminal. They can run in overlays, making them ideal for interactive tools that we don't want cluttering our terminal history.&lt;/p&gt;
&lt;h2 id="key-features"&gt;Key Features&lt;a class="headerlink" href="#key-features" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;kitty-rbw&lt;/code&gt; uses &lt;strong&gt;fzf&lt;/strong&gt; to provide extremely fast fuzzy searching over your rbw vault. Here are its most notable features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Simultaneous search&lt;/strong&gt;: Filter by name, username, and folder at the same time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Direct injection&lt;/strong&gt;: Types the password or username directly into the active terminal window where you launched the kitten. Ideal for &lt;code&gt;sudo&lt;/code&gt; prompts or SSH logins.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clipboard support&lt;/strong&gt;: Copy the username, password, or TOTP code with a single keyboard shortcut.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Usage prioritization&lt;/strong&gt;: The 10 entries you use most frequently appear at the top of the list, making it easy to access common credentials.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Folder shortcuts&lt;/strong&gt;: You can configure the kitten to open pre-filtered by a specific folder (e.g., one for work and one for personal).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="installation"&gt;Installation&lt;a class="headerlink" href="#installation" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To install it, simply clone the repository into your kitty configuration directory:```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;Then, add a keyboard shortcut to your &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="usage-and-shortcuts"&gt;Usage and Shortcuts&lt;a class="headerlink" href="#usage-and-shortcuts" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Once configured, pressing the shortcut (&lt;code&gt;Ctrl+Shift+b&lt;/code&gt; by default) will open a panel with your credentials.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Action&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;Type &lt;strong&gt;password&lt;/strong&gt; into the 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;Type &lt;strong&gt;username&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;Type &lt;strong&gt;username&lt;/strong&gt;, press &lt;code&gt;Tab&lt;/code&gt;, then &lt;strong&gt;password&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;Copy &lt;strong&gt;TOTP&lt;/strong&gt; code to clipboard&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;Copy &lt;strong&gt;password&lt;/strong&gt; to clipboard&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;Copy &lt;strong&gt;username&lt;/strong&gt; to clipboard&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;Sync the vault (&lt;code&gt;rbw sync&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;If you want to know more or contribute to the project, you can find it on 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="Tools"></category><category term="bitwarden"></category><category term="cli"></category><category term="kitty"></category><category term="rust"></category><category term="productivity"></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:```bash
gah install doy/rbw&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;Este&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"&gt;descargará&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;binario&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 n-Quoted"&gt;`rbw`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&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;de&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n n-Quoted"&gt;`rbw-agent`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="c1"&gt;## Configuración inicial&lt;/span&gt;

&lt;span class="n"&gt;Lo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;primero&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;configurar&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;cuenta&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&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;servidor&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;rbw login&lt;/span&gt;
&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>rbw: The Bitwarden terminal client you should be using</title><link href="https://pablocaro.es/en/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:/en/rbw-bitwarden-cli</id><summary type="html">&lt;p&gt;How to install and configure rbw, a Rust implementation of the Bitwarden client, and how to integrate it into your workflows with pi-agent.&lt;/p&gt;</summary><content type="html">&lt;p&gt;While Bitwarden has an official command-line client, it can be slow as it is Node.js-based. For those of us looking for something more agile, &lt;a href="https://github.com/doy/rbw"&gt;rbw&lt;/a&gt; is the ideal solution. It is an unofficial Rust implementation that stands out for being extremely fast and for managing vault unlocking through an agent, preventing you from having to enter your master password for every command.&lt;/p&gt;
&lt;h2 id="installation"&gt;Installation&lt;a class="headerlink" href="#installation" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The easiest way to install it is by downloading the binary directly from its GitHub releases. I use &lt;code&gt;gah&lt;/code&gt; for this purpose:```bash
gah install doy/rbw&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;This&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;command&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;download&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 n-Quoted"&gt;`rbw`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;binary&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;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n n-Quoted"&gt;`rbw-agent`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="c1"&gt;## Initial Setup&lt;/span&gt;

&lt;span class="n"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;first&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;thing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&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;configure&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="k"&gt;account&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;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;server&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;rbw login&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It will ask for your email and the server URL (you can leave it blank if you use the official Bitwarden one or enter the URL of your Vaultwarden instance).&lt;/p&gt;
&lt;p&gt;To start using it, unlock the vault:&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;Unlike the official client, you don't need to export environment variables with session tokens. The &lt;code&gt;rbw&lt;/code&gt; agent takes care of everything in the background.&lt;/p&gt;
&lt;h2 id="basic-usage"&gt;Basic Usage&lt;a class="headerlink" href="#basic-usage" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To get a password quickly:&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;Item Name&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If there are multiple items with the same name, you can use filters or the ID. You can also get custom fields or the username:&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="integration-with-pi-agent"&gt;Integration with pi-agent&lt;a class="headerlink" href="#integration-with-pi-agent" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One of the greatest advantages of having a fast and secure CLI client is the ability to integrate secrets into your development tools.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/mariozechner/pi-agent"&gt;pi-agent&lt;/a&gt; allows executing commands to obtain API keys dynamically. This avoids having to save keys in plain text configuration files. In your &lt;code&gt;settings.json&lt;/code&gt;, you can configure access to a key as follows:&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;The &lt;code&gt;!&lt;/code&gt; prefix tells &lt;code&gt;pi-agent&lt;/code&gt; to execute the command and use its standard output as the key. Thanks to the &lt;code&gt;rbw&lt;/code&gt; agent, this command will run instantly without asking for your master password every time, as long as the vault is unlocked.&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="Tools"></category><category term="bitwarden"></category><category term="cli"></category><category term="rust"></category><category term="security"></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="Python"></category><category term="python"></category><category term="data"></category><category term="cli"></category><category term="tools"></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="linux"></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="Python"></category><category term="python"></category><category term="cli"></category><category term="tools"></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="DevOps"></category><category term="kubernetes"></category><category term="cli"></category><category term="logs"></category><category term="devops"></category><category term="tools"></category></entry><entry><title>DeepDiff: The Swiss Army Knife for Data Comparison in Python</title><link href="https://pablocaro.es/en/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:/en/deepdiff-comparar-datos-python</id><summary type="html">&lt;p&gt;When working with structured data in Python, we often need to compare two dictionaries, JSONs, or complex objects to find out what has changed. While direct comparison (&lt;code&gt;==&lt;/code&gt;) is useful, sometimes we need to understand &lt;em&gt;exactly what&lt;/em&gt; is different: which keys were added, which were removed, or where values have changed …&lt;/p&gt;</summary><content type="html">&lt;p&gt;When working with structured data in Python, we often need to compare two dictionaries, JSONs, or complex objects to find out what has changed. While direct comparison (&lt;code&gt;==&lt;/code&gt;) is useful, sometimes we need to understand &lt;em&gt;exactly what&lt;/em&gt; is different: which keys were added, which were removed, or where values have changed, even in deeply nested structures.&lt;/p&gt;
&lt;p&gt;This is where &lt;strong&gt;&lt;a href="https://github.com/seperman/deepdiff"&gt;DeepDiff&lt;/a&gt;&lt;/strong&gt; comes in.&lt;/p&gt;
&lt;p&gt;DeepDiff is an incredibly powerful Python library that offers much more than simple comparison. It is an essential tool for testing, data validation, and API debugging.&lt;/p&gt;
&lt;h2 id="key-features"&gt;Key Features&lt;a class="headerlink" href="#key-features" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;DeepDiff is not just a tool, it's a suite of utilities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DeepDiff&lt;/strong&gt;: Recursively compares dictionaries, iterables, strings, and other objects. It can ignore order in lists, ignore specific types, or exclude paths from comparison.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DeepSearch&lt;/strong&gt;: Searches for objects within other objects, like a &lt;code&gt;grep&lt;/code&gt; for in-memory data structures.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DeepHash&lt;/strong&gt;: Calculates hashes of objects based on their content. Very useful for deduplication of complex data where key order doesn't matter.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Delta&lt;/strong&gt;: Generates "deltas" (differences) that can be applied to other objects, similar to a &lt;code&gt;git patch&lt;/code&gt; but for Python objects.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="installation"&gt;Installation&lt;a class="headerlink" href="#installation" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The modern and recommended way to install Python tools is using &lt;code&gt;uv&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="as-a-command-line-interface-cli-tool"&gt;As a Command Line Interface (CLI) Tool&lt;a class="headerlink" href="#as-a-command-line-interface-cli-tool" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you just want to use the &lt;code&gt;deep&lt;/code&gt; command in your terminal to compare JSON or YAML files:```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;This&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="k"&gt;install&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 n-Quoted"&gt;`deep`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;command&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;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;system&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;an&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;isolated&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Make&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sure&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;include&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="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;install&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;necessary&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="c1"&gt;### As a Library in Your Project&lt;/span&gt;

&lt;span class="k"&gt;If&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;are&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;going&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="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;within&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;Python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scripts&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;Or if you need the CLI inside your virtual environment as well:&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="usage-examples"&gt;Usage Examples&lt;a class="headerlink" href="#usage-examples" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="in-python"&gt;In Python&lt;a class="headerlink" href="#in-python" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Imagine you have two slightly different API responses and want to know what changed:&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;Product 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;new&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;sale&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;Product 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;sale&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;new&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;# Different order&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;# By default, order matters in lists&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;# Result shows changes in &amp;#39;tags&amp;#39; list and &amp;#39;details.price&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;# If we ignore order in 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;Output:&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;As you can see, it detected the price change but ignored the order change in tags.&lt;/p&gt;
&lt;h3 id="in-the-terminal-cli"&gt;In the Terminal (CLI)&lt;a class="headerlink" href="#in-the-terminal-cli" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;deep&lt;/code&gt; command is very useful for quickly comparing files:&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;# Compare two JSON files&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;You can also use it to extract information or search within large JSON/YAML files without having to write a script.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;DeepDiff is one of those libraries that, once you know about it, you can't stop using. Its flexibility to ignore certain fields (like timestamps or auto-generated IDs) makes it perfect for integration tests and data validation.&lt;/p&gt;
&lt;p&gt;You can view the full documentation at &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="Python"></category><category term="python"></category><category term="data"></category><category term="cli"></category><category term="tools"></category><category term="diff"></category></entry><entry><title>Rendering Markdown in the terminal with Glow</title><link href="https://pablocaro.es/en/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:/en/renderizando-markdown-en-la-terminal-con-glow</id><summary type="html">&lt;p&gt;Discover Glow, a tool to render Markdown directly in the terminal, ideal for reading documentation and READMEs.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://github.com/charmbracelet/glow"&gt;Glow&lt;/a&gt; is a command-line Markdown renderer. It allows you to read local or remote Markdown files, rendering them with syntax highlighting and styles, directly in your terminal.&lt;/p&gt;
&lt;p&gt;I find this tool especially useful now that I spend more time using the terminal with &lt;a href="https://pablocaro.es/en/de-yakuake-a-kitty"&gt;Kitty&lt;/a&gt;, as it allows me to consult documentation and READMEs without having to open a browser or a graphical editor.&lt;/p&gt;
&lt;h2 id="installation"&gt;Installation&lt;a class="headerlink" href="#installation" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To install Glow, we will use &lt;a href="https://pablocaro.es/en/gestionando-instalaciones-desde-github-con-gah"&gt;gah&lt;/a&gt;, a tool that simplifies installing binaries from 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;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;installation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;very&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;simple&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;Once installed, simply run &lt;code&gt;glow&lt;/code&gt; followed by the file you want to view, or even the URL of a README on 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="tools"></category><category term="linux"></category><category term="kitty"></category></entry><entry><title>Shot-scraper: Automated Web Screenshots</title><link href="https://pablocaro.es/en/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:/en/shot-scraper-capturas-pantalla-automatizadas</id><summary type="html">&lt;p&gt;Have you ever needed to take screenshots of a web page programmatically? Perhaps to document your project, monitor UI changes, or generate images for social media. There are many ways to do it, but &lt;strong&gt;&lt;a href="https://shot-scraper.datasette.io/"&gt;shot-scraper&lt;/a&gt;&lt;/strong&gt; stands out for its ease of use and power.&lt;/p&gt;
&lt;p&gt;Created by Simon Willison, &lt;code&gt;shot-scraper&lt;/code&gt; is …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Have you ever needed to take screenshots of a web page programmatically? Perhaps to document your project, monitor UI changes, or generate images for social media. There are many ways to do it, but &lt;strong&gt;&lt;a href="https://shot-scraper.datasette.io/"&gt;shot-scraper&lt;/a&gt;&lt;/strong&gt; stands out for its ease of use and power.&lt;/p&gt;
&lt;p&gt;Created by Simon Willison, &lt;code&gt;shot-scraper&lt;/code&gt; is a command line interface (CLI) tool that wraps Playwright to make taking screenshots trivial.&lt;/p&gt;
&lt;h2 id="installation"&gt;Installation&lt;a class="headerlink" href="#installation" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As with other modern Python tools, the best way to install it is using &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;&lt;span class="n"&gt;Once&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&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;you&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ll need to download the browser it uses under the hood (Chromium):&lt;/span&gt;

&lt;span class="err"&gt;```&lt;/span&gt;&lt;span class="n"&gt;bash&lt;/span&gt;
&lt;span class="n"&gt;shot&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;scraper&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="basic-usage"&gt;Basic Usage&lt;a class="headerlink" href="#basic-usage" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The simplest way to use it is to give it a URL and a filename:&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/en/&lt;span class="w"&gt; &lt;/span&gt;blog.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This will generate an image named &lt;code&gt;blog.png&lt;/code&gt; with the screenshot of the page.&lt;/p&gt;
&lt;h3 id="capturing-specific-selectors"&gt;Capturing Specific Selectors&lt;a class="headerlink" href="#capturing-specific-selectors" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;One of the most powerful features is the ability to capture only a specific element of the page using CSS selectors:&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;This command will capture only the GitHub contribution graph.&lt;/p&gt;
&lt;h3 id="interacting-with-javascript"&gt;Interacting with JavaScript&lt;a class="headerlink" href="#interacting-with-javascript" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Sometimes you need to execute some code before taking the shot. For example, to hide a cookie banner or wait for something to load.&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/en/&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;no-header.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="advanced-automation"&gt;Advanced Automation&lt;a class="headerlink" href="#advanced-automation" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you need to take many screenshots, you can define a YAML configuration file:&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/en/&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/en/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;# Wait 1 second&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And run them all at once:&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="beyond-screenshots"&gt;Beyond Screenshots&lt;a class="headerlink" href="#beyond-screenshots" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;shot-scraper&lt;/code&gt; also includes utilities for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Accessibility&lt;/strong&gt;: Dump the accessibility tree of a page (&lt;code&gt;shot-scraper accessibility&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PDF&lt;/strong&gt;: Generate PDFs of web pages (&lt;code&gt;shot-scraper pdf&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HAR&lt;/strong&gt;: Record HAR files to analyze network traffic (&lt;code&gt;shot-scraper har&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It's one of those tools that, once installed, you find uses for constantly. Perfect for integrating into CI/CD pipelines to generate documentation visuals automatically.&lt;/p&gt;
&lt;p&gt;More info in the &lt;a href="https://shot-scraper.datasette.io/"&gt;official documentation&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="Python"></category><category term="python"></category><category term="cli"></category><category term="tools"></category><category term="web"></category><category term="scraping"></category><category term="screenshots"></category></entry><entry><title>Stern: Kubernetes Logs on Steroids</title><link href="https://pablocaro.es/en/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:/en/stern-kubernetes-logs</id><summary type="html">&lt;p&gt;If you work with Kubernetes, you've probably suffered through the &lt;code&gt;kubectl logs&lt;/code&gt; command. It's useful for viewing the logs of a specific pod, but it falls short when you have multiple replicas of a service or when pods restart and change names.&lt;/p&gt;
&lt;p&gt;This is where &lt;strong&gt;&lt;a href="https://github.com/stern/stern"&gt;Stern&lt;/a&gt;&lt;/strong&gt; comes in.&lt;/p&gt;
&lt;p&gt;Stern allows …&lt;/p&gt;</summary><content type="html">&lt;p&gt;If you work with Kubernetes, you've probably suffered through the &lt;code&gt;kubectl logs&lt;/code&gt; command. It's useful for viewing the logs of a specific pod, but it falls short when you have multiple replicas of a service or when pods restart and change names.&lt;/p&gt;
&lt;p&gt;This is where &lt;strong&gt;&lt;a href="https://github.com/stern/stern"&gt;Stern&lt;/a&gt;&lt;/strong&gt; comes in.&lt;/p&gt;
&lt;p&gt;Stern allows you to "tail" logs from multiple pods and containers within Kubernetes simultaneously. Best of all, it uses regular expressions to select pods, so you don't need to copy and paste those random IDs (&lt;code&gt;pod-1234567890-abcde&lt;/code&gt;).&lt;/p&gt;
&lt;h2 id="installation"&gt;Installation&lt;a class="headerlink" href="#installation" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Stern is very popular and available in most package managers.### Using Krew (recommended)
If you already use &lt;code&gt;kubectl&lt;/code&gt;, Krew is the most natural way to install 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="using-homebrew"&gt;Using Homebrew&lt;a class="headerlink" href="#using-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="direct-binary"&gt;Direct Binary&lt;a class="headerlink" href="#direct-binary" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You can also download the binary directly from their &lt;a href="https://github.com/stern/stern/releases"&gt;GitHub releases&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you use a helper like &lt;code&gt;gah&lt;/code&gt; (GitHub Asset Helper) or custom scripts, installation is as simple as:&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="key-features"&gt;Key Features&lt;a class="headerlink" href="#key-features" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="1-multi-pod-tailing"&gt;1. Multi-pod Tailing&lt;a class="headerlink" href="#1-multi-pod-tailing" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Instead of searching for the exact pod name, you can use a 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;This will show logs from all pods containing "backend" in their name (&lt;code&gt;backend-api&lt;/code&gt;, &lt;code&gt;backend-worker&lt;/code&gt;, etc.), interleaved and colored to easily distinguish them.&lt;/p&gt;
&lt;h3 id="2-filtering-and-exclusion"&gt;2. Filtering and Exclusion&lt;a class="headerlink" href="#2-filtering-and-exclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You can filter log content on the fly without needing &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;# Only show lines containing &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;# Exclude lines containing &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-kubernetes-selectors"&gt;3. Kubernetes Selectors&lt;a class="headerlink" href="#3-kubernetes-selectors" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In addition to regex by name, you can use label selectors, which is much more precise for production environments:&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;my-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-output-formatting"&gt;4. Output Formatting&lt;a class="headerlink" href="#4-output-formatting" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If your logs are in JSON (as is best practice), Stern can pass them through as is so you can process them with tools like &lt;code&gt;jq&lt;/code&gt; or &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;Conclusion&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Stern is one of those tools you install on day one and wonder how you ever lived without it. It makes debugging in distributed environments much less painful by aggregating information coherently.&lt;/p&gt;
&lt;p&gt;Give it a try and your debugging sessions will thank you.&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="DevOps"></category><category term="kubernetes"></category><category term="cli"></category><category term="logs"></category><category term="devops"></category><category term="tools"></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="linux"></category><category term="herramientas"></category><category term="cli"></category></entry><entry><title>Managing GitHub installations with gah</title><link href="https://pablocaro.es/en/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:/en/gestionando-instalaciones-desde-github-con-gah</id><summary type="html">&lt;p class="first last"&gt;Install binaries directly from GitHub releases easily with 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; is a tool that simplifies installing programs distributed via GitHub releases. It's a lightweight alternative when you don't want to rely on your distro's package manager or when you need a newer version.&lt;/p&gt;
&lt;div class="section" id="installation"&gt;
&lt;h2&gt;Installation&lt;/h2&gt;
&lt;p&gt;To install &lt;strong&gt;gah&lt;/strong&gt;, you can use the official script:&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="usage"&gt;
&lt;h2&gt;Usage&lt;/h2&gt;
&lt;p&gt;Once installed, usage is very straightforward. Here are a couple of examples I've used recently to install popular Rust tools:&lt;/p&gt;
&lt;p&gt;To install &lt;cite&gt;fd&lt;/cite&gt; (a fast alternative to &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;To install &lt;cite&gt;ripgrep&lt;/cite&gt; (a fast alternative to &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 handles downloading the correct asset for your architecture, unpacking it, and placing it in your path.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Linux"></category><category term="linux"></category><category term="tools"></category><category term="cli"></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="herramientas"></category><category term="multiline"></category><category term="strings"></category></entry><entry><title>Multiline YAML: Understanding and testing complex strings</title><link href="https://pablocaro.es/en/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:/en/yaml-multilinea-cadenas-complejas</id><summary type="html">&lt;p&gt;Explore YAML options for effectively handling multiline strings, with an interactive demo that allows you to test them in real-time.&lt;/p&gt;</summary><content type="html">&lt;p&gt;YAML has become the de facto format for configuration in many projects, from Kubernetes to CI/CD scripts. One of its most powerful, and sometimes confusing, features is the management of &lt;strong&gt;multiline strings&lt;/strong&gt;. Fortunately, there is an exceptional interactive tool to master this aspect: &lt;a href="https://yaml-multiline.info/"&gt;yaml-multiline.info&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="formats-for-multiline-strings-in-yaml"&gt;Formats for Multiline Strings in YAML&lt;a class="headerlink" href="#formats-for-multiline-strings-in-yaml" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;YAML mainly offers two approaches for defining strings that span multiple lines:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Flow Scalars&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;They are simple strings, often quoted (&lt;code&gt;'...'&lt;/code&gt; or &lt;code&gt;"..."&lt;/code&gt;) or unquoted.&lt;/li&gt;
&lt;li&gt;They ignore indentations and, by default, replace line breaks with spaces.&lt;/li&gt;
&lt;li&gt;Double-quoted strings allow using escape sequences like &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;Block Scalars&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;They offer much more precise control over line breaks and indentation.&lt;/li&gt;
&lt;li&gt;They are indicated with a special character at the beginning of the line where the string starts.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="keys-for-block-multiline-strings"&gt;Keys for Block Multiline Strings&lt;a class="headerlink" href="#keys-for-block-multiline-strings" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The two most important style indicators for block scalars are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;|&lt;/code&gt; (Literal Style)&lt;/strong&gt;: Keeps all line breaks and indentation as is. It is ideal for blocks of text where formatting is crucial (e.g., code, long messages).
    &lt;code&gt;yaml
    literal_string: |
      This is a line.
      This is another.
        With its own indentation.&lt;/code&gt;
    Result: "This is a line.\nThis is another.\n  With its own indentation.\n"&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;&amp;gt;&lt;/code&gt; (Folded Style)&lt;/strong&gt;: Replaces line breaks with spaces, joining lines into a single one. Only double line breaks (blank lines) or lines with greater indentation are kept as line breaks. It is useful for long paragraphs.
    &lt;code&gt;yaml
    folded_string: &amp;gt;
      This is a string
      folded into multiple lines.
      It will keep the paragraph format.&lt;/code&gt;
    Result: "This is a string folded into multiple lines. It will keep the paragraph format.\n"&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In addition to this, there are "chomping" indicators (&lt;code&gt;-&lt;/code&gt; to remove trailing line breaks, &lt;code&gt;+&lt;/code&gt; to keep them, or the default behavior) and indentation indicators that offer even more control.&lt;/p&gt;
&lt;h2 id="real-time-demo"&gt;Real-Time Demo!&lt;a class="headerlink" href="#real-time-demo" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;What makes &lt;a href="https://yaml-multiline.info/"&gt;yaml-multiline.info&lt;/a&gt; an invaluable tool is its &lt;strong&gt;interactive demo&lt;/strong&gt;. You can test different YAML syntaxes in real-time and see how the resulting string is interpreted. It is the best way to visually understand how literal and folded styles work, and how chomping and indentation indicators affect the final result.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Understanding how to handle multiline strings in YAML not only improves the readability of your configuration files but also prevents unexpected errors. The real-time demo of &lt;code&gt;yaml-multiline.info&lt;/code&gt; is a fantastic resource for anyone who regularly works with YAML and wants to master this aspect.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Website&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="Tools"></category><category term="yaml"></category><category term="configuration"></category><category term="development"></category><category term="tools"></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="python"></category><category term="data-science"></category><category term="visualizacion"></category><category term="estilo"></category></entry><entry><title>Pandas Styler: Improve the Presentation of your DataFrames</title><link href="https://pablocaro.es/en/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:/en/pandas-styler-presentacion-dataframes</id><summary type="html">&lt;p&gt;Discover how the Pandas &lt;code&gt;Styler&lt;/code&gt; object allows you to apply conditional formatting and CSS styles to your DataFrames for better data visualization and presentation.&lt;/p&gt;</summary><content type="html">&lt;p&gt;When working with data in Python, Pandas DataFrames are our main tool. However, their default presentation in consoles or notebooks can be very basic. This is where the &lt;strong&gt;Pandas &lt;code&gt;Styler&lt;/code&gt; object&lt;/strong&gt; becomes a fundamental ally to transform simple DataFrames into visually attractive and informative tables.&lt;/p&gt;
&lt;h2 id="what-is-pandas-styler"&gt;What is Pandas &lt;code&gt;Styler&lt;/code&gt;?&lt;a class="headerlink" href="#what-is-pandas-styler" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;Styler&lt;/code&gt; object allows you to apply conditional formatting and CSS styles directly to your Pandas DataFrames. With it, you can highlight patterns, emphasize important values, and improve readability without having to export the data to another tool.&lt;/p&gt;
&lt;h2 id="key-features"&gt;Key Features&lt;a class="headerlink" href="#key-features" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Conditional Formatting&lt;/strong&gt;: Highlight cells, rows, or columns based on conditions defined by you (e.g., values above a threshold, duplicates, etc.).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CSS Styles&lt;/strong&gt;: Apply CSS styles such as background colors, fonts, borders, or alignments to customize the appearance.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Predefined Styles&lt;/strong&gt;: Includes methods like &lt;code&gt;background_gradient()&lt;/code&gt; to apply color gradients that visualize the distribution of values.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Excel Integration&lt;/strong&gt;: Maintains styles when exporting DataFrames to Excel files, facilitating professional reports.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Pandas &lt;code&gt;Styler&lt;/code&gt; object is a simple but powerful tool to take your data presentation to the next level. With a few lines of code, you can make your DataFrames not only a data source but also an effective communication tool.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Original article&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="Programming"></category><category term="pandas"></category><category term="python"></category><category term="data-science"></category><category term="visualization"></category><category term="style"></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="herramientas"></category><category term="cli"></category></entry><entry><title>`fresh`: My preferred terminal editor for daily use</title><link href="https://pablocaro.es/en/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:/en/fresh-editor-terminal</id><summary type="html">&lt;p&gt;Discover &lt;code&gt;fresh&lt;/code&gt;, a fast, modern, modeless terminal text editor, ideal for casual file editing, surpassing &lt;code&gt;nano&lt;/code&gt; and &lt;code&gt;vim&lt;/code&gt; in simplicity for regular use.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Although &lt;code&gt;vim&lt;/code&gt; is an incredibly powerful tool and &lt;code&gt;nano&lt;/code&gt; stands out for its extreme simplicity, I often look for a middle ground for file editing in the terminal. An editor that is fast, modern, and doesn't force me to remember modes or complex key combinations for simple tasks. That's how I discovered &lt;strong&gt;&lt;code&gt;fresh&lt;/code&gt;&lt;/strong&gt;, and it has quickly become my preferred terminal editor for daily use.&lt;/p&gt;
&lt;h2 id="what-is-fresh"&gt;What is &lt;code&gt;fresh&lt;/code&gt;?&lt;a class="headerlink" href="#what-is-fresh" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;fresh&lt;/code&gt; is a text editor for the terminal designed to be &lt;strong&gt;simple, fast, and efficient&lt;/strong&gt;. Unlike &lt;code&gt;vim&lt;/code&gt;, &lt;code&gt;fresh&lt;/code&gt; is a modeless editor, which means you can start typing and editing text immediately, just like you would in a graphical editor. Unlike &lt;code&gt;nano&lt;/code&gt;, &lt;code&gt;fresh&lt;/code&gt; offers a more modern experience with features you expect from a current text editor.&lt;/p&gt;
&lt;h2 id="why-do-i-prefer-it-over-nano-or-vim-for-regular-use"&gt;Why do I prefer it over &lt;code&gt;nano&lt;/code&gt; or &lt;code&gt;vim&lt;/code&gt; for regular use?&lt;a class="headerlink" href="#why-do-i-prefer-it-over-nano-or-vim-for-regular-use" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Modeless&lt;/strong&gt;: The main advantage over &lt;code&gt;vim&lt;/code&gt;. You don't need to learn to switch between insert, normal, visual modes, etc. You simply open the file and edit.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Modern and Fast&lt;/strong&gt;: Despite running in the terminal, &lt;code&gt;fresh&lt;/code&gt; feels responsive and offers a clean interface.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Syntax Highlighting&lt;/strong&gt;: Support for syntax coloring for a wide variety of languages.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mouse Support&lt;/strong&gt;: You can click to move the cursor, select text, or even use the wheel to scroll.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multi-Cursor&lt;/strong&gt;: A very powerful feature that &lt;code&gt;nano&lt;/code&gt; does not offer and &lt;code&gt;vim&lt;/code&gt; requires more complexity to use.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Undo/Redo&lt;/strong&gt;: Full &lt;code&gt;undo&lt;/code&gt;/&lt;code&gt;redo&lt;/code&gt; functionality.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Search and Replace&lt;/strong&gt;: Simple and intuitive search and replace functions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While &lt;code&gt;vim&lt;/code&gt; remains my choice for complex edits or advanced scripting, for "opening a &lt;code&gt;.conf&lt;/code&gt;", modifying a &lt;code&gt;.bashrc&lt;/code&gt;, or writing a small note, &lt;code&gt;fresh&lt;/code&gt; is unbeatable in speed and convenience. &lt;code&gt;nano&lt;/code&gt; is perhaps too basic for my needs, lacking most of the modern features that &lt;code&gt;fresh&lt;/code&gt; offers.&lt;/p&gt;
&lt;h2 id="installation"&gt;Installation&lt;a class="headerlink" href="#installation" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;fresh&lt;/code&gt; is easy to install on most Linux distributions. For example, on Debian/Ubuntu-based systems:&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;Or you can follow the instructions in the official repository to compile it from source or use other package managers.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you are looking for a terminal editor that is a perfect hybrid between the power of &lt;code&gt;vim&lt;/code&gt; and the simplicity of &lt;code&gt;nano&lt;/code&gt;, but with a modern touch and focused on immediate usability, &lt;code&gt;fresh&lt;/code&gt; is an excellent choice. Give it a try, it might become your new favorite!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Official repository&lt;/em&gt;: &lt;a href="https://sinelaw.github.io/fresh/"&gt;&lt;code&gt;fresh&lt;/code&gt; on GitHub&lt;/a&gt;&lt;/p&gt;</content><category term="Tools"></category><category term="fresh"></category><category term="terminal"></category><category term="editor"></category><category term="productivity"></category><category term="linux"></category><category term="tools"></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="Sistemas"></category><category term="linux"></category><category term="rpm"></category><category term="paquetes"></category><category term="comandos"></category><category term="sistemas"></category><category term="administración"></category><category term="dnf"></category><category term="yum"></category></entry><entry><title>How to View Files in an RPM Package on Linux</title><link href="https://pablocaro.es/en/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:/en/ver-archivos-paquete-rpm</id><summary type="html">&lt;p&gt;Quick guide to inspecting the contents of RPM packages and determining which packages own specific files on your Linux system.&lt;/p&gt;</summary><content type="html">&lt;p&gt;On RPM-based Linux systems (like Red Hat, CentOS, Fedora, openSUSE), it is common to interact with packages in &lt;code&gt;.rpm&lt;/code&gt; format. Often, we need to know what files a package contains before installing it, or which package is responsible for a specific file already installed on the system. For this, the &lt;code&gt;rpm&lt;/code&gt; tool offers several useful options.&lt;/p&gt;
&lt;h2 id="1-list-files-of-an-rpm-package-without-installing"&gt;1. List Files of an RPM Package Without Installing&lt;a class="headerlink" href="#1-list-files-of-an-rpm-package-without-installing" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you have a downloaded &lt;code&gt;.rpm&lt;/code&gt; file and want to see what files it will install (and where) without installing it, you can use the &lt;code&gt;-qlp&lt;/code&gt; option:&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;package_name.rpm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-q&lt;/code&gt;: Query mode.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-l&lt;/code&gt;: List files.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-p&lt;/code&gt;: Query a package file instead of an installed one.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Example:&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-find-the-owner-package-of-an-installed-file"&gt;2. Find the Owner Package of an Installed File&lt;a class="headerlink" href="#2-find-the-owner-package-of-an-installed-file" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you find a file on your system and want to know which RPM package it belongs to (useful for debugging or knowing if you can safely delete it), use the &lt;code&gt;-qf&lt;/code&gt; option:&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;/path/to/file
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Example:&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;# Expected output: nginx-1.20.1-1.el8.x86_64&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="3-get-detailed-information-about-a-package"&gt;3. Get Detailed Information about a Package&lt;a class="headerlink" href="#3-get-detailed-information-about-a-package" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To see complete information about a package (version, description, dependencies, etc.), whether installed or an &lt;code&gt;.rpm&lt;/code&gt; file, you can use &lt;code&gt;-qi&lt;/code&gt; (for installed packages) or &lt;code&gt;-qip&lt;/code&gt; (for package files):&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;installed_package_name
&lt;span class="c1"&gt;# Example: rpm -qi nginx&lt;/span&gt;

rpm&lt;span class="w"&gt; &lt;/span&gt;-qip&lt;span class="w"&gt; &lt;/span&gt;package_name.rpm
&lt;span class="c1"&gt;# Example: 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;Conclusion&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Mastering these simple options of the &lt;code&gt;rpm&lt;/code&gt; command gives you much greater control over packages and files on your Linux system, facilitating administration and debugging. They are essential tools for any system administrator or developer working with RPM-based distributions.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Original article&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="Systems"></category><category term="linux"></category><category term="rpm"></category><category term="packages"></category><category term="commands"></category><category term="systems"></category><category term="administration"></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="herramientas"></category><category term="open-source"></category></entry><entry><title>Kooha: Simple and Elegant Screen Recording on Linux</title><link href="https://pablocaro.es/en/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:/en/kooha-captura-pantalla-linux</id><summary type="html">&lt;p&gt;Discover Kooha, a minimalist screen recording application for Linux that combines ease of use with essential features.&lt;/p&gt;</summary><content type="html">&lt;p&gt;In Linux, the need to record the screen simply and efficiently is common, whether to create tutorials, presentations, bug reports, or simply share an experience. Although there are many tools, &lt;strong&gt;Kooha&lt;/strong&gt; stands out for its minimalist approach and its intuitive user interface, based on GTK.&lt;/p&gt;
&lt;h2 id="what-is-kooha"&gt;What is Kooha?&lt;a class="headerlink" href="#what-is-kooha" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Kooha is a free and open-source screen recording application, designed specifically for Linux desktop environments. Its main goal is to offer a hassle-free user experience, focusing on the essential functionalities of a screen recorder.&lt;/p&gt;
&lt;h2 id="why-choose-kooha"&gt;Why Choose Kooha?&lt;a class="headerlink" href="#why-choose-kooha" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Ease of Use&lt;/strong&gt;: Its interface is clear and direct. No complex menus or overwhelming options, making it ideal for users of all levels.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Minimalist Design&lt;/strong&gt;: It integrates perfectly with the look of GNOME and other GTK environments, offering a clean and pleasant visual experience.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Key Features&lt;/strong&gt;: Despite its simplicity, it includes all the necessary options for most recording tasks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Open Source&lt;/strong&gt;: Being open-source, it benefits from the community and allows users to inspect and contribute to the code.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="main-features"&gt;Main Features&lt;a class="headerlink" href="#main-features" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Full Screen Recording&lt;/strong&gt;: Captures everything that happens on your monitor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Specific Window Recording&lt;/strong&gt;: Choose a particular application or window to record.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Selected Area Recording&lt;/strong&gt;: Define a rectangular area on your screen for capture.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Audio Recording&lt;/strong&gt;: Includes audio from your microphone.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Webcam Recording&lt;/strong&gt;: Integrates your webcam image into the recording.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Common Formats&lt;/strong&gt;: Support for popular formats like WebM and MP4.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Quality Options&lt;/strong&gt;: Settings for frame rate (FPS) and video quality.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="installation"&gt;Installation&lt;a class="headerlink" href="#installation" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The most recommended way to install Kooha is via Flatpak, which ensures you get the latest version and all necessary dependencies:&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;It may also be available in the repositories of some Linux distributions or as a Snap package.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Kooha is the perfect tool for those looking for a screen recording solution on Linux that is powerful without being complicated. Its elegance and simplicity make it an excellent addition to any Linux user's productivity arsenal.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Official repository&lt;/em&gt;: &lt;a href="https://github.com/SeaDve/Kooha"&gt;SeaDve/Kooha on GitHub&lt;/a&gt;&lt;/p&gt;</content><category term="Tools"></category><category term="linux"></category><category term="kooha"></category><category term="screen-recorder"></category><category term="productivity"></category><category term="tools"></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="Sin Categoría"></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>OpenEvidence: The Leading Medical Information Platform for Professionals</title><link href="https://pablocaro.es/en/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:/en/open-evidence-plataforma-medica</id><summary type="html">&lt;p&gt;Exploring OpenEvidence, a leading platform that consolidates medical information, clinical findings, and practice guidelines for healthcare professionals in the US.&lt;/p&gt;</summary><content type="html">&lt;p&gt;In the healthcare field, quick and reliable access to the latest and relevant medical information is crucial for clinical decision-making. &lt;strong&gt;OpenEvidence&lt;/strong&gt; positions itself as a leading platform designed to consolidate and present this information in an accessible way to healthcare professionals.&lt;/p&gt;
&lt;h2 id="what-does-openevidence-offer"&gt;What Does OpenEvidence Offer?&lt;a class="headerlink" href="#what-does-openevidence-offer" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;OpenEvidence acts as a nerve center for medical information, signing content agreements with some of the most prestigious publications and organizations in the sector. Its collaborators include:&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;, including its clinical guidelines and pathways.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mayo Clinic Platform&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This ensures that professionals have at their fingertips an authoritative and updated source of knowledge.&lt;/p&gt;
&lt;h2 id="key-features-for-professionals"&gt;Key Features for Professionals&lt;a class="headerlink" href="#key-features-for-professionals" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The platform is designed to meet the specific needs of healthcare professionals, offering:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Primary Evidence&lt;/strong&gt;: Direct access to clinical findings and original research.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Patient Documents&lt;/strong&gt;: Tools to create informative brochures for patients, facilitating communication.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Capabilities Exploration&lt;/strong&gt;: An intuitive system to navigate through a vast library of resources.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clinical Guidelines and Pathways&lt;/strong&gt;: Access to the latest recommendations for the treatment and management of various conditions.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="who-is-it-for"&gt;Who is it For?&lt;a class="headerlink" href="#who-is-it-for" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;OpenEvidence is aimed primarily at &lt;strong&gt;verified healthcare professionals in the US&lt;/strong&gt; and is offered free of charge for this group. It is available through iOS and Android apps, allowing convenient and mobile access to information.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;OpenEvidence represents a significant advance in the democratization of access to high-quality medical information. By consolidating resources from various authoritative sources and offering them on an easy-to-use platform, it allows healthcare professionals to make more informed decisions and, ultimately, improve patient care.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Official website&lt;/em&gt;: &lt;a href="https://www.openevidence.com/"&gt;OpenEvidence&lt;/a&gt;&lt;/p&gt;</content><category term="Miscellaneous"></category><category term="open-evidence"></category><category term="medicine"></category><category term="health"></category><category term="medical-information"></category><category term="health-professionals"></category><category term="research"></category><category term="clinical"></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><category term="herramientas"></category></entry><entry><title>`direnv`: Manage your development environments intelligently</title><link href="https://pablocaro.es/en/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:/en/direnv-gestion-entornos</id><summary type="html">&lt;p&gt;Discover &lt;code&gt;direnv&lt;/code&gt;, a shell extension that automatically loads and unloads environment variables when changing directories, keeping your configuration clean and organized.&lt;/p&gt;</summary><content type="html">&lt;p&gt;In the daily life of a developer, it is common to work with multiple projects, each with its own environment variables, paths (&lt;code&gt;PATH&lt;/code&gt;), tool versions, or credentials. Manually managing these variables can lead to messy configurations in your &lt;code&gt;.profile&lt;/code&gt; or &lt;code&gt;.bashrc&lt;/code&gt;, or errors due to using the wrong environment. This is where &lt;strong&gt;&lt;code&gt;direnv&lt;/code&gt;&lt;/strong&gt; shines, offering an elegant and automatic solution.&lt;/p&gt;
&lt;h2 id="what-is-direnv"&gt;What is &lt;code&gt;direnv&lt;/code&gt;?&lt;a class="headerlink" href="#what-is-direnv" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;direnv&lt;/code&gt; is an extension for your shell (Bash, Zsh, Fish, etc.) that automatically detects when you change directories. When you enter a directory containing an &lt;code&gt;.envrc&lt;/code&gt; file, &lt;code&gt;direnv&lt;/code&gt; loads it; when you leave it, it unloads it. This means your shell environment dynamically adapts to your current project without you having to do anything manually.&lt;/p&gt;
&lt;h2 id="how-does-it-work"&gt;How does it work?&lt;a class="headerlink" href="#how-does-it-work" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;direnv&lt;/code&gt; integrates with your shell through a hook in the &lt;code&gt;cd&lt;/code&gt; command. Every time you change directories, &lt;code&gt;direnv&lt;/code&gt; checks if there is an &lt;code&gt;.envrc&lt;/code&gt; file in the current directory or any of its ancestors. If it finds one (and you have granted permission to load it), it executes the commands defined in that file to modify your environment. Upon leaving the directory, it reverts those changes, cleaning up your environment.&lt;/p&gt;
&lt;h2 id="common-use-cases"&gt;Common Use Cases&lt;a class="headerlink" href="#common-use-cases" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;PATH&lt;/code&gt; Management&lt;/strong&gt;: Adding project-specific binaries to your &lt;code&gt;PATH&lt;/code&gt; temporarily.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Python Virtual Environments&lt;/strong&gt;: Automatically activating a &lt;code&gt;virtualenv&lt;/code&gt; or &lt;code&gt;uv venv&lt;/code&gt; when entering a Python project.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Credentials and API Keys&lt;/strong&gt;: Loading environment variables with database credentials, API keys, or tokens securely (without leaving them in the shell history).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Project-Specific Environment Variables&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;Tool Version Switching&lt;/strong&gt;: Integrating with tools like &lt;code&gt;nvm&lt;/code&gt; or &lt;code&gt;pyenv&lt;/code&gt; to automatically switch Node.js or Python versions.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="basic-installation-bash-example"&gt;Basic Installation (Bash example)&lt;a class="headerlink" href="#basic-installation-bash-example" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Install &lt;code&gt;direnv&lt;/code&gt;&lt;/strong&gt;: You can install it from your package manager (&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;Activate the hook in your shell&lt;/strong&gt;: Add the following line to your &lt;code&gt;~/.bashrc&lt;/code&gt; (or &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;Create an &lt;code&gt;.envrc&lt;/code&gt; file&lt;/strong&gt;: In the root of your project, create an &lt;code&gt;.envrc&lt;/code&gt; file with the variables and commands you want.&lt;/p&gt;
&lt;p&gt;```bash&lt;/p&gt;
&lt;h1 id="envrc-in-the-root-of-your-python-project"&gt;.envrc in the root of your Python project&lt;a class="headerlink" href="#envrc-in-the-root-of-your-python-project" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;layout python  # Automatically activates a virtual environment
export MY_API_KEY="super_secret_project_key"
```&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Allow loading&lt;/strong&gt;: The first time you enter the directory with a new &lt;code&gt;.envrc&lt;/code&gt;, &lt;code&gt;direnv&lt;/code&gt; will ask for confirmation:&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;
Run &lt;code&gt;direnv allow&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="the-direnv-standard-library"&gt;The &lt;code&gt;direnv&lt;/code&gt; Standard Library&lt;a class="headerlink" href="#the-direnv-standard-library" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;direnv&lt;/code&gt; comes with a powerful standard library that includes predefined functions for common tasks, such as &lt;code&gt;layout python&lt;/code&gt; (to manage virtualenvs), &lt;code&gt;use nix&lt;/code&gt;, &lt;code&gt;use node&lt;/code&gt;, etc., greatly simplifying the configuration of your &lt;code&gt;.envrc&lt;/code&gt; files.&lt;/p&gt;
&lt;h2 id="key-benefits"&gt;Key Benefits&lt;a class="headerlink" href="#key-benefits" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Clean environments&lt;/strong&gt;: Your &lt;code&gt;.profile&lt;/code&gt; stays tidy.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consistency&lt;/strong&gt;: Each project has exactly the environment it needs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Productivity&lt;/strong&gt;: No more manual &lt;code&gt;source venv/bin/activate&lt;/code&gt; or remembering to set variables.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;direnv&lt;/code&gt; is a small but powerful tool that transforms the way you manage your development environments, making your life in the terminal much more efficient and error-free.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Original article&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="Tools"></category><category term="direnv"></category><category term="shell"></category><category term="environment"></category><category term="productivity"></category><category term="development"></category><category term="linux"></category><category term="tools"></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="herramientas"></category><category term="contenedores"></category></entry><entry><title>`docker-http-https-echo`: An Essential Tool for Network Debugging</title><link href="https://pablocaro.es/en/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:/en/docker-http-https-echo</id><summary type="html">&lt;p&gt;&lt;code&gt;mendhak/docker-http-https-echo&lt;/code&gt; is a simple Docker container that facilitates debugging network configurations, proxies, and load balancers.&lt;/p&gt;</summary><content type="html">&lt;p&gt;In the complex world of distributed applications, debugging network issues, proxies, load balancers, or firewall rules can be an arduous task. We need a simple way to verify what a server is receiving. This is where a tool like &lt;code&gt;mendhak/docker-http-https-echo&lt;/code&gt; becomes indispensable.&lt;/p&gt;
&lt;h2 id="what-is-mendhakdocker-http-https-echo"&gt;What is &lt;code&gt;mendhak/docker-http-https-echo&lt;/code&gt;?&lt;a class="headerlink" href="#what-is-mendhakdocker-http-https-echo" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It is an extremely lightweight and simple Docker container that functions as an "echo" HTTP and HTTPS server. Its main function is to &lt;strong&gt;respond to any HTTP or HTTPS request with a detailed description of the request itself&lt;/strong&gt;. This includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Request headers.&lt;/li&gt;
&lt;li&gt;HTTP method (GET, POST, PUT, etc.).&lt;/li&gt;
&lt;li&gt;Request URL and parameters.&lt;/li&gt;
&lt;li&gt;Request body.&lt;/li&gt;
&lt;li&gt;Remote IP address of the client.&lt;/li&gt;
&lt;li&gt;TLS/SSL information (if HTTPS).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="why-is-it-useful"&gt;Why is it useful?&lt;a class="headerlink" href="#why-is-it-useful" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This type of echo server is invaluable for a variety of debugging scenarios:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Proxy and Load Balancer Debugging&lt;/strong&gt;: Verify if a reverse proxy or load balancer is rewriting headers, modifying URLs, or passing the request body as you expect.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Firewall and Security Rules&lt;/strong&gt;: Confirm that traffic is reaching the correct destination and is not being blocked or altered.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTTP/HTTPS Header Inspection&lt;/strong&gt;: Useful to see exactly what headers your client or a specific application sends.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integration Testing&lt;/strong&gt;: A predictable target for automated tests of how different services communicate via HTTP/HTTPS.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mixed Content Diagnosis&lt;/strong&gt;: You can use it to verify if HTTP resources are loading correctly in an HTTPS context.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="how-to-use-it"&gt;How to Use It&lt;a class="headerlink" href="#how-to-use-it" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Using it is straightforward via Docker:&lt;/p&gt;
&lt;h3 id="for-http-port-8080"&gt;For HTTP (port 8080)&lt;a class="headerlink" href="#for-http-port-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;Then, you can access &lt;code&gt;http://localhost:8080&lt;/code&gt; with your browser or &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="for-https-port-8443"&gt;For HTTPS (port 8443)&lt;a class="headerlink" href="#for-https-port-8443" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To use HTTPS, you must map the container's HTTPS port. The image already comes with a self-signed certificate for testing.&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;Access &lt;code&gt;https://localhost:8443&lt;/code&gt; (your browser may warn you about the self-signed certificate).&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&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; is one of those simple but incredibly useful tools that every developer and system operator should have on hand. It facilitates understanding network flows and speeds up debugging connectivity issues in any container-based environment.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Original repository&lt;/em&gt;: &lt;a href="https://github.com/mendhak/docker-http-https-echo"&gt;mendhak/docker-http-https-echo on GitHub&lt;/a&gt;&lt;/p&gt;</content><category term="Tools"></category><category term="docker"></category><category term="http"></category><category term="https"></category><category term="debugging"></category><category term="networking"></category><category term="tools"></category><category term="containers"></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="DevOps"></category><category term="kubernetes"></category><category term="postgresql"></category><category term="kubegres"></category><category term="operador"></category><category term="base-de-datos"></category><category term="devops"></category></entry><entry><title>Kubegres: Simplified PostgreSQL Management on Kubernetes</title><link href="https://pablocaro.es/en/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:/en/kubegres-postgresql-kubernetes</id><summary type="html">&lt;p&gt;Discover Kubegres, a Kubernetes operator that simplifies the deployment and management of highly available and scalable PostgreSQL clusters.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Deploying and managing relational databases on Kubernetes can be challenging. Although containers and orchestration offer flexibility, data persistence, high availability, and backups for a database like PostgreSQL require careful consideration. This is where &lt;strong&gt;Kubegres&lt;/strong&gt; comes into play.&lt;/p&gt;
&lt;h2 id="what-is-kubegres"&gt;What is Kubegres?&lt;a class="headerlink" href="#what-is-kubegres" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Kubegres is a &lt;strong&gt;Kubernetes operator&lt;/strong&gt; that allows you to deploy and manage PostgreSQL clusters on Kubernetes simply and efficiently. An operator is an application that extends Kubernetes functionalities, automating complex operational tasks that typically require manual intervention.&lt;/p&gt;
&lt;h2 id="why-use-kubegres"&gt;Why use Kubegres?&lt;a class="headerlink" href="#why-use-kubegres" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Kubegres abstracts much of the complexity of running PostgreSQL in an orchestrated environment, offering key features such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;High Availability (HA)&lt;/strong&gt;: Automatic configuration of PostgreSQL replicas to ensure service continuity in the event of failures.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Replication&lt;/strong&gt;: Support for streaming replication (Primary/Standby) for redundancy and read scalability.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Backups and Restore&lt;/strong&gt;: Facilitates the configuration of backup policies and data recovery.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scalability&lt;/strong&gt;: Allows scaling PostgreSQL read replicas to handle growing workloads.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Easy Updates&lt;/strong&gt;: Manages PostgreSQL version updates within the cluster.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monitoring&lt;/strong&gt;: Integrates mechanisms to monitor the cluster status.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="how-does-it-work"&gt;How does it work?&lt;a class="headerlink" href="#how-does-it-work" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Kubegres uses Kubernetes &lt;strong&gt;Custom Resources (CRs)&lt;/strong&gt;. This means you interact with your PostgreSQL cluster using standard Kubernetes objects (YAML). You define the desired state of your cluster (number of replicas, PostgreSQL version, backup policies) in a YAML file, and Kubegres ensures that state is maintained at all times.&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;# ... other settings for backups, etc.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="benefits-for-developers-and-operations"&gt;Benefits for Developers and Operations&lt;a class="headerlink" href="#benefits-for-developers-and-operations" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Simplification&lt;/strong&gt;: Drastically reduces the complexity of operating PostgreSQL on Kubernetes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automation&lt;/strong&gt;: Tasks like failover, replication, and backups are managed automatically.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consistency&lt;/strong&gt;: Ensures that the state of your database always reflects your desired configuration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DevOps Friendly&lt;/strong&gt;: Allows you to integrate database management into your CI/CD pipelines using the same tools and workflows as for the rest of your K8s applications.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Kubegres is an excellent choice for teams looking for a robust and automated solution for their PostgreSQL needs on Kubernetes.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Original article&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="DevOps"></category><category term="kubernetes"></category><category term="postgresql"></category><category term="kubegres"></category><category term="operator"></category><category term="database"></category><category term="devops"></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="Sistemas"></category><category term="linux"></category><category term="redes"></category><category term="tcp"></category><category term="netstat"></category><category term="ss"></category><category term="sistemas"></category></entry><entry><title>TCP Connection States in GNU/Linux: netstat and ss</title><link href="https://pablocaro.es/en/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:/en/estados-conexion-tcp-netstat-ss</id><summary type="html">&lt;p&gt;Understand the different TCP connection states reported by netstat and ss, from establishment to termination, for better network diagnosis.&lt;/p&gt;</summary><content type="html">&lt;p&gt;In the GNU/Linux world, tools like &lt;code&gt;netstat&lt;/code&gt; and &lt;code&gt;ss&lt;/code&gt; are indispensable for monitoring and diagnosing the state of network connections on our systems. To correctly interpret their output, it is fundamental to understand the different states a TCP connection goes through. This post explores those states, from establishment to termination of a connection.&lt;/p&gt;
&lt;h2 id="phases-of-a-tcp-connection"&gt;Phases of a TCP Connection&lt;a class="headerlink" href="#phases-of-a-tcp-connection" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A TCP connection is managed through three main phases:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Connection Establishment&lt;/strong&gt;: A multi-step handshake process that initiates the connection.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data Transfer&lt;/strong&gt;: Once established, data is sent between the two endpoints.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Connection Termination&lt;/strong&gt;: Closes the connection and releases associated resources.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;During these phases, the Internet socket (the local endpoint of the communication) experiences a series of state changes.&lt;/p&gt;
&lt;h2 id="tcp-socket-states-explained"&gt;TCP Socket States Explained&lt;a class="headerlink" href="#tcp-socket-states-explained" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here are the most common states you might encounter when listing connections with &lt;code&gt;netstat&lt;/code&gt; or &lt;code&gt;ss&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ESTABLISHED&lt;/strong&gt;: The TCP connection is established and ready to transfer data. This is the desired state for an active connection.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SYN_SENT&lt;/strong&gt;: The local socket has sent a SYN packet to the remote server and is waiting for a SYN-ACK response. Indicates that a connection attempt is being made.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SYN_RECV&lt;/strong&gt;: The server has received a SYN packet from a client and has responded with a SYN-ACK, now waiting for an ACK from the client. It is part of the three-way handshake.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FIN_WAIT1&lt;/strong&gt;: The local socket has closed its side of the connection and has sent a FIN packet, waiting for an ACK from the remote peer. The connection is closing actively.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FIN_WAIT2&lt;/strong&gt;: The local socket has received the ACK to its FIN, but is still waiting for a FIN packet from the remote peer to fully close the connection.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TIME_WAIT&lt;/strong&gt;: The socket has waited after closing the connection to ensure that all packets have been transmitted and received by both sides, and to allow duplicate packets to die out in the network. It is a necessary wait state before fully releasing resources.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CLOSE&lt;/strong&gt;: The socket is not in use and there is no connection.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CLOSE_WAIT&lt;/strong&gt;: The remote peer has closed its side of the connection (sent a FIN and been ACKed), and the local socket is waiting for the local application to close its own side.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LAST_ACK&lt;/strong&gt;: The local socket has sent its FIN to the remote peer, and is waiting for the final ACK from the remote peer. This happens after a &lt;code&gt;CLOSE_WAIT&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LISTEN&lt;/strong&gt;: The socket is waiting for incoming connections. Only seen with the &lt;code&gt;--listening (-l)&lt;/code&gt; or &lt;code&gt;--all (-a)&lt;/code&gt; options of &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;: Both sides of the connection have tried to close simultaneously, and the local socket is in the process of sending/receiving FIN and ACK.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UNKNOWN&lt;/strong&gt;: The socket state is unknown or cannot be determined.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="tools-netstat-and-ss"&gt;Tools: &lt;code&gt;netstat&lt;/code&gt; and &lt;code&gt;ss&lt;/code&gt;&lt;a class="headerlink" href="#tools-netstat-and-ss" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can see these states using commands like:&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;# Shows TCP connections, without name resolution (-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;# Modern and faster version of netstat&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;-t&lt;/code&gt; option filters for TCP connections and &lt;code&gt;-n&lt;/code&gt; avoids name resolution, which speeds up the output and shows IPs directly.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Understanding TCP connection states is vital for diagnosing network issues, identifying unresponsive or stuck processes, and understanding how applications behave in terms of connectivity. Knowing what each state means allows you to debug problems more effectively on your GNU/Linux system.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Original article&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="Systems"></category><category term="linux"></category><category term="networking"></category><category term="tcp"></category><category term="netstat"></category><category term="ss"></category><category term="systems"></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="python"></category><category term="logging"></category><category term="logs"></category><category term="depuracion"></category><category term="desarrollo"></category></entry><entry><title>Reduce Noise in Your Python Logs: A Smart Approach</title><link href="https://pablocaro.es/en/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:/en/reduce-ruido-logs-python</id><summary type="html">&lt;p&gt;Learn how to configure Python's &lt;code&gt;logging&lt;/code&gt; module to avoid message overload from third-party libraries, keeping your logs clean and useful.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Python's &lt;code&gt;logging&lt;/code&gt; module is a powerful tool, but it can quickly become a source of "noise" when third-party libraries flood your logs with messages that are not relevant to debugging your application. Keeping logs clean is crucial for efficiently identifying issues and understanding the actual behavior of your code.&lt;/p&gt;
&lt;h2 id="the-problem-with-basicconfig"&gt;The Problem with &lt;code&gt;basicConfig()&lt;/code&gt;&lt;a class="headerlink" href="#the-problem-with-basicconfig" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Many Python developers start using &lt;code&gt;logging&lt;/code&gt; with &lt;code&gt;logging.basicConfig()&lt;/code&gt;. While convenient, this function configures the "root logger" of your application. The problem is that &lt;strong&gt;all loggers in your application, including third-party libraries, are children of the root logger&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This means that if you configure the root logger to the &lt;code&gt;DEBUG&lt;/code&gt; level, you will start seeing debug messages from all the libraries you use, which can be overwhelming and hide the truly important information from your own code.&lt;/p&gt;
&lt;h2 id="the-recommended-solution-granular-configuration"&gt;The Recommended Solution: Granular Configuration&lt;a class="headerlink" href="#the-recommended-solution-granular-configuration" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To avoid this overload, the strategy involves taking more granular control of logging levels.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Name Your Loggers:&lt;/strong&gt;
    Follow the best practice of creating a specific logger for each module of your application using &lt;code&gt;logging.getLogger(__name__)&lt;/code&gt;. This creates a hierarchy of loggers that you can control individually.&lt;/p&gt;
&lt;p&gt;```python&lt;/p&gt;
&lt;h1 id="my_modulepy"&gt;my_module.py&lt;a class="headerlink" href="#my_modulepy" 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 my_function():
    logger.info("My function is running.")
    logger.debug("Debug message in my function.")
```&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configure Levels on Application-Specific Loggers:&lt;/strong&gt;
    Instead of modifying the root logger, set the logging level on the highest-level logger of your own application. This allows your modules to log messages at detailed levels (e.g., &lt;code&gt;DEBUG&lt;/code&gt;), while third-party libraries (which will remain children of the unconfigured root logger, or with a less verbose default configuration) will only show more critical messages (e.g., &lt;code&gt;WARNING&lt;/code&gt; or &lt;code&gt;ERROR&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;```python&lt;/p&gt;
&lt;h1 id="mainpy-or-your-main-entry-point"&gt;main.py (or your main entry point)&lt;a class="headerlink" href="#mainpy-or-your-main-entry-point" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;import logging
from my_package.my_module import my_function&lt;/p&gt;
&lt;h1 id="create-a-logger-for-your-main-application"&gt;Create a logger for your main application&lt;a class="headerlink" href="#create-a-logger-for-your-main-application" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;app_logger = logging.getLogger('my_package') # Or the name of your main package
app_logger.setLevel(logging.DEBUG)&lt;/p&gt;
&lt;h1 id="create-a-handler-for-output-eg-to-console"&gt;Create a handler for output (e.g., to console)&lt;a class="headerlink" href="#create-a-handler-for-output-eg-to-console" 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="add-the-handler-to-your-application-logger"&gt;Add the handler to your application logger&lt;a class="headerlink" href="#add-the-handler-to-your-application-logger" 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="now-the-root-logger-and-its-children-the-libraries-will-not-be-directly-affected"&gt;Now, the root logger (and its children, the libraries) will not be directly affected&lt;a class="headerlink" href="#now-the-root-logger-and-its-children-the-libraries-will-not-be-directly-affected" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;h1 id="you-can-control-the-level-of-specific-libraries-if-you-need-to"&gt;You can control the level of specific libraries if you need to&lt;a class="headerlink" href="#you-can-control-the-level-of-specific-libraries-if-you-need-to" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;logging.getLogger('requests').setLevel(logging.WARNING) # Example for a library&lt;/p&gt;
&lt;p&gt;my_function()
```&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="advanced-tip-first-party-dependency-management"&gt;Advanced Tip: First-Party Dependency Management&lt;a class="headerlink" href="#advanced-tip-first-party-dependency-management" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For projects with multiple internal modules or "first-party" dependencies (that you develop yourself), you can wrap &lt;code&gt;getLogger()&lt;/code&gt; to always prefix the logger name with your organization or project name (e.g., &lt;code&gt;ORG_NAME.my_module&lt;/code&gt;). This allows you to control the logging level of an entire set of internal loggers more easily.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Reducing noise in your logs not only makes them more readable but also more reliable as a debugging tool. By adopting a granular approach to Python's &lt;code&gt;logging&lt;/code&gt; configuration, you ensure that critical information from your application is not lost in a sea of messages from third-party libraries.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Original article&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="Programming"></category><category term="python"></category><category term="logging"></category><category term="logs"></category><category term="debugging"></category><category term="development"></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="Sistemas"></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>Kitty as a Quick Access Terminal: The Convenience of a Dropdown</title><link href="https://pablocaro.es/en/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:/en/kitty-acceso-rapido</id><summary type="html">&lt;p&gt;Configure Kitty for instant and efficient access, combining the power of a modern terminal with the convenience of a dropdown.&lt;/p&gt;</summary><content type="html">&lt;p&gt;One of the most appreciated features of terminals like Yakuake or Guake is their ability to appear and disappear instantly with a single keystroke, offering quick access without interrupting the workflow. &lt;strong&gt;Kitty&lt;/strong&gt;, the GPU-accelerated terminal, can also replicate and improve this experience, combining its power with the convenience of a dropdown terminal.&lt;/p&gt;
&lt;h2 id="the-dropdown-terminal-concept"&gt;The Dropdown Terminal Concept&lt;a class="headerlink" href="#the-dropdown-terminal-concept" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The idea is simple: a terminal that resides in the background and shows or hides on demand. This is especially useful for quick commands, monitoring, or any task that requires brief interaction with the command line.&lt;/p&gt;
&lt;h2 id="kitty-for-quick-access"&gt;Kitty for Quick Access&lt;a class="headerlink" href="#kitty-for-quick-access" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Kitty does not have a native "dropdown" function like Yakuake, but its flexibility and scripting capabilities allow it to be configured for this purpose. The key lies in:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Dedicated Kitty instances:&lt;/strong&gt; You can launch a Kitty instance in a "daemon" or background mode, ready to be activated.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Remote control:&lt;/strong&gt; Kitty offers a remote control protocol (&lt;code&gt;kitty @&lt;/code&gt;) that allows interacting with already open instances.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Window Manager:&lt;/strong&gt; Using the rules of your window manager (KDE, Gnome, i3, Sway, etc.) to assign a hotkey that shows or hides the Kitty window.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="example-configuration"&gt;Example Configuration&lt;a class="headerlink" href="#example-configuration" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Here is a conceptual approach to achieve a quick access terminal with Kitty and a window manager (e.g., with &lt;code&gt;xdotool&lt;/code&gt; for Xorg environments, or similar functions in Wayland):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Launch Kitty in the background:&lt;/strong&gt;
    You can have a Kitty instance that starts automatically with your session and is configured to be "floating" and with a specific size.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Toggle Script:&lt;/strong&gt;
    Create a small script that checks if the "always visible" Kitty window is active and, if it is, hides it; if not, shows it.&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" # A custom class for the Kitty window
KITTY_ID=$(xdotool search --class "$KITTY_CLASS" | head -n 1)&lt;/p&gt;
&lt;p&gt;if [ -z "$KITTY_ID" ]; then
    # If it doesn't exist, launch it
    kitty --class "$KITTY_CLASS" &amp;amp;
else
    # If it exists, toggle visibility
    if xdotool getwindowfocus getwindowpid | grep -q "$(xdotool getwindowpid "$KITTY_ID")"; then
        xdotool windowminimize "$KITTY_ID"
    else
        xdotool windowmap "$KITTY_ID" # Show the window
        xdotool windowraise "$KITTY_ID" # Make sure it's on top
        xdotool windowfocus "$KITTY_ID" # Give it focus
    fi
fi
```&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Keyboard shortcut in your window manager:&lt;/strong&gt;
    Assign a global key (e.g., &lt;code&gt;F12&lt;/code&gt;) to run this script.&lt;/p&gt;
&lt;p&gt;```&lt;/p&gt;
&lt;h1 id="example-in-configi3config-or-similar"&gt;Example in ~/.config/i3/config or similar&lt;a class="headerlink" href="#example-in-configi3config-or-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;And in your &lt;code&gt;kitty.conf&lt;/code&gt;, you can configure the Kitty instance to have the specific class and a suitable position/size:&lt;/p&gt;
&lt;p&gt;```&lt;/p&gt;
&lt;h1 id="kittyconf-configuration-for-the-dropdown-instance"&gt;kitty.conf (configuration for the dropdown instance)&lt;a class="headerlink" href="#kittyconf-configuration-for-the-dropdown-instance" 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="other-appearance-settings"&gt;... other appearance settings ...&lt;a class="headerlink" href="#other-appearance-settings" 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;This method gives you the speed and convenience of a dropdown terminal, but with all the power and customization that Kitty offers. It's the best of both worlds!&lt;/p&gt;</content><category term="Systems"></category><category term="kitty"></category><category term="terminal"></category><category term="quick-access"></category><category term="productivity"></category><category term="linux"></category><category term="shortcuts"></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="Sistemas"></category><category term="kitty"></category><category term="terminal"></category><category term="atajos"></category><category term="teclado"></category><category term="productividad"></category></entry><entry><title>Essential Keyboard Shortcuts in Kitty: Mastering Your Terminal</title><link href="https://pablocaro.es/en/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:/en/atajos-de-teclado-kitty</id><summary type="html">&lt;p&gt;A quick guide to the most useful Kitty keyboard shortcuts to optimize your command-line workflow.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;Kitty&lt;/strong&gt; is a terminal that shines for its performance and high customizability, but where it truly excels is in its keyboard-driven interface. Mastering its shortcuts will allow you to navigate, manage, and manipulate your terminal sessions with amazing efficiency.&lt;/p&gt;
&lt;h2 id="discover-your-shortcuts-show_shortcuts"&gt;Discover Your Shortcuts: &lt;code&gt;show_shortcuts&lt;/code&gt;&lt;a class="headerlink" href="#discover-your-shortcuts-show_shortcuts" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The fastest way to know all the keyboard shortcuts configured in your Kitty instance is by using its own functionality:&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;This will open an interactive window where you can see all shortcuts, both the default ones and those customized in your &lt;code&gt;kitty.conf&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="essential-shortcuts-for-every-day"&gt;Essential Shortcuts for Every Day&lt;a class="headerlink" href="#essential-shortcuts-for-every-day" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here are some of the most useful shortcuts that will help you optimize your workflow the most:&lt;/p&gt;
&lt;h3 id="window-and-split-management-panes"&gt;Window and Split Management (Panes)&lt;a class="headerlink" href="#window-and-split-management-panes" 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;: Opens a new Kitty window.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+w&lt;/code&gt;: Closes the current Kitty window.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+o&lt;/code&gt;: Splits the current window vertically (new pane to the right).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+e&lt;/code&gt;: Splits the current window horizontally (new pane below).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+f&lt;/code&gt;: Toggles between split panes (focus).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+[&lt;/code&gt; / &lt;code&gt;ctrl+shift+]&lt;/code&gt;: Navigates between panes (left/right).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+alt+[&lt;/code&gt; / &lt;code&gt;ctrl+shift+alt+]&lt;/code&gt;: Moves the active pane (left/right).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="tab-management"&gt;Tab Management&lt;a class="headerlink" href="#tab-management" 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;: Opens a new tab.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+q&lt;/code&gt;: Closes the current tab.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+.&lt;/code&gt;: Moves to the next tab.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+,&lt;/code&gt;: Moves to the previous tab.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+alt+.&lt;/code&gt;: Moves the current tab to the right.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+alt+,&lt;/code&gt;: Moves the current tab to the left.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="copy-and-paste"&gt;Copy and Paste&lt;a class="headerlink" href="#copy-and-paste" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Kitty has its own copy/paste implementation, which can be more reliable than the system one when working in remote environments or with multiplexers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+c&lt;/code&gt;: Copies the selected text to the system clipboard.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+v&lt;/code&gt;: Pastes text from the system clipboard.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="font-and-appearance-control"&gt;Font and Appearance Control&lt;a class="headerlink" href="#font-and-appearance-control" 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; (or &lt;code&gt;+&lt;/code&gt;): Increases the font size.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+-&lt;/code&gt;: Decreases the font size.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+backspace&lt;/code&gt;: Resets the font size to the default value.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctrl+shift+k&lt;/code&gt;: Enters "scrollback" mode (to search or select historical text).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="customizing-your-shortcuts-in-kittyconf"&gt;Customizing Your Shortcuts in &lt;code&gt;kitty.conf&lt;/code&gt;&lt;a class="headerlink" href="#customizing-your-shortcuts-in-kittyconf" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can add or modify keyboard shortcuts in your configuration file &lt;code&gt;~/.config/kitty/kitty.conf&lt;/code&gt; using the &lt;code&gt;map&lt;/code&gt; directive. For example, to map &lt;code&gt;F1&lt;/code&gt; for a custom script:&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;Mastering these shortcuts will transform your interaction with the terminal, making it more fluid and intuitive. Experiment and customize Kitty to perfectly adapt to your work style!&lt;/p&gt;</content><category term="Systems"></category><category term="kitty"></category><category term="terminal"></category><category term="shortcuts"></category><category term="keyboard"></category><category term="productivity"></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="Sistemas"></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>From Yakuake to Kitty: My Transition to a Modern Terminal</title><link href="https://pablocaro.es/en/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:/en/de-yakuake-a-kitty</id><summary type="html">&lt;p&gt;Exploring the reasons and benefits of switching from Yakuake to Kitty for a more powerful and customizable terminal experience.&lt;/p&gt;</summary><content type="html">&lt;p&gt;For years, &lt;strong&gt;Yakuake&lt;/strong&gt; has been my go-to terminal in KDE desktop environments. Its quick access convenience, dropping down and hiding with a simple keystroke, is undeniable. However, in my constant search for optimization and performance, I decided to explore alternatives, and &lt;strong&gt;Kitty&lt;/strong&gt; emerged as a standout option.&lt;/p&gt;
&lt;h2 id="why-the-switch"&gt;Why the Switch?&lt;a class="headerlink" href="#why-the-switch" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Although Yakuake is excellent in its niche, Kitty offers a series of advantages that align better with the demands of a modern and demanding workflow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;GPU Accelerated Performance&lt;/strong&gt;: Kitty is designed to leverage the graphics card, resulting in extremely fast and fluid text rendering, especially noticeable in terminals with a lot of scrolling or intense output.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integrated Multiplexing&lt;/strong&gt;: Unlike Yakuake, which relies on traditional tab/window sessions, Kitty incorporates its own multiplexing system (similar to &lt;code&gt;tmux&lt;/code&gt; or &lt;code&gt;screen&lt;/code&gt;) that allows splitting windows, creating tabs, and organizing panes natively and efficiently.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extensive Configurability&lt;/strong&gt;: &lt;code&gt;kitty.conf&lt;/code&gt; allows deep customization, from keyboard shortcuts to appearance, themes, and behavior.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Modern Support&lt;/strong&gt;: Excellent support for Wayland (the future of Linux on the desktop), font ligatures (like Fira Code), and an ecosystem of "kittens" (scripts and extended functionalities).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-platform&lt;/strong&gt;: Although my main use is on Linux, the ability to have a consistent terminal experience across different operating systems is a plus.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="adapting-to-kitty"&gt;Adapting to Kitty&lt;a class="headerlink" href="#adapting-to-kitty" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The transition required adjusting some habits, mainly the way sessions and quick access are managed. However, the advantages far outweigh the initial learning curve. The speed, fluidity, and power of Kitty's built-in features make it an indispensable tool for my daily life.&lt;/p&gt;
&lt;p&gt;Configuring Kitty is a simple process through its &lt;code&gt;kitty.conf&lt;/code&gt; file, where all aspects of the terminal are defined. We will explore more about its configuration and keyboard shortcuts in future posts.&lt;/p&gt;
&lt;p&gt;If you are looking to take your terminal experience to the next level, migrating to Kitty is a step I highly recommend.&lt;/p&gt;</content><category term="Systems"></category><category term="kitty"></category><category term="yakuake"></category><category term="terminal"></category><category term="linux"></category><category term="productivity"></category><category term="tools"></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="python"></category><category term="uv"></category><category term="poethepoet"></category><category term="automatizacion"></category><category term="pyproject.toml"></category></entry><entry><title>Poe the Poet: Automating Tasks in Python Projects with uv</title><link href="https://pablocaro.es/en/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:/en/poe-the-poet-automatizando-tareas-en-proyectos-python-con-uv</id><summary type="html">&lt;p&gt;Discover how Poe the Poet allows you to define and execute custom commands directly from your pyproject.toml, optimizing your workflow in uv environments.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Managing Python projects often involves executing various custom tasks, from building documentation to running tests or helper scripts. While &lt;code&gt;uv&lt;/code&gt; has greatly simplified dependency management, how to integrate and execute these commands directly from &lt;code&gt;pyproject.toml&lt;/code&gt; has been a point of debate. This is where &lt;strong&gt;Poe the Poet&lt;/strong&gt; emerges as an elegant and effective solution.&lt;/p&gt;
&lt;h2 id="what-is-poe-the-poet"&gt;What is Poe the Poet?&lt;a class="headerlink" href="#what-is-poe-the-poet" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Poe the Poet is a tool that allows you to define custom "scripts" or "tasks" directly in your &lt;code&gt;pyproject.toml&lt;/code&gt; file. These tasks can be any command you want to run in the context of your Python environment, making workflow automation much cleaner and centralized. It solves the problem of having to resort to &lt;code&gt;Makefile&lt;/code&gt; or other external scripts for project-specific tasks.&lt;/p&gt;
&lt;h2 id="how-to-use-poe-the-poet-with-uv"&gt;How to Use Poe the Poet with uv&lt;a class="headerlink" href="#how-to-use-poe-the-poet-with-uv" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Integrating Poe the Poet into a project using &lt;code&gt;uv&lt;/code&gt; is straightforward:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Add &lt;code&gt;poethepoet&lt;/code&gt; to your dev dependencies:&lt;/strong&gt;
    First, ensure that &lt;code&gt;poethepoet&lt;/code&gt; is included in your development dependencies group (e.g., &lt;code&gt;dev&lt;/code&gt;) in &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;# ... other project configurations ...&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;# Add Poe the Poet here&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;Define custom tasks:&lt;/strong&gt;
    Then, you can add a &lt;code&gt;[tool.poe.tasks]&lt;/code&gt; section in your &lt;code&gt;pyproject.toml&lt;/code&gt; to define your custom commands.&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="running-tasks-with-uv-run"&gt;Running Tasks with &lt;code&gt;uv run&lt;/code&gt;&lt;a class="headerlink" href="#running-tasks-with-uv-run" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Once Poe the Poet is configured and your tasks are defined, you can easily execute them using &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;# To build the documentation&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;# For the live preview server&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;# To run the &amp;#39;cog&amp;#39; command&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="benefits"&gt;Benefits&lt;a class="headerlink" href="#benefits" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Centralization:&lt;/strong&gt; All your custom tasks live in &lt;code&gt;pyproject.toml&lt;/code&gt;, alongside your dependencies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integration with &lt;code&gt;uv&lt;/code&gt;:&lt;/strong&gt; Tasks run in the virtual environment managed by &lt;code&gt;uv&lt;/code&gt;, ensuring all necessary tools are available.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simplification:&lt;/strong&gt; Avoids the need for &lt;code&gt;Makefile&lt;/code&gt; or complex bash scripts for common tasks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consistency:&lt;/strong&gt; Promotes a more consistent and shareable workflow among collaborators.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Poe the Poet offers a robust and elegant way to extend the capabilities of &lt;code&gt;uv&lt;/code&gt; for task automation in Python projects, enhancing the development experience.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Original article&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="Programming"></category><category term="python"></category><category term="uv"></category><category term="poethepoet"></category><category term="automation"></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>How I Use Git Worktrees for Concurrent Work</title><link href="https://pablocaro.es/en/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:/en/como-uso-git-worktrees</id><summary type="html">&lt;p class="first last"&gt;A practical approach to using git worktrees and maximizing productivity with multiple simultaneous work contexts&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Aleksey Kladov shares his unique approach to using git worktrees, which goes beyond simply switching branches and becomes a complete methodology for concurrent work.&lt;/p&gt;
&lt;img alt="Worktree structure" class="align-center" src="https://matklad.github.io/images/worktree-structure.png" /&gt;
&lt;div class="section" id="the-core-philosophy"&gt;
&lt;h2&gt;The Core Philosophy&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;Git is not a version control system, Git is a toolbox for building a VCS&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This perspective completely changes how we think about git. Instead of limiting ourselves to basic functionalities, we can create our own optimized workflow.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-5-specialized-worktrees"&gt;
&lt;h2&gt;The 5 Specialized Worktrees&lt;/h2&gt;
&lt;p&gt;The author maintains 5 worktrees, each with a specific purpose:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;strong&gt;main&lt;/strong&gt; - Read-only snapshot
- Clean reflection of remote main
- Reference for comparisons
- Never modified locally&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;work&lt;/strong&gt; - Main workspace
- Day-to-day active development
- Frequent commits with minimal messages
- Detached HEAD for maximum flexibility&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;review&lt;/strong&gt; - Dedicated to code reviews
- Clean context to analyze PRs
- Without interfering with ongoing work&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;fuzz&lt;/strong&gt; - Long-running tests
- Fuzzing and tests that last hours
- Runs in parallel without blocking development&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;scratch&lt;/strong&gt; - Unrelated quick tasks
- Experiments
- Urgent fixes
- Ad-hoc investigations&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;# Typical directory structure&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;# main worktree&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;# active development&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;# reviews&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;# experiments&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="advantages-of-the-approach"&gt;
&lt;h2&gt;Advantages of the Approach&lt;/h2&gt;
&lt;dl class="docutils"&gt;
&lt;dt&gt;&lt;strong&gt;Truly Concurrent Work&lt;/strong&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="first last simple"&gt;
&lt;li&gt;Multiple coding activities simultaneously&lt;/li&gt;
&lt;li&gt;No need for stash or complex branch switching&lt;/li&gt;
&lt;li&gt;Clean separation of contexts&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;dt&gt;&lt;strong&gt;Frictionless Workflow&lt;/strong&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="first last simple"&gt;
&lt;li&gt;Instant switching between tasks&lt;/li&gt;
&lt;li&gt;Frequent commits without pressure for perfect messages&lt;/li&gt;
&lt;li&gt;Free experimentation on detached HEAD&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;dt&gt;&lt;strong&gt;Real Parallelization&lt;/strong&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="first last simple"&gt;
&lt;li&gt;Fuzzing running while you develop&lt;/li&gt;
&lt;li&gt;Reviews without interrupting main work&lt;/li&gt;
&lt;li&gt;Background tests without blocks&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/div&gt;
&lt;div class="section" id="advanced-techniques"&gt;
&lt;h2&gt;Advanced Techniques&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Strategic Detached HEAD&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;# In the &amp;#39;work&amp;#39; worktree, work without a branch&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;# Frequent commit, reorganize later&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;Custom Scripts for Common Operations&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 to quickly create/switch worktrees&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;Integration Flow&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;# From the &amp;#39;work&amp;#39; worktree&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;# review 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;# undo temporary commits&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;# select changes&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;# clean final commit&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="key-lessons"&gt;
&lt;h2&gt;Key Lessons&lt;/h2&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;strong&gt;Git is flexible&lt;/strong&gt;: There is no &amp;quot;one right way&amp;quot; to use git&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automate the repetitive&lt;/strong&gt;: Custom scripts for common operations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Separate contexts&lt;/strong&gt;: Each type of work deserves its own space&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cheap commits&lt;/strong&gt;: Commit frequently, organize later&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This approach transforms git from a versioning tool into a complete workflow management platform, maximizing productivity through truly concurrent work.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Original article&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="Programming"></category><category term="git"></category><category term="worktrees"></category><category term="workflow"></category><category term="productivity"></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>Hybrid Search with SQLite: Vector + Full-Text</title><link href="https://pablocaro.es/en/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:/en/busqueda-hibrida-sqlite-vector-texto</id><summary type="html">&lt;p class="first last"&gt;Combining vector search and full-text search in SQLite using Reciprocal Rank Fusion for better results&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Simon Willison presents a fascinating approach to combining vector search and traditional full-text search in SQLite, using a technique called &lt;strong&gt;Reciprocal Rank Fusion (RRF)&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="section" id="the-core-problem"&gt;
&lt;h2&gt;The Core Problem&lt;/h2&gt;
&lt;p&gt;When we have vector searches (based on semantic similarity) and full-text searches (FTS), each returns scores on completely different scales:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;FTS returns relevance scores&lt;/li&gt;
&lt;li&gt;Vector search returns similarity distances&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;How to combine these results effectively?&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-solution-reciprocal-rank-fusion"&gt;
&lt;h2&gt;The Solution: Reciprocal Rank Fusion&lt;/h2&gt;
&lt;p&gt;The RRF technique avoids comparing incompatible scores and instead relies on the &lt;strong&gt;ranking&lt;/strong&gt; of each result within its respective search method.&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;-- FTS subquery with 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;-- Vector subquery with 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="advantages-of-the-hybrid-approach"&gt;
&lt;h2&gt;Advantages of the Hybrid Approach&lt;/h2&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;strong&gt;Flexibility&lt;/strong&gt;: Allows adjusting weights between FTS and vector search&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Robustness&lt;/strong&gt;: Results can appear in one or both methods&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scalability&lt;/strong&gt;: Does not require complex score normalization&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simplicity&lt;/strong&gt;: A single SQL query handles all the logic&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="sqlite-as-a-unified-platform"&gt;
&lt;h2&gt;SQLite as a Unified Platform&lt;/h2&gt;
&lt;p&gt;With extensions like &lt;a class="reference external" href="https://github.com/asg017/sqlite-vec"&gt;sqlite-vec&lt;/a&gt;, SQLite becomes a powerful platform for:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Full-text search (Native FTS5)&lt;/li&gt;
&lt;li&gt;Vector embedding search&lt;/li&gt;
&lt;li&gt;Hybrid combination of both techniques&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This approach democratizes advanced search techniques, making them accessible without complex infrastructure.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Original article&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="Programming"></category><category term="sqlite"></category><category term="search"></category><category term="vectors"></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>Geospatial Data Conflation with DuckDB and Embeddings</title><link href="https://pablocaro.es/en/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:/en/conflacion-datos-geoespaciales-duckdb</id><summary type="html">&lt;p class="first last"&gt;Advanced techniques to integrate geospatial data sources using DuckDB, H3, Ollama, and embedding models&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Drew Breunig presents a fascinating case study of &lt;strong&gt;geospatial data conflation&lt;/strong&gt;: the process of identifying and merging similar records from different sources.&lt;/p&gt;
&lt;div class="section" id="the-challenge-integrating-restaurant-data"&gt;
&lt;h2&gt;The Challenge: Integrating Restaurant Data&lt;/h2&gt;
&lt;p&gt;The goal was to connect two data sources:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;Restaurant inspections&lt;/strong&gt; from Alameda County&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Place data&lt;/strong&gt; from Overture Maps Foundation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Two datasets with similar information but structured differently.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="modern-stack-tools"&gt;
&lt;h2&gt;Modern Stack Tools&lt;/h2&gt;
&lt;dl class="docutils"&gt;
&lt;dt&gt;&lt;strong&gt;DuckDB as the main engine&lt;/strong&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="first last simple"&gt;
&lt;li&gt;&lt;strong&gt;Staging and querying&lt;/strong&gt; large volumes of data&lt;/li&gt;
&lt;li&gt;Native support for geospatial formats&lt;/li&gt;
&lt;li&gt;Exceptional performance for exploratory analysis&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;&lt;strong&gt;H3 for spatial grouping&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;-- Group nearby places using H3 hexagons&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 for local embeddings&lt;/strong&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="first last simple"&gt;
&lt;li&gt;ML framework running locally&lt;/li&gt;
&lt;li&gt;Generation of contextual embeddings&lt;/li&gt;
&lt;li&gt;No dependence on external APIs&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/div&gt;
&lt;div class="section" id="three-matching-approaches"&gt;
&lt;h2&gt;Three Matching Approaches&lt;/h2&gt;
&lt;dl class="docutils"&gt;
&lt;dt&gt;&lt;strong&gt;1. Exact Name Matching&lt;/strong&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="first last simple"&gt;
&lt;li&gt;&lt;strong&gt;Result&lt;/strong&gt;: ~31% matches&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Limitations&lt;/strong&gt;: Chain names, unit numbers in addresses&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;&lt;strong&gt;2. String Similarity (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;-- Comparison combining name and address&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;Result&lt;/strong&gt;: ~68% matches&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Disadvantage&lt;/strong&gt;: Complex SQL with many conditional rules&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. Embedding-Based Matching&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;# Generate contextual description&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;Result&lt;/strong&gt;: ~71% matches&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Advantages&lt;/strong&gt;: Simpler pipeline, greater flexibility&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Disadvantage&lt;/strong&gt;: Longer processing time&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="key-insights"&gt;
&lt;h2&gt;Key Insights&lt;/h2&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;strong&gt;There is no silver bullet&lt;/strong&gt; - each method has specific strengths&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Local tools are powerful&lt;/strong&gt; - DuckDB + Ollama allow sophisticated analysis without cloud&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Embeddings are promising&lt;/strong&gt; - especially for cases with complex context&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Conflation requires iteration&lt;/strong&gt; - combining multiple techniques improves results&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="the-future-of-data-integration"&gt;
&lt;h2&gt;The Future of Data Integration&lt;/h2&gt;
&lt;p&gt;This work shows how modern tools democratize techniques that previously required complex infrastructure. The combination of:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;Analytical databases&lt;/strong&gt; (DuckDB)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Spatial indices&lt;/strong&gt; (H3)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Local ML&lt;/strong&gt; (Ollama)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;...opens new possibilities for intelligent data integration.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Original article&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="Programming"></category><category term="duckdb"></category><category term="geospatial"></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="python"></category><category term="http"></category><category term="httpx"></category><category term="async"></category><category term="requests"></category></entry><entry><title>HTTPX: Modern HTTP Client for Python</title><link href="https://pablocaro.es/en/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:/en/httpx-cliente-http-moderno-python</id><summary type="html">&lt;p class="first last"&gt;HTTPX emerges as the natural successor to requests, offering async support, HTTP/2, and a modern API for contemporary Python applications.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;HTTPX presents itself as the natural evolution of the popular &lt;tt class="docutils literal"&gt;requests&lt;/tt&gt; library, maintaining its simplicity but adding modern capabilities that current applications need.&lt;/p&gt;
&lt;div class="section" id="why-httpx"&gt;
&lt;h2&gt;Why HTTPX?&lt;/h2&gt;
&lt;p&gt;While &lt;tt class="docutils literal"&gt;requests&lt;/tt&gt; remains excellent for synchronous use, HTTPX addresses limitations that have arisen over time:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;No native async support&lt;/strong&gt; in requests&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTTP/2 not available&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Limited type hints&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Less precise timeouts&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="installation-and-configuration"&gt;
&lt;h2&gt;Installation and Configuration&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Basic installation&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;# With HTTP/2 support&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;# With advanced compression&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="familiar-api-modern-capabilities"&gt;
&lt;h2&gt;Familiar API, Modern Capabilities&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Synchronous usage&lt;/strong&gt; - Compatible with 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;# Basic GET&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 with JSON data&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;# Custom headers&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="the-power-of-async-await"&gt;
&lt;h2&gt;The Power of Async/Await&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Asynchronous client&lt;/strong&gt; - The real advantage:&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="advanced-features"&gt;
&lt;h2&gt;Advanced Features&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Strict Timeouts&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;# Granular timeout&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;Data Streaming&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;# Streaming download&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;# Process 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;Testing with WSGI/ASGI Applications&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;# Test FastAPI directly&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;# Direct test without server&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-and-persistent-connections"&gt;
&lt;h2&gt;HTTP/2 and Persistent Connections&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Client with HTTP/2 and connection pooling&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;# Multiple requests reuse connections&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="comparison-requests-vs-httpx"&gt;
&lt;h2&gt;Comparison: 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;Feature&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;Familiar API&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Async support&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;Partial&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Precise timeouts&lt;/td&gt;
&lt;td&gt;Basic&lt;/td&gt;
&lt;td&gt;Advanced&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;WSGI/ASGI Testing&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;Basic&lt;/td&gt;
&lt;td&gt;Advanced&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class="section" id="ideal-use-cases"&gt;
&lt;h2&gt;Ideal Use Cases&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;HTTPX is perfect for&lt;/strong&gt;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;Concurrent APIs&lt;/strong&gt; with async/await&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Microservices&lt;/strong&gt; that need HTTP/2&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Application testing&lt;/strong&gt; FastAPI/Django&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Large-scale scraping&lt;/strong&gt; with streaming&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Modern applications&lt;/strong&gt; with type safety&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Keep requests for&lt;/strong&gt;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;Simple scripts&lt;/strong&gt; without concurrency&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Legacy projects&lt;/strong&gt; already established&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Code with dependencies&lt;/strong&gt; that require requests&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="the-future-of-http-in-python"&gt;
&lt;h2&gt;The Future of HTTP in Python&lt;/h2&gt;
&lt;p&gt;HTTPX represents the way forward for HTTP applications in Python, offering:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;Modern performance&lt;/strong&gt; with async and HTTP/2&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Developer experience&lt;/strong&gt; improved with types&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integrated ecosystem&lt;/strong&gt; with current frameworks&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compatibility&lt;/strong&gt; that eases migration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It's time to consider HTTPX as the default HTTP client for new Python projects.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Official documentation&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="Programming"></category><category term="python"></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>Server-Sent Events: Simplified Real-Time Communication</title><link href="https://pablocaro.es/en/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:/en/server-sent-events-comunicacion-tiempo-real</id><summary type="html">&lt;p&gt;Server-Sent Events offer a simple and efficient alternative to WebSockets for unidirectional server-to-client communication in web applications&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;Server-Sent Events (SSE)&lt;/strong&gt; represent an elegant and simple solution for implementing real-time communication in web applications when we only need the server to send data to the client.&lt;/p&gt;
&lt;h2 id="what-are-server-sent-events"&gt;What are Server-Sent Events?&lt;a class="headerlink" href="#what-are-server-sent-events" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;SSE is a web standard that allows a server to automatically send data to a web page using a persistent HTTP connection. Unlike WebSockets, communication is &lt;strong&gt;unidirectional&lt;/strong&gt;: only the server can send messages to the client.&lt;/p&gt;
&lt;h2 id="basic-implementation"&gt;Basic Implementation&lt;a class="headerlink" href="#basic-implementation" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="client-javascript"&gt;Client (JavaScript)&lt;a class="headerlink" href="#client-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;// Create SSE connection&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;// Listen for generic messages&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;Message received:&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;// Listen for custom events&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;// Handle errors&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;SSE Error:&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;// Close connection&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="server-pythonflask"&gt;Server (Python/Flask)&lt;a class="headerlink" href="#server-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;Generator producing SSE events&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;# Generic event&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;# Send every 10 seconds&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;SSE Endpoint&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;Send specific notification&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;New Update&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;System updated successfully&amp;#39;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;# Event with custom name&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="event-stream-format"&gt;Event Stream Format&lt;a class="headerlink" href="#event-stream-format" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;SSE events follow a specific format:&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;: Event name (optional)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;data&lt;/strong&gt;: Message content&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;id&lt;/strong&gt;: Unique identifier for reconnection&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;retry&lt;/strong&gt;: Retry time in milliseconds&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="ideal-use-cases"&gt;Ideal Use Cases&lt;a class="headerlink" href="#ideal-use-cases" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="1-real-time-feeds"&gt;1. Real-Time Feeds&lt;a class="headerlink" href="#1-real-time-feeds" 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-push-notifications"&gt;2. Push Notifications&lt;a class="headerlink" href="#2-push-notifications" 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;// Show browser notification&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-real-time-dashboard"&gt;3. Real-Time Dashboard&lt;a class="headerlink" href="#3-real-time-dashboard" 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="advantages-of-sse"&gt;Advantages of SSE&lt;a class="headerlink" href="#advantages-of-sse" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Simplicity&lt;/strong&gt;: Simpler API than WebSockets&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automatic Reconnection&lt;/strong&gt;: Browser retries automatically&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Efficiency&lt;/strong&gt;: Lower overhead than polling&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compatibility&lt;/strong&gt;: Works over standard HTTP/HTTPS&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Firewall-friendly&lt;/strong&gt;: Does not require special ports&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="important-limitations"&gt;Important Limitations&lt;a class="headerlink" href="#important-limitations" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Unidirectional&lt;/strong&gt;: Only server → client&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Connection Limits&lt;/strong&gt;: Browsers limit simultaneous connections&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data Format&lt;/strong&gt;: Text only (though JSON works fine)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No Native Compression&lt;/strong&gt;: Unlike WebSockets&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="sse-vs-websockets-when-to-use-which"&gt;SSE vs WebSockets: When to Use Which?&lt;a class="headerlink" href="#sse-vs-websockets-when-to-use-which" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="use-sse-when"&gt;Use SSE when:&lt;a class="headerlink" href="#use-sse-when" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;You only need server → client&lt;/li&gt;
&lt;li&gt;Simplicity is a priority&lt;/li&gt;
&lt;li&gt;Periodic updates (feeds, notifications)&lt;/li&gt;
&lt;li&gt;Compatibility with proxies/firewalls is important&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="use-websockets-when"&gt;Use WebSockets when:&lt;a class="headerlink" href="#use-websockets-when" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;You need bidirectional communication&lt;/li&gt;
&lt;li&gt;Ultra-low latency is critical&lt;/li&gt;
&lt;li&gt;Intensive data exchange&lt;/li&gt;
&lt;li&gt;Full control over the protocol&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="error-handling-and-reconnection"&gt;Error Handling and Reconnection&lt;a class="headerlink" href="#error-handling-and-reconnection" 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;SSE connection established&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;SSE Error:&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;// Manual reconnection with exponential backoff&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;Attempting to reconnect...&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;Conclusion&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Server-Sent Events offers a pragmatic solution for real-time communication when WebSockets is overkill. Its simplicity, automatic reconnection, and compatibility make it an excellent choice for feeds, notifications, and real-time dashboards.&lt;/p&gt;
&lt;p&gt;For applications requiring server-to-client updates without the complexity of WebSockets, SSE is the perfect tool.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Official documentation&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="Programming"></category><category term="javascript"></category><category term="sse"></category><category term="real-time"></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="Progamación"></category><category term="python"></category></entry><entry><title>About Versions</title><link href="https://pablocaro.es/en/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:/en/una-de-versiones</id><summary type="html"></summary><content type="html">&lt;p&gt;Do you want to know the correct way to name versions? That's what &lt;a class="reference external" href="https://www.python.org/dev/peps/pep-0440/"&gt;PEP 440&lt;/a&gt; is for: Version Identification and Dependency Specification.&lt;/p&gt;
&lt;p&gt;Perhaps the most noteworthy thing is the use of those suffixes .dev0, etc. for eggs in development.&lt;/p&gt;
</content><category term="Programming"></category><category term="python"></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="Trucos"></category><category term="linux"></category></entry><entry><title>MobaXterm. Much more than an ssh client</title><link href="https://pablocaro.es/en/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:/en/mobaxterm-mucho-más-que-un-cliente-ssh</id><summary type="html"></summary><content type="html">&lt;p&gt;As you know, I am not a Windows person. A Linux user for years, after going through multiple flavors of Debian, now my thing is &lt;a class="reference external" href="https://es.opensuse.org/Portal:Tumbleweed"&gt;OpenSuse Tumbleweed&lt;/a&gt;. But I also have a Windows 7 work computer that I use occasionally.
For that, &lt;a class="reference external" href="http://mobaxterm.mobatek.net/"&gt;MobaXterm&lt;/a&gt; comes in very handy.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://mobaxterm.mobatek.net/"&gt;MobaXterm&lt;/a&gt; is a set of very interesting applications to work as in Linux and connect to remote computers:
terminal, ssh client, X server, rdp, vnc, etc.&lt;/p&gt;
</content><category term="Tricks"></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="pósters"></category><category term="diy"></category><category term="herramientas"></category></entry><entry><title>PosteRazor: Create Giant Posters from Any Image</title><link href="https://pablocaro.es/en/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:/en/posterazor-crear-posters-grandes</id><summary type="html">&lt;p&gt;PosteRazor converts any image into a giant poster by splitting the image into printable pages that you can assemble&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;PosteRazor&lt;/strong&gt; is a free and open source tool that solves a daily problem: &lt;strong&gt;how to print a large image when you only have a home printer?&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="what-does-posterazor-do"&gt;What does PosteRazor do?&lt;a class="headerlink" href="#what-does-posterazor-do" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;PosteRazor takes a raster image and &lt;strong&gt;"cuts" it into pieces&lt;/strong&gt; that can then be printed and assembled to form a complete poster. It converts a single image into a multi-page PDF document ready for printing.&lt;/p&gt;
&lt;h2 id="key-features"&gt;Key Features&lt;a class="headerlink" href="#key-features" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Wizard-like interface&lt;/strong&gt; with 5 simple steps&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multi-platform&lt;/strong&gt;: Windows, macOS, Linux&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Web version&lt;/strong&gt; available via WebAssembly&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Completely free&lt;/strong&gt; and open source&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="ideal-use-cases"&gt;Ideal Use Cases&lt;a class="headerlink" href="#ideal-use-cases" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Decorative posters&lt;/strong&gt; for home, school, or office&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DIY projects&lt;/strong&gt; for decoration&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Printing giant images&lt;/strong&gt; that exceed standard page size&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Large format visual presentations&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="how-does-it-work"&gt;How does it work?&lt;a class="headerlink" href="#how-does-it-work" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;You load your image&lt;/li&gt;
&lt;li&gt;You select the desired poster size&lt;/li&gt;
&lt;li&gt;You configure your printer's page format&lt;/li&gt;
&lt;li&gt;PosteRazor calculates the division automatically&lt;/li&gt;
&lt;li&gt;You generate the PDF and print each page&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="the-perfect-solution"&gt;The Perfect Solution&lt;a class="headerlink" href="#the-perfect-solution" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;PosteRazor democratizes the creation of large posters. You no longer need expensive printing services - &lt;strong&gt;with any home printer you can create posters of any size you want&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Official site&lt;/em&gt;: &lt;a href="https://posterazor.sourceforge.io/"&gt;PosteRazor&lt;/a&gt;&lt;/p&gt;</content><category term="Tools"></category><category term="posterazor"></category><category term="printing"></category><category term="posters"></category><category term="diy"></category><category term="tools"></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="Trucos"></category><category term="linux"></category></entry><entry><title>Unread Mail in Gmail's Primary Tab</title><link href="https://pablocaro.es/en/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:/en/gmail_correo_no_leido_solo_en_principal</id><summary type="html">&lt;p&gt;As always, another reminder note.&lt;/p&gt;
&lt;p&gt;If you filter mail in gmail by unread (&lt;code&gt;is:unread&lt;/code&gt;), you get all unread mail,
which in my case always includes hundreds of notifications, social, promotions, or forums which is usually not what I'm looking for.&lt;/p&gt;
&lt;p&gt;To search for unread emails &lt;em&gt;from the Primary tab …&lt;/em&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;As always, another reminder note.&lt;/p&gt;
&lt;p&gt;If you filter mail in gmail by unread (&lt;code&gt;is:unread&lt;/code&gt;), you get all unread mail,
which in my case always includes hundreds of notifications, social, promotions, or forums which is usually not what I'm looking for.&lt;/p&gt;
&lt;p&gt;To search for unread emails &lt;em&gt;from the Primary tab&lt;/em&gt; the query to perform is this:&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="Tricks"></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>Technical Documentation with Sphinx, Paver, and Cog</title><link href="https://pablocaro.es/en/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:/en/documentacion-tecnica-sphinx-paver-cog</id><summary type="html">&lt;p&gt;Automated workflow to create technical documentation using Sphinx, Paver, and Cog, eliminating repetitive tasks and manual errors in code examples&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;Doug Hellmann&lt;/strong&gt; presents a robust workflow for technical documentation that eliminates repetitive manual work and errors in code examples.&lt;/p&gt;
&lt;h2 id="the-problem-to-solve"&gt;The Problem to Solve&lt;a class="headerlink" href="#the-problem-to-solve" 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;Common Challenges&lt;/strong&gt;:
- Keeping &lt;strong&gt;code examples updated&lt;/strong&gt;
- &lt;strong&gt;Multiple output formats&lt;/strong&gt; (HTML, PDF, blog)
- &lt;strong&gt;Repetitive tasks&lt;/strong&gt; of building and publishing
- &lt;strong&gt;Copy-paste errors&lt;/strong&gt; in program output&lt;/p&gt;
&lt;h2 id="the-solution-trio-of-tools"&gt;The Solution: Trio of Tools&lt;a class="headerlink" href="#the-solution-trio-of-tools" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="sphinx-documentation-engine"&gt;Sphinx: Documentation Engine&lt;a class="headerlink" href="#sphinx-documentation-engine" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;reStructuredText conversion&lt;/strong&gt; to multiple formats&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Customizable themes&lt;/strong&gt; with Jinja templates&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automatic generation&lt;/strong&gt; of indices and cross-references&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="paver-build-automation"&gt;Paver: Build Automation&lt;a class="headerlink" href="#paver-build-automation" 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;Solves&lt;/strong&gt;: Automation of repetitive build processes&lt;/p&gt;
&lt;h3 id="cog-automatic-code-insertion"&gt;Cog: Automatic Code Insertion&lt;a class="headerlink" href="#cog-automatic-code-insertion" 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;example.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;Key Advantage&lt;/strong&gt;: Program output is &lt;strong&gt;automatically updated&lt;/strong&gt; when regenerating documentation.&lt;/p&gt;
&lt;h2 id="production-workflow"&gt;Production Workflow&lt;a class="headerlink" href="#production-workflow" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="1-writing-in-restructuredtext"&gt;1. Writing in reStructuredText&lt;a class="headerlink" href="#1-writing-in-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;Usage Example&lt;/span&gt;
&lt;span class="gh"&gt;=============&lt;/span&gt;

We run the 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 my_script.py\n&amp;quot;)
   result = subprocess.run([&amp;#39;python&amp;#39;, &amp;#39;my_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-automated-build"&gt;2. Automated Build&lt;a class="headerlink" href="#2-automated-build" 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;# A single command for everything&lt;/span&gt;
paver&lt;span class="w"&gt; &lt;/span&gt;html

&lt;span class="c1"&gt;# Or for multiple destinations&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-multi-destination-publishing"&gt;3. Multi-Destination Publishing&lt;a class="headerlink" href="#3-multi-destination-publishing" 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;Personal Blog&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Project Website&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;PDF Documentation&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="advantages-of-the-approach"&gt;Advantages of the Approach&lt;a class="headerlink" href="#advantages-of-the-approach" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="guaranteed-consistency"&gt;Guaranteed Consistency&lt;a class="headerlink" href="#guaranteed-consistency" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Examples always updated&lt;/strong&gt; with real code&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Uniform format&lt;/strong&gt; across multiple destinations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No manual transcription&lt;/strong&gt; errors&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="maximized-efficiency"&gt;Maximized Efficiency&lt;a class="headerlink" href="#maximized-efficiency" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;One source, multiple outputs&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Complete automation&lt;/strong&gt; of the pipeline&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Focus on content&lt;/strong&gt;, not process&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="maintainability"&gt;Maintainability&lt;a class="headerlink" href="#maintainability" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Changes in a single source&lt;/strong&gt; propagate automatically&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integrated testing&lt;/strong&gt; of code examples&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unique versioning&lt;/strong&gt; for all documentation&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="practical-implementation"&gt;Practical Implementation&lt;a class="headerlink" href="#practical-implementation" 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;# full pavement.py&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;This workflow represents &lt;strong&gt;intelligent automation&lt;/strong&gt; of technical documentation: write once, publish everywhere, always updated.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Original source&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="Documentation"></category><category term="sphinx"></category><category term="paver"></category><category term="cog"></category><category term="documentation"></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="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>Determining RAM slots in use on Linux</title><link href="https://pablocaro.es/en/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:/en/determinar-slots-ram-uso-linux</id><summary type="html">&lt;p&gt;Linux commands to determine how many RAM slots are occupied and their capacity using dmidecode, lshw, and hardware inspection techniques&lt;/p&gt;</summary><content type="html">&lt;p&gt;How many &lt;strong&gt;RAM slots do you have occupied&lt;/strong&gt; and how many are free? Linux provides tools to inspect memory configuration without opening the computer.&lt;/p&gt;
&lt;h2 id="dmidecode-the-main-tool"&gt;dmidecode: The Main Tool&lt;a class="headerlink" href="#dmidecode-the-main-tool" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="basic-memory-information"&gt;Basic Memory Information&lt;a class="headerlink" href="#basic-memory-information" 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;# View all memory information&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;# Specific memory type (table 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;# Installed sizes only&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;Typical Output&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="detailed-information-per-slot"&gt;Detailed Information per Slot&lt;a class="headerlink" href="#detailed-information-per-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;# Granular information for each slot (table 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;# Compact summary&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;Information Provided&lt;/strong&gt;:
- &lt;strong&gt;Locator&lt;/strong&gt;: Physical slot identifier (DIMM_A1, DIMM_B1, etc.)
- &lt;strong&gt;Size&lt;/strong&gt;: Installed capacity or "No Module Installed"
- &lt;strong&gt;Type&lt;/strong&gt;: DDR3, DDR4, etc.
- &lt;strong&gt;Speed&lt;/strong&gt;: Speed in MT/s&lt;/p&gt;
&lt;h2 id="lshw-alternative-with-structured-format"&gt;lshw: Alternative with Structured Format&lt;a class="headerlink" href="#lshw-alternative-with-structured-format" 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;# Memory information with 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;# Physical memory only (no cache)&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;# More compact format&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="useful-scripts-for-analysis"&gt;Useful Scripts for Analysis&lt;a class="headerlink" href="#useful-scripts-for-analysis" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="occupied-slots-counter"&gt;Occupied Slots Counter&lt;a class="headerlink" href="#occupied-slots-counter" 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;=== RAM Slot Analysis ===&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;Total Slots: &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;Occupied Slots: &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;Free Slots: &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="detailed-summary"&gt;Detailed Summary&lt;a class="headerlink" href="#detailed-summary" 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;=== Current RAM Configuration ===&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;: Empty&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="additional-useful-information"&gt;Additional Useful Information&lt;a class="headerlink" href="#additional-useful-information" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="maximum-supported-capacity"&gt;Maximum Supported Capacity&lt;a class="headerlink" href="#maximum-supported-capacity" 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;# Maximum total capacity&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;# Maximum number of devices&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="speed-and-type-verification"&gt;Speed and Type Verification&lt;a class="headerlink" href="#speed-and-type-verification" 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;# Configured speed&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;# Memory type&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="example-of-interpreted-output"&gt;Example of Interpreted Output&lt;a class="headerlink" href="#example-of-interpreted-output" 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;Interpretation&lt;/strong&gt;: 
- 4 total slots (DIMM_A1, A2, B1, B2)
- 2 slots occupied with 8GB DDR4 each
- 2 free slots available&lt;/p&gt;
&lt;h2 id="important-limitations"&gt;Important Limitations&lt;a class="headerlink" href="#important-limitations" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Accuracy&lt;/strong&gt;: Command-line tools may not perfectly reflect physical hardware&lt;/p&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Privileges&lt;/strong&gt;: Root permissions are required to access DMI information&lt;/p&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Compatibility&lt;/strong&gt;: Some embedded systems may not provide complete information&lt;/p&gt;
&lt;p&gt;For &lt;strong&gt;maximum certainty&lt;/strong&gt;, physical inspection is still recommended, but these tools provide sufficiently accurate information for most cases.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Original source&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="linux"></category><category term="ram"></category><category term="dmidecode"></category><category term="lshw"></category><category term="hardware"></category><category term="memory"></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><category term="finanzas"></category></entry><entry><title>Introduction to High-Frequency Trading Systems</title><link href="https://pablocaro.es/en/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:/en/introduccion-trading-alta-frecuencia</id><summary type="html">&lt;p&gt;Fundamental concepts about High-Frequency Trading (HFT) systems, programs that execute thousands of trades per day in fractions of a second&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;High-Frequency Trading&lt;/strong&gt; (HFT) programs represent the extreme automation of financial investments, operating at speeds that surpass any human capability.&lt;/p&gt;
&lt;h2 id="what-is-hft"&gt;What is HFT?&lt;a class="headerlink" href="#what-is-hft" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;HFT systems are &lt;strong&gt;programs designed to invest in financial markets&lt;/strong&gt; with the following characteristics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Massive volume&lt;/strong&gt;: They buy and sell up to &lt;strong&gt;tens of thousands of stocks or options&lt;/strong&gt; per day&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extreme speed&lt;/strong&gt;: Each operation can last &lt;strong&gt;fractions of a second&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Small margins&lt;/strong&gt;: &lt;strong&gt;Very reduced profit&lt;/strong&gt; per individual operation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cumulative effect&lt;/strong&gt;: In their &lt;strong&gt;global computation, they become very effective&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="fundamental-principles"&gt;Fundamental Principles&lt;a class="headerlink" href="#fundamental-principles" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="speed-as-a-competitive-advantage"&gt;Speed as a Competitive Advantage&lt;a class="headerlink" href="#speed-as-a-competitive-advantage" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Ultra-low latency&lt;/strong&gt;: Every millisecond counts&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Physical proximity&lt;/strong&gt;: Servers close to exchanges&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Specialized hardware&lt;/strong&gt;: FPGA, optimized fiber optic cables&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="algorithmic-strategies"&gt;Algorithmic Strategies&lt;a class="headerlink" href="#algorithmic-strategies" 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;: Providing constant liquidity&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Statistical arbitrage&lt;/strong&gt;: Taking advantage of price discrepancies&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Temporal arbitrage&lt;/strong&gt;: Exploiting differences between markets&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="key-technologies"&gt;Key Technologies&lt;a class="headerlink" href="#key-technologies" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="optimized-hardware"&gt;Optimized Hardware&lt;a class="headerlink" href="#optimized-hardware" 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; for ultra-fast processing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Co-located servers&lt;/strong&gt; in exchange datacenters&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;High-speed fiber optic networks&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="specialized-software"&gt;Specialized Software&lt;a class="headerlink" href="#specialized-software" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Low-latency algorithms&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optimized network protocols&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Real-time risk management systems&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="impact-on-markets"&gt;Impact on Markets&lt;a class="headerlink" href="#impact-on-markets" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="benefits"&gt;Benefits&lt;a class="headerlink" href="#benefits" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Greater liquidity&lt;/strong&gt; in markets&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tighter spreads&lt;/strong&gt; between bid/ask&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Improved price efficiency&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="controversies"&gt;Controversies&lt;a class="headerlink" href="#controversies" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Increased volatility&lt;/strong&gt; in times of stress&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unfair advantage&lt;/strong&gt; over traditional investors&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Potential market manipulations&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="the-future-of-hft"&gt;The Future of HFT&lt;a class="headerlink" href="#the-future-of-hft" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The race for speed continues to evolve towards:
- &lt;strong&gt;Artificial Intelligence&lt;/strong&gt; and machine learning
- &lt;strong&gt;Real-time sentiment analysis&lt;/strong&gt;
- &lt;strong&gt;Quantum computing&lt;/strong&gt; experimental&lt;/p&gt;
&lt;p&gt;HFT exemplifies how &lt;strong&gt;technology completely redefines&lt;/strong&gt; traditional industries, creating new opportunities and ethical challenges.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Original source&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="Finance"></category><category term="trading"></category><category term="hft"></category><category term="high-frequency"></category><category term="algorithms"></category><category term="finance"></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="Seguridad"></category><category term="nmap"></category><category term="network-discovery"></category><category term="arp-scan"></category><category term="netdiscover"></category><category term="seguridad"></category></entry><entry><title>How to Find Live Hosts on Your Local Network</title><link href="https://pablocaro.es/en/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:/en/encontrar-hosts-vivos-red</id><summary type="html">&lt;p&gt;Methods and tools to discover active devices on local networks using nmap, arp-scan, netdiscover, and other reconnaissance techniques&lt;/p&gt;</summary><content type="html">&lt;p&gt;Discovering &lt;strong&gt;which devices are active&lt;/strong&gt; on your local network is a fundamental task for network administrators and security professionals. Here are the most effective techniques.&lt;/p&gt;
&lt;h2 id="nmap-the-main-tool"&gt;Nmap: The Main Tool&lt;a class="headerlink" href="#nmap-the-main-tool" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="basic-host-scan"&gt;Basic Host Scan&lt;a class="headerlink" href="#basic-host-scan" 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;# Simple Ping scan&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;# Scan with specific TCP ports&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;# UDP scan for special devices&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;: Host discovery only, no port scan
&lt;strong&gt;-PS&lt;/strong&gt;: TCP SYN discovery on specific ports
&lt;strong&gt;-PU&lt;/strong&gt;: UDP discovery (useful for embedded devices)&lt;/p&gt;
&lt;h3 id="more-exhaustive-scans"&gt;More Exhaustive Scans&lt;a class="headerlink" href="#more-exhaustive-scans" 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;# Full scan with OS detection&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;# TCP Connect Scan (less intrusive)&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;# Firewall bypass with fragmentation&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="alternative-tools"&gt;Alternative Tools&lt;a class="headerlink" href="#alternative-tools" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="arp-scan-arp-based"&gt;arp-scan: ARP Based&lt;a class="headerlink" href="#arp-scan-arp-based" 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;# Local subnet scan&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;# With specific interface&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;# Simplified local scan&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;Advantage&lt;/strong&gt;: Works even with strict firewalls&lt;/p&gt;
&lt;h3 id="netdiscover-passiveactive-discovery"&gt;netdiscover: Passive/Active Discovery&lt;a class="headerlink" href="#netdiscover-passiveactive-discovery" 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;# Active discovery&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;# Passive mode (listen only)&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-mobile-scanner"&gt;fing: Mobile Scanner&lt;a class="headerlink" href="#fing-mobile-scanner" 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;# Fast scan&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;# With manufacturer details&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="post-scan-verification"&gt;Post-Scan Verification&lt;a class="headerlink" href="#post-scan-verification" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="check-arp-table"&gt;Check ARP Table&lt;a class="headerlink" href="#check-arp-table" 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;# View current ARP table&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;# On modern systems&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;# Clear and rescan&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="advanced-techniques"&gt;Advanced Techniques&lt;a class="headerlink" href="#advanced-techniques" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="wireshark-for-passive-analysis"&gt;Wireshark for Passive Analysis&lt;a class="headerlink" href="#wireshark-for-passive-analysis" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Capture traffic on the network interface&lt;/li&gt;
&lt;li&gt;Filter by ARP: &lt;code&gt;arp&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Analyze broadcast traffic&lt;/li&gt;
&lt;li&gt;Identify devices by traffic patterns&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="automation-scripts"&gt;Automation Scripts&lt;a class="headerlink" href="#automation-scripts" 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;# Full local network scan&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;Scanning network...&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;Verifying 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="important-considerations"&gt;Important Considerations&lt;a class="headerlink" href="#important-considerations" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="firewall-limitations"&gt;Firewall Limitations&lt;a class="headerlink" href="#firewall-limitations" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Some devices&lt;/strong&gt; block ping/ICMP&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Corporate firewalls&lt;/strong&gt; may filter scans&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Different ports&lt;/strong&gt; may reveal "hidden" devices&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="legal-considerations"&gt;Legal Considerations&lt;a class="headerlink" href="#legal-considerations" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Authorization needed&lt;/strong&gt;: Only scan your own networks
⚠️ &lt;strong&gt;Corporate policies&lt;/strong&gt;: Verify before scanning in enterprise environments&lt;/p&gt;
&lt;h3 id="optimization-by-network-type"&gt;Optimization by Network Type&lt;a class="headerlink" href="#optimization-by-network-type" 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;# Home network (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;# Corporate network (servers, stations)&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;# Industrial network (embedded devices)&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;The &lt;strong&gt;combination of multiple techniques&lt;/strong&gt; provides the most complete picture of active devices on your network.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Original source&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="Security"></category><category term="nmap"></category><category term="network-discovery"></category><category term="arp-scan"></category><category term="netdiscover"></category><category term="security"></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="herramientas"></category><category term="desarrollo"></category><category term="networking"></category><category term="testing"></category></entry><entry><title>Powerful Command Line Tools for Developers</title><link href="https://pablocaro.es/en/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:/en/herramientas-desarrolladores-linea-comandos</id><summary type="html">&lt;p&gt;Collection of essential command line tools for developers: curl, ngrep, netcat, sshuttle, siege, and mitmproxy&lt;/p&gt;</summary><content type="html">&lt;p&gt;The &lt;strong&gt;command line remains the most powerful environment&lt;/strong&gt; for developers. These specialized tools dramatically expand your debugging, testing, and analysis capabilities.&lt;/p&gt;
&lt;h2 id="curl-the-http-swiss-army-knife"&gt;Curl: The HTTP Swiss Army Knife&lt;a class="headerlink" href="#curl-the-http-swiss-army-knife" 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;# Get your public IP&lt;/span&gt;
curl&lt;span class="w"&gt; &lt;/span&gt;ifconfig.me

&lt;span class="c1"&gt;# Inspect response headers&lt;/span&gt;
curl&lt;span class="w"&gt; &lt;/span&gt;-I&lt;span class="w"&gt; &lt;/span&gt;https://example.com

&lt;span class="c1"&gt;# POST with JSON data&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.example.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Main usage&lt;/strong&gt;: Network data transfer, API testing, HTTP inspection.&lt;/p&gt;
&lt;h2 id="ngrep-grep-for-network-traffic"&gt;Ngrep: Grep for Network Traffic&lt;a class="headerlink" href="#ngrep-grep-for-network-traffic" 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;# Filter HTTP traffic by keyword&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;# Capture by specific IP&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;# Monitor traffic on specific port&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;Main usage&lt;/strong&gt;: Network packet analysis, protocol debugging, traffic monitoring.&lt;/p&gt;
&lt;h2 id="netcat-nc-the-networking-swiss-army-knife"&gt;Netcat (nc): The Networking Swiss Army Knife&lt;a class="headerlink" href="#netcat-nc-the-networking-swiss-army-knife" 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;# Port scanning&lt;/span&gt;
nc&lt;span class="w"&gt; &lt;/span&gt;-zv&lt;span class="w"&gt; &lt;/span&gt;example.com&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;-1000

&lt;span class="c1"&gt;# Transfer file between servers&lt;/span&gt;
&lt;span class="c1"&gt;# Receiver: nc -l 1234 &amp;gt; received_file&lt;/span&gt;
&lt;span class="c1"&gt;# Sender: nc destination_server 1234 &amp;lt; file_to_send&lt;/span&gt;

&lt;span class="c1"&gt;# Create simple HTTP server&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\nHello world&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;Main usage&lt;/strong&gt;: Port scanning, file transfer, connectivity testing.&lt;/p&gt;
&lt;h2 id="sshuttle-easy-vpn-with-ssh"&gt;Sshuttle: Easy VPN with SSH&lt;a class="headerlink" href="#sshuttle-easy-vpn-with-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;# Tunnel all traffic&lt;/span&gt;
sshuttle&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt; &lt;/span&gt;user@server&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;# Tunnel only a subnet&lt;/span&gt;
sshuttle&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt; &lt;/span&gt;user@server&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;# Bypass geo-blocking&lt;/span&gt;
sshuttle&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt; &lt;/span&gt;remote_server&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;Main usage&lt;/strong&gt;: Secure tunneling, geo-restriction bypass, protection on public WiFi.&lt;/p&gt;
&lt;h2 id="siege-http-load-bombardment"&gt;Siege: HTTP Load Bombardment&lt;a class="headerlink" href="#siege-http-load-bombardment" 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;# Simple load test&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://example.com

&lt;span class="c1"&gt;# Test with multiple 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;# Specific benchmark&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.example.com/endpoint
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Main usage&lt;/strong&gt;: Performance testing, HTTP benchmarking, load simulation.&lt;/p&gt;
&lt;h2 id="mitmproxy-spy-on-https-traffic"&gt;Mitmproxy: Spy on HTTP/S Traffic&lt;a class="headerlink" href="#mitmproxy-spy-on-https-traffic" 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;# Basic proxy&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;# Transparent mode&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;# Save traffic to file&lt;/span&gt;
mitmdump&lt;span class="w"&gt; &lt;/span&gt;-w&lt;span class="w"&gt; &lt;/span&gt;capture.flow
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Main usage&lt;/strong&gt;: Web application debugging, HTTPS traffic inspection, API analysis.&lt;/p&gt;
&lt;h2 id="typical-workflow"&gt;Typical Workflow&lt;a class="headerlink" href="#typical-workflow" 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. Scan connectivity&lt;/span&gt;
nc&lt;span class="w"&gt; &lt;/span&gt;-zv&lt;span class="w"&gt; &lt;/span&gt;api.example.com&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;443&lt;/span&gt;

&lt;span class="c1"&gt;# 2. Analyze response&lt;/span&gt;
curl&lt;span class="w"&gt; &lt;/span&gt;-I&lt;span class="w"&gt; &lt;/span&gt;https://api.example.com

&lt;span class="c1"&gt;# 3. Monitor traffic&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. Load test&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.example.com

&lt;span class="c1"&gt;# 5. Detailed debugging&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="why-command-line"&gt;Why Command Line?&lt;a class="headerlink" href="#why-command-line" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Speed&lt;/strong&gt;: No GUI overhead&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scriptability&lt;/strong&gt;: Complete automation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Flexibility&lt;/strong&gt;: Combination with pipes and redirects&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Universality&lt;/strong&gt;: Available on any server&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These tools turn your terminal into a &lt;strong&gt;complete networking and testing laboratory&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Original source&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="Tools"></category><category term="cli"></category><category term="tools"></category><category term="development"></category><category term="networking"></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>Sysdig: Cloud-Native Security Platform with AI</title><link href="https://pablocaro.es/en/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:/en/sysdig-seguridad-cloud-nativa</id><summary type="html">&lt;p&gt;Sysdig offers a comprehensive security platform for cloud-native environments that combines AI, real-time visibility, and open source innovation&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;Sysdig&lt;/strong&gt; is a cloud security platform that provides comprehensive, real-time security solutions for cloud-native environments, resolving the conflict between the needs of security and development teams.&lt;/p&gt;
&lt;h2 id="key-features"&gt;Key Features&lt;a class="headerlink" href="#key-features" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="intelligent-security-ai"&gt;Intelligent Security AI&lt;a class="headerlink" href="#intelligent-security-ai" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Contextual AI&lt;/strong&gt; that understands cloud environments and guides actionable insights&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;76% reduction&lt;/strong&gt; in mean time to resolution (MTTR)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="real-time-visibility"&gt;Real-Time Visibility&lt;a class="headerlink" href="#real-time-visibility" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Continuous monitoring of cloud activities and potential risks&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Threat detection in 2 seconds&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Filters noise to highlight only critical issues&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="open-innovation"&gt;Open Innovation&lt;a class="headerlink" href="#open-innovation" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Based on open source technologies like &lt;strong&gt;Falco&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Transparency and customization in cloud security&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="key-benefits"&gt;Key Benefits&lt;a class="headerlink" href="#key-benefits" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;95% savings&lt;/strong&gt; in vulnerability management time&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;98% reduction&lt;/strong&gt; in vulnerability noise&lt;/li&gt;
&lt;li&gt;Ultra-fast threat detection&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="value-proposition"&gt;Value Proposition&lt;a class="headerlink" href="#value-proposition" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Sysdig enables security and development teams to work more effectively, &lt;strong&gt;enabling rapid innovation without compromising security&lt;/strong&gt;. The platform is used by major companies like IBM, Booking.com, and Neo4j to secure their cloud environments.&lt;/p&gt;
&lt;p&gt;This tool represents the future of cloud-native security: &lt;strong&gt;intelligent, fast, and uncompromising&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Official site&lt;/em&gt;: &lt;a href="https://sysdig.com/"&gt;Sysdig&lt;/a&gt;&lt;/p&gt;</content><category term="Tools"></category><category term="sysdig"></category><category term="security"></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="Python"></category><category term="python"></category><category term="generadores"></category><category term="itertools"></category><category term="decoradores"></category><category term="yield"></category></entry><entry><title>Python: Create Repeating Generators</title><link href="https://pablocaro.es/en/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:/en/python-generadores-repetibles</id><summary type="html">&lt;p&gt;Techniques in Python to create generators that can be iterated multiple times, overcoming the "one-shot" limitation of standard generators&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;Generators in Python are "one-shot"&lt;/strong&gt; - they can only be iterated once. How to create generators that can be reused multiple times?&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;a class="headerlink" href="#the-problem" 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;my_generator&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;my_generator&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;# [] - Empty!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="solution-1-class-approach-most-robust"&gt;Solution 1: Class Approach (Most Robust)&lt;a class="headerlink" href="#solution-1-class-approach-most-robust" 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;# Usage&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;my_repeatable_generator&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;my_repeatable_generator&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] - Works!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="solution-2-itertoolscycle-for-infinite-sequences"&gt;Solution 2: itertools.cycle() for Infinite Sequences&lt;a class="headerlink" href="#solution-2-itertoolscycle-for-infinite-sequences" 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;# Repeat a sequence infinitely&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;# Take only the first 9 elements&lt;/span&gt;
&lt;span class="n"&gt;result&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;result&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="solution-3-lambda-wrapper-simple"&gt;Solution 3: Lambda Wrapper (Simple)&lt;a class="headerlink" href="#solution-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;my_generator&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 to create new instance each time&lt;/span&gt;
&lt;span class="n"&gt;repeatable_generator&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;my_generator&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;repeatable_generator&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;repeatable_generator&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="solution-4-factory-function"&gt;Solution 4: Factory Function&lt;a class="headerlink" href="#solution-4-factory-function" 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;create_generator&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generator&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;data&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;generator&lt;/span&gt;

&lt;span class="c1"&gt;# Usage&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;create_generator&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="when-to-use-each-approach"&gt;When to use each approach?&lt;a class="headerlink" href="#when-to-use-each-approach" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="decorator-class-multigen"&gt;Decorator Class (&lt;code&gt;@multigen&lt;/code&gt;)&lt;a class="headerlink" href="#decorator-class-multigen" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Complex generators&lt;/strong&gt; with parameters&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Maximum flexibility&lt;/strong&gt; and reuse&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Preserves metadata&lt;/strong&gt; of the original generator&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;Infinite repetition&lt;/strong&gt; of small sequences&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Known cyclic patterns&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Very efficient&lt;/strong&gt; in memory&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;Simple cases&lt;/strong&gt; without parameters&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rapid prototyping&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stateless generators&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; between simplicity and flexibility&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Generators with initial configuration&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;More explicit code&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="performance-considerations"&gt;Performance Considerations&lt;a class="headerlink" href="#performance-considerations" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Memory&lt;/strong&gt;: Repeatable generators may require storing initial parameters
⚠️ &lt;strong&gt;CPU&lt;/strong&gt;: Each iteration recalculates from the start&lt;/p&gt;
&lt;p&gt;The choice depends on whether you need &lt;strong&gt;exact repetition&lt;/strong&gt; or &lt;strong&gt;efficient reuse&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Original source&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="Python"></category><category term="python"></category><category term="generators"></category><category term="itertools"></category><category term="decorators"></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="Sistemas"></category><category term="linux"></category></entry><entry><title>Middle click in firefox</title><link href="https://pablocaro.es/en/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:/en/boton_central_firefox</id><summary type="html">&lt;p class="first last"&gt;Stop firefox from opening the pasted url with the middle mouse button&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Another reminder note.
I usually open links in a new window in firefox using the middle button.
It often happens to me that I have a url in the clipboard and firefox then opens it for me instead of the link.
To avoid this, simply go to &lt;code&gt;about:config&lt;/code&gt; and set the property &lt;code&gt;middlemouse.contentLoadURL&lt;/code&gt; to false.&lt;/p&gt;
</content><category term="Systems"></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="Frontend"></category><category term="bootstrap"></category><category term="css"></category><category term="dropdown"></category><category term="flexbox"></category><category term="html"></category></entry><entry><title>Bootstrap: Two Links in the Same Row in Dropdown</title><link href="https://pablocaro.es/en/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:/en/bootstrap-dropdown-enlaces-horizontal</id><summary type="html">&lt;p&gt;CSS technique to place two links on the same horizontal row within a Bootstrap dropdown menu using flexbox&lt;/p&gt;</summary><content type="html">&lt;p&gt;Sometimes you need to &lt;strong&gt;place two links side by side&lt;/strong&gt; in the same row within a Bootstrap dropdown. The solution combines structured HTML and CSS with flexbox.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;a class="headerlink" href="#the-problem" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;By default, Bootstrap renders dropdown links as block elements, occupying the entire row:&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;!-- Default behavior: each link on its row --&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;Link 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;Link 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="the-solution"&gt;The Solution&lt;a class="headerlink" href="#the-solution" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="structured-html"&gt;Structured HTML&lt;a class="headerlink" href="#structured-html" 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;On the third row&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;Also on the third row&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;Key&lt;/strong&gt;: Multiple &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; links within the same &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt; with a custom class.&lt;/p&gt;
&lt;h3 id="css-with-flexbox"&gt;CSS with Flexbox&lt;a class="headerlink" href="#css-with-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;Key&lt;/strong&gt;: &lt;code&gt;inline-flex&lt;/code&gt; overrides Bootstrap's default block display.&lt;/p&gt;
&lt;h2 id="complete-implementation"&gt;Complete Implementation&lt;a class="headerlink" href="#complete-implementation" 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;
        Menu &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;First option&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;Second option&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;Option 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;Option 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;Last option&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="additional-considerations"&gt;Additional Considerations&lt;a class="headerlink" href="#additional-considerations" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="spacing-between-links"&gt;Spacing between links&lt;a class="headerlink" href="#spacing-between-links" 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="when-to-use-it"&gt;When to use it?&lt;a class="headerlink" href="#when-to-use-it" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Related actions&lt;/strong&gt; (Edit/Delete)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Binary options&lt;/strong&gt; (Yes/No, Enable/Disable)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compact navigation&lt;/strong&gt; in tight spaces&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This technique maintains &lt;strong&gt;correct HTML semantics&lt;/strong&gt; while leveraging the flexibility of flexbox for more complex layouts.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Original source&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="Frontend"></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="Testing"></category><category term="galen"></category><category term="testing"></category><category term="responsive"></category><category term="layout"></category><category term="selenium"></category></entry><entry><title>Galen Framework: Automated Testing for Responsive Design</title><link href="https://pablocaro.es/en/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:/en/galen-framework-testing-responsive</id><summary type="html">&lt;p&gt;Galen Framework simplifies automated testing of responsive layouts by verifying the position and appearance of elements across different devices&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;Galen Framework&lt;/strong&gt; is an automated testing tool designed specifically to &lt;strong&gt;test responsive web designs&lt;/strong&gt;. Its main purpose is to simplify layout testing by verifying the positioning and appearance of web elements on different device sizes.&lt;/p&gt;
&lt;h2 id="key-features"&gt;Key Features&lt;a class="headerlink" href="#key-features" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="intelligent-layout-testing"&gt;Intelligent Layout Testing&lt;a class="headerlink" href="#intelligent-layout-testing" 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="full-responsive-support"&gt;Full Responsive Support&lt;a class="headerlink" href="#full-responsive-support" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Automatic resizing&lt;/strong&gt; of the browser to defined sizes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-device testing&lt;/strong&gt; in multiple resolutions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rule verification&lt;/strong&gt; specific per screen&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="human-and-flexible-syntax"&gt;Human and Flexible Syntax&lt;a class="headerlink" href="#human-and-flexible-syntax" 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="advanced-capabilities"&gt;Advanced Capabilities&lt;a class="headerlink" href="#advanced-capabilities" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Selenium Grid compatibility&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Visual testing&lt;/strong&gt; with image comparison&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Color scheme verification&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Detailed HTML reports&lt;/strong&gt; with highlighted errors&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="multi-language-support"&gt;Multi-Language Support&lt;a class="headerlink" href="#multi-language-support" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Tests written in:
- &lt;strong&gt;JavaScript&lt;/strong&gt;
- &lt;strong&gt;Java&lt;/strong&gt;
- Other languages with integrated parameterization&lt;/p&gt;
&lt;h2 id="why-galen"&gt;Why Galen?&lt;a class="headerlink" href="#why-galen" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Before Galen, layout testing was &lt;strong&gt;complex and error-prone&lt;/strong&gt;. Galen introduces:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Declarative syntax&lt;/strong&gt; easy to read&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Relational verification&lt;/strong&gt; between elements&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Native responsive testing&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Visual reports&lt;/strong&gt; understandable&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="typical-use-case"&gt;Typical Use Case&lt;a class="headerlink" href="#typical-use-case" 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;// Open browser, resize and test&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 democratizes responsive design testing, making &lt;strong&gt;layout testing as easy as functional testing&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Open source framework&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="Testing"></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="Redes"></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="DevOps"></category><category term="nginx"></category><category term="reverse-proxy"></category><category term="x-forwarded-for"></category><category term="ip-real"></category><category term="configuración"></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="SQL"></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>The Story Behind X-Forwarded-For and X-Real-IP</title><link href="https://pablocaro.es/en/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:/en/historia-x-forwarded-for-x-real-ip</id><summary type="html">&lt;p&gt;Analysis of X-Forwarded-For and X-Real-IP headers, their origins, differences, and how to determine the client's real IP in architectures with multiple proxies&lt;/p&gt;</summary><content type="html">&lt;p&gt;Determining the &lt;strong&gt;client's real IP&lt;/strong&gt; in proxy architectures is more complex than it seems. The &lt;code&gt;X-Forwarded-For&lt;/code&gt; and &lt;code&gt;X-Real-IP&lt;/code&gt; headers have different purposes and limitations.&lt;/p&gt;
&lt;h2 id="x-forwarded-for-the-full-chain"&gt;X-Forwarded-For: The Full Chain&lt;a class="headerlink" href="#x-forwarded-for-the-full-chain" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="format-and-purpose"&gt;Format and Purpose&lt;a class="headerlink" href="#format-and-purpose" 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;Tracks the original IP&lt;/strong&gt; of the client through multiple hops&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Accumulates IPs&lt;/strong&gt; as it passes through proxies&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Not mandatory&lt;/strong&gt; for all proxies&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="critical-limitations"&gt;Critical Limitations&lt;a class="headerlink" href="#critical-limitations" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Easily forgeable&lt;/strong&gt;: A client can add fake IPs
⚠️ &lt;strong&gt;Potentially unreliable&lt;/strong&gt; for determining the real IP&lt;/p&gt;
&lt;h2 id="x-real-ip-the-direct-approach"&gt;X-Real-IP: The Direct Approach&lt;a class="headerlink" href="#x-real-ip-the-direct-approach" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="characteristics"&gt;Characteristics&lt;a class="headerlink" href="#characteristics" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Less documentation&lt;/strong&gt; officially available&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Commonly used&lt;/strong&gt; alongside X-Forwarded-For&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simpler approach&lt;/strong&gt; to passing the client IP&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="limitation"&gt;Limitation&lt;a class="headerlink" href="#limitation" 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 definitive standards&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="recommended-nginx-configuration"&gt;Recommended Nginx Configuration&lt;a class="headerlink" href="#recommended-nginx-configuration" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To extract the most accurate IP:&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;# Configure known proxies&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;# Enable recursive processing&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;# Use X-Forwarded-For as source&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="the-real-challenge"&gt;The Real Challenge&lt;a class="headerlink" href="#the-real-challenge" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Determining the "real" client IP is inherently complex&lt;/strong&gt; due to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Multiple network hops&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Uncooperative proxies&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Potential IP spoofing&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lack of uniform standards&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="best-practices"&gt;Best Practices&lt;a class="headerlink" href="#best-practices" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Validate trusted sources&lt;/strong&gt; of headers&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Correctly configure&lt;/strong&gt; known proxies&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Do not blindly trust&lt;/strong&gt; client headers&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Implement logging&lt;/strong&gt; for debugging&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Real reliability&lt;/strong&gt; depends more on your network architecture than on the specific headers used.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Original source&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="Networking"></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: Configuring X-Forwarded-For in Reverse Proxy</title><link href="https://pablocaro.es/en/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:/en/nginx-x-forwarded-for-reverse-proxy</id><summary type="html">&lt;p&gt;How to configure Nginx to pass the client's real IP to backend servers using the X-Forwarded-For header in reverse proxy configurations&lt;/p&gt;</summary><content type="html">&lt;p&gt;When you use &lt;strong&gt;Nginx as a reverse proxy&lt;/strong&gt;, you need the backend server to see the &lt;strong&gt;client's real IP&lt;/strong&gt;, not the proxy's IP. The &lt;code&gt;X-Forwarded-For&lt;/code&gt; header solves this problem.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;a class="headerlink" href="#the-problem" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Without configuration, the backend server only sees the Nginx proxy IP, losing crucial information about the real client for:
- Accurate &lt;strong&gt;access logs&lt;/strong&gt;
- Correct &lt;strong&gt;geolocation&lt;/strong&gt;
- &lt;strong&gt;Rate limiting&lt;/strong&gt; by real IP
- Reliable &lt;strong&gt;traffic analysis&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="solution-1-simple-ip"&gt;Solution 1: Simple IP&lt;a class="headerlink" href="#solution-1-simple-ip" 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;Usage&lt;/strong&gt;: When Nginx is the only proxy in the chain.
&lt;strong&gt;Result&lt;/strong&gt;: Header contains only the original client IP.&lt;/p&gt;
&lt;h2 id="solution-2-ip-chain"&gt;Solution 2: IP Chain&lt;a class="headerlink" href="#solution-2-ip-chain" 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;Usage&lt;/strong&gt;: When there are multiple proxies in the chain.
&lt;strong&gt;Result&lt;/strong&gt;: Preserves existing IPs and adds the current one.&lt;/p&gt;
&lt;h2 id="practical-configuration"&gt;Practical Configuration&lt;a class="headerlink" href="#practical-configuration" 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="related-headers"&gt;Related Headers&lt;a class="headerlink" href="#related-headers" 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;: Most recent client IP&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;X-Forwarded-For&lt;/strong&gt;: Complete chain of IPs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;X-Forwarded-Proto&lt;/strong&gt;: Original protocol (http/https)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Host&lt;/strong&gt;: Original hostname&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="verification"&gt;Verification&lt;a class="headerlink" href="#verification" 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;# On the backend server, check the 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;# Or in your application&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="security-considerations"&gt;Security Considerations&lt;a class="headerlink" href="#security-considerations" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Important&lt;/strong&gt;: X-Forwarded-* headers can be forged by clients. In production environments, ensure that only your proxy sets them.&lt;/p&gt;
&lt;p&gt;This configuration is essential to maintain &lt;strong&gt;full traceability&lt;/strong&gt; in reverse proxy architectures.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Original source&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="DevOps"></category><category term="nginx"></category><category term="reverse-proxy"></category><category term="x-forwarded-for"></category><category term="real-ip"></category><category term="configuration"></category></entry><entry><title>SQL: Select Rows with Max Value per Group</title><link href="https://pablocaro.es/en/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:/en/sql-select-rows-max-value</id><summary type="html">&lt;p&gt;SQL techniques to select complete rows with the maximum value of a column within groups, comparing performance of subqueries, joins, and window functions&lt;/p&gt;</summary><content type="html">&lt;p&gt;A common problem in SQL: &lt;strong&gt;how to select the complete rows that contain the maximum value of a column for each group?&lt;/strong&gt; Not just the maximum value, but the &lt;strong&gt;entire row&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;a class="headerlink" href="#the-problem" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Given a table with &lt;code&gt;id&lt;/code&gt; and &lt;code&gt;rev&lt;/code&gt; (revision), we want to get the complete row with the highest &lt;code&gt;rev&lt;/code&gt; for each &lt;code&gt;id&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="solution-1-subquery-with-in"&gt;Solution 1: Subquery with IN&lt;a class="headerlink" href="#solution-1-subquery-with-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;Pros&lt;/strong&gt;: Simple and readable
&lt;strong&gt;Cons&lt;/strong&gt;: Can be slow on large tables&lt;/p&gt;
&lt;h2 id="solution-2-join-with-subquery"&gt;Solution 2: Join with Subquery&lt;a class="headerlink" href="#solution-2-join-with-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;Pros&lt;/strong&gt;: Better performance than IN
&lt;strong&gt;Cons&lt;/strong&gt;: More verbose&lt;/p&gt;
&lt;h2 id="solution-3-window-functions-mysql-8"&gt;Solution 3: Window Functions (MySQL 8+)&lt;a class="headerlink" href="#solution-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;Pros&lt;/strong&gt;: More efficient for large datasets
&lt;strong&gt;Cons&lt;/strong&gt;: Requires modern SQL versions&lt;/p&gt;
&lt;h2 id="performance-considerations"&gt;Performance Considerations&lt;a class="headerlink" href="#performance-considerations" 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; are usually the most efficient&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Joins&lt;/strong&gt; balance readability and performance&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Subqueries with IN&lt;/strong&gt; are the simplest but less scalable&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="when-to-use-each"&gt;When to Use Each?&lt;a class="headerlink" href="#when-to-use-each" 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;: Small tables, simple code&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Join&lt;/strong&gt;: Balance between performance and compatibility&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Window functions&lt;/strong&gt;: Large datasets, modern SQL available&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The most robust and modern pattern is &lt;strong&gt;window functions&lt;/strong&gt;, which also allow handling ties and edge cases more elegantly.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Original source&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="SQL"></category><category term="sql"></category><category term="mysql"></category><category term="queries"></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><category term="python"></category></entry><entry><title>Repeating a function with threads</title><link href="https://pablocaro.es/en/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:/en/repetiendo-una-función-con-hilos</id><summary type="html">&lt;p class="first last"&gt;Let's use threads in python to periodically execute a function&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I have a service (with &lt;a class="reference external" href="https://github.com/jnrbsn/daemonocle"&gt;daemonocle&lt;/a&gt; which I will talk about another day) running all the time on an &lt;a class="reference external" href="http://www.hardkernel.com/main/products/prdt_info.php"&gt;odroid c1&lt;/a&gt;.
I considered it was better to control with threads some periodic tasks that I have to repeat (which I was already using)
instead of the typical cron entry.&lt;/p&gt;
&lt;p&gt;This way I can better control their operation. The advantages for me in this case and for these specific tasks:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;I already have the logging configuration performed by the main process. These tasks leave their info in my rotational logs.&lt;/li&gt;
&lt;li&gt;Stopping (starting) the service stops everything at once.&lt;/li&gt;
&lt;li&gt;I measure resource consumption as a whole&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is the snippet used.&lt;/p&gt;
&lt;p&gt;In &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;In &lt;code&gt;main.py&lt;/code&gt; you can see its usage, one of the functions receives a parameter:&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;I should tell you that I stop these threads and clean resources in the shutdown callback event which is one of the best things about &lt;code&gt;daemonocle&lt;/code&gt;.
You already know that python is not capable of fully parallelizing threads due to the GIL, but for simple tasks like these with very little load
it is simple and works perfectly.&lt;/p&gt;
</content><category term="Programming"></category><category term="python"></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="Sistemas"></category><category term="django"></category><category term="python"></category></entry><entry><title>Install InSync on Opensuse</title><link href="https://pablocaro.es/en/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:/en/instalar-insync-en-opensuse</id><summary type="html"></summary><content type="html">&lt;p&gt;I've been testing &lt;a class="reference external" href="https://www.insynchq.com/"&gt;insync&lt;/a&gt; for several days and I think I'll happily pay for it.&lt;/p&gt;
&lt;p&gt;I downloaded two packages, one for the dolphin context menu and another (the fedora one) for the program itself:&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;Everything working perfectly on my opensuse&lt;/p&gt;
</content><category term="Systems"></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="Sistemas"></category><category term="linux"></category></entry><entry><title>NetHogs see consumed bandwidth</title><link href="https://pablocaro.es/en/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:/en/nethogs-ver-ancho-de-banda-consumido</id><summary type="html">&lt;p&gt;To measure the bandwidth I am using right now, I use NetHogs&lt;/p&gt;
&lt;p&gt;By default, it looks for a network interface called &amp;quot;eth0&amp;quot;. If it doesn't find it, it complains like this:&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;</summary><content type="html">&lt;p&gt;To measure the bandwidth I am using right now, I use NetHogs&lt;/p&gt;
&lt;p&gt;By default, it looks for a network interface called &amp;quot;eth0&amp;quot;. If it doesn't find it, it complains like this:&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;You can see your network configuration with the &lt;em&gt;ip&lt;/em&gt; command:&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;As you can see on my computer, I have two interfaces (apart from the loopback one) which are enp0s25 (my wired network card)
and the wifi wlp3s0, which I am connected to now and is the one I will use (I could list both).&lt;/p&gt;
&lt;p&gt;So by running:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ sudo nethogs enp0s25
&lt;/pre&gt;
&lt;p&gt;I can see the processes that are consuming the most bandwidth.&lt;/p&gt;
&lt;img alt="Nethogs" class="align-center" src="images/nethogs.png" /&gt;
&lt;p&gt;The command help is:&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="Systems"></category><category term="linux"></category></entry></feed>