<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://agarnung.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://agarnung.github.io/" rel="alternate" type="text/html" /><updated>2026-04-14T18:44:43+00:00</updated><id>https://agarnung.github.io/feed.xml</id><title type="html">agarnung.github.io</title><subtitle>This site is under continuous development</subtitle><author><name>Alejandro Garnung Menéndez</name><email>garnungalejandro@gmail.com</email></author><entry><title type="html">Computación Neuromórfica</title><link href="https://agarnung.github.io/blog/computaci%C3%B3n-neurom%C3%B3rfica" rel="alternate" type="text/html" title="Computación Neuromórfica" /><published>2026-04-08T00:00:00+00:00</published><updated>2026-04-08T00:00:00+00:00</updated><id>https://agarnung.github.io/blog/computaci%C3%B3n-neurom%C3%B3rfica</id><content type="html" xml:base="https://agarnung.github.io/blog/computaci%C3%B3n-neurom%C3%B3rfica"><![CDATA[<h2 id="introducción">Introducción</h2>

<p>Ya comentamos en <a href="./quantum-post">un post</a> alguna cosa sencilla sobre la computación cuántica (superposición, entrelazamiento de bits cuánticos o qubits…) y, por supuesto, principalmente todo el blog se fundamenta en el framework de la computación clásica (algoritmos secuenciales y deterministas, puertas lógicas y álgebra de Bool, bits como unidad básica, arquitectura de Von Neumann…), pero existe (al menos) un tercer tipo de computación que está poniéndose de moda desde hace un tiempo, y es la computación neuromórfica.</p>

<blockquote>
  <p>[!TIP]
La arquitectura de <strong>Von Neumann</strong>, en la que se basan los computadores clásicos y la gran mayoría del hardware moderno, consta principalmente de una unidad central de procesamiento (CPU), memoria principal (RAM) y unidades de entrada y salida (E/S) o periféricas, donde tanto datos como programas se almacenan en la misma memoria.
Excepcionalmente (en menor medida), también usa la arquitectura <strong>Harvard</strong>, sobre todo para microcontroladores (e.g. ATMega, PIC…) y procesadores de señales (e.g. DSPs), que tiene buses independientes porque usa memorias separadas (un bloque para instrucciones y otro distinto para datos), lo que lo hace más rápido en ciertas tareas, la poder leer una instrucción y guardar un datos simultáneamente, por ejemplo.</p>
</blockquote>

<p><img src="https://www.researchgate.net/profile/Prasanna-Date/publication/358255092/figure/fig1/AS:1118564325031937@1643697925232/Comparison-of-the-von-Neumann-architecture-with-the-neuromorphic-architecture-These_W640.jpg" alt="vonneumann-vs-neuromorphic" /></p>

<p>Es un concepto en el que ya se pensaba desde hace tiempo, pero requería algo de investigación teórica y evolución de nuevo hardware para llevar al mundo real. Si pensamos en el cerebro humano, sabemos que las neuronas no son rígidas, ni en el sentido individual (nacen y mueren neuronas constantemente a lo largo de nuestra vida [aunque esta tasa varíe con la edad]) ni en el sentido colectivo (los enlaces y sinapsis entre neuronas y grupos de neuronas se degradan, reorganizan y refuerzan de maneras impredecibles). Por tanto, las redes neuronales clásicas (con pesos y <em>biases</em>) son, en este sentido, una limitación arquitectónica muy simplificada de la realidad. Un modelo más fidedigno necesitaría dar flexibilidad a las conexiones (por lo menos), i.e. la red debería aprender cómo auto-organizarse dinámicamente.</p>

<p>En esta línea de pensamiento, los investigadores han ido desarrollando herramientas alrededor de la computación neuromórfica, que recoge esta idea y (hoy día) es capaz de emular en hardware real el comportamiento biológico del cerebro mediante el procesamiento de impulsos (<em>spikes</em>) y la integración de memoria (e.g. dinamismo de las neuronas) y cálculo (e.g. acción impulsiva de estas) en una misma unidad, eliminando el “cuello de botella” de Von Neumann.</p>

<blockquote>
  <p>[!WARNING]
El cuello de botella de la arquitectura de Von Neumann es una limitación muy conocida y frecuente que se da cuando existe una diferencia de rendimiento reconocible entre la CPU y la memoria. En esta situación, al compartir un mismo bus para datos e instrucciones, la CPU queda inactiva esperando información, ya que su velocidad de procesamiento puede superar con creces la capacidad de transferencia de la memoria (ya sea desde memoria volátil o no volátil).</p>
</blockquote>

<p>Además, hay evidencias de que la computación neuromórfica permite la ejecución de modelos de IA más eficientes y sostenibles, lo que acelera el interés institucional en ella. Y los avances en investigación han evolucionado efectivamente hasta hoy día, cuando ya existen sistemas de escala masiva y aplicaciones comerciales <em>edge</em> para desarrollar soluciones basadas en ello.</p>

<h2 id="sobre-este-enfoque">Sobre este enfoque</h2>

<p>Expuesto en 2024, <a href="https://download.intel.com/newsroom/archive/2025/es-xl-2024-04-17-intel-construye-el-sistema-neuromorfico-mas-grande-del-mundo-para-posibilitar-una-ia-mas-sostenible.pdf">Hala Point</a> es el sistema de computación neuromórfica más grande del mundo, desarrollado por Intel, en los <a href="https://www.sandia.gov/">Laboratorios Nacionales Sandía 🍉</a>, que utiliza 1152 procesadores neuromórficos <strong>Loihi 2 / 3</strong> para emular 1150 millones de neuronas (el equivalente al cerebro de un búho 🦉), logrando una eficiencia energética (aprox. &gt; 15 TOPS/W) de hasta 50 veces superior al equiparable en GPUs convencionales (con cargas de trabajo similares).</p>

<p>En el borde, destacan procesadores como el <a href="https://brainchip.com/ip/9">Akida de Brainchip</a>, que se integran en soluciones IoT funcionales e incluso en <a href="https://brainchip.com/brainchip-mercedes-neuromorphic-ev-concept-car/">vehículos</a> <a href="https://www.motor.com.co/seccion/revista-motor/vision-iconic-el-mercedes-neuromorfico_20723">de Mercedes</a>, permitiendo inferencia de altas prestaciones en tiempo real sin depender de la nube.</p>

<p><img src="https://brainchip.com/wp-content/uploads/2025/04/3.-AXI-Bus-Interconnect-diagram-1-1-1200x683.png" alt="Akida" /></p>

<p>Otros procesadores neuromórficos son <a href="https://research.ibm.com/blog/northpole-ibm-ai-chip">NorthPole</a> de IBM (arquitectura digital de alta densidad para inferencia de visión y lenguaje), <a href="https://open-neuromorphic.org/neuromorphic-computing/hardware/spinnaker-2-university-of-dresden/">SpiNNaker 1 / 2</a> de la Univ. de Manchester y la Univ. de Dresden (arquitectura masivamente paralela específicamente diseñada y optimizada para simulación o modelización cerebral), o <a href="https://www.synsense.ai/products/xylo/">Xylo</a> de SynSense (específicamente para audio, señales eléctricas o nerviosas y sensores de baja potencia).</p>

<p><em>¿En qué conceptos se basa la arquitectura neuromórfica (<strong>neuromorfología</strong>)?</em>:</p>

<ul>
  <li>Las <strong>SNN</strong> (<em>Spiking Neural Networks</em>) son la arquitectura fundamental; se basan en que la neuronas reales no transmiten valores continuos, sino “picos” (<em><strong>spikes</strong></em>) de energía en momentos discretos. Entonces, si no necesidad de disparar un evento, no hay consumo eléctrico (mayor eficiencia).</li>
  <li>El sistema (la red) solo se activa cuando detecta cambios en los datos de entrada (como pasa en un ojo humano… ni siquiera prestamos atención a lo que pasa en el <em>background</em> o fondo aunque esté visible a nuestros ojos, a no ser que aparezca algo distintivo “de repente” que nos llame la atención) (<strong>procesamiento basado en eventos</strong>), reduciendo así el consumo energético incluso en varios órdenes de magnitud.</li>
  <li>Los circuitos neuronales tienen <strong>plasticidad</strong> (pueden deformarse); pueden aprender y reconfigurar sus conexiones sinápticas en tiempo real, sin depender de un entrenamiento masivo previo en la nube.</li>
</ul>

<h2 id="librerías-y-frameworks">Librerías y frameworks</h2>

<p><em>¿Y qué librerías podemos utiliar tanto para programar el hardware (silicio) neuromórfico así como para emularlo en computadores clásicos?</em>:</p>

<table>
  <thead>
    <tr>
      <th>Librería / Framework</th>
      <th>Función Principal</th>
      <th>Enfoque de Programación</th>
      <th>Hardware Real</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Lava (Intel)</td>
      <td>Orquestación y despliegue de aplicaciones neuromórficas de extremo a extremo.</td>
      <td>Agnóstico: permite desarrollar algoritmos que corren tanto en CPUs como en chips Loihi.</td>
      <td>✅ Sí (Optimizado para Loihi 1/2)</td>
    </tr>
    <tr>
      <td>snntorch</td>
      <td>Entrenamiento de SNNs mediante gradiente descendente (backpropagation).</td>
      <td>Deep Learning: basado en PyTorch; ideal para investigadores de IA clásica.</td>
      <td>❌ No directamente (requiere exportar el modelo)</td>
    </tr>
    <tr>
      <td>Nengo</td>
      <td>Simulación de sistemas cognitivos complejos y funciones matemáticas a gran escala.</td>
      <td>Arquitectura: usa el NEF (Neural Engineering Framework) para modelar el cerebro.</td>
      <td>✅ Sí (Loihi, SpiNNaker, FPGAs)</td>
    </tr>
    <tr>
      <td>SpikingJelly</td>
      <td>Framework de aprendizaje profundo para SNNs con soporte para aceleración en GPU.</td>
      <td>PyTorch-based: muy popular para visión artificial y procesamiento de señales.</td>
      <td>⚠️ Solo vía exportación (e.g. a chips de SynSense)</td>
    </tr>
    <tr>
      <td>Rockstar</td>
      <td>Optimización de hiperparámetros y ajuste de redes de impulsos en tiempo real.</td>
      <td>Ajuste Fino: se enfoca en que la red sea eficiente y precisa bajo restricciones.</td>
      <td>❌ Principalmente simulación/emulación</td>
    </tr>
  </tbody>
</table>

<h2 id="trabajo-futuro">Trabajo futuro</h2>

<p>Entre las posibles líneas de trabajo e investigación, se abren las siguientes:</p>

<ul>
  <li><strong>Integrar</strong> computación cuántica con neuromórfica para optimizar el aprendizaje automático y la gestión de sensores. Así como <strong>complementar</strong> a las GPU actuales, no sustituirlas (e.g. usar GPU para aprendizaje y entrenamiento masivo, y procesadores neuromórficos para re-aprendizaje e inferencia).</li>
  <li>Utilizar procesadores neuromórficos como cerbro de robots humanoides modernos o robots colaborativos (<strong>Cobots</strong>), permitiéndoles interactuar físicamente con el mundo con una latencia mínima y una adaptación continua al entorno.</li>
  <li>Integrar <strong>implantes en cerebros</strong> de seres vivos que recojan, decodifiquen y exploten señales e impulsos neuronales reales, previa simulación y aprovechando su bajo consumo y mínima generación de calor.</li>
  <li>Incorporar circuitos integrados neuromórficos en <strong>dispositivos móviles</strong>, para ejecutar aplicaciones de IA (e.g. asistentes personales) locales sin sacrificar apenas batería.</li>
  <li>Aprovehcar todos sus principios de funcionamiento para cubrir aplicaciones con nuevos sensores, e.g. usando <a href="https://foro3d.com/articulos/como-funcionan-los-sensores-de-vision-basados-en-eventos.html"><strong>sensores de visión por eventos</strong></a> (o de la <strong>fuente de datos que sea</strong>), que no capturan fotogramas, sino cambios de luz por píxel (e.g. aplicable a drones de alta velocidad y viligancia 24/7).</li>
  <li>Uso de <strong>memristores</strong> (componentes electrónicos que “recuerdan” su resistencia pasada) para alcanzar densidades de sinapsis cada vez más cercanas a las del cerebro humano, en un espacio mínimo cada vez más reducido.</li>
</ul>

<p>Como desventaja, actualmente aún es un problema abierto qué <strong>algoritmos</strong> diseñar y usar <strong>para entrenar las redes neuromórficas</strong> tan eficazmente como hace el <em>backpropagation</em> y sus variantes en las redes tradicionales.</p>]]></content><author><name>Alejandro Garnung Menéndez</name><email>garnungalejandro@gmail.com</email></author><category term="computer science" /><category term="divulgation" /><summary type="html"><![CDATA[Un primer acercamiento a este paradigma de computación.]]></summary></entry><entry><title type="html">Ideas</title><link href="https://agarnung.github.io/blog/ideas" rel="alternate" type="text/html" title="Ideas" /><published>2026-04-08T00:00:00+00:00</published><updated>2026-04-08T00:00:00+00:00</updated><id>https://agarnung.github.io/blog/ideas</id><content type="html" xml:base="https://agarnung.github.io/blog/ideas"><![CDATA[<p>Pues esto, similarmente al <a href="./web-resources">post de las webs</a>, esto lo utilizaré más bien como “lista de la compra” personal en la que anotaré ideas de proyectos personales que podría hacer en un futuro indeterminado. Antes directamente creaba el repo en Github o comenzaba un Word de notas; quizá era extralimitarse. Ahora anotaré aquí las ideas con una breve descripción y las iré despachando, si eso. Por tanto, esto está sometido a modificación continua.</p>

<p>Además, condensando las ideas en unas frases me cuido de que no se difumine en algo inmanejable, sino que vayan al grano con la información crítica necesaria y suficiente.</p>

<h2 id="ideas">Ideas</h2>

<ul>
  <li>Proyecto sencillo de audio, con micrófono y Arduino: https://www.instructables.com/DIY-Arduino-Spy-Bug/.</li>
  <li>App de fotografía con Flutter preselccionando filtros y ver en tiempo real (o lo que se pueda; o pulsando un botón para ver previsualización) en preview cómo queda el filtro en la cámra, para evitar tener que post-procesar editándola. Así sacamos la foto ya editada total o parcialmente desde la propia app, hacia la galetía. No necesita (necesariamente) IA; sería procesamiento de imagen clásico (histograma, filtros, efectos avanzados…).</li>
  <li>Proyecto con simulación holográfica mediante técnica de <em>Pepper’s ghost</em> (cubículo de vidrio o cristal, pantalla, proyecto, motor para girar base…), para proeyctar e.g. videoclips musicales de Hydra. Usar <a href="https://www.amazon.es/Raspberry-Pi-Copy-Camera-pour/dp/B0BRY6MVXL?mcid=5a5e0c30e3fd3b0d9f0d0f4a7fe578e2&amp;tag=googshopes-21&amp;linkCode=df0&amp;hvadid=699858699962&amp;hvpos=&amp;hvnetw=g&amp;hvrand=1562724060649989535&amp;hvpone=&amp;hvptwo=&amp;hvqmt=&amp;hvdev=m&amp;hvdvcmdl=&amp;hvlocint=&amp;hvlocphy=9192367&amp;hvtargid=pla-2017690033194&amp;hvocijid=1562724060649989535-B0BRY6MVXL-&amp;hvexpln=0&amp;th=1">Raspi Pico o Nano</a>. <a href="https://www.instagram.com/reel/DVLvzaBjXkg/">Ejemplo</a>.</li>
  <li>App de movil con Flutter que sea una lupa con voz, para grabar con móvil texto y que vaya diciendo (TTS) lo que graba. Lupa parlante inteligente (e.g. detectar el texto útil o ROI y hacer seguimiento).</li>
  <li>Esta unidad <a href="https://es.aliexpress.com/item/1005006946443492.html?isdl=y&amp;aff_fsk=_okkENJu&amp;src=RedbrainESGeneral&amp;aff_platform=aff_feeds&amp;aff_short_key=_okkENJu&amp;pdp_npi=4%40dis%21EUR%213.13%212.35%21%21%21%21%21%40%2112000046076884059%21afff%21%21%21&amp;dp=CjwKCAjw-dfOBhAjEiwAq0RwIwNS-QMF59hz7lUb_4EJJh_AkxKrsuoPSWkdjRo2gUix_aHmwKOgxxoCPlYQAvD_BwE">ESP32 Seeed Studio XIAO ESP32C6</a> tiene WiFi 6 y es muy pequeña; tiene muy buena pinta para hacer algún proyecto a pequeña escala, quizá con pantalla TFT.</li>
  <li>Hay toda una gama de <a href="https://docs.espressif.com/projects/esp-adf/en/latest/get-started/index.html#development-board-overview">modelos de ESP32</a> que integran audio en incluso audio + cámara y se pueden usar con la <a href="https://docs.espressif.com/projects/esp-idf/en/v5.0.4/esp32c3/libraries-and-frameworks/libs-frameworks.html">librería de audio oficial de Espressif</a>, por lo que se podría utilizar estas placas “todo en uno” para algún proyecto entretenido, ahorrándonos el tener que ensamblar micrófono y/o amplificador I2S aparte.</li>
  <li>Ya hice alguna experimentación en mi PC con cuantización masiva en 2 bits mediante <a href="https://share.google/QJgu35CaWYEyjYvIb">BitNet de Microsoft</a> y tal, pero estaría bien llevarlo a un proyecto palpable a mediana escala, e.g. integrándolo en una RPi que sirva como consulta bibliográfica, o como consulta en una especie de “Internet offline” general, incluso entablando chat contra el LLM mediante Telegram o app móvil. Manteniendo privacidad total.</li>
  <li>Hay un componente rotativo muy bonito que se llama <a href="https://m.elecrow.com/pages/shop/product/details?id=208393&amp;">CrowPanel</a>, de 1,28 pulgadas (ver <a href="https://www.instructables.com/Build-Simple-Retro-Style-VFO-Variable-Frequency-Os/">este proyecto</a>, por ejemplo), que podría aprovechar para hacer un dial de efectos para la guitarra, por ejemplo (con electrónica analógica, <a href="https://github.com/torvalds/GuitarPedal">como Linus</a>, que es mucho más divertida).</li>
  <li>Diseñar un mecanismo (impresión 3D, relé, motor paso a paso, app móvil, BT…) para apagar una regleta (pulsando mecánicamente el botón, no eléctricamente, aunque se podría también) desde un botón del móvil. Inlcuso que encienda unas luces led (las luces NO las controlaría el sistema, por simplicidad; serían independientes); se puede aprovechar el mismo mecanismo de relé por radiofrecuencia o usar MQTT o la plataforma Blynk o un servidor web o algo. Por ejemplo, ver el <a href="https://www.reddit.com/r/arduino/comments/za13zz/can_ws2812b_neopixel_addressable_led_strips_be/?tl=es-es">WS2812B (NeoPixel)</a>, el <a href="https://www.amazon.es/sk6812/s?k=sk6812">SK6812 (RGBW)</a> o el <a href="https://www.ledodm.com/es/What-is-WS2815-LED-Strip-id64742207.html">WS2815</a>. Y debería ser un <a href="https://www.reddit.com/r/3Dprinting/comments/1r8srpy/desktop_companion/">gatito 3D</a> (que usa <a href="https://github.com/upiir/esp32s3_oled_dasai_mochi">gráficos de Dasia</a> personalizados) que hable con un pequeñito <a href="https://www.amazon.es/esp32-c3-super-mini/s?k=esp32+c3+super+mini">ESP32-C3 SuperMini</a> o un <a href="https://es.aliexpress.com/item/1005007004089798.html">ESP32-S3 Zero</a> por API HTTP/JSON, ESP-Now o MQTT. Iría impreso en 3D con un display TFT incluso, con 2 botones uno para cada funcionalidad: https://www.reddit.com/r/3Dprinting/comments/1r8srpy/desktop_companion/. Ver también <a href="https://kno.wled.ge/">WLED</a>, que es un framework que ya dota a un ESP32 de capacidad para controlar luces LED y con app móvil incluso. Ver también también este <a href="https://www.instructables.com/ButtonBot-a-DIY-Solution-to-Automate-Any-Switch-Wi/">ButtonBot</a>; los típicos <a href="https://www.amazon.es/fingerbot-plus/s?k=fingerbot+plus">FingerBot</a> llega a ser demasiado caro y no suele valer para interruptores de tipo <em>rocker switch</em> de <a href="https://m.media-amazon.com/images/I/512Ec9eutlL._AC_UF1000,1000_QL80_.jpg">regletas tradicionales</a>… La alternativa (cableada) es usar un <a href="https://shellyspain.com/shelly-1-gen3.html">Shelly 1</a> (relé interno), con <a href="https://apps.apple.com/es/app/shelly-smart-control/id1660045967">app propia</a>.</li>
  <li>Con un ESP32-S3-N16R8 (o incluso uno más pequeño), se puede diseñar un sistema de <strong>telefonillo simplificado</strong> (pican al timbre =&gt; se comienza a grabar =&gt; se envían los datos a un streaming al que se acceder por una aplicación =&gt; desde la aplicación web grabamos y enviamos audio por WebSockets =&gt; reproducimos el audio por un amplificador digital y un altavoz desde el ESP32 &lt;=&gt; miesntras grabamos audio con un micrófono I2S también en el ESP32). Habría que empalmar el trigger dle timbre existente con nuestro sistema mediante un optoacoplador + relé o bien mediante un sensor de corriente. Y si queda hueco para más periféricos, también poder abrir la puerta mediante señal eléctrica desde la app.</li>
  <li>Tamagotchi DIY con el ESP32 SEED S3 (ya incluye el cargador de batería de litio integrado; solo hace falta comprar la LiPo) y pantalla TFT. Simplemente se conecta el cable USB-C al XIAO y cargará la batería automáticamente. La batería debería ser de, por lo menos, 500 mAh, 3,7 V (polímero de litio) y de descarga mínima 1C, como <a href="https://es.aliexpress.com/item/1005011855121528.html?spm=a2g0o.productlist.main.2.549frdEmrdEmS6&amp;algo_pvid=f5fca22f-1343-4c71-911c-6420ddc44ecd&amp;algo_exp_id=f5fca22f-1343-4c71-911c-6420ddc44ecd-1&amp;pdp_ext_f=%7B%22order%22%3A%2234%22%2C%22eval%22%3A%221%22%2C%22fromPage%22%3A%22search%22%7D&amp;pdp_npi=6%40dis%21EUR%217.15%214.79%21%21%2155.76%2137.36%21%402103894417761127883935239e9c55%2112000056797460558%21sea%21ES%216234875547%21X%211%210%21n_tag%3A-29919%3Bd%3Ad4437884%3Bm03_new_user%3A-29895&amp;curPageLogUid=s5l0ZmbZimTK&amp;utparam-url=scene%3Asearch%7Cquery_from%3A%7Cx_object_id%3A1005011855121528%7C_p_origin_prod%3A">esta</a>, que es suficientemente pequeña (tamaño 602535). Y conectar el BLK al micro, no a alimentación, para ahorrar vida de la batería. Ojo, que el Seed XIAO no tiene conector JST; habría que soldar uno o cortar los cables de la batería (de uno en uno, para no cortocircuitarla) para soldarlos a sus pines.</li>
</ul>]]></content><author><name>Alejandro Garnung Menéndez</name><email>garnungalejandro@gmail.com</email></author><category term="misc" /><summary type="html"><![CDATA[Ideas, TODOs, potenciales proyectos postergados hasta un futuro incierto.]]></summary></entry><entry><title type="html">Casual Physics - Wheels</title><link href="https://agarnung.github.io/blog/casual-physics-wheels" rel="alternate" type="text/html" title="Casual Physics - Wheels" /><published>2026-04-05T00:00:00+00:00</published><updated>2026-04-05T00:00:00+00:00</updated><id>https://agarnung.github.io/blog/casual-physics-wheels</id><content type="html" xml:base="https://agarnung.github.io/blog/casual-physics-wheels"><![CDATA[<h2 id="por-qué-hay-más-presión-cuando-se-calientan-las-ruedas">¿Por qué hay más presión cuando se calientan las ruedas?</h2>

<p>Es bien sabido (véase <a href="https://practicatest.com/preguntas/qB/la-presion-de-los-neumaticos-es-correcta-despues-de-un-viaje-se-da-cuenta-de-que-han-aumentado-la-presion-que-debe-de-hacer/ZZyXoA===">la susodicha pregunta</a> de la que me surgió esta cuestión) que al aumentar la temperatura de las ruedas, tras un buen trayecto en coche (por ejemplo), a causa de la fricción entre estas y el suelo, la presión de los neumáticos (i.e. la presión del aire en su interior) aumenta y, por tanto, aumenta el riesgo de reventón, aparecen deformaciones y es recomendable dejar enfriar los neumáticos un tiempo prudencial.</p>

<p>Pero, <em>¿por qué pasa esto?</em></p>

<p>Este fenómeno se explica mediante principios termodinámicos, el comportamiento de los gases y las propiedades de los materiales. Revisemos algo de teoría física fundamental.</p>

<h3 id="ley-de-los-gases-ideales-relación-presión-temperatura">Ley de los gases ideales: relación presión-temperatura</h3>

<p>La ley de los gases ideales nos dice:</p>

\[P V = n R T,\]

<p>donde \(P\) es la presión del gas, \(V\) es el volumen que ocupa en su continente (e.g. el del neumático), \(n\) son los moles de gas (aquí aire) en el neumático, \(R\) es la constante universal de los gases y \(T\) es la temperatura absoluta del gas (en Kelvin; desde el <a href="https://es.wikipedia.org/wiki/Cero_absoluto">cero absoluto</a>).</p>

<blockquote>
  <p>[!WARNING]
Un <strong>gas ideal</strong> es un modelo teórico de partículas sin volumen ni atracción entre sí. Aunque en la realidad no existen, los gases nobles (como el helio) y el aire se comportan como tales en condiciones normales de presión y temperatura. El aire deja de serlo (incluso bajo asunciones débiles) solo bajo frío o presión extremos.
Por eso, aunque el aire no es un gas ideal perfecto, a las <a href="https://www.oponeo.es/herramientas/presion-neumaticos?srsltid=AfmBOoqjxPaim2s-_QEo5JbNUNM285R05hqBRcsbxhHglhKduTuicwrs">presiones típicas de los neumáticos</a> (30–35 psi, es decir 2,0–2,4 bar) y temperaturas moderadas, su comportamiento se aproxima al modelo ideal.</p>
</blockquote>

<p>El proceso físico experimentado es el siguiente: al <a href="https://www.prontubeam.com/articulos/2025-09-26-Explicacion-fundamentos-teoria-rodadura">rodar</a>, la fricción entre el neumático y el pavimento, junto a la deformación cíclica (a períodos regulares) de la goma, genera calor (\(Q\)). Esto eleva la temperatura del aire dentro del neumático (\(T \uparrow\)). Si suponemos que el volumen (\(V\)) del neumático no varía significativamente (debido a su rigidez estructural y a que el continente es estanco), un aumento de \(T\) implica un aumento proporcional de \(P\), ya que \(n\) y \(R\) son constantes. Es decir, la presión aumenta de manera natural.</p>

<p>Por ejemplo, si la temperatura aumenta de 20 °C (293 K) a 50 °C (323 K), la presión se incrementa en un factor de \(323/293 \approx 1{,}1\), es decir, un 10 % (suponiendo volumen constante).</p>

<p>Pero dejándolo enfriar, \(P\) volvería a valores inferiores (camino inverso) y al medir en frío se comprobaría que que vuelve a la normalidad.</p>

<blockquote>
  <p>[!NOTE]
Algunos neumáticos se pueden <a href="https://www.continental-neumaticos.es/b2c/tire-knowledge/nitrogen-in-tires/">hinchar con nitrógeno</a>, lo que aumenta la vida útil y minimiza la variación de presión en comparación con el aire, pues el nitrógeno tiene menor permeabilidad (las moléculas son más grandes y por tanto escapan más lentamente a través del caucho) y es menos susceptible a cambios de humedad y oxidación de la llanta, ya que es un gas seco (i.e. que no contiene moléculas de H2O).
Con respecto al <a href="https://www.youtube.com/watch?v=7z4E1Vzoeu4">tamaño molecular</a>, el nitrógeno (N2) tiene moléculas de aprox. 300 picómetros y el oxígeno (O2) de aprox. 292 picómetros.</p>
</blockquote>

<h3 id="restricciones-materiales-elasticidad-del-caucho">Restricciones materiales: elasticidad del caucho</h3>

<p>Hay un matiz en cuanto a la expansión térmica del <a href="https://www.oponeo.es/blog/temperatura-de-los-neumaticos">neumático</a>: los materiales del neumático (caucho, acero, fibras…) se <a href="https://www.grainingf1.com/como-funciona-el-caucho-en-los-neumaticos/">dilatan con el calor</a>, aumentando ligeramente \(V\). Sin embargo, la estructura rígida del neumático limita esta expansión, haciendo que el efecto dominante siga siendo el aumento de \(P\) por \(T \uparrow\).</p>

<p>El caucho es un material viscoelástico:</p>

<ul>
  <li>
    <p>Al calentarse, se vuelve más flexible o suave, pero su capacidad para mantener la forma bajo presión evita una expansión significativa.</p>
  </li>
  <li>
    <p>La energía térmica también aumenta la agitación de las moléculas de aire, incrementando la frecuencia de colisiones con las paredes del neumático (\(P \uparrow\)).</p>
  </li>
</ul>

<h2 id="proceso-de-enfriamiento">Proceso de enfriamiento</h2>

<p><em>¿Y cuánto tardará en volver a presión normal?</em> Esto depende de varios factores:</p>

<ol>
  <li>Velocidad de enfriamiento y presión inicial del aire interno (nos guía la termodinámica).</li>
  <li>Propiedades térmicas del material del neumático (nos guía la ciencia de materiales).</li>
  <li>Condiciones ambientales (o de contorno; temperatura exterior, viento, etc.).</li>
</ol>

<h3 id="enfriamiento-interno">Enfriamiento interno</h3>

<p>El aire dentro del neumático pierde calor siguiendo un decaimiento exponencial, modelado aproximadamente por la <a href="http://www.sc.ehu.es/sbweb/fisica/estadistica/otros/enfriamiento/enfriamiento.htm">ley del enfriamiento de Newton</a> como:</p>

\[T(t) = T_\mathrm{ambiente} + (T_\mathrm{inicial} - T_\mathrm{ambiente})\, e^{-k t},\]

<p>donde \(T(t)\) es la temperatura del aire en el tiempo \(t\), \(k\) es la constante de enfriamiento (depende del material y el entorno) y \(t\) es el tiempo transcurrido.</p>

<p>La presión, bajo el supuesto de volumen aproximadamente constante, se ajusta según la <a href="https://www.educaplus.org/gases/ley_gaylussac.html">ley de Gay-Lussac</a> (\(P \propto T\) a \(V\) cte.), por lo que:</p>

\[P(t) = P_\mathrm{frío}\,\frac{T(t)}{T_\mathrm{frío}}.\]

<blockquote>
  <p>[!NOTE]
La ley de Gay-Lussac establece que la presión de un volumen fijo de gas es directamente proporcional a su temperatura (en Kelvin).</p>
</blockquote>

<p>Esto constituye una <strong>solución simplificada del modelo de enfriamiento del neumático</strong>, que asume que la masa del neumático es mucho mayor que la del aire y que la temperatura del neumático cambia lentamente comparada con la del aire.</p>

<h3 id="conductividad-térmica-del-material">Conductividad térmica del material</h3>

<p>La <a href="https://es.wikipedia.org/wiki/Conductividad_t%C3%A9rmica">conductividad térmica</a> es un factor que afecta al tiempo de enfriamiento del neumático. Se refiere a la capacidad intrínseca de un material para conducir calor; cuanto menor conductividad tenga, más aislante es y cuanto mayor conductividad tenga, mejor transmite el calor. El caucho, como la mayoría de los <a href="https://hyperphysics.gsu.edu/hbasees/Tables/thrcn.html">materiales no metálicos</a>, tiene una conductividad térmica <a href="http://cte-web.iccl.es/materiales.php?a=18">bastante baja</a> (aprox. 0,1–0,2 W/(m·K)), lo que lo hace mal conductor (y por eso los disipadores electrónicos son metálicos).</p>

<blockquote>
  <p>[!NOTE]
¿Y entonces por qué los termos son de metal, si un material no metálico evitaría mejor el “escape” del calor? Pues es que no son de metal enteramente: suelen tener pared interna metálica y externa, pero estando separadas por un vacío que reduce conducción y convección; incluso a veces hay un cristal interior para aportar la deseada baja conductividad. El metal básicamente aporta robustez.</p>
</blockquote>

<p>Así, la transferencia de calor desde el aire interno al exterior se ve ralentizada usando un neumático como material continente. Los refuerzos metálicos pueden ayudar a disipar calor, pero solo parcialmente.</p>

<h3 id="condiciones-ambientales">Condiciones ambientales</h3>

<p>Aunque secundarias, son condiciones de contorno que pueden afectar al proceso. Por ejemplo, una temperatura exterior baja (e.g. 10 °C) aceleraría el enfriamiento (mayor diferencia térmica o gradiente). El viento aumentaría la convección y reduciría el tiempo de enfriamiento. Y la exposición al sol o al asfalto caliente lo ralentizaría.</p>

<h2 id="modelo-termodinámico">Modelo termodinámico</h2>

<p>Con todo esto, nuestro objetivo es estimar cuánto tardará el neumático en enfriarse, digamos, hasta temperatura ambiente, i.e.: predecir el tiempo que tarda en volver a su presión “en frío”.</p>

<p>Modelamos el neumático como dos subsistemas acoplados:</p>

<ol>
  <li>Aire interno (masa \(m_\mathrm{aire}\), calor específico \(c_\mathrm{aire}\)).</li>
  <li>Estructura del neumático (masa \(m_\mathrm{neum}\), calor específico \(c_\mathrm{neum}\)).</li>
</ol>

<blockquote>
  <p>[!NOTE]
<a href="https://areacooling.com/es/glosario-de-terminos-hvac/calor-especifico">Calor específico</a>: cantidad de calor para elevar 1 kg en un grado Kelvin. Si requiere mucha energía para calentar, el calor específico es alto.</p>
</blockquote>

<p>El <a href="https://daniilopez10.wordpress.com/segundo-corte/conduccion-conveccion-y-redaccion/">calor fluye</a> desde el aire interior hacia la estructura del neumático y luego al ambiente.</p>

<h3 id="conducción-a-través-de-la-goma">Conducción a través de la goma</h3>

<p>El flujo de calor \(\dot{Q}_\mathrm{cond}\) a través de la pared del neumático (espesor \(L\), área \(A\), conductividad \(k\)) está dado por la <a href="http://www.sc.ehu.es/sbweb/fisica_/transporte/cond_calor/conduccion/conduccion.html">ley de Fourier</a>:</p>

\[\dot{Q}_\mathrm{cond} = \frac{k A}{L}\,(T_\mathrm{aire} - T_\mathrm{neum}).\]

<p>Balance para el aire (pérdida de calor por conducción igual a disminución de energía interna):</p>

\[\dot{Q}_\mathrm{cond} = - m_\mathrm{aire}\, c_\mathrm{aire}\,\frac{\mathrm{d}T_\mathrm{aire}}{\mathrm{d}t}.\]

<p>Combinando con Fourier:</p>

\[\frac{\mathrm{d}T_\mathrm{aire}}{\mathrm{d}t} = -\frac{k A}{m_\mathrm{aire}\, c_\mathrm{aire}\, L}\,(T_\mathrm{aire} - T_\mathrm{neum}) \quad \text{(ecuación 1).}\]

<p>El signo negativo indica que \(T_\mathrm{aire}\) disminuye con el tiempo si \(T_\mathrm{aire} &gt; T_\mathrm{neum}\).</p>

<h3 id="convección-en-la-superficie-exterior">Convección en la superficie exterior</h3>

<p>El calor disipado al ambiente \(\dot{Q}_\mathrm{conv}\) sigue la ley de Newton:</p>

\[\dot{Q}_\mathrm{conv} = h\, A\,(T_\mathrm{neum} - T_\mathrm{ambiente}),\]

<p>donde \(h\) es el coeficiente de convección (depende del viento, geometría, etc.).</p>

<p>Balance para la estructura del neumático: recibe calor del aire (\(\dot{Q}_\mathrm{cond}\)) y lo pierde por convección (\(\dot{Q}_\mathrm{conv}\)):</p>

\[\dot{Q}_\mathrm{cond} - \dot{Q}_\mathrm{conv} = m_\mathrm{neum}\, c_\mathrm{neum}\,\frac{\mathrm{d}T_\mathrm{neum}}{\mathrm{d}t}.\]

<p>Sustituyendo \(\dot{Q}_\mathrm{cond}\) y \(\dot{Q}_\mathrm{conv}\):</p>

\[\frac{\mathrm{d}T_\mathrm{neum}}{\mathrm{d}t} = \frac{k A}{m_\mathrm{neum}\, c_\mathrm{neum}\, L}\,(T_\mathrm{aire} - T_\mathrm{neum}) - \frac{h A}{m_\mathrm{neum}\, c_\mathrm{neum}}\,(T_\mathrm{neum} - T_\mathrm{ambiente}) \quad \text{(ecuación 2).}\]

<h3 id="sistema-completo">Sistema completo</h3>

<p>Sin simplificaciones adicionales y considerando todas las interacciones térmicas y masas, se obtiene un sistema acoplado de ecuaciones diferenciales para \(T_\mathrm{aire}(t)\) y \(T_\mathrm{neum}(t)\).</p>

<p>Definiciones útiles:</p>

\[\alpha = \frac{k A}{m_\mathrm{aire}\, c_\mathrm{aire}\, L}, \quad
\beta = \frac{k A}{m_\mathrm{neum}\, c_\mathrm{neum}\, L}, \quad
\gamma = \frac{h A}{m_\mathrm{neum}\, c_\mathrm{neum}}.\]

<p>Y una forma linealizada del acoplamiento puede escribirse como:</p>

\[\frac{\mathrm{d}}{\mathrm{d}t}
\begin{pmatrix} T_\mathrm{aire} \\ T_\mathrm{neum} \end{pmatrix}
=
\begin{pmatrix} -\alpha &amp; \alpha \\ \beta &amp; -(\beta + \gamma) \end{pmatrix}
\begin{pmatrix} T_\mathrm{aire} \\ T_\mathrm{neum} \end{pmatrix}
+
\begin{pmatrix} 0 \\ \gamma\, T_\mathrm{ambiente} \end{pmatrix}.\]

<h2 id="simulación">Simulación</h2>

<p>Podemos simular este modelo térmico para ver qué pasaría si dejáramos una rueda a <strong>60 °C</strong> enfriarse al <strong>ambiente</strong> (supuesto <strong>20 °C</strong> en el script), <strong>quieta</strong>, durante <strong>2 horas</strong> de ventana de cálculo (o el recorrido que sea en la malla temporal).</p>

<p>Se usa el solver <code class="language-plaintext highlighter-rouge">odeint</code> de <strong>SciPy</strong> para ODEs de primer orden; se compara un <strong>modelo acoplado de dos nodos</strong> (aire + neumático) con un <strong>modelo simple de Newton</strong> (una sola exponencial). La <strong>presión</strong> se obtiene de la <strong>ley de Gay-Lussac</strong> a volumen casi constante:</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="n">np</span>
<span class="kn">from</span> <span class="nn">scipy.integrate</span> <span class="kn">import</span> <span class="n">odeint</span>
<span class="kn">import</span> <span class="nn">matplotlib.pyplot</span> <span class="k">as</span> <span class="n">plt</span>

<span class="c1"># --- PARÁMETROS COMUNES ---
</span><span class="n">T_amb</span> <span class="o">=</span> <span class="mi">20</span> <span class="o">+</span> <span class="mf">273.15</span>      <span class="c1"># Temperatura ambiente (20 °C en Kelvin)
</span><span class="n">T_inicial_aire</span> <span class="o">=</span> <span class="mi">60</span> <span class="o">+</span> <span class="mf">273.15</span> <span class="c1"># Neumático caliente tras viaje (60 °C)
</span><span class="n">T_inicial_neum</span> <span class="o">=</span> <span class="mi">50</span> <span class="o">+</span> <span class="mf">273.15</span> <span class="c1"># Goma caliente (50 °C)
</span><span class="n">P_frio_target</span> <span class="o">=</span> <span class="mf">2.2</span>      <span class="c1"># Presión recomendada en frío (bar)
</span>
<span class="c1"># Tiempo de simulación
</span><span class="n">t</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">linspace</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">4</span> <span class="o">*</span> <span class="mi">3600</span><span class="p">,</span> <span class="mi">1000</span><span class="p">)</span> <span class="c1"># horas en segundos
</span>
<span class="c1"># --- 1. MODELO COMPLEJO (DOS NODOS) ---
</span><span class="n">A</span> <span class="o">=</span> <span class="mf">0.6</span>                  <span class="c1"># Área de contacto térmica (m^2)
</span><span class="n">L</span> <span class="o">=</span> <span class="mf">0.02</span>                 <span class="c1"># Espesor del caucho (2 cm)
</span><span class="n">k_caucho</span> <span class="o">=</span> <span class="mf">0.15</span>          <span class="c1"># Conductividad térmica (W/m·K)
</span><span class="n">h_conv</span> <span class="o">=</span> <span class="mi">10</span>              <span class="c1"># Coeficiente convección aire quieto (W/m^2·K)
</span><span class="n">m_aire</span> <span class="o">=</span> <span class="mf">0.1</span>             <span class="c1"># Masa de aire estimada (kg)
</span><span class="n">c_aire</span> <span class="o">=</span> <span class="mi">718</span>             <span class="c1"># Calor específico aire a V cte (J/kg·K)
</span><span class="n">m_neum</span> <span class="o">=</span> <span class="mf">10.0</span>            <span class="c1"># Masa del neumático (kg)
</span><span class="n">c_neum</span> <span class="o">=</span> <span class="mi">1050</span>            <span class="c1"># Calor específico del caucho (J/kg·K)
</span>
<span class="c1"># Coeficientes del sistema de EDOs
</span><span class="n">alpha</span> <span class="o">=</span> <span class="p">(</span><span class="n">k_caucho</span> <span class="o">*</span> <span class="n">A</span><span class="p">)</span> <span class="o">/</span> <span class="p">(</span><span class="n">m_aire</span> <span class="o">*</span> <span class="n">c_aire</span> <span class="o">*</span> <span class="n">L</span><span class="p">)</span>
<span class="n">beta</span> <span class="o">=</span> <span class="p">(</span><span class="n">k_caucho</span> <span class="o">*</span> <span class="n">A</span><span class="p">)</span> <span class="o">/</span> <span class="p">(</span><span class="n">m_neum</span> <span class="o">*</span> <span class="n">c_neum</span> <span class="o">*</span> <span class="n">L</span><span class="p">)</span>
<span class="n">gamma</span> <span class="o">=</span> <span class="p">(</span><span class="n">h_conv</span> <span class="o">*</span> <span class="n">A</span><span class="p">)</span> <span class="o">/</span> <span class="p">(</span><span class="n">m_neum</span> <span class="o">*</span> <span class="n">c_neum</span><span class="p">)</span>

<span class="c1"># --- MODELO MATEMÁTICO ---
</span><span class="k">def</span> <span class="nf">modelo_termico</span><span class="p">(</span><span class="n">y</span><span class="p">,</span> <span class="n">t</span><span class="p">):</span>
    <span class="n">T_a</span><span class="p">,</span> <span class="n">T_n</span> <span class="o">=</span> <span class="n">y</span>
    <span class="c1"># dT_aire/dt = -alpha * (T_aire - T_neum)
</span>    <span class="n">dTa_dt</span> <span class="o">=</span> <span class="o">-</span><span class="n">alpha</span> <span class="o">*</span> <span class="p">(</span><span class="n">T_a</span> <span class="o">-</span> <span class="n">T_n</span><span class="p">)</span>
    <span class="c1"># dT_neum/dt = beta * (T_aire - T_neum) - gamma * (T_neum - T_ambiente)
</span>    <span class="n">dTn_dt</span> <span class="o">=</span> <span class="n">beta</span> <span class="o">*</span> <span class="p">(</span><span class="n">T_a</span> <span class="o">-</span> <span class="n">T_n</span><span class="p">)</span> <span class="o">-</span> <span class="n">gamma</span> <span class="o">*</span> <span class="p">(</span><span class="n">T_n</span> <span class="o">-</span> <span class="n">T_amb</span><span class="p">)</span>
    <span class="k">return</span> <span class="p">[</span><span class="n">dTa_dt</span><span class="p">,</span> <span class="n">dTn_dt</span><span class="p">]</span>

<span class="c1"># Resolución
</span><span class="n">sol</span> <span class="o">=</span> <span class="n">odeint</span><span class="p">(</span><span class="n">modelo_termico</span><span class="p">,</span> <span class="p">[</span><span class="n">T_inicial_aire</span><span class="p">,</span> <span class="n">T_inicial_neum</span><span class="p">],</span> <span class="n">t</span><span class="p">)</span>
<span class="n">T_aire_complejo</span> <span class="o">=</span> <span class="n">sol</span><span class="p">[:,</span> <span class="mi">0</span><span class="p">]</span>

<span class="c1"># Cálculo de la Presión (Ley de Gay-Lussac: P1/T1 = P2/T2)
# P(t) = P_frio * (T_aire(t) / T_ambiente)
</span><span class="n">P_compleja</span> <span class="o">=</span> <span class="n">P_frio_target</span> <span class="o">*</span> <span class="p">(</span><span class="n">T_aire_complejo</span> <span class="o">/</span> <span class="n">T_amb</span><span class="p">)</span>

<span class="c1"># --- 2. MODELO SIMPLE (NEWTON) ---
# Usamos un k_newton ajustado para que la pendiente inicial sea similar
</span><span class="n">k_newton</span> <span class="o">=</span> <span class="mf">0.0008</span> 
<span class="n">T_newton</span> <span class="o">=</span> <span class="n">T_amb</span> <span class="o">+</span> <span class="p">(</span><span class="n">T_inicial_aire</span> <span class="o">-</span> <span class="n">T_amb</span><span class="p">)</span> <span class="o">*</span> <span class="n">np</span><span class="p">.</span><span class="n">exp</span><span class="p">(</span><span class="o">-</span><span class="n">k_newton</span> <span class="o">*</span> <span class="n">t</span><span class="p">)</span>
<span class="n">P_newton</span> <span class="o">=</span> <span class="n">P_frio_target</span> <span class="o">*</span> <span class="p">(</span><span class="n">T_newton</span> <span class="o">/</span> <span class="n">T_amb</span><span class="p">)</span>

<span class="c1"># --- CÁLCULO DEL 95% DEL ENFRIAMIENTO ---
# El régimen permanente es T_amb. El 95% de la caída es: T_inicial - 0.95*(T_inicial - T_amb)
</span><span class="n">umbral_T</span> <span class="o">=</span> <span class="n">T_inicial_aire</span> <span class="o">-</span> <span class="mf">0.95</span> <span class="o">*</span> <span class="p">(</span><span class="n">T_inicial_aire</span> <span class="o">-</span> <span class="n">T_amb</span><span class="p">)</span>

<span class="c1"># Encontrar el índice donde se cruza el umbral
</span><span class="n">idx_complejo</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">where</span><span class="p">(</span><span class="n">T_aire_complejo</span> <span class="o">&lt;=</span> <span class="n">umbral_T</span><span class="p">)[</span><span class="mi">0</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span>
<span class="n">idx_newton</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">where</span><span class="p">(</span><span class="n">T_newton</span> <span class="o">&lt;=</span> <span class="n">umbral_T</span><span class="p">)[</span><span class="mi">0</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span>

<span class="n">t_95_complejo</span> <span class="o">=</span> <span class="n">t</span><span class="p">[</span><span class="n">idx_complejo</span><span class="p">]</span> <span class="o">/</span> <span class="mi">60</span>  <span class="c1"># en minutos
</span><span class="n">t_95_newton</span> <span class="o">=</span> <span class="n">t</span><span class="p">[</span><span class="n">idx_newton</span><span class="p">]</span> <span class="o">/</span> <span class="mi">60</span>      <span class="c1"># en minutos
</span>
<span class="c1"># --- VISUALIZACIÓN UNIFICADA ---
</span><span class="n">plt</span><span class="p">.</span><span class="n">figure</span><span class="p">(</span><span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">14</span><span class="p">,</span> <span class="mi">6</span><span class="p">))</span>

<span class="c1"># Gráfica de Temperaturas
</span><span class="n">plt</span><span class="p">.</span><span class="n">subplot</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">plot</span><span class="p">(</span><span class="n">t</span><span class="o">/</span><span class="mi">60</span><span class="p">,</span> <span class="n">T_aire_complejo</span> <span class="o">-</span> <span class="mf">273.15</span><span class="p">,</span> <span class="s">'b-'</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="s">'Aire (Modelo Complejo)'</span><span class="p">,</span> <span class="n">linewidth</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">plot</span><span class="p">(</span><span class="n">t</span><span class="o">/</span><span class="mi">60</span><span class="p">,</span> <span class="n">T_newton</span> <span class="o">-</span> <span class="mf">273.15</span><span class="p">,</span> <span class="s">'orange'</span><span class="p">,</span> <span class="n">linestyle</span><span class="o">=</span><span class="s">'dotted'</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="s">'Aire (Modelo Simple)'</span><span class="p">,</span> <span class="n">linewidth</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">axvline</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="n">t_95_complejo</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="s">'gray'</span><span class="p">,</span> <span class="n">linestyle</span><span class="o">=</span><span class="s">'--'</span><span class="p">,</span> <span class="n">alpha</span><span class="o">=</span><span class="mf">0.6</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="sa">f</span><span class="s">'95% Complejo (</span><span class="si">{</span><span class="n">t_95_complejo</span><span class="si">:</span><span class="p">.</span><span class="mi">1</span><span class="n">f</span><span class="si">}</span><span class="s"> min)'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">axvline</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="n">t_95_newton</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="s">'gray'</span><span class="p">,</span> <span class="n">linestyle</span><span class="o">=</span><span class="s">':'</span><span class="p">,</span> <span class="n">alpha</span><span class="o">=</span><span class="mf">0.6</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="sa">f</span><span class="s">'95% Simple (</span><span class="si">{</span><span class="n">t_95_newton</span><span class="si">:</span><span class="p">.</span><span class="mi">1</span><span class="n">f</span><span class="si">}</span><span class="s"> min)'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">axhline</span><span class="p">(</span><span class="n">y</span><span class="o">=</span><span class="n">T_amb</span><span class="o">-</span><span class="mf">273.15</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="s">'green'</span><span class="p">,</span> <span class="n">linestyle</span><span class="o">=</span><span class="s">':'</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="s">'Temp. Ambiente'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">title</span><span class="p">(</span><span class="s">'Evolución de la Temperatura'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">xlabel</span><span class="p">(</span><span class="s">'Tiempo (minutos)'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">ylabel</span><span class="p">(</span><span class="s">'Temperatura (°C)'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">legend</span><span class="p">()</span>
<span class="n">plt</span><span class="p">.</span><span class="n">grid</span><span class="p">(</span><span class="bp">True</span><span class="p">)</span>

<span class="c1"># Gráfica de Presión
</span><span class="n">plt</span><span class="p">.</span><span class="n">subplot</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">plot</span><span class="p">(</span><span class="n">t</span><span class="o">/</span><span class="mi">60</span><span class="p">,</span> <span class="n">P_compleja</span><span class="p">,</span> <span class="s">'purple'</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="s">'Aire (Modelo Complejo)'</span><span class="p">,</span> <span class="n">linewidth</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">plot</span><span class="p">(</span><span class="n">t</span><span class="o">/</span><span class="mi">60</span><span class="p">,</span> <span class="n">P_newton</span><span class="p">,</span> <span class="s">'orange'</span><span class="p">,</span> <span class="n">linestyle</span><span class="o">=</span><span class="s">'dotted'</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="s">'Aire (Modelo Simple)'</span><span class="p">,</span> <span class="n">linewidth</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">axvline</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="n">t_95_complejo</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="s">'gray'</span><span class="p">,</span> <span class="n">linestyle</span><span class="o">=</span><span class="s">'--'</span><span class="p">,</span> <span class="n">alpha</span><span class="o">=</span><span class="mf">0.6</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="sa">f</span><span class="s">'95% Complejo (</span><span class="si">{</span><span class="n">t_95_complejo</span><span class="si">:</span><span class="p">.</span><span class="mi">1</span><span class="n">f</span><span class="si">}</span><span class="s"> min)'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">axvline</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="n">t_95_newton</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="s">'gray'</span><span class="p">,</span> <span class="n">linestyle</span><span class="o">=</span><span class="s">':'</span><span class="p">,</span> <span class="n">alpha</span><span class="o">=</span><span class="mf">0.6</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="sa">f</span><span class="s">'95% Simple (</span><span class="si">{</span><span class="n">t_95_newton</span><span class="si">:</span><span class="p">.</span><span class="mi">1</span><span class="n">f</span><span class="si">}</span><span class="s"> min)'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">axhline</span><span class="p">(</span><span class="n">y</span><span class="o">=</span><span class="n">P_frio_target</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="s">'black'</span><span class="p">,</span> <span class="n">linestyle</span><span class="o">=</span><span class="s">'--'</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="s">'Objetivo en Frío'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">title</span><span class="p">(</span><span class="s">'Evolución de la Presión'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">xlabel</span><span class="p">(</span><span class="s">'Tiempo (minutos)'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">ylabel</span><span class="p">(</span><span class="s">'Presión (bar)'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">legend</span><span class="p">()</span>
<span class="n">plt</span><span class="p">.</span><span class="n">grid</span><span class="p">(</span><span class="bp">True</span><span class="p">)</span>

<span class="n">plt</span><span class="p">.</span><span class="n">tight_layout</span><span class="p">()</span>
<span class="n">plt</span><span class="p">.</span><span class="n">savefig</span><span class="p">(</span><span class="s">'simulation_wheel_all.png'</span><span class="p">)</span>
</code></pre></div></div>

<p>Y el resultado se ve gráficamente:</p>

<p><img src="../assets/blog_images/2026-04-05-casual-physics-wheels/simulation_wheel_all.png" alt="simulation_wheel_all" /></p>

<p>En el <strong>modelo de Newton</strong> (una sola exponencial para el aire), el enfriamiento parece <strong>uniforme</strong>. El criterio del <strong>95 %</strong> del salto térmico hasta <strong>temperatura ambiente</strong> sugiere del orden de <strong>~62,5 min</strong> para estar cerca del objetivo de <strong>~2,2 bar</strong> en frío (según el umbral definido en el script).</p>

<p>En el <strong>modelo acoplado</strong> (<strong>dos nodos</strong>: aire + neumático), la curva del aire se <strong>aplana</strong> al cabo de unos minutos porque la <strong>goma</strong> (y la estructura) siguen <strong>calientes</strong> y limitan cuánto puede bajar el aire. Eso alarga el tiempo respecto al modelo simple en <strong>~28 %</strong> (en este ejemplo: del orden de <strong>~80 min</strong> frente a <strong>~62,5 min</strong> para el mismo criterio del <strong>95 %</strong>).</p>

<p>En el modelo acoplado, el <strong>aire interno</strong> pierde temperatura rápido al inicio, pero el ritmo lo frena la <strong>goma</strong> y el <strong>acero</strong> del neumático: actúan como <strong>reserva térmica</strong> o <strong>suelo térmico</strong> (el aire no se enfría más rápido de lo que el contacto con esa masa caliente permite).</p>

<p>El sistema tarda en acercarse al <strong>equilibrio</strong> con el ambiente; damos por bueno un estado <strong>cercano al régimen permanente</strong> cuando se alcanza el <strong>95 %</strong> del camino hacia <strong>temperatura ambiente</strong>, con <strong>convección</strong> moderada (aire quieto en el script).</p>

<p>Estos resultados muestran que <strong>medir la presión en caliente</strong> tras un viaje da una <strong>lectura alta</strong> respecto al valor <strong>en frío</strong>. Si se <strong>purga aire</strong> para bajar a <strong>2,2 bar</strong> caliente, al enfriarse la <strong>presión</strong> puede quedar por debajo de lo seguro (e.g. <strong>~1,9 bar</strong>), empeorando <strong>consumo</strong> (<strong>resistencia a la rodadura</strong>, mayor <strong>área de contacto</strong>) y <strong>riesgo de hidroplaneo</strong>.</p>

<p>La <strong>baja conductividad térmica del caucho</strong> explica en buena parte que el enfriamiento no sea instantáneo: es un <strong>aislante</strong> que retiene calor.</p>

<p>El <strong>modelo de dos nodos</strong> es más fiel que el <strong>Newton</strong> simple porque el <strong>aire</strong> no intercambia calor solo con el <strong>exterior</strong>, sino con la <strong>estructura</strong> del neumático, que tiene <strong>inercia térmica</strong> propia.</p>

<p>Si usáramos <strong>solo Newton</strong>, <strong>ignoraríamos la masa</strong> del neumático y el hecho de que el calor atraviesa el <strong>caucho</strong>: sería como una <strong>capa invisible</strong> sin acumulación de calor.</p>

<p>En <strong>Newton</strong> la temperatura cae como una <strong>única exponencial</strong>; no aparece el <strong>freno</strong> que sí da la <strong>curva acoplada</strong> cuando la estructura sigue caliente. El modelo simple <strong>no modela</strong> que entre el aire y el ambiente frío hay <strong>varios kilogramos</strong> de material caliente.</p>

<p>Por tanto, el <strong>tiempo de espera</strong> calculado solo con <strong>Newton</strong> puede <strong>subestimar</strong> lo que hace falta; el <strong>modelo completo</strong> incorpora la <strong>geometría térmica</strong> relevante del caso.</p>

<p>Si nos quisiérmos poner quisquillosos, también podríamos añadir al modelo: efecto de <strong>radiacón</strong> (a altas temperaturas, el neumático irradia como cuerpo negro una cantidad no despreciable de calor hacia el suelo y el entorno, no solo por convección), de la <strong>humedad</strong> del aire real del neumático, <strong>convección natural Vs. forzada</strong> (aumentaría el valor de \(h\), e.g. a 20-30 W/(m^2·K) si hay viento) o incluso considerar que el interior de la goma está más caliente que el exterior (incorporación de la <strong>ecuación de calor en derivadas parciales</strong> para añadir dependencia espacial o análisis del <strong>número de Biot</strong> para estudiar el comportamiento de la transferencia de calor).</p>

<h2 id="referencias">Referencias</h2>

<ul>
  <li><a href="https://publicaciones.uniovi.es/catalogo/publicaciones/-/asset_publisher/pW5r/content/transmision-de-calor-apuntes-1;jsessionid=F8940E5968C12E3FCD67A7162F62F581?redirect=%2Fcatalogo%2Fpublicaciones%3Fp_p_id%3D101_INSTANCE_pW5r%26p_p_lifecycle%3D0%26p_p_state%3Dnormal%26p_p_mode%3Dview%26p_p_col_id%3Dcolumn-1%26p_p_col_pos%3D1%26p_p_col_count%3D5%26_101_INSTANCE_pW5r_c1nntag%3Dpublicacion%252C%26p_r_p_564233524_tag%3D%26_101_INSTANCE_pW5r_delta%3D12%26_101_INSTANCE_pW5r_keywords%3D%26_101_INSTANCE_pW5r_advancedSearch%3Dfalse%26_101_INSTANCE_pW5r_andOperator%3Dtrue%26cur%3D37">Transmisión de calor. Apuntes</a></li>
</ul>]]></content><author><name>Alejandro Garnung Menéndez</name><email>garnungalejandro@gmail.com</email></author><category term="physics" /><category term="thermodynamics" /><category term="divulgation" /><summary type="html"><![CDATA[Gas ideal, Gay-Lussac, enfriamiento de Newton y modelo térmico del neumático.]]></summary></entry><entry><title type="html">Maldición de la dimensionalidad en LLMs</title><link href="https://agarnung.github.io/blog/curse-of-dimensionality-in-llms" rel="alternate" type="text/html" title="Maldición de la dimensionalidad en LLMs" /><published>2026-04-04T00:00:00+00:00</published><updated>2026-04-04T00:00:00+00:00</updated><id>https://agarnung.github.io/blog/curse-of-dimensionality-in-llms</id><content type="html" xml:base="https://agarnung.github.io/blog/curse-of-dimensionality-in-llms"><![CDATA[<p>La maldición de la dimensionalidad suele aparecer/preocupar cuando trabajamos con espacios de muchas dimensiones, pero entonces, si los embeddings de lenguaje tienen son vectores de cientos o miles de dimensiones, <em>¿por qué no aparece de la misma forma que en otros problemas?</em></p>

<h2 id="qué-es-la-maldición-de-dimensionalidad">¿Qué es la maldición de dimensionalidad?</h2>

<p>Antes de nada: <a href="https://es.wikipedia.org/wiki/Maldici%C3%B3n_de_la_dimensi%C3%B3n"><strong>la maldición de la dimensionalidad</strong></a> de <a href="https://es.wikipedia.org/wiki/Richard_Bellman">Bellman</a> se refiere a varios fenómenos que ocurren cuando trabajamos en espacios de alta dimensión, que no ocurren en otros de baja dimensión:</p>

<ul>
  <li><strong>Distancias se vuelven similares</strong>: en espacios de alta dimensión, la mayoría de las distancias entre puntos tienden a ser muy similares (las distancias a los vecinos más cercanos y más lejanos ya no se vuelven muy parecias),</li>
  <li><strong>Volumen se concentra en la superficie</strong>: la mayor parte del volumen de una esfera de alta dimensión está cerca de su superficie.</li>
  <li><strong>Datos se vuelven escasos</strong>: se necesitan exponencialmente más datos para llenar un espacio de alta dimensión. El costo computacional puede crecer exponencialmente con la dimensión.</li>
</ul>

<h2 id="por-qué-no-aparece-en-embeddings-de-llms">¿Por qué no aparece en embeddings de LLMs?</h2>

<p>Hay varias razones clave por las que los <em>embeddings</em> (vectores numéricos que representan el significa semántico de unidades de lenguaje como palabras o frases) de los modelos de lenguaje no sufren tanto este problema porque:</p>

<ul>
  <li>
    <p>Los embeddings viven en un <strong>subespacio estructurado</strong>: en teoría, la maldición aparece cuando los datos están <strong>dispersos aleatoriamente</strong> en un espacio de alta dimensión. Pero los embeddings de modelos basados en la arquitectura <strong>Transformer</strong> y, por tanto, no son aleatorios, sino que se <strong>aprenden durante el entrenamiento</strong>. Asimismo, el modelo empuja tokens con significado similar a <strong>regiones cercanas</strong> del espacio y, con esto, la distribución real suele ocupar un <strong>subespacio de menor dimensión efectiva</strong>. Es decir, aunque el vector tenga 768 o 4096 dimensiones, la <strong>dimensión intrínseca</strong> puede ser mucho menor, lo que reduce el efecto de la maldición.</p>
  </li>
  <li>
    <p>Las métricas usadas funcionan bien en alta dimensión: muchos problemas de alta dimensión ocurren con ciertas métricas (como la <a href="https://es.wikipedia.org/wiki/Distancia_euclidiana">distancia euclídea</a> o <a href="https://es.wikipedia.org/wiki/Norma_vectorial#Ejemplos">norma L2</a> pura). En embeddings se usan métricas como la <a href="https://es.wikipedia.org/wiki/Similitud_coseno">similitud coseno</a>, su versión <a href="https://es.wikipedia.org/wiki/Similitud_coseno#Similitud_Coseno_Suave">suave</a> u <a href="https://www.youtube.com/watch?v=YDdKiQNw80c">otras</a>. Estas métricas funcionan bien porque <strong>normalizan magnitudes</strong> (pues no dependen del tamaño absoluto del vector) y <strong>comparan ángulos, no distancias puras</strong> (que hace que la separación semántica sea estable incluso con miles de dimensiones).</p>
  </li>
</ul>

<blockquote>
  <p>[!TIP]
La magnitud puede introducir ruido en el espacio de embeddings, porque esta puede variar por razones irrelevantes (e.g. frecuencia de palabras, escala que aplique el modelo, etc.) y no siempre captura significado semántico puro.</p>
</blockquote>

<p>No solo esto, sino que aún más dimensiones pueden ayudar. Esto es porque en representaciones semánticas, <strong>más dimensiones permiten codificar muchas propiedades a la vez</strong>. Sin embargo, <strong>cada dimensión no tiene un significado propio</strong>, sino que el significado está <strong>distribuido entre muchas dimensiones</strong> (<a href="https://deepai.org/machine-learning-glossary-and-terms/distributed-representation"><em>Distributed Representation</em></a>). Así, propiedades como “diferenciado” o “agraviado” no están en un solo eje, sino en <strong>patrones del vector completo</strong>.</p>

<blockquote>
  <p>[!TIP] Más dimensiones == más capacidad para representar combinaciones complejas, no “más etiquetas individuales”.</p>
</blockquote>

<p>Además, el entrenamiento regulariza el espacio. Esto significa que los embeddings se aprenden con <strong>millones o miles de millones de ejemplos</strong> y durante ese proceso se evita que los vectores se vuelvan arbitrarios, pues aparecen <strong>estructuras geométricas útiles</strong> de manera natural; algo observado en trabajos con modelos de aprendizaje de representaciones en espacios vectoriales como <a href="https://arxiv.org/abs/1301.3781">Word2Vec</a>, <a href="https://nlp.stanford.edu/pubs/glove.pdf">GloVe</a> y otros modelos modernos de embeddings.</p>

<p><em>El espacio aprendido tiene estructura que mitiga los problemas típicos de alta dimensión.</em></p>

<h2 id="un-ejemplo-famoso">Un <a href="https://p.migdal.pl/blog/2017/01/king-man-woman-queen-why/">ejemplo famoso</a></h2>

\[\text{vector("rey")} - \text{vector("hombre")} + \text{vector("mujer")} \approx \text{vector("reina")}\]

<p>Esto muestra que el espacio aprendido tiene <strong>estructura algebraica</strong>; no es aleatorio.</p>

<p>Lo que sí aparece es <strong>concentración de distancias</strong>. En alta dimensión ocurre un fenómeno relacionado, y es que muchas distancias se vuelven similares; algo llamado como <em><strong>concentration of measure</strong></em>.</p>

<p>Por eso en sistemas reales se usan técnicas como <strong>normalización</strong> (para estabilizar las distancias), <strong>reducción de dimensión (PCA)</strong> (para reducir a dimensiones más manejables) o <a href="https://www.elastic.co/blog/understanding-ann"><strong>índices ANN (<em>Approximate Nearest Neighbor</em>)</strong></a> (para búsqueda eficiente en alta dimensión).</p>

<blockquote>
  <p>[!NOTE] Visualización conceptual
Imagínese un espacio de 4096 dimensiones como una habitación enorme. La maldición de dimensionalidad diría que los datos están dispersos aleatoriamente por toda la habitación.</p>

  <p>Pero en embeddings de LLMs:</p>

  <ul>
    <li>Los datos están <strong>organizados en regiones</strong> (palabras similares cerca).</li>
    <li>Ocupan un <strong>subespacio de menor dimensión</strong> (como una superficie de un objeto, o en las zonas de calor de la habitación).</li>
    <li>Las <strong>métricas usadas</strong> (e.g. <em>cosine similarity</em>) son estables en este espacio estructurado.</li>
  </ul>
</blockquote>

<p>Un acercamiento al tema algo menos básico: <a href="https://www.youtube.com/watch?v=BbYV8UfMJSA">Lecture 4 “Curse of Dimensionality / Perceptron” - Cornell CS4780 SP17</a>.</p>]]></content><author><name>Alejandro Garnung Menéndez</name><email>garnungalejandro@gmail.com</email></author><category term="computer science" /><category term="divulgation" /><summary type="html"><![CDATA[¿Por qué no aparece la maldición de dimensionalidad en embeddings de LLMs?]]></summary></entry><entry><title type="html">Claves y criptografía cuántica</title><link href="https://agarnung.github.io/blog/quantum-post" rel="alternate" type="text/html" title="Claves y criptografía cuántica" /><published>2026-03-24T00:00:00+00:00</published><updated>2026-03-24T00:00:00+00:00</updated><id>https://agarnung.github.io/blog/quantum-post</id><content type="html" xml:base="https://agarnung.github.io/blog/quantum-post"><![CDATA[<p>Trabajando en <a href="https://www.fundacionctic.org/es/proyectos?combine=&amp;field_sector_target_id=All&amp;field_ambito_geografico_target_id=All&amp;field_tecnologias_target_id=34&amp;ambito_tid=All">una empresa</a> donde la investigación en computación cuántica es el pan de cada día, me parece inevitable dedicar un post a este tema, por introductorio que sea (tampoco doy para más).</p>

<p>Una pregunta común que me hago es: <strong><em>¿por qué no podemos simplemente aumentar el tamaño de las claves para protegernos de los computadores cuánticos?</em></strong> La respuesta no es muy compleja pero tiene raíces muy arraigadas en la seguridad criptográfica.</p>

<h2 id="el-problema-fundamental">El problema fundamental</h2>

<p>La razón fundamental de la cuestión es que <strong>las computadoras cuánticas no atacan al cifrado simétrico y al asimétrico de la misma manera</strong>. En el cifrado simétrico aumentar el tamaño de la clave es una solución práctica, mientras que en el asimétrico es ineficaz contra la amenaza cuántica, debido a la forma en que funcionan sus algoritmos…</p>

<h2 id="por-qué-el-impacto-de-shor-vs-grover">…¿Por qué? El impacto de Shor vs. Grover</h2>

<h3 id="cifrado-simétrico-aes-256-des-blowfish-etc">Cifrado simétrico (AES-256, DES, Blowfish, etc.)</h3>

<p>Todo (en teoría) cifrado simétrico se ve afectado (debilitado) por el <a href="https://es.wikipedia.org/wiki/Algoritmo_de_Grover"><strong>algoritmo de Grover</strong></a>.</p>

<ul>
  <li>Este algoritmo reduce la seguridad a la mitad.</li>
  <li>Por tanto una clave de 256 bits equivale a una de 128 bits frente a un ataque cuántico.</li>
  <li><strong>Esto sigue siendo seguro</strong>, pues AES-256 con Grover sigue siendo equivalente a AES-128 clásico, que es considerado razonablemente seguro.</li>
</ul>

<blockquote>
  <p>[!NOTE] Por dar números, con fuerza bruta, el espacio de las claves es de \(2^{128} \approx 3.4 \times 10^{38}\). Siendo muy optimistas, una máquina muy potente hoy día puede hacer 2.000 millones de intentos de claves diferentes por segundo. Pues usemos 1.000 veces más por margen de seguridad, i.e., 2 millones de millones (\(2 \times 10^{12}\)) de claves/segundo. Esto son \(3.4 \times 10^{38} / 2 \times 10^{12} = 1.7 \times 10^{26}\) segundos \(\approx 5.39 \times 10^{18}\) años, que son aprox. <strong>5.39 trillones de años</strong> (escala larga). El universo tiene \(\sim 1.38 \times 10^{10}\) años. Es decir, tardaríamos <strong>~390 millones de veces</strong> la edad del universo.</p>

  <p>Y si el algoritmo de Grover reduce de \(2^n\) a \(2^{n/2}\), la seguridad efectiva de un AES-128 se reduciría a \(\approx 2^{64}\). Supongamos que una máquina es capaz de hacer \(10^{12}\) operaciones cuánticas/segundo (sobredimensionando); tardaría aprox. \(2^{64} / 10^{12} \approx 1.8 \times 10^7\) s \(\approx 0.6\) años \(\sim 7\) meses. Esto suponiendo un escenario perfecto (millones de qubits lógicos, corrección de errores masivos, circuitos extremadamente profundos, etc.).</p>

  <p><strong>Conclusión:</strong> Mientras que el AES-128 es inexpugnable para la computación clásica, la llegada de un ordenador cuántico maduro obligará a dar el salto al <strong>AES-256</strong>. Al duplicar la longitud de la clave, incluso tras la reducción de Grover, nos quedaría una seguridad efectiva de \(2^{128}\), devolviendo el tiempo de ruptura a cifras astronómicas imposibles de alcanzar, pues pasar de 128 a 256 bits no duplica el tiempo, sino que lo multiplica por 2128 (un número de 39 dígitos).</p>
</blockquote>

<p><strong>Solución práctica</strong>: para el cifrado simétrico, con simplemente duplicar el tamaño de la clave (e.g. de 128 a 256 bits), es suficiente para contar con una seguridad critpgráfica fiable hoy día.</p>

<h3 id="cifrado-asimétrico-rsa-ecc">Cifrado asimétrico (RSA, ECC)</h3>

<p>Todo (en teoría) cifrado asimétrico se ve afectado (roto <em>por completo</em>) por el <a href="https://es.wikipedia.org/wiki/Algoritmo_de_Shor"><strong>algoritmo de Shor</strong></a>.</p>

<p>Shor <strong>no es un ataque de fuerza bruta</strong>, sino un algoritmo matemático diseñado para encontrar períodos en funciones. Lo que hace es romper la <strong>factorización de números primos</strong> (base de RSA) de manera <strong>exponencial</strong>, no lineal, lo que significa que el tiempo necesario para factorizar números inmensos crece de manera irrisoria con el tamaño del número, haciendo plausible un problema que era intratable con ordenadores clásicos. También rompe el <strong>problema del logaritmo discreto</strong> (base de la <a href="https://www.digicert.com/es/faq/cryptography/what-is-elliptic-curve-cryptography">Criptografía de Curva Elíptica [ECC]</a>).</p>

<p>Aquí, el problema es que aumentar el tamaño de la clave no soluciona eficazmente esta carencia intrínseca que logra explotar el algoritmo ante Shor.</p>

<blockquote>
  <p>[!TIP]
Aumentar el tamaño de la clave es como usar una llave más pesada para abrir una puerta vulnerable cuando hay algo (los computadores cuánticos) que puede hacer desaparecer la puerta. La solución es cambiar el tipo de acceso o puerta, directamente.</p>
</blockquote>

<p><em>¿Y por qué es ineficiente aumentar el tamaño de las claves asimétricas?</em></p>

<p>Aumentar el tamaño de las claves asimétricas (por ejemplo, de RSA-2048 a RSA-4096) no ofrece una protección proporcional, sino que lo único que causa es que se descifrado (de lado del usuario o receptor estándar) se vuelva impracticable y exponencialmente más costoso. Es decir, para resistir una computadora cuántica, las claves asimétricas tendrían que ser <strong>tan grandes</strong> que harían que los meros intercambios de claves fueran inusualmente lentos e imposibles en dispositivos con recursos limitados (IoT, móviles, etc.). Sin hablar de la mera genercaión de estas claves.</p>

<p>Es decir, que realmente la solución <strong>no es más grande, sino diferente</strong>. Básicamente la solución más fácil al cifrado asimétrico no es usar claves más grandes, sino <strong>migrar a la Criptografía Post-Cuántica (PQC)</strong>.</p>

<p><em>¿Qué es la criptografía post-cuántica?</em></p>

<p>Son <strong>nuevos algoritmos</strong> (basados en redes o celosía [<em>lattices</em>], códigos, o isogenias [aplicación lineal entre dos curvas elıpticas]) que no dependen de la factorización de números primos, ni de los logaritmos discretos de métodos asimétricos. Son un reto incluso para las computadoras cuánticas.</p>

<p>Ejemplos de algoritmos post-cuánticos son:</p>

<ul>
  <li><strong>CRYSTALS-Kyber</strong>: cifrado de clave pública basado en redes.</li>
  <li><strong>CRYSTALS-Dilithium</strong>: firma digital basada en redes.</li>
  <li><strong>SPHINCS+</strong>: firma digital basada en hash.</li>
  <li><strong>NTRU</strong>: criptosistema de clave pública.</li>
</ul>

<p>Estos algoritmos están siendo estandarizados por el <strong>NIST (National Institute of Standards and Technology)</strong> en un proceso que comenzó en 2016. Aunque los fundamentos de los sistemas post-cuánticos ya nacieron mucho antes, e.g. NTRU, <a href="https://dl.acm.org/doi/10.1145/237814.237866">Shor</a> y <a href="https://dl.acm.org/doi/10.1145/237814.237866">Grover</a> son de los 90.</p>

<blockquote>
  <p>[!NOTE]
¿Y por qué si toda esta teoría es de los 90, no se hizo famosa la computación cuántica hasta hace unos pocos años?
Pues porque si bien el “software” ya estaba estudiado, por aquel entonces no existía la maquinaria o “hardware” para ejecutar los algoritmos. Hoy día, la <strong>superconducción</strong> y los <a href="https://www.ionq.com/"><strong>láseres avanzados</strong></a> permiten crear <strong>qubits reales y estables</strong>.</p>
</blockquote>

<h2 id="estado-actual-y-migración">Estado actual y migración</h2>

<p>Aunque las computadoras cuánticas capaces de romper RSA-2048 aún no existen:</p>

<ul>
  <li>Existe el <strong><em>“harvest now, decrypt later”</em></strong>: los atacantes están almacenando datos cifrados hoy para descifrarlos cuando tengan computadoras cuánticas (véase <a href="https://www.platinumciber.com/es/y2q-apocalipsis-cuantico/">Y2Q (<em>Year-to-Quantum</em>) o “Día-Q”</a>).</li>
  <li>La <strong>migración toma tiempo</strong>: cambiar sistemas criptográficos es un proceso largo y complejo.</li>
</ul>

<p>Por eso, una estrategia factible de mitigación valora:</p>

<ol>
  <li><strong>Identificar sistemas críticos</strong>: qué necesita protección post-cuántica.</li>
  <li><strong>Evaluar algoritmos PQC</strong>: probar y validar nuevos algoritmos.</li>
  <li><strong>Implementar híbridos</strong>: usar tanto algoritmos clásicos como post-cuánticos durante la transformación.</li>
  <li><strong>Planificar la transición</strong>: hacer una migración gradual y controlada según un órden de prioridad lógico.</li>
</ol>

<h2 id="resumen">Resumen</h2>

<p>En resumen, podemos rescatar 4 ideas atómicas de todo esto:</p>

<ul>
  <li>Cifrado <strong>simétrico</strong> es vulnerable: aumentar claves funciona (duplicar tamaño es suficiente; Grover no ofrece ventaja práctica ilimitada).</li>
  <li>Cifrado <strong>asimétrico</strong> es quebreable: aumentar claves no funciona (Shor rompe la base matemática).</li>
  <li><strong>Solución</strong>: migrar a <strong>criptografía post-cuántica</strong>.</li>
  <li><strong>Estado actual</strong>: algoritmos PQC están siendo estandarizados por NIST.</li>
</ul>

<h2 id="referencias">Referencias</h2>

<ul>
  <li>NIST Post-Quantum Cryptography Standardization: <a href="https://csrc.nist.gov/projects/post-quantum-cryptography">https://csrc.nist.gov/projects/post-quantum-cryptography</a></li>
  <li>Shor, P. W. (1997). “Polynomial-time algorithms for prime factorization and discrete logarithms on a quantum computer.” <em>SIAM Journal on Computing</em>, 26(5), 1484-1509. <a href="https://epubs.siam.org/doi/10.1137/S0097539795293172">Paper</a>.</li>
  <li>Grover, L. K. (1996). “A fast quantum mechanical algorithm for database search.” <em>Proceedings of the 28th Annual ACM Symposium on Theory of Computing</em>. <a href="https://dl.acm.org/doi/10.1145/237814.237866">Paper</a>.</li>
  <li>Nielsen, M. A., &amp; Chuang, I. L. (2010). Quantum Computation and Quantum Information. Cambridge University Press.</li>
</ul>]]></content><author><name>Alejandro Garnung Menéndez</name><email>garnungalejandro@gmail.com</email></author><category term="computer science" /><category term="divulgation" /><summary type="html"><![CDATA[Computación cuántica y criptografía - por qué aumentar claves no es (o puede no ser) suficiente]]></summary></entry><entry><title type="html">¿Qué había antes de los LLMs?</title><link href="https://agarnung.github.io/blog/llms-history" rel="alternate" type="text/html" title="¿Qué había antes de los LLMs?" /><published>2026-03-17T00:00:00+00:00</published><updated>2026-03-17T00:00:00+00:00</updated><id>https://agarnung.github.io/blog/llms-history</id><content type="html" xml:base="https://agarnung.github.io/blog/llms-history"><![CDATA[<p>Hoy en día, el flujo estándar para interactuar con la mayoría de modelos de lenguaje grandes (LLMs) es usar <code class="language-plaintext highlighter-rouge">requests</code> con la API de OpenAI (principalmente) o servicios similares. Pero esto no es desde hace mucho (bueno, <a href="https://en.wikipedia.org/wiki/Large_language_model#History">ni los LLM</a> son de hace mucho). En este post visitaremos brevísimamente cuáles eran los “estándares” antes y cómo ha ido evolucionando la forma de programar e interactuar con los modelos de lenguaje.</p>

<p>Aunque realmente se puede resumir en que no hubo un único estándar <em>per se</em>, pero sí una evolución que podemos dividir en varias etapas claras.</p>

<h2 id="pre-2018-antes-de-los-llms-modernos">Pre-2018: antes de los LLMs modernos</h2>

<p>Antes de 2018 (aproximadamente), <strong>no existía un “estándar LLM”</strong> porque no existían LLMs como servicio. Lo habitual era trabajar con:</p>

<ul>
  <li><strong>Modelos locales</strong>: ejecutados directamente en tu máquina.</li>
  <li><strong>Librerías específicas por framework</strong>: cada framework tenía su propia interfaz.</li>
  <li><strong>APIs directas</strong>: llamadas a funciones en Python (NLTK, Gensim, Scikit-learn, spaCy…) o C++ (MITIE, Dlib, OpenNLP…), sin HTTP ni JSON.</li>
</ul>

<p>O sea, se trabajaba alrededor de librerías que se centraban más en el <em><strong>feature engineering</strong></em> manual y modelos estadísticos o predictivos de <strong>n-gramas</strong>, o <strong>redes neuronales</strong> relativamente poco pesadas computacionalmente, a diferencia de los modelos basados en <strong>Transformers</strong> actuales.</p>

<p>Por ejemplo, en la época, entre otras técnicas muy utilizadas destacan:</p>

<ul>
  <li><a href="https://lamyiowce.github.io/word2viz/"><strong>word2vec</strong></a>: para manejo de embeddings de palabras, sin arquitectura de lenguaje. U otros modelos como <strong>seq2seq</strong> usando LSTM.</li>
  <li><a href="https://www-nlp.stanford.edu/projects/glove/"><strong>GloVe</strong></a>: aprendizaje no supervisado para obtener embeddings globales de palabras, modelos estáticos.</li>
  <li><a href="https://github.com/facebookresearch/fastText?tab=readme-ov-file"><strong>fastText</strong></a>: “library for efficient learning of word representations and sentence classification”.</li>
  <li><strong><a href="https://arxiv.org/abs/1412.3555">RNNs</a>/<a href="https://direct.mit.edu/neco/article-abstract/9/8/1735/6109/Long-Short-Term-Memory?redirectedFrom=fulltext">LSTMs</a>/<a href="https://www.researchgate.net/publication/349277064_Bidirectional_LSTM_Networks_for_Improved_Phoneme_Classification_and_Recognition">Bidirectional-LSTMs</a>/<a href="https://arxiv.org/abs/1406.1078">GRU</a></strong> como predecesores de los Transformers/LLMs: modelos recurrentes con arquitecturas diseñadas manualmente o, en algunos casos, combinadas con modelos probabilísticos como <a href="https://dl.acm.org/doi/10.5555/645530.655813">CRF</a> o <a href="http://yann.lecun.com/exdb/publis/pdf/lecun-06.pdf">grafos de factores</a> o <a href="http://yann.lecun.com/exdb/publis/pdf/lecun-98.pdf">GTNs</a>, que podía resultar en sistemas bastante complejos.</li>
</ul>

<blockquote>
  <p>[!IMPORTANT] Lejos de estar obsoletas, muchas de estas herramientas son idóneas hoy día, incluso con todos los avances “académicos” y en <em>benchmarks</em>, para ejecutar modelos de lenguaje y aplicaciones de PLN en dispositivos de muy bajas prestaciones, o en tiempo real, o cuando no se dispone de grandes conjuntos de datos.</p>
</blockquote>

<p>Las interfaces típicamente eran programáticas o por <a href="https://fasttext.cc/docs/en/cheatsheet.html#content">línea de comandos (CLI)</a>, del estilo:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Python API directa
</span><span class="o">&gt;</span> <span class="n">model</span><span class="p">.</span><span class="n">generate</span><span class="p">(</span><span class="n">tokens</span><span class="p">)</span>
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># CLI</span>
<span class="nv">$ </span>fairseq-generate
</code></pre></div></div>

<p>Es decir, no se usaba HTTP, JSON, servicios remotos ni nada similar (al menos en general). Todo era local y directo.</p>

<h2 id="2018-2020-con-transformers-pero-aún-en-fase-de-investigación">2018-2020: con transformers pero aún en fase de investigación</h2>

<p>Con la llegada de [<strong>BERT (<em>Bidirectional Encoder Representations from Transformers__)**](https://www.ultralytics.com/glossary/bert-bidirectional-encoder-representations-from-transformers) (2018) y <a href="https://es.wikipedia.org/wiki/GPT-2">**GPT-2**</a> (2019), aparecieron los transformers modernos, pero el estándar _de facto</em> seguía siendo **llamadas a librerías, no a servicios.</strong></p>

<p>Aunque antes de los Transformers de <a href="https://arxiv.org/abs/1706.03762"><em>Attention is All You Need</em></a>, ya se habría <a href="https://arxiv.org/abs/1409.0473">introducido la atención</a> como un parche para mejorar las LSTMs en traducción.</p>

<p>Los frameworks que más se usaban era, aun así:, <strong>Hugging Face transformers</strong> y <strong>PyTorch / TensorFlow</strong>.</p>

<p>De nuevo con una interfaz típica:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">tokenizer</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
<span class="n">model</span><span class="p">(</span><span class="n">input_ids</span><span class="p">)</span>
</code></pre></div></div>

<p>La escena seguía igual:</p>

<ul>
  <li>Los modelos corrían en tu máquina.</li>
  <li>Eran <em>stateless</em> (i.e. sin estado, sin memoria de conversación).</li>
  <li>No había conversación ni prompts largos.</li>
  <li>Todo era local y bajo tu control.</li>
</ul>

<h2 id="2020-2022-primeros-llms-como-servicio-openai-gpt-3">2020-2022: primeros LLMs como servicio (OpenAI GPT-3)</h2>

<p>Aquí ya nació el cambio importante, con el lanzamiento de <a href="https://es.wikipedia.org/wiki/GPT-3"><strong>GPT-3</strong></a> en 2020, cuando OpenAI introdujo el concepto de LLM como servicio.</p>

<p>Con esto, se empezó a adoptar el nuevo estándar propuesto: <strong>HTTP + JSON sobre REST</strong>, que no surgió por consenso de la industria, sino porque <strong>OpenAI lo impuso</strong> y así siguió haciéndolo la comunidad, desarrollador tras desarrollador.</p>

<p>Ahora el uso del modelo se convertía en algo abstraíble, desligado a tu máquina, del tipo:</p>

<pre><code class="language-txt">POST /v1/completions

{
  "prompt": "Once upon a time",
  "max_tokens": 100
}
</code></pre>

<p>Pero aún no había streaming “serio”, herramientas (<em>tools</em>), ni <em>function calling</em>.</p>

<p>Y probablemente <strong>REST</strong> se eligió por simplicidad o comodidad, no por ser lo mejor técnicamente. Otras opciones a HTTP/REST pudieron ser gRPC (<em>Google Remote Procedure Call</em>), WebSockets o Message queues (Kafka, RabbitMQ…).</p>

<h2 id="2023-2024-entran-las-requests">2023-2024: entran las “requests”</h2>

<p>Aquí ya se consolida lo que hoy día podríamos llamar “el flujo de requests”. Comúnmente mezclándolos con <em>frameworks</em> de orquestación como LangChain/LangGraph, LlamaIndex…, <em>backends</em> como vLLM, proxies como LiteLLM, etc.</p>

<p>Es un estándar moderno que tiene múltiples características que lo hacen ventajoso respecto a los anteriores:</p>

<ul>
  <li><strong>HTTP</strong>: protocolo universal.</li>
  <li><strong>JSON</strong>: formato de datos estándar.</li>
  <li><strong>Stateless</strong>: cada petición es independiente.</li>
  <li><strong>Autenticación por token</strong>: Uso de API keys para uso seguro de agentes como servicios.</li>
  <li><strong>Streaming por SSE</strong>: <em>Server-Sent Events</em> para respuestas en tiempo real (ir mandándolas de la que se va pensando/generando, no esperar a finalizar…).</li>
</ul>

<p>Por ejemplo:</p>

<pre><code class="language-txt">POST /v1/chat/completions
</code></pre>

<p>Y probablemente acogió tanto éxito porque funciona desde cualquier lenguaje (mayor) y desde <code class="language-plaintext highlighter-rouge">curl</code>, también detrás de proxies, es fácil de versionar y es un protocolo que parece universal.</p>

<h3 id="endpoints-comunes-de-openai-v1">Endpoints comunes de OpenAI <code class="language-plaintext highlighter-rouge">/v1/</code></h3>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">/v1/completions</code></strong> (Legacy): para modelos antiguos no ajustados para chat (como modelos base GPT-3) para completar prompts de texto crudo.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">/v1/chat/completions</code></strong>: el estándar para IA conversacional moderna, manejando mensajes estructurados (<em>system</em>, <em>user</em>, <em>assistant</em>) y reteniendo contexto.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">/v1/responses</code></strong>: la última unificación, diseñada para inputs multimodales (texto/imágenes) y características avanzadas de agentes como <em>tool use</em> y <em>reasoning</em>.</li>
</ul>

<blockquote>
  <p>[!TIP] 
¿Y por qué el <code class="language-plaintext highlighter-rouge">/v1/</code> en los endpoints?</p>

  <p>Parece arbitrario, pero el <code class="language-plaintext highlighter-rouge">/v1/</code> en endpoints como <code class="language-plaintext highlighter-rouge">/v1/completions</code> simplemente significa <strong>version 1</strong>, indicando la primera iteración mayor de ese diseño de API específico.</p>
</blockquote>

<h2 id="por-qué-no-hubo-un-estándar-formal-tipo-sql">¿Por qué no hubo un estándar formal, tipo SQL?</h2>

<p>Bien es cierto que el campo evolucionó tan rápido (cada mes traía cambios o avances que incluían el <em>hype</em> de parecer fundamentales) que quizá por eso se estandarizó esta solución y no se desarrollaron otras a gran escala. Así que cada empreas tiró por su lado, y nadie se preocupó en mejorar un estándar que ya pareció muy aceptable, sino continuar la “carrera” de la IA por el lado que mejor pudiera, e.g. OpenAI → chat, Anthropic → IA constitucional, Google → herramientas, Meta → inferencia local, Microsoft → nube… Cabe la pena mencionar los <a href="https://modelcontextprotocol.io/docs/getting-started/intro">MCP de Anthropic</a>, intento no fallido de estandarizar la adición de plugins a los agentes conversacionales.</p>

<h2 id="pequeña-tabla-comparativa">Pequeña tabla comparativa…</h2>

<p>…del “estándar” de cada época:</p>

<table>
  <thead>
    <tr>
      <th>Época</th>
      <th>Interfaz dominante</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Pre-2018</td>
      <td>API local (Python/C++)</td>
    </tr>
    <tr>
      <td>2018-2020</td>
      <td>HuggingFace / PyTorch</td>
    </tr>
    <tr>
      <td>2020-2022</td>
      <td>REST <code class="language-plaintext highlighter-rouge">/completions</code></td>
    </tr>
    <tr>
      <td>2023-hoy</td>
      <td>REST <code class="language-plaintext highlighter-rouge">/chat</code> (+ <em>tools</em> + <em>streaming</em>…)</td>
    </tr>
  </tbody>
</table>

<blockquote>
  <p>[!NOTE]
<em>Stateless</em> vs <em>stateful</em>.</p>

  <p><strong>Stateless</strong> significa que una aplicación trata cada petición de forma independiente, sin recordar interacciones pasadas (sin “memoria”), haciéndola altamente escalable y tolerante a fallos (e.g. APIs REST, servidores web). La desventaja es que el desarrollador tiene que ocuparse de enviar todo el historial en cada petición si quiere usar chat con memoria, lo que aumenta drásticamente el uso de tokens. 
<strong>Stateful</strong> significa que la aplicación retiene información (estado) de peticiones previas, requiriendo almacenamiento en el servidor para contexto, llevando a gestión de sesión más fácil pero escalado complejo (e.g. bases de datos, servidores de correo, banca).</p>

  <p>Los LLMs modernos como servicio son típicamente <strong>stateless</strong> en el nivel de API, aunque podrían mantener contexto dentro de una conversación mediante el historial de mensajes (siendo la manera correcta de pasar el historial mediante las <em>request</em> exactas, i.e. especificando <em>assistant</em>, <em>user</em> y resto de etiquetas…).</p>
</blockquote>

<h2 id="bonus-flujo-de-procesamiento-de-los-modelos-en-nlp">Bonus: Flujo de procesamiento de los modelos en NLP</h2>

<p>A continuación se muestran unos diagramas muy minimalistas que ilustran las diferentes maneras de las que los distintos modelos de procesamiento de lenguaje manipulan los datos, a un alto nivel:</p>

<h3 id="1-rnn--lstm-arquitectura-recurrente">1. RNN / LSTM (Arquitectura Recurrente)</h3>
<p>El concepto clave aquí es el <strong>bucle de retroalimentación</strong> y el estado oculto (\(h_t\)) que viaja a través del tiempo.</p>

<pre><code class="language-mermaid">graph LR
    X((Input t)) --&gt; Cell[Capa Recurrente]
    H_prev((h t-1)) --&gt; Cell
    Cell --&gt; H_next((h t))
    Cell --&gt; Y((Output t))
    
    style Cell fill:#f9f,stroke:#333,stroke-width:2px
</code></pre>

<h3 id="2-bi-lstm--crf-etiquetado-de-secuencias">2. Bi-LSTM + CRF (Etiquetado de Secuencias)</h3>
<p>Este era el estándar para NER (<em>Named Entity Recognition</em>) (“Alejandro escribe este post” =&gt; Alejandro = <code class="language-plaintext highlighter-rouge">PERSON</code>, etc.). La Bi-LSTM extrae contexto en ambas direcciones y el CRF asegura que la secuencia de etiquetas final sea “legal” (tenga coherencia global).</p>

<pre><code class="language-mermaid">graph TD
    Input[Secuencia de Palabras] --&gt; BiLSTM[Bi-LSTM: Contexto Izq-Der]
    BiLSTM --&gt; Potentials[Potenciales de Emisión]
    Potentials --&gt; CRF{Capa CRF}
    CRF --&gt; Labels[Secuencia de Etiquetas Optimizada]

    style CRF fill:#ffcc00,stroke:#333
</code></pre>

<blockquote>
  <p>[!NOTE]
El Problema del <strong>Sesgo de Etiqueta (<em>Label Bias</em>)</strong></p>

  <p>¿Por qué era necesario el CRF?</p>

  <p>Los modelos que deciden la etiqueta de cada palabra de forma independiente (como una RNN simple con Softmax) sufren de “visión de túnel”. Un <strong>CRF</strong> soluciona esto al no mirar solo la palabra actual, sino la transición entre etiquetas. Por ejemplo, en una tarea de nombres propios, el CRF “sabe” que después de una etiqueta <code class="language-plaintext highlighter-rouge">B-PER</code> (Inicio de persona) es muy probable que venga un <code class="language-plaintext highlighter-rouge">I-PER</code> (Continuación de persona) y nunca un <code class="language-plaintext highlighter-rouge">B-LOC</code> (Inicio de lugar) a mitad de un apellido. Esto optimiza la <strong>secuencia global</strong> mediante la minimización de una función de energía, un concepto que <strong>LeCun</strong> defendió en sus <em>Energy-Based Models</em>.</p>

  <p>Este problema se vuelve irrelevante con los modelos de hoy día, desde luego, pues sustituyendo la optimización global de etiquetas (CRF) por una atención global de entrada básicamente, pasamos de “corregir los errores al final” a “entender tan bien el contexto que no cometemos el error desde el principio”.</p>
</blockquote>

<h3 id="3-factor-graphs--gtn">3. Factor Graphs / GTN</h3>
<p>Basado en el <a href="http://yann.lecun.com/exdb/publis/pdf/lecun-98.pdf">paper de 1998</a> de Yann LeCun. Es un flujo donde diferentes módulos (como una CNN para caracteres o <strong>ConvNet</strong>) se combinan mediante una función de energía para encontrar/optimizar el camino de menor coste.</p>

<pre><code class="language-mermaid">graph LR
    In[Entrada] --&gt; Mod1[Módulo 1]
    Mod1 --&gt; Graph{Grafo de Factores}
    Graph --&gt; Energy[Función de Energía]
    Energy --&gt; Viterbi[Búsqueda de Camino Mínimo]
    Viterbi --&gt; Out[Salida Estructurada]

    style Graph fill:#00ffff,stroke:#333
</code></pre>

<h3 id="4-transformers-atención-paralela">4. Transformers (Atención Paralela)</h3>
<p>A diferencia de las RNNs, aquí no hay flechas que vuelvan atrás; todo se procesa en paralelo mediante la matriz de atención.</p>

<pre><code class="language-mermaid">graph TD
    In[Tokens de Entrada] --&gt; Embed[Embeddings + Positional Encoding]
    Embed --&gt; Attn[Multi-Head Attention]
    Attn --&gt; FF[Feed Forward Network]
    FF --&gt; Out[Probabilidades del Siguiente Token]

    style Attn fill:#bbf,stroke:#333,stroke-width:2px
</code></pre>

<h2 id="referencias">Referencias</h2>

<ul>
  <li><a href="https://platform.openai.com/docs/api-reference">OpenAI API Documentation</a></li>
  <li><a href="https://stackoverflow.com/questions/76192496/openai-v1-completions-vs-v1-chat-completions-end-points">Stack Overflow: OpenAI v1 completions vs v1 chat completions</a></li>
  <li><a href="https://huggingface.co/docs/transformers">Hugging Face Transformers</a></li>
  <li><a href="https://www.youtube.com/playlist?list=PLAqhIrjkxbuWI23v9cThsA9GvCAUhRvKZ">Neural Networks: Zero to Hero</a></li>
</ul>]]></content><author><name>Alejandro Garnung Menéndez</name><email>garnungalejandro@gmail.com</email></author><category term="computer science" /><category term="divulgation" /><summary type="html"><![CDATA[Brevísima historia de la programación en NLP (Natural Language Processing)]]></summary></entry><entry><title type="html">Creating music videos with Hydra</title><link href="https://agarnung.github.io/blog/hydra" rel="alternate" type="text/html" title="Creating music videos with Hydra" /><published>2026-03-01T00:00:00+00:00</published><updated>2026-03-01T00:00:00+00:00</updated><id>https://agarnung.github.io/blog/hydra</id><content type="html" xml:base="https://agarnung.github.io/blog/hydra"><![CDATA[<p><img src="../assets/blog_images/2026-03-01-hydra/hydra-music-recorder.png" alt="hydra-music-recorder" /></p>

<p>In this post I explain how I use <a href="https://hydra.ojack.xyz/">Hydra</a> to create audio-reactive visuals and generate music videos automatically.</p>

<p><strong>GitHub Repo:</strong> <a href="https://github.com/agarnung/hydra-music-recorder">https://github.com/agarnung/hydra-music-recorder</a><br />
<strong>YouTube Channel:</strong> <a href="https://www.youtube.com/@agarnungm">https://www.youtube.com/@agarnungm</a></p>

<h2 id="what-is-hydra">What is Hydra?</h2>

<p>Hydra is a visual synthesizer inspired by modular audio synthesizers, but instead of generating sound, it produces graphics and video in real-time. It runs directly in the browser, allowing you to create complex visual compositions using JavaScript code. It works by chaining functions that transform and combine images, similar to how modules are connected in systems like <a href="https://derivative.ca/">TouchDesigner</a> or <a href="https://cycling74.com/">Max/MSP</a>.</p>

<p>The main features of Hydra include:</p>

<ul>
  <li><strong>Modular syntax</strong>: Chain functions like <code class="language-plaintext highlighter-rouge">osc().rotate().color().out()</code> to create complex visuals</li>
  <li><strong>Audio analysis</strong>: Integrates FFT analysis to make visuals react to audio in real-time</li>
  <li><strong>Multiple buffers</strong>: Allows working with sources (<code class="language-plaintext highlighter-rouge">s0</code>, <code class="language-plaintext highlighter-rouge">s1</code>, …) and output buffers (<code class="language-plaintext highlighter-rouge">o0</code>, <code class="language-plaintext highlighter-rouge">o1</code>, …) to create compositions with feedback and mixing</li>
  <li><strong>Browser execution</strong>: Everything runs in the browser using WebGL, without complex installations</li>
</ul>

<p>To see all Hydra functions, check out <a href="https://hydra.ojack.xyz/api/#functions/modulateScale/0">its official documentation</a>.</p>

<h2 id="workflow">Workflow</h2>

<p>My workflow for creating music videos with Hydra is based on the <a href="https://github.com/agarnung/hydra-music-recorder">hydra-music-recorder</a> project, which automates the process of generating customizable visualizations synchronized with offline audio.</p>

<p><strong>1. Audio preparation</strong></p>

<p>The system works with a <strong>local MP3/WAV audio file</strong> as the source. It loads directly in the browser and is analyzed using the Web Audio API.</p>

<p><strong>2. Patch configuration</strong></p>

<p>Each patch is a JavaScript file that defines the visual composition. Patches use audio analysis to control visual parameters:</p>

<ul>
  <li><strong>Low frequencies (bass)</strong>: Control slow movement, scale, zoom, and rotation</li>
  <li><strong>Mid frequencies</strong>: Affect deformation, texture, and color</li>
  <li><strong>High frequencies</strong>: Generate brightness, flashes, and granular effects</li>
</ul>

<p>The system analyzes audio via FFT and exposes values like <code class="language-plaintext highlighter-rouge">impulse.value</code> (energy of the configured range) and <code class="language-plaintext highlighter-rouge">impulse.infra.value</code> (energy of very low frequencies) that can be used directly in Hydra functions, allowing complete freedom to customize amazing visuals.</p>

<p><strong>3. Rendering and recording</strong></p>

<p>Once the patch is configured and audio is loaded:</p>

<ol>
  <li>Start a local server (<code class="language-plaintext highlighter-rouge">./scripts/serve.sh audio.wav</code>)</li>
  <li>Open the browser at <code class="language-plaintext highlighter-rouge">http://localhost:8123</code></li>
  <li>The visual renders in real-time, reacting to the audio</li>
  <li>Record directly from the browser using the record button, which captures the canvas at high resolution (considering <code class="language-plaintext highlighter-rouge">devicePixelRatio</code> for maximum quality)</li>
  <li>The video downloads automatically in WebM format</li>
</ol>

<p>The system also includes scripts for offline rendering using <strong>Puppeteer</strong>, allowing deterministic frame generation and final video assembly with <strong>ffmpeg</strong>.</p>

<p><strong>Note</strong>: The project includes different versions of the main Hydra file (<code class="language-plaintext highlighter-rouge">hydrav1.js</code>, <code class="language-plaintext highlighter-rouge">hydrav2.js</code>) that were developed during experimentation with different HUD designs and audio signal processing approaches. The current version (<code class="language-plaintext highlighter-rouge">hydra.js</code>) is the one that works best for my workflow.</p>

<h2 id="scripts">Scripts</h2>

<p>The project includes several scripts to automate the recording, rendering, and composition process:</p>

<ul>
  <li>
    <p><strong><code class="language-plaintext highlighter-rouge">serve.sh</code></strong>: Starts a local HTTP server on port 8123 and configures the audio file. It automatically handles port cleanup and generates the <code class="language-plaintext highlighter-rouge">audio-config.js</code> file with the selected audio. Usage: <code class="language-plaintext highlighter-rouge">./scripts/serve.sh audio.wav</code></p>
  </li>
  <li>
    <p><strong><code class="language-plaintext highlighter-rouge">merge_audio_video.sh</code></strong>: Merges a video file (WebM) with an audio file (MP3/WAV), intelligently handling different formats and codecs. It automatically detects durations, trims if necessary, and preserves quality by copying video streams when possible. Supports MKV (lossless with FLAC), WebM (Opus audio), and MP4 formats. Usage: <code class="language-plaintext highlighter-rouge">./scripts/merge_audio_video.sh video.webm audio.wav [output.mkv]</code></p>
  </li>
  <li>
    <p><strong><code class="language-plaintext highlighter-rouge">video_to_HD.sh</code></strong>: Converts a video to HD format (1080p) with codecs optimized for YouTube. Uses H.264 with high profile, 12 Mbps bitrate, and AAC audio at 320 kbps. Automatically handles aspect ratio with padding if needed. Usage: <code class="language-plaintext highlighter-rouge">./scripts/video_to_HD.sh input.mkv [output.mkv]</code></p>
  </li>
</ul>

<h2 id="patches-used-in-music-videos">Patches used in music videos</h2>

<p>Below I list the patches I’ve used to create the visuals for my music videos, each with its corresponding link to the final result on YouTube. All patches are available in the <a href="https://github.com/agarnung/hydra-music-recorder/tree/main/patches">patches directory</a> of the repository.</p>

<h3 id="sinestesia"><a href="https://www.youtube.com/watch?v=HqKgcVJlu6E">Sinestesia</a></h3>

<p><img src="https://img.youtube.com/vi/HqKgcVJlu6E/maxresdefault.jpg" alt="Sinestesia" /></p>

<div style="text-align: center;">
<iframe width="800" height="450" src="https://www.youtube.com/embed/HqKgcVJlu6E" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="" style="max-width: 100%;"></iframe>
</div>

<table>
  <tbody>
    <tr>
      <td><a href="https://www.youtube.com/watch?v=HqKgcVJlu6E">YouTube link: <code class="language-plaintext highlighter-rouge">https://www.youtube.com/watch?v=HqKgcVJlu6E</code></a></td>
      <td><a href="https://github.com/agarnung/hydra-music-recorder/blob/main/patches/patchSinestesia.js">Patch code: <code class="language-plaintext highlighter-rouge">patchSinestesia.js</code></a></td>
    </tr>
  </tbody>
</table>

<p>Noise-based patch with smooth modulation. Uses <code class="language-plaintext highlighter-rouge">noise(2)</code> with bass-reactive speed control (<code class="language-plaintext highlighter-rouge">impulse.value</code>) to create a fluid “ocean” effect. Features a four-sided shape mask, blue color palette (0.2, 0.4, 0.8), and feedback rotation from previous frames.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">base</span> <span class="o">=</span> <span class="nx">noise</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="cm">/* FORMULA: return X + (impulse.value * Y);
       
       ---------------------------------------------------------
       VALUE X (here is 0.05) -&gt; "REST" SPEED
       ---------------------------------------------------------
       Minimum speed when there is NO bass playing.
       ↑ IF YOU INCREASE X: Background will always be nervous, never stops.
       ↓ IF YOU DECREASE X: Background stays almost frozen in silences.
  
       ---------------------------------------------------------
       VALUE Y (here is 0.1) -&gt; "ACCELERATION" POWER
       ---------------------------------------------------------
       How much extra speed is added at once when bass enters.
       ↑ IF YOU INCREASE Y: Change is violent (visual whip).
       ↓ IF YOU DECREASE Y: Change is subtle (just breathes a bit).
    */</span>
    
    <span class="c1">//       X                 Y</span>
    <span class="k">return</span> <span class="mf">0.05</span> <span class="o">+</span> <span class="p">(</span><span class="nx">impulse</span><span class="p">.</span><span class="nx">value</span> <span class="o">*</span> <span class="mf">0.1</span><span class="p">);</span>
  <span class="p">})</span>
  <span class="p">.</span><span class="nx">color</span><span class="p">(</span><span class="mf">0.2</span><span class="p">,</span> <span class="mf">0.4</span><span class="p">,</span> <span class="mf">0.8</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">modulate</span><span class="p">(</span><span class="nx">noise</span><span class="p">(</span><span class="mi">3</span><span class="p">),</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="mf">0.5</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">mask</span><span class="p">(</span><span class="nx">shape</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="mf">0.8</span><span class="p">,</span> <span class="mf">0.5</span><span class="p">))</span>
  <span class="p">.</span><span class="nx">contrast</span><span class="p">(</span><span class="mf">1.1</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">modulateRotate</span><span class="p">(</span><span class="nx">src</span><span class="p">(</span><span class="nx">o0</span><span class="p">),</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="mf">0.1</span><span class="p">);</span>
</code></pre></div></div>

<h3 id="albedo"><a href="https://youtu.be/Ut4zZMRd_cc">Albedo</a></h3>

<p><img src="https://img.youtube.com/vi/Ut4zZMRd_cc/maxresdefault.jpg" alt="Albedo" /></p>

<div style="text-align: center;">
<iframe width="800" height="450" src="https://www.youtube.com/embed/Ut4zZMRd_cc" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="" style="max-width: 100%;"></iframe>
</div>

<table>
  <tbody>
    <tr>
      <td><a href="https://youtu.be/Ut4zZMRd_cc">YouTube link: <code class="language-plaintext highlighter-rouge">https://youtu.be/Ut4zZMRd_cc</code></a></td>
      <td><a href="https://github.com/agarnung/hydra-music-recorder/blob/main/patches/patchAlbedo.js">Patch code: <code class="language-plaintext highlighter-rouge">patchAlbedo.js</code></a></td>
    </tr>
  </tbody>
</table>

<p>Voronoi-based composition (<code class="language-plaintext highlighter-rouge">voronoi(5)</code>) that deforms according to mid-range audio frequencies. Uses heavy pixelation (2000x2000), horizontal scrolling, and a turquoise color scheme (0.2, 0.5, 0.55) to create pulsing, organic cells.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">base</span> <span class="o">=</span> <span class="nx">voronoi</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mf">0.1</span><span class="p">,</span> 
    <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="k">return</span> <span class="mf">1.5</span> <span class="o">+</span> <span class="p">(</span><span class="nx">impulse</span><span class="p">.</span><span class="nx">value</span> <span class="o">*</span> <span class="mf">0.5</span><span class="p">);</span> <span class="c1">// default 1.5</span>
  <span class="p">})</span>
  <span class="p">.</span><span class="nx">color</span><span class="p">(</span><span class="mf">0.2</span><span class="p">,</span> <span class="mf">0.5</span><span class="p">,</span> <span class="mf">0.55</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">modulate</span><span class="p">(</span><span class="nx">noise</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mf">0.01</span><span class="p">).</span><span class="nx">add</span><span class="p">(</span><span class="nx">gradient</span><span class="p">(),</span> <span class="mf">0.15</span><span class="p">),</span> <span class="mf">0.35</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">pixelate</span><span class="p">(</span><span class="mi">2000</span><span class="p">,</span> <span class="mi">2000</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">src</span><span class="p">(</span><span class="nx">o0</span><span class="p">).</span><span class="nx">scrollX</span><span class="p">(</span><span class="mf">0.001</span><span class="p">),</span> <span class="mf">0.05</span><span class="p">)</span>
</code></pre></div></div>

<h3 id="sosiego"><a href="https://youtu.be/LwdEHEPYqjg">Sosiego</a></h3>

<p><img src="https://img.youtube.com/vi/LwdEHEPYqjg/maxresdefault.jpg" alt="Sosiego" /></p>

<div style="text-align: center;">
<iframe width="800" height="450" src="https://www.youtube.com/embed/LwdEHEPYqjg" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="" style="max-width: 100%;"></iframe>
</div>

<table>
  <tbody>
    <tr>
      <td><a href="https://youtu.be/LwdEHEPYqjg">YouTube link: <code class="language-plaintext highlighter-rouge">https://youtu.be/LwdEHEPYqjg</code></a></td>
      <td><a href="https://github.com/agarnung/hydra-music-recorder/blob/main/patches/patchSosiego.js">Patch code: <code class="language-plaintext highlighter-rouge">patchSosiego.js</code></a></td>
    </tr>
  </tbody>
</table>

<p>Minimalist visual with smooth noise (<code class="language-plaintext highlighter-rouge">noise(3)</code>) and bass-reactive color modulation. Uses a custom <code class="language-plaintext highlighter-rouge">bassBlend()</code> function that inversely maps bass intensity to blend amount—strong bass creates sharper transitions, silence creates smoother visuals. Features vertical scroll, colorama, and pixelate modulation.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Bass-reactive blend control</span>
<span class="kd">function</span> <span class="nx">bassBlend</span><span class="p">()</span> <span class="p">{</span>
  <span class="c1">// ENGINE.val ∈ [0,1]</span>
  <span class="c1">// Strong bass → small blend</span>
  <span class="c1">// Silence → large blend</span>
  <span class="k">return</span> <span class="nx">clamp</span><span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="nx">ENGINE</span><span class="p">.</span><span class="nx">val</span> <span class="o">*</span> <span class="mf">1.2</span><span class="p">,</span> <span class="mf">0.5</span><span class="p">,</span> <span class="mf">0.9</span><span class="p">)</span>
<span class="p">}</span>

<span class="nx">speed</span> <span class="o">=</span> <span class="mf">0.5</span>

<span class="nx">noise</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mf">0.02</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">modulateScrollY</span><span class="p">(</span><span class="nx">osc</span><span class="p">(</span><span class="mf">0.5</span><span class="p">),</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="nx">bassBlend</span><span class="p">()</span> <span class="o">*</span> <span class="mf">0.3</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">color</span><span class="p">(</span>
    <span class="p">()</span> <span class="o">=&gt;</span> <span class="mf">0.1</span> <span class="o">-</span> <span class="nx">bassBlend</span><span class="p">()</span> <span class="o">*</span> <span class="mf">0.05</span><span class="p">,</span>
    <span class="p">()</span> <span class="o">=&gt;</span> <span class="mf">0.7</span> <span class="o">-</span> <span class="nx">bassBlend</span><span class="p">()</span> <span class="o">*</span> <span class="mf">0.08</span><span class="p">,</span>
    <span class="p">()</span> <span class="o">=&gt;</span> <span class="mf">0.5</span> <span class="o">-</span> <span class="nx">bassBlend</span><span class="p">()</span> <span class="o">*</span> <span class="mf">0.01</span>
  <span class="p">)</span>
  <span class="p">.</span><span class="nx">colorama</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">luma</span><span class="p">(</span><span class="mf">0.59</span><span class="p">,</span> <span class="mf">0.1</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">modulatePixelate</span><span class="p">(</span><span class="nx">noise</span><span class="p">(</span><span class="mi">3</span><span class="p">),</span> <span class="mi">500</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">blend</span><span class="p">(</span><span class="nx">src</span><span class="p">(</span><span class="nx">o0</span><span class="p">).</span><span class="nx">scale</span><span class="p">(</span><span class="mf">1.002</span><span class="p">),</span> <span class="mf">0.9</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">out</span><span class="p">(</span><span class="nx">o0</span><span class="p">)</span>

<span class="nx">processingLoop</span><span class="p">();</span>
</code></pre></div></div>

<h3 id="zozobra"><a href="https://www.youtube.com/watch?v=O1qz4I04cRE">Zozobra</a></h3>

<p><img src="https://img.youtube.com/vi/O1qz4I04cRE/maxresdefault.jpg" alt="Zozobra" /></p>

<div style="text-align: center;">
<iframe width="800" height="450" src="https://www.youtube.com/embed/O1qz4I04cRE" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="" style="max-width: 100%;"></iframe>
</div>

<table>
  <tbody>
    <tr>
      <td><a href="https://www.youtube.com/watch?v=O1qz4I04cRE">YouTube link: <code class="language-plaintext highlighter-rouge">https://www.youtube.com/watch?v=O1qz4I04cRE</code></a></td>
      <td><a href="https://github.com/agarnung/hydra-music-recorder/blob/main/patches/patchZozobra.js">Patch code: <code class="language-plaintext highlighter-rouge">patchZozobra.js</code></a></td>
    </tr>
  </tbody>
</table>

<p>3D sphere rendered using a custom GLSL function <code class="language-plaintext highlighter-rouge">sphereDisplacement2</code> for ray-marching displacement. The sphere texture combines oscillators with thresholding and noise. Bass frequencies (after 18.25 seconds) dramatically increase displacement, causing violent deformation. Composited over an abstract background using masking.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">hush</span><span class="p">()</span>

<span class="nx">speed</span> <span class="o">=</span> <span class="mf">0.35</span> <span class="c1">// Global variable that scales time for all Hydra. Low number (0.1, 0.2) to go slow</span>

<span class="c1">// 1. Define the function first</span>
<span class="nx">setFunction</span><span class="p">({</span>
  <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">sphereDisplacement2</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">combineCoord</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">inputs</span><span class="p">:</span> <span class="p">[</span>
    <span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">radius</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">float</span><span class="dl">'</span><span class="p">,</span> <span class="na">default</span><span class="p">:</span> <span class="mf">4.0</span> <span class="p">},</span>
    <span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">rot</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">float</span><span class="dl">'</span><span class="p">,</span> <span class="na">default</span><span class="p">:</span> <span class="mf">0.0</span> <span class="p">}</span>
  <span class="p">],</span>
  <span class="na">glsl</span><span class="p">:</span> <span class="s2">`
    vec2 pos = _st - 0.5;
    vec3 rpos = vec3(0.0, 0.0, -10.0);
    vec3 rdir = normalize(vec3(pos * 3.0, 1.0));
    float d = 0.0;

    for(int i = 0; i &lt; 16; ++i){
      float height = length(_c0);
      d = length(rpos) - (radius + height);
      rpos += d * rdir;
      if (abs(d) &lt; 0.001) break;
    }

    if(d &gt; 0.05) {
      // "Dead" coordinate for background (we'll use this to crop)
      return vec2(0.05, 0.05); 
    } else {
      return vec2(
        atan(rpos.z, rpos.x) + rot,
        atan(length(rpos.xz), rpos.y)
      );
    }
  `</span>
<span class="p">})</span>

<span class="c1">// 2. Render the SPHERE in buffer o1</span>
<span class="c1">// We use .color(0,0,0) at the beginning to clear the background of this buffer</span>
<span class="nx">src</span><span class="p">(</span><span class="nx">o1</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">layer</span><span class="p">(</span>
    <span class="nx">osc</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mf">0.1</span><span class="p">,</span> <span class="mf">0.75</span><span class="p">)</span>
  	<span class="p">.</span><span class="nx">thresh</span><span class="p">(</span><span class="mf">0.5</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span>
  	<span class="p">.</span><span class="nx">blend</span><span class="p">(</span><span class="nx">noise</span><span class="p">(</span><span class="mf">2.5</span><span class="p">),</span> <span class="mf">0.5</span><span class="p">)</span> <span class="c1">// Sphere texture</span>
    <span class="p">.</span><span class="nx">sphereDisplacement2</span><span class="p">(</span>
      <span class="c1">// Here's the trick: we mix noise with background feedback (src(o0))</span>
      <span class="c1">// so the sphere deforms with what happens behind it.</span>
      <span class="nx">noise</span><span class="p">(</span>
        <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
          <span class="kd">const</span> <span class="nx">bassEffect</span> <span class="o">=</span> <span class="nx">audio</span><span class="p">.</span><span class="nx">currentTime</span> <span class="o">&gt;=</span> <span class="mf">18.25</span> <span class="p">?</span> <span class="mi">25</span> <span class="o">*</span> <span class="nx">impulse</span><span class="p">.</span><span class="nx">infra</span><span class="p">.</span><span class="nx">value</span> <span class="p">:</span> <span class="mi">2</span><span class="p">;</span>
          <span class="k">return</span> <span class="nx">bassEffect</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="p">,</span> <span class="mf">0.5</span><span class="p">),</span> 
      <span class="mf">4.0</span><span class="p">,</span> 
      <span class="nx">time</span> <span class="o">*</span> <span class="mf">0.4</span>
    <span class="p">)</span>
  <span class="p">)</span>
  <span class="p">.</span><span class="nx">out</span><span class="p">(</span><span class="nx">o1</span><span class="p">)</span>

<span class="c1">// 3. Render the BACKGROUND and compose in o0</span>
<span class="nx">src</span><span class="p">(</span><span class="nx">o1</span><span class="p">)</span>
  <span class="c1">// --- BACKGROUND ---</span>
  <span class="p">.</span><span class="nx">modulate</span><span class="p">(</span><span class="nx">noise</span><span class="p">(</span><span class="mi">3</span><span class="p">),</span><span class="mf">0.25</span><span class="p">).</span><span class="nx">thresh</span><span class="p">(</span><span class="mf">0.85</span><span class="p">,</span> <span class="mf">0.9</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">blend</span><span class="p">(</span><span class="nx">noise</span><span class="p">(</span><span class="mi">4</span><span class="p">),</span><span class="mf">0.1</span><span class="p">).</span><span class="nx">colorama</span><span class="p">(</span><span class="mf">0.2</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">blend</span><span class="p">(</span><span class="nx">gradient</span><span class="p">(</span><span class="mf">0.5</span><span class="p">).</span><span class="nx">hue</span><span class="p">(</span><span class="mf">0.1</span><span class="p">),</span><span class="mf">0.1</span><span class="p">)</span>

  <span class="c1">// --- COMPOSITION ---</span>
  <span class="c1">// Put o1 (the sphere) on top</span>
  <span class="c1">// We use .mask() with thresh to erase the black square around the sphere</span>
  <span class="p">.</span><span class="nx">layer</span><span class="p">(</span>
    <span class="nx">src</span><span class="p">(</span><span class="nx">o0</span><span class="p">)</span>
    <span class="p">.</span><span class="nx">mask</span><span class="p">(</span><span class="nx">src</span><span class="p">(</span><span class="nx">o1</span><span class="p">).</span><span class="nx">thresh</span><span class="p">(</span><span class="mf">0.75</span><span class="p">))</span> <span class="c1">// Crop what is black/dark</span>
  <span class="p">)</span>
  <span class="p">.</span><span class="nx">out</span><span class="p">(</span><span class="nx">o0</span><span class="p">)</span>
</code></pre></div></div>

<h3 id="anhedonia"><a href="https://www.youtube.com/watch?v=D68Kswu17kI">Anhedonia</a></h3>

<p><img src="https://img.youtube.com/vi/D68Kswu17kI/maxresdefault.jpg" alt="Anhedonia" /></p>

<div style="text-align: center;">
<iframe width="800" height="450" src="https://www.youtube.com/embed/D68Kswu17kI" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="" style="max-width: 100%;"></iframe>
</div>

<table>
  <tbody>
    <tr>
      <td><a href="https://www.youtube.com/watch?v=D68Kswu17kI">YouTube link: <code class="language-plaintext highlighter-rouge">https://www.youtube.com/watch?v=D68Kswu17kI</code></a></td>
      <td><a href="https://github.com/agarnung/hydra-music-recorder/blob/main/patches/patchAnhedonia.js">Patch code: <code class="language-plaintext highlighter-rouge">patchAnhedonia.js</code></a></td>
    </tr>
  </tbody>
</table>

<p>Noise-based patch with pixelate modulation creating a granular, textured effect. Uses <code class="language-plaintext highlighter-rouge">noise(3)</code> scaled to 0.5 with sinusoidal pixelation. After 7 seconds, bass frequencies (<code class="language-plaintext highlighter-rouge">impulse.infra.value</code>) activate scale modulation, causing dramatic expansion and contraction. Blends two noise layers for depth.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">let</span> <span class="nx">base</span> <span class="o">=</span> <span class="nx">noise</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">modulatePixelate</span><span class="p">(</span>
    <span class="nx">noise</span><span class="p">(</span><span class="mi">3</span><span class="p">),</span>
    <span class="p">()</span> <span class="o">=&gt;</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">sin</span><span class="p">(</span><span class="mi">2</span> <span class="o">*</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">PI</span> <span class="o">*</span> <span class="mf">0.2</span>  <span class="o">*</span> <span class="nx">time</span><span class="p">)</span> <span class="o">-</span> <span class="mi">2</span> <span class="o">*</span> <span class="mi">1</span><span class="p">,</span>
    <span class="mi">512</span>
  <span class="p">)</span>
  <span class="p">.</span><span class="nx">scale</span><span class="p">(</span><span class="mf">0.5</span><span class="p">)</span>

<span class="nx">base</span>
<span class="p">.</span><span class="nx">blend</span><span class="p">(</span>
    <span class="nx">noise</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">0</span><span class="p">).</span><span class="nx">modulateScale</span><span class="p">(</span>
        <span class="nx">noise</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span>
        <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">bassEffect</span> <span class="o">=</span> <span class="nx">audio</span><span class="p">.</span><span class="nx">currentTime</span> <span class="o">&gt;=</span> <span class="mi">7</span> <span class="p">?</span> <span class="mf">1.25</span> <span class="o">*</span> <span class="nx">impulse</span><span class="p">.</span><span class="nx">infra</span><span class="p">.</span><span class="nx">value</span> <span class="p">:</span> <span class="mi">0</span><span class="p">;</span>
        <span class="k">return</span> <span class="nx">bassEffect</span><span class="p">;</span>
        <span class="p">},</span> 
        <span class="mi">1</span>
    <span class="p">),</span>
    <span class="mf">0.25</span> <span class="c1">// Blend control (0-1)</span>
    <span class="p">)</span>
    
<span class="p">.</span><span class="nx">out</span><span class="p">(</span><span class="nx">o0</span><span class="p">)</span>
</code></pre></div></div>

<h3 id="anhelo"><a href="https://youtu.be/u_PhKfNZNbU">Anhelo</a></h3>

<p><img src="https://img.youtube.com/vi/u_PhKfNZNbU/maxresdefault.jpg" alt="Anhelo" /></p>

<div style="text-align: center;">
<iframe width="800" height="450" src="https://www.youtube.com/embed/u_PhKfNZNbU" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="" style="max-width: 100%;"></iframe>
</div>

<table>
  <tbody>
    <tr>
      <td><a href="https://youtu.be/u_PhKfNZNbU">YouTube link: <code class="language-plaintext highlighter-rouge">https://youtu.be/u_PhKfNZNbU</code></a></td>
      <td><a href="https://github.com/agarnung/hydra-music-recorder/blob/main/patches/patchAnhelo.js">Patch code: <code class="language-plaintext highlighter-rouge">patchAnhelo.js</code></a></td>
    </tr>
  </tbody>
</table>

<p>Multi-layered composition combining warm and cool palettes. Base layer uses <code class="language-plaintext highlighter-rouge">noise(2)</code> with kaleidoscope (2-fold symmetry) and a 50-sided shape mask. Blends with a second layer featuring Voronoi-like structures with heavy pixelation. Blend ratio changes dynamically at 14 seconds from static to bass-reactive.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Bass-reactive blend control</span>
<span class="kd">function</span> <span class="nx">bassBlend</span><span class="p">()</span> <span class="p">{</span>
  <span class="c1">// ENGINE.val ∈ [0,1]</span>
  <span class="c1">// Strong bass → small blend</span>
  <span class="c1">// Silence → large blend</span>
  <span class="k">return</span> <span class="nx">clamp</span><span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="nx">ENGINE</span><span class="p">.</span><span class="nx">val</span> <span class="o">*</span> <span class="mf">1.2</span><span class="p">,</span> <span class="mf">0.5</span><span class="p">,</span> <span class="mf">0.9</span><span class="p">)</span>
<span class="p">}</span>

<span class="nx">speed</span> <span class="o">=</span> <span class="mf">0.5</span>

<span class="c1">// Base layer</span>
<span class="nx">noise</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mf">0.15</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">color</span><span class="p">(</span><span class="mf">0.65</span><span class="p">,</span> <span class="mf">0.4</span><span class="p">,</span> <span class="mf">0.4</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">modulate</span><span class="p">(</span><span class="nx">noise</span><span class="p">(</span><span class="mi">3</span><span class="p">),</span> <span class="p">()</span> <span class="o">=&gt;</span>  <span class="mf">0.5</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">mask</span><span class="p">(</span><span class="nx">shape</span><span class="p">(</span><span class="mi">50</span><span class="p">,</span> <span class="mf">0.8</span><span class="p">,</span> <span class="mf">0.5</span><span class="p">))</span>
  <span class="p">.</span><span class="nx">kaleid</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">modulateRotate</span><span class="p">(</span><span class="nx">src</span><span class="p">(</span><span class="nx">o0</span><span class="p">),</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="mf">0.1</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">contrast</span><span class="p">(</span><span class="mf">1.5</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">blend</span><span class="p">(</span>
    <span class="nx">noise</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
      <span class="p">.</span><span class="nx">color</span><span class="p">(</span><span class="mf">0.2</span><span class="p">,</span> <span class="mf">0.5</span><span class="p">,</span> <span class="mf">0.6</span><span class="p">)</span>
      <span class="p">.</span><span class="nx">modulate</span><span class="p">(</span>
        <span class="nx">noise</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mf">0.01</span><span class="p">).</span><span class="nx">add</span><span class="p">(</span><span class="nx">gradient</span><span class="p">(),</span> <span class="mf">0.15</span><span class="p">),</span>
        <span class="mf">0.5</span>
      <span class="p">)</span>
      <span class="p">.</span><span class="nx">pixelate</span><span class="p">(</span><span class="mi">2000</span><span class="p">,</span> <span class="mi">2000</span><span class="p">)</span>
      <span class="p">.</span><span class="nx">scrollX</span><span class="p">(</span><span class="mf">0.001</span><span class="p">)</span>
      <span class="p">.</span><span class="nx">scale</span><span class="p">(</span><span class="mf">1.1</span><span class="p">),</span>
    <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">(</span><span class="nx">audio</span><span class="p">.</span><span class="nx">currentTime</span> <span class="o">&lt;=</span> <span class="mi">14</span><span class="p">)</span> <span class="p">?</span> <span class="mf">0.85</span> <span class="p">:</span> <span class="nx">bassBlend</span><span class="p">()</span>
  <span class="p">)</span>

  <span class="p">.</span><span class="nx">out</span><span class="p">(</span><span class="nx">o0</span><span class="p">)</span>

<span class="nx">processingLoop</span><span class="p">();</span>
</code></pre></div></div>

<h3 id="saudade"><a href="https://www.youtube.com/watch?v=1lCX5sV1jv4">Saudade</a></h3>

<p><img src="https://img.youtube.com/vi/1lCX5sV1jv4/maxresdefault.jpg" alt="Saudade" /></p>

<div style="text-align: center;">
<iframe width="800" height="450" src="https://www.youtube.com/embed/1lCX5sV1jv4" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="" style="max-width: 100%;"></iframe>
</div>

<table>
  <tbody>
    <tr>
      <td><a href="https://www.youtube.com/watch?v=1lCX5sV1jv4">YouTube link: <code class="language-plaintext highlighter-rouge">https://www.youtube.com/watch?v=1lCX5sV1jv4</code></a></td>
      <td><a href="https://github.com/agarnung/hydra-music-recorder/blob/main/patches/patchSaudade.js">Patch code: <code class="language-plaintext highlighter-rouge">patchSaudade.js</code></a></td>
    </tr>
  </tbody>
</table>

<p>Voronoi-based patch with expansive wave effects in a gray nebula aesthetic. Uses <code class="language-plaintext highlighter-rouge">voronoi(10)</code> with very low brightness (0.01) and animated colorama effects. Features modulation with noise and gradients, kaleidoscope effects, and a four-sided shape mask. Includes a fake blur effect via scaling and blending with previous frames.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Expansive wave in gray nebula</span>

<span class="nx">voronoi</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span><span class="mf">0.1</span><span class="p">,</span><span class="mf">0.1</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">color</span><span class="p">(</span><span class="mf">0.4</span><span class="p">,</span><span class="mf">0.4</span><span class="p">,</span><span class="mf">0.65</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">colorama</span><span class="p">([</span><span class="mf">0.005</span><span class="p">,</span><span class="mf">0.015</span><span class="p">,</span><span class="mf">0.02</span><span class="p">,</span><span class="mf">0.025</span><span class="p">].</span><span class="nx">fast</span><span class="p">(</span><span class="mi">1</span><span class="p">))</span>
  <span class="p">.</span><span class="nx">brightness</span><span class="p">(</span><span class="mf">0.01</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">modulate</span><span class="p">(</span><span class="nx">noise</span><span class="p">(</span><span class="mi">2</span><span class="p">).</span><span class="nx">add</span><span class="p">(</span><span class="nx">gradient</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span><span class="mf">0.1</span><span class="p">),</span><span class="mi">5</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">modulateScale</span><span class="p">(</span><span class="nx">osc</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span><span class="o">-</span><span class="mf">0.5</span><span class="p">,</span><span class="mi">0</span><span class="p">).</span><span class="nx">kaleid</span><span class="p">(</span><span class="mi">100</span><span class="p">).</span><span class="nx">scale</span><span class="p">(</span><span class="mf">0.5</span><span class="p">),</span><span class="mi">1</span><span class="p">,</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">contrast</span><span class="p">(</span><span class="mf">1.35</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">mask</span><span class="p">(</span><span class="nx">shape</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="mf">0.8</span><span class="p">,</span> <span class="mf">0.25</span><span class="p">))</span>

  <span class="c1">// Fake blur</span>
  <span class="p">.</span><span class="nx">scale</span><span class="p">(</span><span class="mf">1.1</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">src</span><span class="p">(</span><span class="nx">o0</span><span class="p">).</span><span class="nx">scale</span><span class="p">(</span><span class="mf">0.99</span><span class="p">),</span> <span class="mf">0.25</span><span class="p">)</span> <span class="c1">//  &lt;= PUT IMPULSE HERE IN 0.05-0.5</span>

  <span class="p">.</span><span class="nx">out</span><span class="p">(</span><span class="nx">o0</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="technical-considerations">Technical considerations</h2>

<h3 id="resolution-and-quality">Resolution and quality</h3>

<p>To avoid pixelated appearance, it’s important to correctly configure the canvas resolution considering <code class="language-plaintext highlighter-rouge">devicePixelRatio</code>:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">dpr</span> <span class="o">=</span> <span class="nb">window</span><span class="p">.</span><span class="nx">devicePixelRatio</span> <span class="o">||</span> <span class="mi">1</span><span class="p">;</span>
<span class="nx">canvas</span><span class="p">.</span><span class="nx">width</span> <span class="o">=</span> <span class="nx">width</span> <span class="o">*</span> <span class="nx">dpr</span><span class="p">;</span>
<span class="nx">canvas</span><span class="p">.</span><span class="nx">height</span> <span class="o">=</span> <span class="nx">height</span> <span class="o">*</span> <span class="nx">dpr</span><span class="p">;</span>
<span class="nx">hydra</span><span class="p">.</span><span class="nx">setResolution</span><span class="p">(</span><span class="nx">canvas</span><span class="p">.</span><span class="nx">width</span><span class="p">,</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">height</span><span class="p">);</span>
</code></pre></div></div>

<h3 id="frequency-mapping">Frequency mapping</h3>

<p>The system allows configuring specific frequency ranges to analyze different parts of the spectrum. For example, to focus on bass:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">INTEREST_RANGE</span> <span class="o">=</span> <span class="p">{</span> 
  <span class="na">min</span><span class="p">:</span> <span class="mi">10</span><span class="p">,</span> <span class="c1">// Minimum frequency (Hz)</span>
  <span class="na">max</span><span class="p">:</span> <span class="mi">35</span>  <span class="c1">// Maximum frequency (Hz)</span>
<span class="p">};</span>
</code></pre></div></div>

<h3 id="video-recording">Video recording</h3>

<p>Recording is done via the <code class="language-plaintext highlighter-rouge">MediaRecorder</code> API, capturing the canvas stream at 30 FPS. Bitrate adjusts automatically according to resolution and can be configured via the <code class="language-plaintext highlighter-rouge">RECORDING_QUALITY_SCALE</code> variable (0.5 for medium quality, 1.0 for maximum quality).</p>

<h2 id="references-and-resources">References and resources</h2>

<ul>
  <li><a href="https://hydra.ojack.xyz/">Hydra - Online editor</a></li>
  <li><a href="https://github.com/hydra-synth/hydra">Hydra repository on GitHub</a></li>
  <li><a href="https://hydra.ojack.xyz/docs/">Hydra documentation</a></li>
  <li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API">Web Audio API - MDN</a></li>
</ul>

<h2 id="todo">TODO</h2>

<ul>
  <li>
    <p>In the future, it would be good to develop an improvement to accept <strong>live audio input</strong> as a second audio source. To use guitar or bass connected via sound card, it’s necessary to configure an audio loopback (PulseAudio on Linux, BlackHole on macOS, VB-Audio Cable on Windows…) so the browser can access the input.</p>
  </li>
  <li>
    <p>The current system is not very intuitive or visually appealing to configure. It would be beneficial to create an <strong>interactive HUD</strong> that allows users to truly customize and create patches online, making the process more accessible and less programming-intensive.</p>
  </li>
</ul>]]></content><author><name>Alejandro Garnung Menéndez</name><email>garnungalejandro@gmail.com</email></author><category term="misc" /><summary type="html"><![CDATA[Experiments with Hydra to generate audio-reactive visuals and create music videos automatically]]></summary></entry><entry><title type="html">Web resources</title><link href="https://agarnung.github.io/blog/web-resources" rel="alternate" type="text/html" title="Web resources" /><published>2026-02-24T00:00:00+00:00</published><updated>2026-02-24T00:00:00+00:00</updated><id>https://agarnung.github.io/blog/web-resources</id><content type="html" xml:base="https://agarnung.github.io/blog/web-resources"><![CDATA[<p><img src="https://i.pinimg.com/originals/b4/e3/71/b4e371619042d1e80918d09904e90f7d.gif" alt="matrix_gif" /></p>

<p>En esta publicación mantenida de forma continua, simplemente iré listando y explicando brevemente una selección de recursos web interesantes que he encontrado y que disfruto en Internet.
El objetivo no es más complejo que no perderlos de vista y poder revisitarlos cuando me apetezca, pues me resulta más fiable dejarlos registrados de forma estática que confiar en la caché o el historial de mi navegador (o en la barra de favoritos), lo cual ya me ha hecho perder el rastro más de una vez.</p>

<h2 id="mi-lista-de-recursos-web-interesantes">Mi lista de recursos web interesantes</h2>

<ul>
  <li><a href="http://www.catb.org/~esr/">http://www.catb.org/~esr/</a></li>
</ul>

<div align="center">

El santuario del código abierto, donde Eric S. Raymond desmonta mitos hackers con un enfoque "Don't panic" (un hacker es diferente que un cracker) y ensayos que cambiaron la filosofía del software.

</div>

<p><br /></p>

<ul>
  <li><a href="https://www.tomdalling.com/toms-data-onion/">https://www.tomdalling.com/toms-data-onion/</a></li>
</ul>

<div align="center">

¿Te gustan los acertijos? ¿Te gusta programar? ¿Te gustan los misterios y la autorreferencia? Si alguna es verdadera, te podrá interesar este subdominio de Tom Dalling (autor del <a href="https://www.programmingforbeginnersbook.com/">entretenido libro para principiantes en programación</a>), en el que esconde en un payload (un blob de datos que han sido ofuscados en cierta manera) una serie de acertijos empaquetados, uno dentro de otro, que se van resolviendo en cualquier lenguaje de programación. Una idea curiosa. Tom Dalling también tiene un blog en su web personal en el que habla sobre cosas tan interesantes como <a href="https://www.tomdalling.com/blog/2009/">buenas prácticas de programación en el diseño de clases</a>, <a href="https://www.tomdalling.com/blog/2012/">tutoriales de OpenGL</a> y muchas cosas más.

</div>

<p><br /></p>

<ul>
  <li><a href="https://learnopengl.com/">https://learnopengl.com/</a></li>
</ul>

<div align="center">

Si quieres aprender OpenGL, no hay recurso web mejor que este. Aprenderás en C++ el framework a través de tutoriales guiados sumamente reconfortantes. Por supuesto, también puedes consultar la inmensa documentación de la gente de Khronos (<a href="https://www.khronos.org/opengl/">https://www.khronos.org/opengl/</a>).

</div>

<p><br /></p>

<ul>
  <li><a href="https://godbolt.org/">https://godbolt.org/</a></li>
</ul>

<div align="center">

El microscopio del código: escribe C++ y observa en tiempo real cómo se compila a ensamblador, línea a línea.

</div>

<p><br /></p>

<ul>
  <li><a href="https://johnwlambert.github.io/subgradient-methods/">https://johnwlambert.github.io/subgradient-methods/</a></li>
</ul>

<div align="center">

Buen blog personal de investigador de Google DeepMind.

</div>

<p><br /></p>

<ul>
  <li><a href="https://clouard.users.greyc.fr/Pantheon/experiments/index-en.html">https://clouard.users.greyc.fr/Pantheon/experiments/index-en.html</a></li>
</ul>

<div align="center">

La web de ejemplos de una pequeña librería de procesamiento de imagen, Pandore de GREYC. Contiene varias aplicaciones de PdI clásico, que aplican herramientas tan sencillas pero bellas como morfología, template matching, DFT, etc. para mostrar que a veces se pueden hacer grandes cosas para problemas reales complejos.

</div>

<p><br /></p>

<ul>
  <li><a href="https://digilogicelectronica.wordpress.com/">https://digilogicelectronica.wordpress.com/</a></li>
</ul>

<div align="center">

Un blog muy sencillo y conciso como interesante (como lo describieron en alguno de sus comentarios) para proyectos de juguete con Arduino, IoT, sistemas embebidos, etc. Ameno de leer.

</div>

<p><br /></p>

<ul>
  <li><a href="https://shallowsky.com/blog/">https://shallowsky.com/blog/</a></li>
</ul>

<div align="center">

El blog de Akkana Peck, a software developer specializing in Linux and open source, and I sometimes do technical and science writing (su web personal es <a href="https://shallowsky.com/">esta</a>), que dedica buena parte de su tiempo libre a divulgar sobre muy divertidos posts de ciencias de la tecnología y naturaleza, con un toque muy personal. Este es su repositorio de github, que he encontrado muy útil para <a href="https://shallowsky.com/blog/hardware/programming-breadboard-atmega.html">proyectos de Arduino</a> muy entretenidos.

</div>

<p><br /></p>

<ul>
  <li><a href="https://newscrewdriver.com/">https://newscrewdriver.com/</a></li>
</ul>

<div align="center">

A Project Diary of Coding, Making, and Tinkering

</div>

<p><br /></p>

<ul>
  <li><a href="https://www.instructables.com/">https://www.instructables.com/</a></li>
</ul>

<div align="center">

Proyectos de electrónica DIY muy entretenidos, por ejemplo <a href="https://www.instructables.com/USE-a-PUSH-BUTTON-AS-a-SWITCH-/">este</a> para controlar un ventilador con un relé. Muy sencillos pero es una comunidad muy abierta en la que se publica cuándo uno recrea el diseño de otro. Uno de mis favoritos: <a href="https://www.instructables.com/LIDAR-With-Custom-ThreeJS-Graphics/">https://www.instructables.com/LIDAR-With-Custom-ThreeJS-Graphics/</a>.

</div>

<p><br /></p>

<ul>
  <li><a href="https://matematicas.uam.es/~fernando.chamizo/">https://matematicas.uam.es/~fernando.chamizo/</a></li>
</ul>

<div align="center">

Interesante web personal con todo tipo de notas, artículos, ideas, apuntes... de matematicas, física y computación... del profesor Fernando Chamizo; además ese estilo de HTML/CSS puro tiene su encanto... o su desencanto.

</div>

<p><br /></p>

<ul>
  <li><a href="https://zzz.dog/">https://zzz.dog/</a></li>
</ul>

<div align="center">

Motor gráfico para diseñar animaciones 3D SVG sin mucha personalidad que se pueden meter fácilmente en canvas web.

</div>

<p><br /></p>

<ul>
  <li>
    <p><a href="https://www.matematicasypoesia.com.es/index.htm">https://www.matematicasypoesia.com.es/index.htm</a></p>
  </li>
  <li>
    <p><a href="https://learn.sparkfun.com/tutorials/9dof-razor-imu-m0-hookup-guide">https://learn.sparkfun.com/tutorials/9dof-razor-imu-m0-hookup-guide</a></p>
  </li>
  <li>
    <p><a href="https://es.gizmodo.com/">https://es.gizmodo.com/</a></p>
  </li>
  <li>
    <p><a href="https://leetcode.com/studyplan/top-interview-150/">https://leetcode.com/studyplan/top-interview-150/</a></p>
  </li>
  <li>
    <p><a href="https://www.neuraldesigner.com/blog/">https://www.neuraldesigner.com/blog/</a></p>
  </li>
  <li>
    <p><a href="https://visapp.scitevents.org/">https://visapp.scitevents.org/</a></p>
  </li>
  <li>
    <p><a href="https://iapr.org/">https://iapr.org/</a></p>
  </li>
  <li>
    <p><a href="https://archive.org/">https://archive.org/</a></p>
  </li>
  <li>
    <p><a href="https://www.pnas.org/">https://www.pnas.org/</a></p>
  </li>
  <li>
    <p><a href="https://ocw.uniovi.es/course/index.php?categoryid=11">https://ocw.uniovi.es/course/index.php?categoryid=11</a></p>
  </li>
  <li>
    <p><a href="https://www.songho.ca/index.html">https://www.songho.ca/index.html</a></p>
  </li>
  <li>
    <p><a href="https://robotica.unileon.es/index.php?title=Home">https://robotica.unileon.es/index.php?title=Home</a></p>
  </li>
  <li>
    <p><a href="https://vimm.net/">https://vimm.net/</a></p>
  </li>
  <li>
    <p><a href="https://explainshell.com/">https://explainshell.com/</a></p>
  </li>
</ul>

<div align="center">

Explicar cualquier comando de shell de Linux que pongas, taxonómica y visualmente.

</div>

<p><br /></p>

<ul>
  <li>
    <p><a href="https://www.um.es/LEQ/laser/index.htm">https://www.um.es/LEQ/laser/index.htm</a></p>
  </li>
  <li>
    <p><a href="https://mathematical-tours.github.io/book/">https://mathematical-tours.github.io/book/</a></p>
  </li>
  <li>
    <p><a href="https://mathematical-tours.github.io/">https://mathematical-tours.github.io/</a></p>
  </li>
  <li>
    <p><a href="https://omaralkousa-image-thresholding-web-ap-plication.streamlit.app/">https://omaralkousa-image-thresholding-web-ap-plication.streamlit.app/</a></p>
  </li>
  <li>
    <p><a href="http://www.numerical-tours.com/matlab/">http://www.numerical-tours.com/matlab/</a></p>
  </li>
  <li>
    <p><a href="https://library.fiveable.me/convex-geometry/unit-11/conjugate-functions-fenchel-duality/study-guide/RMtFfcV0O6VFSKfN">https://library.fiveable.me/convex-geometry/unit-11/conjugate-functions-fenchel-duality/study-guide/RMtFfcV0O6VFSKfN</a></p>
  </li>
  <li>
    <p><a href="https://eqworld.ipmnet.ru/index.htm">https://eqworld.ipmnet.ru/index.htm</a></p>
  </li>
  <li>
    <p><a href="https://startupnextdoor.com/">https://startupnextdoor.com/</a></p>
  </li>
  <li>
    <p><a href="https://webglsamples.org/">https://webglsamples.org/</a></p>
  </li>
  <li>
    <p><a href="https://www.uv.es/uvweb/fisica/es/demostraciones-experimentales-fisica-aula/catalogo-demos-1286053686292.html">https://www.uv.es/uvweb/fisica/es/demostraciones-experimentales-fisica-aula/catalogo-demos-1286053686292.html</a></p>
  </li>
  <li>
    <p><a href="https://ffshrine.org/">https://ffshrine.org/</a></p>
  </li>
  <li>
    <p><a href="https://www.profetolocka.com.ar/">https://www.profetolocka.com.ar/</a></p>
  </li>
  <li>
    <p><a href="https://imarvi2.blogs.uv.es/">https://imarvi2.blogs.uv.es/</a></p>
  </li>
  <li>
    <p><a href="http://hyperphysics.phy-astr.gsu.edu/hbase/hframe.html">http://hyperphysics.phy-astr.gsu.edu/hbase/hframe.html</a></p>
  </li>
  <li>
    <p><a href="https://picandocodigo.net/about/">https://picandocodigo.net/about/</a></p>
  </li>
</ul>

<div align="center">

Buen blog.

</div>

<p><br /></p>

<ul>
  <li><a href="https://aras-p.info/blog/2020/07/26/Black-Hole-Demo/">https://aras-p.info/blog/2020/07/26/Black-Hole-Demo/</a></li>
</ul>

<div align="center">

Web personal de Aras, un desarrollador de Blender y Unity, muy dedicada la computación y arte gráfico.

</div>

<p><br /></p>

<ul>
  <li>
    <p><a href="https://www.hackster.io/">https://www.hackster.io/</a></p>
  </li>
  <li>
    <p><a href="https://www.kornia.org/tutorials/">https://www.kornia.org/tutorials/</a></p>
  </li>
  <li>
    <p><a href="https://programmerhumor.io/">https://programmerhumor.io/</a></p>
  </li>
</ul>

<div align="center">

Memes y humor de programación.

</div>

<p><br /></p>

<ul>
  <li><a href="https://www.michaelmauboussin.com/writing">https://www.michaelmauboussin.com/writing</a></li>
</ul>

<div align="center">

La página personal de Michael Mauboussin, famoso inversor director de un fondo mutuo de Morgan Stanley; especialmente interesantes sus artículos sobre finanzas y comprensión del mercado.

</div>

<p><br /></p>

<ul>
  <li><a href="https://mathshistory.st-andrews.ac.uk/OfTheDay/today/">https://mathshistory.st-andrews.ac.uk/OfTheDay/today/</a></li>
</ul>

<div align="center">

Matemáticos del día (de hoy).

</div>

<p><br /></p>

<ul>
  <li><a href="https://apod.nasa.gov/apod/">https://apod.nasa.gov/apod/</a>/<a href="https://apod.nasa.gov/apod/archivepix.html">https://apod.nasa.gov/apod/archivepix.html</a></li>
</ul>

<div align="center">

Cada día un imagen del cosmos distinta + una descripción _desta_ por parte de un astrónomo.

</div>

<p><br /></p>

<ul>
  <li><a href="https://audionyq.com/">https://audionyq.com/</a></li>
</ul>

<div align="center">

Lenguaje funcional de creación musical de uno de los colaboradores de Audacity

</div>

<p><br /></p>

<ul>
  <li><a href="https://hjwwalters.com/real-time-edge-detection-using-esp32-cam/">https://hjwwalters.com/real-time-edge-detection-using-esp32-cam/</a></li>
</ul>

<div align="center">

Un blog de un fanático de la tecnología ("Jack of all trades; master of none") (¡pero mejor que aprendiz de uno solo!), e.g. Sobel en ESP32 <a href="https://hjwwalters.com/real-time-edge-detection-using-esp32-cam/">https://hjwwalters.com/real-time-edge-detection-using-esp32-cam/</a>.

</div>

<p><br /></p>

<ul>
  <li><a href="https://www.daviddomminney.com/videos/pixel-noise-music-from-images">https://www.daviddomminney.com/videos/pixel-noise-music-from-images</a></li>
</ul>

<div align="center">

Web del músico y geek David Domminney Fowler, colaborador de Computerphile con interesantísimos vídeos de la ciencia de la música (i.e. Fourier) como [Pixel Noise (Music from Images)](https://www.youtube.com/watch?v=c4cKlez0OCM), [Cómo funciona Shazam (¡probablemente!)](https://www.youtube.com/watch?v=RRsq9apr5QY) y [Coding a Guitar Sound in C](https://www.youtube.com/watch?v=Ly1v1BRVpz4).

</div>

<p><br /></p>

<ul>
  <li><a href="https://doc.lagout.org/">https://doc.lagout.org/</a></li>
</ul>

<div align="center">

Las llamo las X-Files. Son un gigantesco repertorio de recursos científico-ingenieriles y, sobre todo, de ciencias de la computación, de algún gran contribuidor francés que siempre me resultó de gran valor. Sobre todo los libros escaneados y recursos de fabricantes. Los tenía descargados a <a href="https://www.httrack.com/">HTTrack Website Copier</a>.

</div>

<p><br /></p>

<ul>
  <li><a href="https://www.habrador.com/">https://www.habrador.com/</a></li>
</ul>

<div align="center">

Ligado a su perfil de Github, fantástico contenido sobre física, Unity y ciencias de la computación.

</div>

<p><br /></p>

<ul>
  <li><a href="https://www.norwegiancreations.com/">https://www.norwegiancreations.com/</a></li>
</ul>

<div align="center">

Esta gente de la empresa Norwegian Creations, que hacen proyectos de electrónica y microcontroladores y tienen un blog muy interesante contando sus proyectos; los encontré buscando sobre cómo escribir en EEPROM (memoria no volátil) desde micrcontroladores (cuidado, que tienen un ciclo de vida máximo y si te pasas escribiendo datos cada segundo en ellos puedes acabar su vida en menos de una semana, así que lee antes de escribir [que leer no desgasta] y solo escribe si hace falta).

</div>

<h2 id="webs-para-consultar-libros">Webs para consultar libros</h2>

<ul>
  <li>
    <p><a href="https://oceanofpdf.com/">https://oceanofpdf.com/</a></p>
  </li>
  <li>
    <p><a href="https://www.circuitos-electricos.com/">https://www.circuitos-electricos.com/</a></p>
  </li>
  <li>
    <p><a href="https://zoboko.com/">https://zoboko.com/</a></p>
  </li>
  <li>
    <p><a href="https://www.pdfdrive.com/">https://www.pdfdrive.com/</a></p>
  </li>
  <li>
    <p><a href="https://vdoc.pub/">https://vdoc.pub/</a></p>
  </li>
  <li>
    <p><a href="https://www.gutenberg.org/">https://www.gutenberg.org/</a></p>
  </li>
  <li>
    <p><a href="https://librarygenesis.es/">https://librarygenesis.es/</a></p>
  </li>
  <li>
    <p><a href="https://www.freelibros.net/">https://www.freelibros.net/</a></p>
  </li>
  <li>
    <p><a href="https://www.infobooks.org/free-pdf-books/engineering/#Systems%20Engineering%20Books">https://www.infobooks.org/free-pdf-books/engineering/#Systems%20Engineering%20Books</a></p>
  </li>
  <li>
    <p><a href="https://zoboko.com/category/programming">https://zoboko.com/category/programming</a></p>
  </li>
  <li>
    <p><a href="https://dokumen.pub/">https://dokumen.pub/</a></p>
  </li>
  <li>
    <p><a href="http://178.140.10.58:8083/">http://178.140.10.58:8083/</a></p>
  </li>
</ul>

<h2 id="canales-de-youtube">Canales de Youtube</h2>

<ul>
  <li>
    <p><a href="https://www.youtube.com/@ENGINEERINGTUTORIAL2468">https://www.youtube.com/@ENGINEERINGTUTORIAL2468</a></p>
  </li>
  <li>
    <p><a href="https://www.youtube.com/@barkerds/videos">https://www.youtube.com/@barkerds/videos</a></p>
  </li>
  <li>
    <p><a href="https://www.youtube.com/watch?v=8CXReb7f0Eo&amp;ab_channel=DPRGclips">https://www.youtube.com/watch?v=8CXReb7f0Eo&amp;ab_channel=DPRGclips</a></p>
  </li>
  <li>
    <p><a href="https://www.youtube.com/watch?v=rH7Cdqp6W_g&amp;ab_channel=ABBDrivesUS">https://www.youtube.com/watch?v=rH7Cdqp6W_g&amp;ab_channel=ABBDrivesUS</a></p>
  </li>
  <li>
    <p><a href="https://www.youtube.com/channel/UCvl0K_r7dVFE2zG3uzU-GAg/playlists">https://www.youtube.com/channel/UCvl0K_r7dVFE2zG3uzU-GAg/playlists</a></p>
  </li>
  <li>
    <p><a href="https://www.youtube.com/@firstprinciplesofcomputerv3258/videos">https://www.youtube.com/@firstprinciplesofcomputerv3258/videos</a></p>
  </li>
  <li>
    <p><a href="https://www.youtube.com/@CloserToTruthTV/videos">https://www.youtube.com/@CloserToTruthTV/videos</a></p>
  </li>
  <li>
    <p><a href="https://www.youtube.com/@SteveMould/videos">https://www.youtube.com/@SteveMould/videos</a></p>
  </li>
  <li>
    <p><a href="https://www.youtube.com/@AndreasSpiess/videos">https://www.youtube.com/@AndreasSpiess/videos</a></p>
  </li>
  <li>
    <p><a href="https://www.youtube.com/@EEVblog/videos">https://www.youtube.com/@EEVblog/videos</a></p>
  </li>
  <li>
    <p><a href="https://www.youtube.com/@THUNKShow/featured">https://www.youtube.com/@THUNKShow/featured</a></p>
  </li>
  <li>
    <p><a href="https://www.youtube.com/TheRoyalInstitution/videos">https://www.youtube.com/TheRoyalInstitution/videos</a></p>
  </li>
  <li>
    <p><a href="https://www.youtube.com/@ComputerVisionFoundation">https://www.youtube.com/@ComputerVisionFoundation</a></p>
  </li>
  <li>
    <p><a href="https://www.youtube.com/@mitocw">https://www.youtube.com/@mitocw</a></p>
  </li>
  <li>
    <p><a href="https://www.youtube.com/@khanacademy/playlists">https://www.youtube.com/@khanacademy/playlists</a></p>
  </li>
  <li>
    <p><a href="https://www.youtube.com/@BenEater">https://www.youtube.com/@BenEater</a></p>
  </li>
  <li>
    <p><a href="https://www.youtube.com/@blackpenredpen/playlists">https://www.youtube.com/@blackpenredpen/playlists</a></p>
  </li>
  <li>
    <p><a href="https://www.youtube.com/@ClaytonDarwin/playlists">https://www.youtube.com/@ClaytonDarwin/playlists</a></p>
  </li>
  <li>
    <p><a href="https://www.youtube.com/@Javier_Garcia">https://www.youtube.com/@Javier_Garcia</a></p>
  </li>
  <li>
    <p><a href="https://www.youtube.com/@pratikian/videos">https://www.youtube.com/@pratikian/videos</a></p>
  </li>
  <li>
    <p><a href="https://www.youtube.com/@RichRadke">https://www.youtube.com/@RichRadke</a></p>
  </li>
  <li>
    <p><a href="https://www.youtube.com/@DuarteCorporationTutoriales">https://www.youtube.com/@DuarteCorporationTutoriales</a></p>
  </li>
  <li>
    <p><a href="https://www.youtube.com/user/talizadeh/videos">https://www.youtube.com/user/talizadeh/videos</a></p>
  </li>
  <li>
    <p><a href="https://www.youtube.com/@IsaacMorenoGallo">https://www.youtube.com/@IsaacMorenoGallo</a></p>
  </li>
  <li>
    <p><a href="https://www.youtube.com/@profrobbob/featured">https://www.youtube.com/@profrobbob/featured</a></p>
  </li>
  <li>
    <p><a href="https://www.youtube.com/@Dronebotworkshop">https://www.youtube.com/@Dronebotworkshop</a></p>
  </li>
  <li>
    <p><a href="https://www.youtube.com/channel/UCwukH1pDTWsv2DuT18dE1RA">https://www.youtube.com/channel/UCwukH1pDTWsv2DuT18dE1RA</a></p>
  </li>
</ul>

<div align="center">

I'm Iceman, RFID hacking is my passion (nada más que comentar... es el autor de grandes librerías para Proxmark3, entre otros)

</div>

<p>Continuará…</p>]]></content><author><name>Alejandro Garnung Menéndez</name><email>garnungalejandro@gmail.com</email></author><category term="misc" /><summary type="html"><![CDATA[A curated list of interesting web resources. Constantly updated.]]></summary></entry><entry><title type="html">Almacenamiento digital y mantenimiento de discos</title><link href="https://agarnung.github.io/blog/digital-storage" rel="alternate" type="text/html" title="Almacenamiento digital y mantenimiento de discos" /><published>2026-02-12T00:00:00+00:00</published><updated>2026-02-12T00:00:00+00:00</updated><id>https://agarnung.github.io/blog/digital-storage</id><content type="html" xml:base="https://agarnung.github.io/blog/digital-storage"><![CDATA[<h2 id="cronología-de-tecnologías-de-almacenamiento">Cronología de tecnologías de almacenamiento</h2>

<p>El almacenamiento digital ha evolucionado significativamente desde los primeros sistemas. Comprender esta evolución ayuda a entender por qué ciertas prácticas de mantenimiento son necesarias:</p>

<ul>
  <li>
    <p><strong>HDDs (1956 - presente)</strong>: Los discos duros mecánicos utilizan platos magnéticos giratorios para almacenar datos. Son sensibles a campos magnéticos externos y pueden sufrir desmagnetización con el tiempo.</p>
  </li>
  <li>
    <p><strong>SSDs (1991 - presente)</strong>: Las unidades de estado sólido emplean celdas de memoria flash NAND. La tecnología ha evolucionado desde SLC (Single-Level Cell) hasta QLC (Quad-Level Cell), cada una con diferentes características de velocidad, durabilidad y retención de datos.</p>
  </li>
  <li>
    <p><strong>Futuro</strong>: Aunque <a href="https://www.lenovo.com/es/es/glossary/optane-memory/?srsltid=AfmBOop1ZBZK9SlIgdmDFF4-0JNFFPzxjRejJ3pcizGo6Fw6zRoGeWoK">Intel Optane</a> fue discontinuado, tecnologías emergentes como <a href="https://www.techno-science.net/es/noticias/mram-memoria-informatica-del-futuro-finalmente-aqui-N26725.html">MRAM</a> (Magnetoresistive RAM), <a href="https://www.profesionalreview.com/2022/08/20/reram/">ReRAM</a> (Resistive RAM) y <a href="https://es.wikipedia.org/wiki/Memoria_hologr%C3%A1fica">almacenamiento holográfico</a> prometen nuevas capacidades.</p>
  </li>
</ul>

<p>Para entender mejor cómo funcionan los SSDs a nivel electrónico, recomiendo los videos de <a href="https://www.youtube.com/results?search_query=how+ssd+works">Branch Education en YouTube</a> que explican en detalle la estructura interna y el funcionamiento de estas unidades.</p>

<h2 id="por-qué-leer-todos-los-archivos-de-un-disco">Por qué leer todos los archivos de un disco</h2>

<p>Tiene sentido realizar una lectura completa de todos los archivos de un disco (HDD/SSD) periódicamente por varias razones importantes:</p>

<p><strong>Prevención de “bit rot”</strong>: En HDDs tradicionales, los datos almacenados magnéticamente pueden degradarse con el tiempo si no se acceden regularmente. Los campos magnéticos pueden debilitarse gradualmente.</p>

<p><strong>Refresh de celdas NAND</strong>: En SSDs, las celdas de memoria flash almacenan electrones. Con el tiempo, especialmente en celdas TLC y QLC, estos electrones pueden fugarse, causando pérdida de datos. La lectura periódica activa los circuitos de corrección de errores (ECC).</p>

<p><strong>Detección temprana de fallos</strong>: Al leer todo el disco, puedes identificar sectores problemáticos o bloques defectuosos antes de que fallen completamente, permitiendo realizar backups preventivos.</p>

<h2 id="cómo-funciona-el-proceso-de-lectura">Cómo funciona el proceso de lectura</h2>

<h3 id="para-hdds-discos-duros-mecánicos">Para HDDs (discos duros mecánicos)</h3>

<p>Los datos se almacenan magnéticamente en platos giratorios. Con el tiempo, los campos magnéticos pueden debilitarse debido a la desmagnetización natural. La lectura periódica “refresca” la señal magnética al activar los cabezales de lectura, lo que ayuda a mantener la integridad de los datos.</p>

<h3 id="para-ssds-unidades-de-estado-sólido">Para SSDs (unidades de estado sólido)</h3>

<p>Utilizan celdas de memoria flash NAND que almacenan electrones en celdas de memoria. Los electrones pueden fugarse con el tiempo, especialmente en tecnologías de mayor densidad como TLC (Triple-Level Cell) y QLC (Quad-Level Cell). La lectura activa los circuitos de corrección de errores (ECC), que pueden detectar y corregir errores menores antes de que se vuelvan críticos.</p>

<h2 id="recomendaciones-importantes">Recomendaciones importantes</h2>

<p><strong>Para SSDs</strong>: No es recomendable abusar de este proceso, ya que cada operación de lectura/escritura consume ciclos de vida limitados de las celdas NAND. Sin embargo, una lectura periódica es beneficiosa para activar la <a href="https://www.kingston.com/es/ssd/data-protection">corrección de errores</a>.</p>

<p><strong>Frecuencia recomendada</strong>:</p>

<ul>
  <li><strong>HDDs</strong>: cada 6-12 meses.</li>
  <li><strong>SSDs</strong>: cada 1-2 años (o usar TRIM periódico como alternativa).</li>
</ul>

<p><strong>Otras alternativas posiblemente mejores</strong>:</p>

<ul>
  <li><strong>Para HDDs</strong>: usar <code class="language-plaintext highlighter-rouge">badblocks -svn /dev/sdX</code> para verificar la integridad del disco.</li>
  <li><strong>Para SSDs</strong>: usar <code class="language-plaintext highlighter-rouge">fstrim -v /</code> en Linux o la optimización de unidades en Windows para mantener el rendimiento.</li>
</ul>

<blockquote>
  <p>[!IMPORTANT] 
Este proceso bastaría realizarlo unas 2 veces al año, como máximo. No es necesario ejecutarlo con mayor frecuencia.</p>
</blockquote>

<h2 id="scripts-de-lectura-segura">Scripts de lectura segura</h2>

<p>Se pueden desarrollar scripts que, además de cumplir el propósito de “engrasar” las puertas del dispositivo de almacenamiento, cuente cierta protección para impedir leer o escribir letras críticas del sistema, evitando accidentes que podrían ralentizar o afectar al sistema operativo sobre el que se monte la unidad de memoria.</p>

<h3 id="identificar-la-unidad-de-almacenamiento">Identificar la unidad de almacenamiento</h3>

<p>Antes de ejecutar los scripts, necesitamos identificar qué unidad se quiere inspeccionar.</p>

<p><strong>En Linux</strong>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Listar todos los discos y particiones</span>
lsblk

<span class="c"># Ver información detallada de discos</span>
<span class="nb">sudo </span>fdisk <span class="nt">-l</span>

<span class="c"># Ver puntos de montaje</span>
<span class="nb">df</span> <span class="nt">-h</span>

<span class="c"># Ver solo discos (no particiones)</span>
lsblk <span class="nt">-d</span> <span class="nt">-o</span> NAME,SIZE,TYPE,MODEL
</code></pre></div></div>

<p>El comando <code class="language-plaintext highlighter-rouge">lsblk</code> mostrará algo como:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>NAME   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
sda      8:0    0 465.8G  0 disk 
├─sda1   8:1    0   512M  0 part /boot/efi
├─sda2   8:2    0   100G  0 part /
└─sda3   8:3    0 365.3G  0 part /mnt/datos
</code></pre></div></div>

<p>En este ejemplo, <code class="language-plaintext highlighter-rouge">/mnt/datos</code> sería el punto de montaje a usar con el script.</p>

<blockquote>
  <p>[!NOTE]
Si quisiéramos verlo desde la WSL, una opción simple es <code class="language-plaintext highlighter-rouge">cd /mnt &amp;&amp; dh -f</code>, con lo que veríamos el nombre de las unidades que monta Windows automáticamente. Si no, tendríamos que <a href="https://learn.microsoft.com/es-es/windows/wsl/wsl2-mount-disk">montar en la WSL manualmente la unidad</a> desde Windows; así veríamos el dispositivo como un disco real (<code class="language-plaintext highlighter-rouge">/dev/sdX</code>) desde la WSL.</p>
</blockquote>

<p><strong>En Windows</strong>:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Listar todas las unidades</span><span class="w">
</span><span class="n">Get-PSDrive</span><span class="w"> </span><span class="nt">-PSProvider</span><span class="w"> </span><span class="nx">FileSystem</span><span class="w">

</span><span class="c"># Ver información detallada</span><span class="w">
</span><span class="n">Get-Volume</span><span class="w">

</span><span class="c"># Ver letras de unidad disponibles</span><span class="w">
</span><span class="p">[</span><span class="n">System.IO.DriveInfo</span><span class="p">]::</span><span class="n">GetDrives</span><span class="p">()</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Where-Object</span><span class="w"> </span><span class="p">{</span><span class="bp">$_</span><span class="o">.</span><span class="nf">IsReady</span><span class="p">}</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Select-Object</span><span class="w"> </span><span class="nx">Name</span><span class="p">,</span><span class="w"> </span><span class="nx">DriveType</span><span class="p">,</span><span class="w"> </span><span class="nx">TotalSize</span><span class="p">,</span><span class="w"> </span><span class="nx">AvailableFreeSpace</span><span class="w">
</span></code></pre></div></div>

<p>Que mostraría algo como:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Name</span><span class="w">           </span><span class="nx">Used</span><span class="w"> </span><span class="p">(</span><span class="n">GB</span><span class="p">)</span><span class="w">     </span><span class="n">Free</span><span class="w"> </span><span class="p">(</span><span class="n">GB</span><span class="p">)</span><span class="w"> </span><span class="n">Provider</span><span class="w">      </span><span class="nx">Root</span><span class="w">                                               </span><span class="nx">CurrentLocation</span><span class="w">
</span><span class="o">----</span><span class="w">           </span><span class="o">---------</span><span class="w">     </span><span class="o">---------</span><span class="w"> </span><span class="o">--------</span><span class="w">      </span><span class="o">----</span><span class="w">                                               </span><span class="o">---------------</span><span class="w">
</span><span class="n">C</span><span class="w">                 </span><span class="nx">460</span><span class="p">,</span><span class="nx">21</span><span class="w">         </span><span class="nx">16</span><span class="p">,</span><span class="nx">08</span><span class="w"> </span><span class="nx">FileSystem</span><span class="w">    </span><span class="nx">C:\</span><span class="w">                                                </span><span class="nx">Users\Alejandro</span><span class="w">
</span><span class="n">D</span><span class="w">                   </span><span class="nx">2</span><span class="p">,</span><span class="nx">16</span><span class="w">         </span><span class="nx">55</span><span class="p">,</span><span class="nx">15</span><span class="w"> </span><span class="nx">FileSystem</span><span class="w">    </span><span class="nx">D:\</span><span class="w">
</span></code></pre></div></div>

<h3 id="protecciones-implementadas">Protecciones implementadas</h3>

<p><strong>Para Windows</strong>:</p>

<ul>
  <li>Bloqueo de C: (sistema) → rechazo automático.</li>
  <li>Bloqueo de A: y B: (disquetes) → por precaución.</li>
  <li>Detección de Windows/Program Files/Users → doble verificación.</li>
  <li>Dos confirmaciones separadas → evita pulsaciones accidentales.</li>
  <li>Verificación de existencia → no falla en unidades inexistentes.</li>
</ul>

<p><strong>Para Linux</strong>:</p>

<ul>
  <li>Bloqueo de /, /boot, /etc, /usr, /var → rutas críticas del sistema.</li>
  <li>Bloqueo del dispositivo raíz → detecta si está en el mismo disco que /.</li>
  <li>Advertencia en /home → opcional, requiere confirmación extra.</li>
  <li>Verificación de espacio libre → evita problemas si está lleno.</li>
  <li>Dos confirmaciones + advertencias explícitas.</li>
</ul>

<h3 id="script-para-linux"><a href="../assets/blog_data/2026-02-12-digital-storage/read_storage_in_linux.sh">Script para Linux</a></h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>
<span class="c"># Lector seguro con BLOQUEO de particiones del sistema</span>
<span class="c"># IMPOSIBLE ejecutar sobre /, /boot, /home, etc.</span>

<span class="nv">TARGET</span><span class="o">=</span><span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span>
<span class="o">[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$TARGET</span><span class="s2">"</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">echo</span> <span class="s2">"Uso: </span><span class="nv">$0</span><span class="s2"> /punto/montaje"</span> <span class="o">&amp;&amp;</span> <span class="nb">exit </span>1
<span class="o">[</span> <span class="o">!</span> <span class="nt">-d</span> <span class="s2">"</span><span class="nv">$TARGET</span><span class="s2">"</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">echo</span> <span class="s2">"Error: </span><span class="nv">$TARGET</span><span class="s2"> no existe."</span> <span class="o">&amp;&amp;</span> <span class="nb">exit </span>1

<span class="c"># ========== LISTA NEGRA DE DIRECTORIOS PROHIBIDOS ==========</span>
<span class="nv">SYSTEM_PATHS</span><span class="o">=(</span><span class="s2">"/"</span> <span class="s2">"/boot"</span> <span class="s2">"/boot/efi"</span> <span class="s2">"/etc"</span> <span class="s2">"/usr"</span> <span class="s2">"/var"</span> <span class="s2">"/lib"</span> <span class="s2">"/lib64"</span><span class="o">)</span>
<span class="nv">HOME_PATH</span><span class="o">=</span><span class="s2">"/home"</span>
<span class="nv">ROOT_DEVICE</span><span class="o">=</span><span class="si">$(</span>findmnt <span class="nt">-n</span> <span class="nt">-o</span> SOURCE /<span class="si">)</span>

<span class="c"># ========== VERIFICACIÓN 1: No es ruta del sistema? ==========</span>
<span class="k">for </span>sys_path <span class="k">in</span> <span class="s2">"</span><span class="k">${</span><span class="nv">SYSTEM_PATHS</span><span class="p">[@]</span><span class="k">}</span><span class="s2">"</span><span class="p">;</span> <span class="k">do
    if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$TARGET</span><span class="s2">"</span> <span class="o">=</span> <span class="s2">"</span><span class="nv">$sys_path</span><span class="s2">"</span> <span class="o">]</span> <span class="o">||</span> <span class="o">[[</span> <span class="s2">"</span><span class="nv">$TARGET</span><span class="s2">"</span> <span class="o">==</span> <span class="s2">"</span><span class="nv">$sys_path</span><span class="s2">"</span>/<span class="k">*</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
        </span><span class="nb">echo</span> <span class="s2">"❌ ERROR CRÍTICO: </span><span class="nv">$TARGET</span><span class="s2"> es una ruta del sistema operativo"</span> <span class="o">&gt;</span>&amp;2
        <span class="nb">echo</span> <span class="s2">"   Esto podría PARALIZAR tu sistema"</span> <span class="o">&gt;</span>&amp;2
        <span class="nb">exit </span>1
    <span class="k">fi
done</span>

<span class="c"># ========== VERIFICACIÓN 2: No es /home? (opcional pero recomendado) ==========</span>
<span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$TARGET</span><span class="s2">"</span> <span class="o">=</span> <span class="s2">"</span><span class="nv">$HOME_PATH</span><span class="s2">"</span> <span class="o">]</span> <span class="o">||</span> <span class="o">[[</span> <span class="s2">"</span><span class="nv">$TARGET</span><span class="s2">"</span> <span class="o">==</span> <span class="s2">"</span><span class="nv">$HOME_PATH</span><span class="s2">"</span>/<span class="k">*</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
    </span><span class="nb">echo</span> <span class="s2">"⚠️  ADVERTENCIA: </span><span class="nv">$TARGET</span><span class="s2"> está en /home"</span> <span class="o">&gt;</span>&amp;2
    <span class="nb">echo</span> <span class="s2">"   Continuar podría ralentizar tus programas activos"</span> <span class="o">&gt;</span>&amp;2
    <span class="nb">read</span> <span class="nt">-p</span> <span class="s2">"¿Continuar de todos modos? (SOLO 'SI'): "</span> CONFIRM
    <span class="o">[</span> <span class="s2">"</span><span class="nv">$CONFIRM</span><span class="s2">"</span> <span class="o">!=</span> <span class="s2">"SI"</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">exit </span>1
<span class="k">fi</span>

<span class="c"># ========== VERIFICACIÓN 3: No es el dispositivo raíz? ==========</span>
<span class="nv">TARGET_DEVICE</span><span class="o">=</span><span class="si">$(</span>findmnt <span class="nt">-n</span> <span class="nt">-o</span> SOURCE <span class="s2">"</span><span class="nv">$TARGET</span><span class="s2">"</span> 2&gt;/dev/null <span class="o">||</span> <span class="nb">echo</span> <span class="s2">"unknown"</span><span class="si">)</span>
<span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$TARGET_DEVICE</span><span class="s2">"</span> <span class="o">!=</span> <span class="s2">"unknown"</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$TARGET_DEVICE</span><span class="s2">"</span> <span class="o">=</span> <span class="s2">"</span><span class="nv">$ROOT_DEVICE</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
    </span><span class="nb">echo</span> <span class="s2">"❌ BLOQUEO: </span><span class="nv">$TARGET</span><span class="s2"> está en el dispositivo raíz (</span><span class="nv">$ROOT_DEVICE</span><span class="s2">)"</span> <span class="o">&gt;</span>&amp;2
    <span class="nb">echo</span> <span class="s2">"   Esto afectaría TODO el sistema operativo"</span> <span class="o">&gt;</span>&amp;2
    <span class="nb">exit </span>1
<span class="k">fi</span>

<span class="c"># ========== VERIFICACIÓN 4: Espacio libre (no &lt; 1%) ==========</span>
<span class="nv">USED_PCT</span><span class="o">=</span><span class="si">$(</span><span class="nb">df</span> <span class="nt">--output</span><span class="o">=</span>pcent <span class="s2">"</span><span class="nv">$TARGET</span><span class="s2">"</span> | <span class="nb">tail</span> <span class="nt">-1</span> | <span class="nb">tr</span> <span class="nt">-dc</span> <span class="s1">'0-9'</span><span class="si">)</span>
<span class="nv">FREE_PCT</span><span class="o">=</span><span class="k">$((</span><span class="m">100</span> <span class="o">-</span> USED_PCT<span class="k">))</span>

<span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$FREE_PCT</span><span class="s2">"</span> <span class="nt">-lt</span> 1 <span class="o">]</span><span class="p">;</span> <span class="k">then
    </span><span class="nb">echo</span> <span class="s2">"❌ ERROR: Menos del 1% de espacio libre en </span><span class="nv">$TARGET</span><span class="s2">"</span> <span class="o">&gt;</span>&amp;2
    <span class="nb">echo</span> <span class="s2">"   Libera espacio antes de continuar"</span> <span class="o">&gt;</span>&amp;2
    <span class="nb">exit </span>1
<span class="k">fi</span>

<span class="c"># ========== CONFIRMACIÓN FINAL ==========</span>
<span class="nb">echo</span> <span class="s2">"=== CONFIRMACIÓN DE SEGURIDAD ==="</span>
<span class="nb">echo</span> <span class="s2">"Punto de montaje: </span><span class="nv">$TARGET</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"Dispositivo: </span><span class="nv">$TARGET_DEVICE</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"Espacio libre: </span><span class="nv">$FREE_PCT</span><span class="s2">%"</span>
<span class="nb">echo</span> <span class="s2">""</span>
<span class="nb">echo</span> <span class="s2">"⚠️  ÚLTIMA ADVERTENCIA:"</span>
<span class="nb">echo</span> <span class="s2">"   • Tiempo estimado: VARIAS HORAS"</span>
<span class="nb">echo</span> <span class="s2">"   • Solo LECTURA - CERO modificaciones"</span>
<span class="nb">echo</span> <span class="s2">"   • Puede ralentizar la unidad temporalmente"</span>
<span class="nb">echo</span> <span class="s2">""</span>

<span class="nb">read</span> <span class="nt">-p</span> <span class="s2">"¿ESTÁS SEGURO? (escribe 'CONFIRMAR'): "</span> CONFIRM1
<span class="o">[</span> <span class="s2">"</span><span class="nv">$CONFIRM1</span><span class="s2">"</span> <span class="o">!=</span> <span class="s2">"CONFIRMAR"</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">echo</span> <span class="s2">"Cancelado."</span> <span class="o">&amp;&amp;</span> <span class="nb">exit </span>0

<span class="nb">read</span> <span class="nt">-p</span> <span class="s2">"¿REALMENTE para </span><span class="nv">$TARGET</span><span class="s2">? (escribe 'SI' otra vez): "</span> CONFIRM2
<span class="o">[</span> <span class="s2">"</span><span class="nv">$CONFIRM2</span><span class="s2">"</span> <span class="o">!=</span> <span class="s2">"SI"</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">echo</span> <span class="s2">"Cancelado."</span> <span class="o">&amp;&amp;</span> <span class="nb">exit </span>0

<span class="c"># ========== EJECUCIÓN ==========</span>
<span class="nb">echo</span> <span class="s2">"[INICIANDO] Lectura segura de </span><span class="nv">$TARGET</span><span class="s2"> ..."</span>

<span class="nv">TOTAL_FILES</span><span class="o">=</span><span class="si">$(</span>find <span class="s2">"</span><span class="nv">$TARGET</span><span class="s2">"</span> <span class="nt">-type</span> f <span class="o">!</span> <span class="nt">-path</span> <span class="s2">"*/.snapshots/*"</span> <span class="o">!</span> <span class="nt">-path</span> <span class="s2">"*/lost+found/*"</span> 2&gt;/dev/null | <span class="nb">wc</span> <span class="nt">-l</span><span class="si">)</span>

<span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$TOTAL_FILES</span><span class="s2">"</span> <span class="nt">-eq</span> 0 <span class="o">]</span><span class="p">;</span> <span class="k">then
    </span><span class="nb">echo</span> <span class="s2">"No hay archivos que leer."</span>
    <span class="nb">exit </span>0
<span class="k">fi

</span><span class="nb">echo</span> <span class="s2">"Archivos a leer: </span><span class="nv">$TOTAL_FILES</span><span class="s2">"</span>

<span class="nv">COUNTER</span><span class="o">=</span>0
<span class="k">while </span><span class="nv">IFS</span><span class="o">=</span> <span class="nb">read</span> <span class="nt">-r</span> FILE<span class="p">;</span> <span class="k">do
    </span><span class="nv">COUNTER</span><span class="o">=</span><span class="k">$((</span>COUNTER <span class="o">+</span> <span class="m">1</span><span class="k">))</span>

    <span class="k">if</span> <span class="o">[</span> <span class="k">$((</span>COUNTER <span class="o">%</span> <span class="m">100</span><span class="k">))</span> <span class="nt">-eq</span> 0 <span class="o">]</span><span class="p">;</span> <span class="k">then
        </span><span class="nv">PCT</span><span class="o">=</span><span class="k">$((</span>COUNTER <span class="o">*</span> <span class="m">100</span> <span class="o">/</span> TOTAL_FILES<span class="k">))</span>
        <span class="nb">echo</span> <span class="nt">-ne</span> <span class="s2">"Progreso: </span><span class="nv">$PCT</span><span class="s2">% (</span><span class="nv">$COUNTER</span><span class="s2">/</span><span class="nv">$TOTAL_FILES</span><span class="s2">)</span><span class="se">\r</span><span class="s2">"</span>
    <span class="k">fi</span>

    <span class="c"># LECTURA SEGURA con dd (solo lectura)</span>
    <span class="nb">dd </span><span class="k">if</span><span class="o">=</span><span class="s2">"</span><span class="nv">$FILE</span><span class="s2">"</span> <span class="nv">of</span><span class="o">=</span>/dev/null <span class="nv">status</span><span class="o">=</span>none 2&gt;/dev/null <span class="o">||</span> <span class="nb">true

</span><span class="k">done</span> &lt; &lt;<span class="o">(</span>
    find <span class="s2">"</span><span class="nv">$TARGET</span><span class="s2">"</span> <span class="nt">-type</span> f <span class="se">\</span>
        <span class="o">!</span> <span class="nt">-path</span> <span class="s2">"*/.snapshots/*"</span> <span class="se">\</span>
        <span class="o">!</span> <span class="nt">-path</span> <span class="s2">"*/lost+found/*"</span> <span class="se">\</span>
        2&gt;/dev/null
<span class="o">)</span>

<span class="nb">echo</span> <span class="s2">""</span>
<span class="nb">echo</span> <span class="s2">"✅ Proceso completado. </span><span class="nv">$TOTAL_FILES</span><span class="s2"> archivos leídos."</span>
</code></pre></div></div>

<p>Para ejecutarlo:</p>

<p><strong>Script de Linux (<code class="language-plaintext highlighter-rouge">.sh</code>)</strong>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 1. Haz el script ejecutable</span>
<span class="nb">chmod</span> +x read_storage_in_linux.sh

<span class="c"># 2. Ejecuta con el punto de montaje (NO uses /, /boot, /home directamente)</span>
<span class="nb">sudo</span> ./read_storage_in_linux.sh /mnt/datos

<span class="c"># Ejemplo con unidad externa montada en /media/usuario/disco</span>
<span class="nb">sudo</span> ./read_storage_in_linux.sh /media/usuario/disco
</code></pre></div></div>

<blockquote>
  <p>[!WARNING] 
Nunca ejecutes estos scripts en la unidad del sistema operativo (C:\ en Windows, / en Linux). El script de Linux tiene protecciones integradas, pero siempre verifica el punto de montaje antes de ejecutar.</p>
</blockquote>

<h3 id="script-para-python-multiplataforma"><a href="../assets/blog_data/2026-02-12-digital-storage/read_storage.py">Script para Python</a> (multiplataforma)</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">time</span>
<span class="kn">from</span> <span class="nn">tqdm</span> <span class="kn">import</span> <span class="n">tqdm</span>

<span class="k">def</span> <span class="nf">read_all_files</span><span class="p">(</span><span class="n">root_dir</span><span class="p">):</span>
    <span class="c1"># Obtener lista total de archivos
</span>    <span class="k">print</span><span class="p">(</span><span class="s">"Buscando todos los archivos..."</span><span class="p">)</span>
    <span class="n">all_files</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="k">for</span> <span class="n">root</span><span class="p">,</span> <span class="n">dirs</span><span class="p">,</span> <span class="n">files</span> <span class="ow">in</span> <span class="n">os</span><span class="p">.</span><span class="n">walk</span><span class="p">(</span><span class="n">root_dir</span><span class="p">):</span>
        <span class="k">for</span> <span class="nb">file</span> <span class="ow">in</span> <span class="n">files</span><span class="p">:</span>
            <span class="n">file_path</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">path</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="n">root</span><span class="p">,</span> <span class="nb">file</span><span class="p">)</span>
            <span class="n">all_files</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">file_path</span><span class="p">)</span>
    
    <span class="c1"># Leer cada archivo con barra de progreso
</span>    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Leyendo </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">all_files</span><span class="p">)</span><span class="si">}</span><span class="s"> archivos..."</span><span class="p">)</span>
    <span class="k">for</span> <span class="n">file_path</span> <span class="ow">in</span> <span class="n">tqdm</span><span class="p">(</span><span class="n">all_files</span><span class="p">,</span> <span class="n">unit</span><span class="o">=</span><span class="s">'file'</span><span class="p">):</span>
        <span class="k">try</span><span class="p">:</span>
            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">file_path</span><span class="p">,</span> <span class="s">'rb'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
                <span class="c1"># Leer el archivo en bloques para no saturar memoria
</span>                <span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
                    <span class="n">chunk</span> <span class="o">=</span> <span class="n">f</span><span class="p">.</span><span class="n">read</span><span class="p">(</span><span class="mi">1024</span><span class="o">*</span><span class="mi">1024</span><span class="p">)</span> <span class="c1"># 1MB = 1048576 bits por chunk
</span>                    <span class="k">if</span> <span class="ow">not</span> <span class="n">chunk</span><span class="p">:</span>
                        <span class="k">break</span>
        <span class="k">except</span> <span class="p">(</span><span class="n">PermissionError</span><span class="p">,</span> <span class="nb">IOError</span><span class="p">)</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
            <span class="c1"># Ignorar archivos inaccesibles
</span>            <span class="k">pass</span>

<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">"__main__"</span><span class="p">:</span>
    <span class="kn">import</span> <span class="nn">sys</span>
    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">2</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="s">"Uso: python disk_refresh.py /ruta/al/disco"</span><span class="p">)</span>
        <span class="n">sys</span><span class="p">.</span><span class="nb">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
    
    <span class="n">start_time</span> <span class="o">=</span> <span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">()</span>
    <span class="n">read_all_files</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
    <span class="n">end_time</span> <span class="o">=</span> <span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">()</span>
    
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"</span><span class="se">\n</span><span class="s">Proceso completado en </span><span class="si">{</span><span class="n">end_time</span> <span class="o">-</span> <span class="n">start_time</span><span class="si">:</span><span class="p">.</span><span class="mi">2</span><span class="n">f</span><span class="si">}</span><span class="s"> segundos"</span><span class="p">)</span>
</code></pre></div></div>

<p>Para ejecutarlo:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 1. Asegúrate de tener tqdm instalado (e.g. en un entorno virtual)</span>
pip <span class="nb">install </span>tqdm

<span class="c"># 2. Ejecuta el script (funciona en Linux, Windows y macOS)</span>
python read_storage.py /ruta/al/disco

<span class="c"># Ejemplo en Linux</span>
python read_storage.py /mnt/datos

<span class="c"># Ejemplo en Windows</span>
python read_storage.py D:<span class="se">\</span>

<span class="c"># Ejemplo en macOS</span>
python read_storage.py /Volumes/DiscoExterno
</code></pre></div></div>

<blockquote>
  <p>[!NOTE] 
El término “chunks” (o “trozos” en español) en el script se refiere a la técnica de leer archivos en porciones pequeñas en lugar de cargarlos completamente en memoria. Esto es especialmente importante cuando se trabaja con archivos grandes o muchos archivos.</p>

  <p><strong>¿Por qué usar chunks?</strong></p>
  <ul>
    <li><strong>Eficiencia de memoria</strong>: evita saturar la RAM al no cargar archivos completos.</li>
    <li><strong>Robustez</strong>: permite manejar archivos extremadamente grandes que podrían causar problemas si se leen de una vez.</li>
    <li><strong>Flexibilidad</strong>: puedes ajustar el tamaño del chunk según las necesidades.</li>
  </ul>
</blockquote>

<h2 id="bonus-formatos-de-disco-y-tamaño-en-disco">Bonus: Formatos de disco y tamaño en disco</h2>

<p>Haciendo un backup de mi SSD me di cuenta de algo interesante: tras comparar y sincronizar unidireccionalmente todos los archivos de mi SSD (formato específico) a mi HDD (formato diferente), el primero pesaba casi el doble que el segundo, aun teniendo los mismos archivos.</p>

<p>Y es que el formato en que esté el disco importa significativamente.</p>

<p><img src="../assets/blog_data/2026-02-12-digital-storage/formatos.png" alt="Comparación de formatos de disco" /></p>

<p><img src="../assets/blog_data/2026-02-12-digital-storage/formatos2.png" alt="Análisis detallado de formatos" /></p>

<h3 id="diferencia-entre-tamaño-y-tamaño-en-disco">Diferencia entre tamaño y tamaño en disco</h3>

<p><strong>¿Qué significan esos dos valores?</strong></p>

<ul>
  <li><strong>Tamaño</strong>: es la suma real de los bytes usados por los archivos (por ejemplo, 43 MB).</li>
  <li><strong>Tamaño en disco</strong>: es el espacio físico ocupado en el disco, teniendo en cuenta que cada archivo usa bloques fijos. En el ejemplo, 270 MB.</li>
</ul>

<p><strong>¿Por qué el “tamaño en disco” puede ser mucho mayor?</strong></p>

<p><strong>1. Muchísimos archivos pequeños</strong>: el sistema de archivos (NTFS, exFAT, etc.) asigna espacio en “clústeres” (por ejemplo, 64 KB por archivo). Si tienes miles de archivos pequeñísimos (como logs, metadatos, mini archivos de configuración), cada uno puede ocupar un clúster entero aunque solo pese 1 KB. Ejemplo: 10,000 archivos de 1 KB en un sistema con clústeres de 64 KB = 10,000 × 64 KB = 640 MB en disco, aunque el “tamaño real” sea solo 10 MB.</p>

<p><strong>2. Tamaño de clúster muy grande</strong>: si el disco está formateado con un tamaño de clúster alto (por ejemplo, 64 KB o más), desperdicias más espacio con archivos pequeños.</p>

<p><strong>3. Archivos comprimidos que se expanden al ocupar disco</strong>: algunos archivos están comprimidos (por ejemplo, backups, archivos temporales) y su tamaño lógico es pequeño, pero el sistema les asigna mucho más espacio en disco.</p>

<p><strong>4. Permisos o journaling</strong>: algunos sistemas de archivos mantienen información extra (como permisos, journaling, datos extendidos), que también ocupa espacio aunque no se cuenta en el “tamaño” lógico.</p>

<p><strong>¿Cómo investigarlo?</strong></p>

<p>Para ver exactamente qué archivos están generando esa diferencia:</p>

<ul>
  <li><strong>En Windows</strong>: usar <a href="https://windirstat.net/">WinDirStat</a> o <a href="https://www.jam-software.com/treesize">TreeSize Free</a>.</li>
  <li><strong>En macOS</strong>: usar <code class="language-plaintext highlighter-rouge">du -sh</code> y <code class="language-plaintext highlighter-rouge">Get Info</code> en Terminal, o apps como <a href="https://daisydiskapp.com/">DaisyDisk</a>.</li>
  <li><strong>En Linux</strong>: comando útil:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">du</span> <span class="nt">-sh</span> carpeta <span class="c"># tamaño lógico  </span>
<span class="nb">du</span> <span class="nt">-sh</span> <span class="nt">--apparent-size</span> carpeta <span class="c"># tamaño real</span>
</code></pre></div>    </div>
  </li>
</ul>

<p><strong>¿Qué poder hacer para enmendarlo?</strong></p>

<ul>
  <li>Verificar si hay archivos pequeños en masa.</li>
  <li>Considerar reformatear con un tamaño de clúster más pequeño si es necesario.</li>
  <li>Usar <a href="https://www.softzone.es/2016/08/31/la-compresion-ntfs-windows-realizarla/">compresión NTFS</a> si el disco está lleno y muchos archivos son redundantes (<strong>lo que me sirvió</strong>).</li>
  <li>Eliminar archivos temporales o inútiles (por ejemplo, .log, .tmp, .bak).</li>
</ul>]]></content><author><name>Alejandro Garnung Menéndez</name><email>garnungalejandro@gmail.com</email></author><category term="Linux" /><category term="informatics" /><summary type="html"><![CDATA[Mantenimiento preventivo de unidades de almacenamiento en general para prevenir pérdida de datos.]]></summary></entry><entry><title type="html">Linux utils</title><link href="https://agarnung.github.io/blog/linux-utils" rel="alternate" type="text/html" title="Linux utils" /><published>2026-02-10T00:00:00+00:00</published><updated>2026-02-10T00:00:00+00:00</updated><id>https://agarnung.github.io/blog/linux-utils</id><content type="html" xml:base="https://agarnung.github.io/blog/linux-utils"><![CDATA[<p>Cada vez que instalo una distribución de Linux para uso frecuente, suelo crear varios aliases, atajos y utilidades que me han servido a lo largo de los años. Vamos a comentar algunos y registrarlos para “copiar y pegarlos” la próxima vez:</p>

<blockquote>
  <p>[!IMPORTANT] 
Por cierto, <a href="https://www.youtube.com/watch?v=mfv0V1SxbNA">chequea el video</a> con Linus y Linus construyendo el PC <em>perfecto</em> 🙂.</p>
</blockquote>

<h3 id="liberar-espacio-en-disco">Liberar Espacio en Disco</h3>

<blockquote>
  <p>[!NOTE] 
Los comandos <code class="language-plaintext highlighter-rouge">du</code>, <code class="language-plaintext highlighter-rouge">rm</code>, <code class="language-plaintext highlighter-rouge">apt</code>, <code class="language-plaintext highlighter-rouge">snap</code> y <code class="language-plaintext highlighter-rouge">journalctl</code> son herramientas del sistema de archivos y gestión de paquetes de Linux. Modificar <code class="language-plaintext highlighter-rouge">.bashrc</code> afecta al entorno de shell del usuario actual.</p>
</blockquote>

<p>Muchas veces, después de eliminar descargas, documentos e imágenes redundantes, aún necesitamos liberar espacio. Estos aliases ayudan a gestionar ese problema:</p>

<p>Agrega al archivo <code class="language-plaintext highlighter-rouge">~/.bashrc</code>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Muestra los 10 archivos/directorios más grandes en el directorio actual</span>
<span class="nb">alias </span><span class="nv">ducks</span><span class="o">=</span><span class="s1">'du -cks * | sort -rn | head'</span>
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Elimina la caché de miniaturas generadas por gestores de archivos (GNOME/Thunar/Nemo)</span>
<span class="nb">alias </span><span class="nv">limpiaThumbnails</span><span class="o">=</span><span class="s1">'rm -rfv ~/.cache/thumbnails'</span>
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Vacía la papelera del usuario (Trash) según estándar XDG</span>
<span class="nb">alias </span><span class="nv">limpiaBasura</span><span class="o">=</span><span class="s1">'cd ~/.local/share/Trash &amp;&amp; rm -rf *'</span>
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Limpia caché de paquetes APT y elimina dependencias innecesarias</span>
<span class="nb">alias </span><span class="nv">limpiaCache</span><span class="o">=</span><span class="s1">'sudo apt-get autoclean &amp;&amp; sudo apt-get clean &amp;&amp; sudo apt-get autoremove'</span>
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Elimina versiones antiguas de paquetes Snap que están deshabilitadas</span>
<span class="nb">alias </span><span class="nv">limpiaSnaps</span><span class="o">=</span><span class="s1">'LANG=C snap list --all | while read snapname ver rev trk pub notes; do if [[ $notes = *disabled* ]]; then sudo snap remove "$snapname" --revision="$rev"; fi; done'</span>
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Reduce el tamaño de los logs del systemd journal a solo los últimos 3 días</span>
<span class="nb">alias </span><span class="nv">limpiaJournals</span><span class="o">=</span><span class="s1">'sudo journalctl --vacuum-time=3d'</span>
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Ejecuta todas las limpiezas anteriores en secuencia</span>
<span class="nb">alias </span><span class="nv">limpiaTodo</span><span class="o">=</span><span class="s1">'limpiaThumbnails &amp;&amp; limpiaBasura &amp;&amp; limpiaCache &amp;&amp; limpiaSnaps &amp;&amp; limpiaJournals'</span>
</code></pre></div></div>

<h3 id="alias-útiles">Alias útiles</h3>

<blockquote>
  <p>[!NOTE] 
<code class="language-plaintext highlighter-rouge">chown</code> cambia el propietario de archivos, <code class="language-plaintext highlighter-rouge">du</code> mide uso de disco, <code class="language-plaintext highlighter-rouge">export -f</code> hace funciones disponibles en subshells.</p>
</blockquote>

<p>Agrega al archivo <code class="language-plaintext highlighter-rouge">~/.bashrc</code>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Cambia recursivamente el propietario de archivos/directorios al usuario actual</span>
<span class="nb">alias </span><span class="nv">mio</span><span class="o">=</span><span class="s1">'sudo chown $(whoami):$(whoami) -R .'</span>
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Función para ver el tamaño de directorios ordenados por uso (no alias)</span>
<span class="k">function </span>lsize <span class="o">{</span> 
    <span class="nb">du</span> <span class="nt">-h</span> <span class="nt">--max-depth</span><span class="o">=</span>1 <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> | <span class="nb">sort</span> <span class="nt">-rh</span> 
<span class="o">}</span>
<span class="nb">export</span> <span class="nt">-f</span> lsize
</code></pre></div></div>
<h3 id="seguridad-protección-contra-rm--rf-">Seguridad: Protección contra <code class="language-plaintext highlighter-rouge">rm -rf /</code></h3>

<blockquote>
  <p>[!WARNING] 
Modificar comandos del sistema en <code class="language-plaintext highlighter-rouge">/usr/bin/</code> requiere permisos root y puede romper el sistema. Puedes considerar usar un alias o función en su lugar.</p>
</blockquote>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Alternativa más segura (agregar al ~/.bashrc en lugar de modificar /usr/bin/rm):</span>
<span class="nb">alias rm</span><span class="o">=</span><span class="s1">'rm -i'</span> <span class="c"># Pide confirmación antes de eliminar</span>
</code></pre></div></div>

<p>Si aún quieres la protección mediante wrapper, crea un script en <code class="language-plaintext highlighter-rouge">/usr/local/bin/rm</code>:
.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>
<span class="c"># Protección contra eliminación recursiva forzada del sistema de archivos raíz</span>
<span class="k">if</span> <span class="o">[[</span> <span class="s2">"</span><span class="nv">$*</span><span class="s2">"</span> <span class="o">=</span>~ <span class="se">\-</span><span class="o">[</span>rf]<span class="k">*</span><span class="se">\s</span><span class="k">*</span>/ <span class="o">]]</span><span class="p">;</span> <span class="k">then
    </span><span class="nb">echo</span> <span class="s2">"[ERROR] Comando prohibido: rm </span><span class="nv">$*</span><span class="s2">"</span>
    <span class="nb">echo</span> <span class="s2">"Use 'rm --preserve-root' o elimine rutas específicas explícitamente."</span>
    <span class="nb">exit </span>1
<span class="k">else</span>
    /bin/rm <span class="s2">"</span><span class="nv">$@</span><span class="s2">"</span>
<span class="k">fi</span>
</code></pre></div></div>

<blockquote>
  <p>[!NOTE] 
En sistemas modernos, <code class="language-plaintext highlighter-rouge">rm --preserve-root</code> es la protección estándar. Considera usar <code class="language-plaintext highlighter-rouge">alias rm='rm --preserve-root'</code> en su lugar.</p>
</blockquote>

<h3 id="personalización-del-terminal">Personalización del terminal</h3>

<p>Por supuesto, también suelo personalizar mi bash (o tu <em>zsh</em> o lo que uses tú) con un heades de echo de strings en el bashrc, usando <a href="https://patorjk.com/software/taag/#p=display&amp;f=Graffiti&amp;t=Type+Something+&amp;x=none&amp;v=4&amp;h=4&amp;w=80&amp;we=false">esta web</a>:</p>

<p>Agrega esto al final de tu ~/.bashrc para mostrar un encabezado al abrir el terminal:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> <span class="s2">"   _____  .__              __                   .___              "</span>
<span class="nb">echo</span> <span class="s2">"  /  _  </span><span class="se">\ </span><span class="s2">|  |   ____     |__|____    ____    __| _/______  ____  "</span>
<span class="nb">echo</span> <span class="s2">" /  /_</span><span class="se">\ </span><span class="s2"> </span><span class="se">\|</span><span class="s2">  | _/ __ </span><span class="se">\ </span><span class="s2">   |  </span><span class="se">\_</span><span class="s2">_  </span><span class="se">\ </span><span class="s2"> /    </span><span class="se">\ </span><span class="s2"> / __ |</span><span class="se">\_</span><span class="s2">  __ </span><span class="se">\/</span><span class="s2">  _ </span><span class="se">\ </span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"/    |    </span><span class="se">\ </span><span class="s2"> |_</span><span class="se">\ </span><span class="s2"> ___/    |  |/ __ </span><span class="se">\|</span><span class="s2">   |  </span><span class="se">\/</span><span class="s2"> /_/ | |  | </span><span class="se">\(</span><span class="s2">  &lt;_&gt; )"</span>
<span class="nb">echo</span> <span class="s2">"</span><span class="se">\_</span><span class="s2">___|__  /____/</span><span class="se">\_</span><span class="s2">__  &gt;</span><span class="se">\_</span><span class="s2">_|  (____  /___|  /</span><span class="se">\_</span><span class="s2">___ | |__|   </span><span class="se">\_</span><span class="s2">___/ "</span>
<span class="nb">echo</span> <span class="s2">"        </span><span class="se">\/</span><span class="s2">          </span><span class="se">\/\_</span><span class="s2">_____|    </span><span class="se">\/</span><span class="s2">     </span><span class="se">\/</span><span class="s2">      </span><span class="se">\/</span><span class="s2">               "</span>
<span class="nb">echo</span> <span class="s2">"                                                                  "</span>
</code></pre></div></div>

<p><img src="../assets/blog_images/2026-02-10-linux-utils/alejandro.png" alt="alejandro" /></p>

<blockquote>
  <p>[!TIP] 
Puedes usar <code class="language-plaintext highlighter-rouge">neofetch</code> o <code class="language-plaintext highlighter-rouge">screenfetch</code> para mostrar información del sistema con estilo ASCII. Instálalo con <code class="language-plaintext highlighter-rouge">sudo apt install neofetch</code>.</p>
</blockquote>

<h3 id="nvidia-smi-full">nvidia-smi-full</h3>

<p>A veces necesito saber quién ejecuto, o el comando completo que lanzó un proceso que está ocupando/consumiendo GPU. Para no tener que lanzar <code class="language-plaintext highlighter-rouge">ps -fp &lt;ID_PROCESO&gt;</code> o similares, podemos capturar la salida de <code class="language-plaintext highlighter-rouge">nvidia-smi</code> y mejorarla:</p>

<p>Creamos el archivo del script:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>nano /usr/local/bin/nvidia-smi-full
</code></pre></div></div>

<p>Pegamos esto dentro:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>

nvidia-smi

<span class="nb">echo
echo</span> <span class="s2">"===================== FULL PROCESS COMMANDS (SORTED BY GPU MEM) ====================="</span>
<span class="nb">printf</span> <span class="s2">"%-5s %-8s %-10s %-6s %-12s %s</span><span class="se">\n</span><span class="s2">"</span> <span class="s2">"GPU"</span> <span class="s2">"PID"</span> <span class="s2">"USER"</span> <span class="s2">"TYPE"</span> <span class="s2">"GPU-MEM"</span> <span class="s2">"FULL COMMAND"</span>
<span class="nb">echo</span> <span class="s2">"---------------------------------------------------------------------------------------"</span>

nvidia-smi | <span class="nb">awk</span> <span class="s1">'
/Processes:/ {inproc=1; next}
inproc &amp;&amp; /^\|/ &amp;&amp; $2 ~ /^[0-9]+$/ &amp;&amp; $5 ~ /^[0-9]+$/ {
    gpu=$2
    pid=$5
    type=$6
    mem=$(NF-1)
    gsub("MiB","",mem)
    print gpu","pid","type","mem
}
'</span> | <span class="nb">sort</span> <span class="nt">-t</span><span class="s1">','</span> <span class="nt">-k4</span> <span class="nt">-nr</span> | <span class="k">while </span><span class="nv">IFS</span><span class="o">=</span><span class="s1">','</span> <span class="nb">read</span> <span class="nt">-r</span> gpu pid <span class="nb">type </span>mem<span class="p">;</span> <span class="k">do</span>

    user<span class="o">=</span><span class="si">$(</span>ps <span class="nt">-o</span> <span class="nv">user</span><span class="o">=</span> <span class="nt">-p</span> <span class="s2">"</span><span class="nv">$pid</span><span class="s2">"</span> 2&gt;/dev/null<span class="si">)</span>

    if <span class="o">[[</span> <span class="nt">-r</span> /proc/<span class="nv">$pid</span>/cmdline <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
        cmd<span class="o">=</span><span class="si">$(</span><span class="nb">tr</span> <span class="s1">'\0'</span> <span class="s1">' '</span> &lt; /proc/<span class="nv">$pid</span>/cmdline<span class="si">)</span>
        [[ <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$cmd</span><span class="s2">"</span> <span class="o">]]</span> <span class="o">&amp;&amp;</span> <span class="nv">cmd</span><span class="o">=</span><span class="s2">"[kernel thread or exited]"</span>
    else
        cmd<span class="o">=</span><span class="s2">"[not accessible]"</span>
    fi

    printf <span class="s2">"%-5s %-8s %-10s %-6s %-12s %s</span><span class="se">\n</span><span class="s2">"</span> <span class="se">\</span>
        <span class="s2">"</span><span class="nv">$gpu</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$pid</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$user</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$type</span><span class="s2">"</span> <span class="s2">"</span><span class="k">${</span><span class="nv">mem</span><span class="k">}</span><span class="s2">MiB"</span> <span class="s2">"</span><span class="nv">$cmd</span><span class="s2">"</span>

<span class="k">done</span>
</code></pre></div></div>

<p>Finalmente le damos permisos de ejecución:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo chmod</span> +x /usr/local/bin/nvidia-smi-full
</code></pre></div></div>

<p>La salida será algo así:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>agarnung@chomsky:~<span class="nv">$ </span>nvidia-smi-full
Thu Feb 19 10:24:42 2026
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 580.126.09             Driver Version: 580.126.09     CUDA Version: 13.0     |
+-----------------------------------------+------------------------+----------------------+
| GPU  Name                 Persistence-M | Bus-Id          Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |           Memory-Usage | GPU-Util  Compute M. |
|                                         |                        |               MIG M. |
|<span class="o">=========================================</span>+<span class="o">========================</span>+<span class="o">======================</span>|
|   0  NVIDIA RTX A6000               Off |   00000000:01:00.0 Off |                  Off |
| 30%   25C    P8              5W /  300W |   40874MiB /  49140MiB |      0%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+
|   1  NVIDIA RTX A6000               Off |   00000000:08:00.0 Off |                  Off |
| 30%   23C    P8             15W /  300W |   36109MiB /  49140MiB |      0%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+
+-----------------------------------------------------------------------------------------+
| Processes:                                                                              |
|  GPU   GI   CI              PID   Type   Process name                        GPU Memory |
|        ID   ID                                                               Usage      |
|<span class="o">=========================================================================================</span>|
|    0   N/A  N/A            1971      G   /usr/lib/xorg/Xorg                       18MiB |
|    0   N/A  N/A            2187      G   /usr/bin/gnome-shell                     14MiB |
|    0   N/A  N/A         1533058      C   VLLM::EngineCore                      40532MiB |
|    0   N/A  N/A         3041554      C   /app/llama-server                       262MiB |
|    1   N/A  N/A            1971      G   /usr/lib/xorg/Xorg                        4MiB |
|    1   N/A  N/A         1531689      C   /opt/app-root/bin/python3               844MiB |
|    1   N/A  N/A         1533062      C   VLLM::EngineCore                      31434MiB |
|    1   N/A  N/A         1533154      C   VLLM::EngineCore                       1728MiB |
|    1   N/A  N/A         1533200      C   VLLM::EngineCore                       1742MiB |
|    1   N/A  N/A         3041554      C   /app/llama-server                       300MiB |
+-----------------------------------------------------------------------------------------+
<span class="o">=====================</span> FULL PROCESS COMMANDS <span class="o">(</span>SORTED BY GPU MEM<span class="o">)</span> <span class="o">=====================</span>
GPU   PID      USER       TYPE   GPU-MEM      FULL COMMAND
<span class="nt">---------------------------------------------------------------------------------------</span>
0     1533058  root       C      40532MiB     VLLM::EngineCore
1     1533062  root       C      31434MiB     VLLM::EngineCore
1     1533200  root       C      1742MiB      VLLM::EngineCore
1     1533154  root       C      1728MiB      VLLM::EngineCore
1     1531689  leon       C      844MiB       /opt/app-root/bin/python3 /opt/app-root/bin/docling-serve run
1     3041554  root       C      300MiB       /app/llama-server <span class="nt">--host</span> 127.0.0.1 <span class="nt">--port</span> 35927 <span class="nt">--sleep-idle-seconds</span> 600 <span class="nt">--alias</span> Qwen3-0.6B-Q5_K_M <span class="nt">--model</span> /models/Qwen3-0.6B-Q5_K_M.gguf
0     3041554  root       C      262MiB       /app/llama-server <span class="nt">--host</span> 127.0.0.1 <span class="nt">--port</span> 35927 <span class="nt">--sleep-idle-seconds</span> 600 <span class="nt">--alias</span> Qwen3-0.6B-Q5_K_M <span class="nt">--model</span> /models/Qwen3-0.6B-Q5_K_M.gguf
0     1971     gdm        G      18MiB        /usr/lib/xorg/Xorg vt1 <span class="nt">-displayfd</span> 3 <span class="nt">-auth</span> /run/user/120/gdm/Xauthority <span class="nt">-nolisten</span> tcp <span class="nt">-background</span> none <span class="nt">-noreset</span> <span class="nt">-keeptty</span> <span class="nt">-novtswitch</span> <span class="nt">-verbose</span> 3
0     2187     gdm        G      14MiB        /usr/bin/gnome-shell
1     1971     gdm        G      4MiB         /usr/lib/xorg/Xorg vt1 <span class="nt">-displayfd</span> 3 <span class="nt">-auth</span> /run/user/120/gdm/Xauthority <span class="nt">-nolisten</span> tcp <span class="nt">-background</span> none <span class="nt">-noreset</span> <span class="nt">-keeptty</span> <span class="nt">-novtswitch</span> <span class="nt">-verbose</span> 3
</code></pre></div></div>

<p>(ahora muestra el comando real de <code class="language-plaintext highlighter-rouge">/proc/PID/cmdline</code> que lanzó el proceso [tanto de computación—<strong>C</strong>—, como gráfico—<strong>G</strong>—(e.g. <code class="language-plaintext highlighter-rouge">/usr/lib/xorg/Xorg</code>)] y los ordena por consumo de GPU descencentemente).</p>

<h4 id="usuario-no-root">Usuario no root</h4>

<p>Si no tenemos acceso como superusuario al sistema, sino que somos un usuario más del equipo, podemos igualmente definir un archivo para scripts propios, solo que hay que añadirlo luego al <a href="https://rootsudo.wordpress.com/2014/04/06/el-path-la-ruta-de-linux-variables-de-entorno/"><code class="language-plaintext highlighter-rouge">PATH</code></a>:</p>

<p>Creamos nuestra carpeta de scripts en nuestro <code class="language-plaintext highlighter-rouge">home</code>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir</span> <span class="nt">-p</span> ~/bin
</code></pre></div></div>

<p>Creamos y pegamos ahí el script, como antes, y le damos permisos de ejecución. Luego añadimos la ruta al archivo de configuración de nuestro shell:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> <span class="s1">'export PATH="$HOME/bin:$PATH"'</span> <span class="o">&gt;&gt;</span> ~/.bashrc <span class="o">&amp;&amp;</span> <span class="nb">source</span> ~/.bashrc 
</code></pre></div></div>

<p>Tras esto, cualquier script que pongamos en <code class="language-plaintext highlighter-rouge">~/bin</code> y marquemos como ejecutable (<code class="language-plaintext highlighter-rouge">chmod +x</code>) se podrá llamar desde cualquier lugar.</p>

<p>Aunque si no queremos tocar el <code class="language-plaintext highlighter-rouge">PATH</code>, también podríamos ejecutarlo con la ruta completa, e.g. <code class="language-plaintext highlighter-rouge">$ ~/bin/nvidia-smi-full</code></p>

<h3 id="vaca-dinámica-con-quotes-api-api-ninjas">Vaca dinámica con Quotes API (<a href="https://api-ninjas.com/profile">API Ninjas</a>)</h3>

<p>Sí, literalmente una vaca que aparece al inicio de cada terminal abierto y te lanza una frase famosa distinta cada vez…</p>

<p>Dependencias:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt <span class="nb">install </span>curl jq cowsay
</code></pre></div></div>

<p>Y necesitas crear una cuenta en (<a href="https://api-ninjas.com/profile">API Ninjas</a>) para obtener tu clave API, que has de poner en el <em>placeholder</em> de debajo, dentro del <code class="language-plaintext highlighter-rouge">~/.bashrc</code>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># La vaca que quota</span>
<span class="nv">API_KEY</span><span class="o">=</span><span class="s2">"aquí-tu-key"</span>

<span class="k">if </span><span class="nb">command</span> <span class="nt">-v</span> cowsay <span class="o">&gt;</span>/dev/null 2&gt;&amp;1<span class="p">;</span> <span class="k">then
    </span><span class="nv">respuesta</span><span class="o">=</span><span class="si">$(</span>curl <span class="nt">-s</span> <span class="nt">--fail</span> <span class="se">\</span>
        <span class="s2">"https://api.api-ninjas.com/v2/randomquotes?categories=wisdom,success"</span> <span class="se">\</span>
        <span class="nt">-H</span> <span class="s2">"X-Api-Key: </span><span class="nv">$API_KEY</span><span class="s2">"</span><span class="si">)</span>

    <span class="k">if</span> <span class="o">[</span> <span class="nv">$?</span> <span class="nt">-eq</span> 0 <span class="o">]</span><span class="p">;</span> <span class="k">then
        </span><span class="nv">frase</span><span class="o">=</span><span class="si">$(</span><span class="nb">echo</span> <span class="s2">"</span><span class="nv">$respuesta</span><span class="s2">"</span> | jq <span class="nt">-r</span> <span class="s1">'.[0].quote'</span><span class="si">)</span>
        <span class="nv">autor</span><span class="o">=</span><span class="si">$(</span><span class="nb">echo</span> <span class="s2">"</span><span class="nv">$respuesta</span><span class="s2">"</span> | jq <span class="nt">-r</span> <span class="s1">'.[0].author'</span><span class="si">)</span>

        <span class="k">if</span> <span class="o">[</span> <span class="nt">-n</span> <span class="s2">"</span><span class="nv">$frase</span><span class="s2">"</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$frase</span><span class="s2">"</span> <span class="o">!=</span> <span class="s2">"null"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
            </span>cowsay <span class="s2">"</span><span class="nv">$frase</span><span class="s2"> — </span><span class="nv">$autor</span><span class="s2">"</span>
        <span class="k">else
            </span>cowsay <span class="s2">"Bienvenido Alejandro"</span>
        <span class="k">fi
    fi
fi</span>
</code></pre></div></div>

<p>Es cada apertura de terminal:</p>

<ul>
  <li>Se hace una llamada HTTP.</li>
  <li>Se recibe el JSON con la frase.</li>
  <li>Se extrae la cita.</li>
  <li>La vaca la muge (habría estado bien escribir esto en <a href="https://esolangs.org/wiki/COW">COW</a>…).</li>
</ul>

<p>Se verá algo así:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>alejandro@DESKTOP-AIFFN1L:/mnt/c/Users/Alejandro<span class="nv">$ </span><span class="nb">source</span> ~/.bashrc
 ________________________________________
/ Embrace every good opportunity you     <span class="se">\</span>
<span class="se">\ </span>encounter — some will get you informed /
 <span class="nt">----------------------------------------</span>
        <span class="se">\ </span>  ^__^
         <span class="se">\ </span> <span class="o">(</span>oo<span class="o">)</span><span class="se">\_</span>______
            <span class="o">(</span>__<span class="o">)</span><span class="se">\ </span>      <span class="o">)</span><span class="se">\/\</span>
                <span class="o">||</span><span class="nt">----w</span> |
                <span class="o">||</span>     <span class="o">||</span>
</code></pre></div></div>

<h2 id="recarga-la-configuración">Recarga la Configuración</h2>

<p>Después de editar <code class="language-plaintext highlighter-rouge">~/.bashrc</code>, ejecuta:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">source</span> ~/.bashrc
<span class="c"># O simplemente cierra y reabre el terminal</span>
</code></pre></div></div>

<blockquote>
  <p>[!WARNING] 
Nunca ejecutes comandos <code class="language-plaintext highlighter-rouge">rm -rf</code> con rutas que no hayas verificado. Considera usar <code class="language-plaintext highlighter-rouge">trash-cli</code> (<code class="language-plaintext highlighter-rouge">sudo apt install trash-cli</code>) para mover archivos a la papelera en lugar de eliminarlos permanentemente.</p>
</blockquote>

<h2>…</h2>

<p>Se irán añadiendo más…</p>]]></content><author><name>Alejandro Garnung Menéndez</name><email>garnungalejandro@gmail.com</email></author><category term="Linux" /><summary type="html"><![CDATA[Unas utilidades de terminal que siempre uso en Linux.]]></summary></entry></feed>