<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Blog | Pablo Sanxiao</title>
    <link>https://psanxiao.com/blog.html</link>
    <description>Artículos y reflexiones sobre tecnología, software libre, sistemas de información geográfica (GIS), deporte y proyectos personales.</description>
    <language>es-es</language>
    <atom:link href="https://psanxiao.com/feed.xml" rel="self" type="application/rss+xml" />
    
        <item>
            <title>Gemma 4 en local: Del chat a la automatización con OpenCode y LM Studio</title>
            <link>https://psanxiao.com/posts/2026-04-22-gemma4-local-con-lmstudio-opencode.html</link>
            <guid isPermaLink="true">https://psanxiao.com/posts/2026-04-22-gemma4-local-con-lmstudio-opencode.html</guid>
            <pubDate>Wed, 22 Apr 2026 00:00:00 +0000</pubDate>
            <description><![CDATA[<p>Hasta hace muy poco, los modelos locales eran poco más que una curiosidad técnica o juguetes para tareas extremadamente simples. El lanzamiento reciente de Gemma 4 se presenta como un paso adelante: un modelo que promete capacidades de razonamiento serias en un tamaño lo suficientemente contenido como para correr en un portátil convencional.</p>
<p>Esto no significa necesariamente que vayamos a cancelar nuestras suscripciones hoy mismo. La propuesta de valor de Gemma 4 es, sobre el papel, ayudarnos a ahorrar tokens de nuestras cuentas de pago y ganar privacidad en tareas mecánicas o procesamiento de datos locales. En este post, vamos a comprobar si realmente es capaz de actuar como un agente operativo configurándolo con LM Studio y OpenCode.</p>
<h2 id="el-fundamento-que-significan-los-numeros">El Fundamento: ¿Qué significan los números?</h2>
<p>Al buscar Gemma 4, verás nomenclaturas como "26B A4B". Descifrarlas es vital para no bloquear tu máquina:</p>
<ul>
<li>
<p>Los Parámetros (B): La "B" significa Billions (miles de millones). Son las variables que el modelo ajustó durante su entrenamiento. A más parámetros, mayor capacidad de razonamiento.</p>
</li>
<li>
<p>Arquitectura MoE (Mixture of Experts): El modelo 26B A4B tiene 26 mil millones de parámetros totales, pero solo activa 4 mil millones (Active) por cada token generado. Obtienes la inteligencia de un modelo grande con la velocidad de uno pequeño, aunque necesitas RAM para alojar los 26B enteros.</p>
</li>
</ul>
<h2 id="el-formato-gguf-vs-mlx">El Formato: GGUF vs MLX</h2>
<ul>
<li>
<p><strong>GGUF: El estándar universal</strong>. Es el formato más común para Windows o Linux. Su gran ventaja es el GPU Offloading: si el modelo no cabe entero en tu tarjeta gráfica (VRAM), GGUF permite que la CPU ayude con el trabajo sobrante de forma transparente.</p>
</li>
<li>
<p><strong>MLX: El "traje a medida" para Apple Silicon</strong>. Si tienes un Mac (M1 a M4), el formato MLX es superior. Está diseñado por Apple para aprovechar la memoria unificada, permitiendo que la CPU y la GPU compartan datos instantáneamente sin cuellos de botella. Es más rápido y eficiente que cualquier otro formato en esta arquitectura.</p>
</li>
</ul>
<h2 id="cuantizacion-por-que-hablamos-de-bits">Cuantización: ¿Por qué hablamos de "Bits"?</h2>
<p>Los pesos originales de un modelo ocupan mucha memoria (16 bits). Cuantizar es reducir esos bits para que el modelo quepa en hardware doméstico:</p>
<ul>
<li>
<p><em>8 bits</em>: Casi sin pérdida de inteligencia, pero pesado.</p>
</li>
<li>
<p><em>4 bits (Q4_K_M)</em>: El estándar de oro. El modelo ocupa una cuarta parte del original con una pérdida mínima de razonamiento. Siempre que puedas, apunta a este nivel.</p>
</li>
<li>
<p><em>2 bits</em>: El modelo se vuelve "torpe" y pierde coherencia lógica. Solo como último recurso.</p>
</li>
</ul>
<h2 id="licencia-apache-20-un-cambio-de-paradigma">Licencia Apache 2.0: Un cambio de paradigma</h2>
<p>Uno de los aspectos más disruptivos de Gemma 4 no es técnico, sino legal. Históricamente, modelos como Llama o las versiones previas de Gemma utilizaban licencias personalizadas de "pesos abiertos" que imponían restricciones de uso comercial o límites de usuarios.</p>
<p>Gemma 4 rompe con esto al adoptar la licencia Apache 2.0. A diferencia de los modelos anteriores que eran "abiertos pero con condiciones", Apache 2.0 es una licencia permisiva de software libre real. Esto permite a cualquier desarrollador o empresa modificar, distribuir y usar el modelo con mayor libertad.</p>
<h2 id="mas-alla-del-chat-gemma-4-en-modo-agente">Más allá del chat: Gemma 4 en Modo Agente</h2>
<p>Aquí es donde Gemma 4 brilla frente a sus predecesores. Ha sido diseñada con soporte nativo para function-calling y flujos de trabajo agénticos. Mientras que en LM Studio solo tienes un chat, herramientas como OpenCode permiten que Gemma 4 actúe como un agente de codificación.</p>
<h3 id="que-es-opencode">¿Qué es OpenCode?</h3>
<p>OpenCode es un agente de código abierto para tu terminal o IDE que puede leer tus archivos, ejecutar comandos de shell y aplicar correcciones automáticamente. No solo te da el código; crea el archivo, lo prueba y lo itera hasta que funciona.</p>
<h3 id="configuracion-del-entorno-paso-a-paso">Configuración del entorno (paso a paso)</h3>
<p>Para tener este flujo de trabajo, necesitamos conectar ambas herramientas:</p>
<p><strong>En LM Studio</strong>:</p>
<ul>
<li>
<p>Descarga Gemma 4 26B A4B (o la versión E4B si tienes poca RAM).</p>
</li>
<li>
<p>Ve a la pestaña de Local Server y actívalo en el puerto 1234.</p>
</li>
<li>
<p>Asegúrate de subir el GPU Offload al máximo y ajustar el contexto a un mínimo de 32K.</p>
</li>
</ul>
<p><strong>En OpenCode</strong>:</p>
<p>Configura el archivo opencode.json para que apunte a tu servidor local:</p>
<div class="codehilite"><pre><span></span><code><span class="nt">&quot;lmstudio&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
<span class="w">  </span><span class="nt">&quot;name&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;LM Studio&quot;</span><span class="p">,</span>
<span class="w">  </span><span class="nt">&quot;npm&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;@ai-sdk/openai-compatible&quot;</span><span class="p">,</span>
<span class="w">  </span><span class="nt">&quot;options&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="nt">&quot;baseURL&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;http://localhost:1234/v1&quot;</span><span class="p">,</span>
<span class="w">    </span><span class="nt">&quot;apiKey&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;lm-studio&quot;</span>
<span class="w">  </span><span class="p">},</span>
<span class="w">  </span><span class="nt">&quot;models&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="nt">&quot;gemma-4-e2b&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
<span class="w">      </span><span class="nt">&quot;name&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Gemma 4 Local (LM Studio)&quot;</span>
<span class="w">    </span><span class="p">}</span>
<span class="w">  </span><span class="p">}</span>
<span class="p">}</span>
</code></pre></div>

<p>Ahora, desde tu terminal, puedes lanzar comandos como: opencode "refactoriza este componente usando hooks de React" y el agente usará a Gemma 4 para realizar la tarea directamente en tu sistema de archivos.</p>
<p>Aunque sobre el papel todo parece sencillo, suele haber un problema con estas configuraciones. Le pides a opencode que haga algo usando gemma 4 y en lugar de esa acción te entrega un mensaje diciendo como se hace. Las herramientas no se terminan de entender entre sí. Raro en informática verdad?.</p>
<h2 id="que-es-la-paralisis-del-modelo">¿Qué es la parálisis del modelo?</h2>
<p>Aunque Gemma 4 está entrenada para actuar como agente, su entrenamiento de refuerzo (RLHF) a menudo prioriza ser un "asistente servicial de chat". Ante una orden como "lista los archivos", el modelo puede caer en la parálisis de explicar en lugar de ejecutar. Te dirá "Para ver los archivos deberías usar ls" en lugar de emitir el comando técnico que OpenCode necesita procesar.</p>
<h3 id="el-system-prompt-como-protocolo-operativo">El System Prompt como Protocolo Operativo</h3>
<p>Para romper esta inercia, debemos configurar un System Prompt en LM Studio que actúe como un "contrato de ejecución". No le estamos enseñando capacidades nuevas; le estamos indicando qué rol operativo debe asumir en este flujo de trabajo, por ejemplo:</p>
<p>"Eres un asistente de programación integrado en el terminal mediante OpenCode. Obligatorio: Para cualquier acción que requiera el sistema (leer archivos, listar directorios, ejecutar comandos), utiliza siempre el formato de herramientas de OpenCode. No te limites a escribir el comando, ejecútalo."</p>
<p>Este prompt garantiza que el modelo pase del modo "consultor" al modo "ejecutor", emitiendo las señales JSON correctas para que OpenCode actúe sobre tu sistema de archivos.</p>
<h2 id="conclusion">Conclusión</h2>
<p>Gemma 4 es el primer modelo abierto que realmente permite un desarrollo local-first con capacidades agénticas algo serias. Pero seamos realistas, no se puede comparar con los servicios por API de los big players. Es una buena alternativa para no gastar tokens en tareas sencillas. Y Evidentemente, si tienes que trabajar con datos sensibles o necesitas privacidad y trabajar en local, aunque sea limitado, puede ser una gran ayuda.</p>
<p>Esto abre posibilidades interesantes, además, para explorar técnicas de trabajo con IA sin quemar el presupuesto de tokens. Eso sí, no esperes que te permita olvidarte de tus suscripciones actuales, porque las tareas que vas a poder delegar todavía están limitadas. Toca probarla a fondo y ver hasta dónde llega.</p>]]></description>
        </item>
    
        <item>
            <title>Optimización de flujos de trabajo con OpenCode y Ollama: IA local para tareas sencillas</title>
            <link>https://psanxiao.com/posts/2026-03-28-optimiza-flujo-trabajo-ia-local-opencode-ollama.html</link>
            <guid isPermaLink="true">https://psanxiao.com/posts/2026-03-28-optimiza-flujo-trabajo-ia-local-opencode-ollama.html</guid>
            <pubDate>Sat, 28 Mar 2026 00:00:00 +0000</pubDate>
            <description><![CDATA[<p>En el desarrollo de software, a menudo nos enfrentamos a tareas que, aunque sencillas, resultan tediosas: escribir scripts de automatización, comparar estructuras de archivos en diferentes directorios o realizar transformaciones de datos repetitivas. Aunque herramientas en la nube como Claude o GPT-4 son excelentes para tareas complejas, utilizarlas para estos procesos menores implica un consumo innecesario de tokens y una dependencia constante de servicios de pago.</p>
<p>La alternativa más eficiente hoy en día es el uso de <strong>modelos de lenguaje locales</strong>. Gracias a la integración entre <strong>Ollama</strong> y el proyecto CLI <strong>OpenCode</strong>, es posible ejecutar un asistente potente directamente en tu máquina, sin costes de suscripción y con total privacidad.</p>
<h2 id="1-verificacion-de-hardware-que-puedes-ejecutar">1. Verificación de hardware: ¿Qué puedes ejecutar?</h2>
<p>El primer paso para trabajar en local es conocer la capacidad real de tu equipo. No necesitas una estación de trabajo de última generación para tareas de código básicas, pero sí un mínimo de recursos para que la experiencia sea fluida.</p>
<p>Una herramienta muy recomendable para esto es <a href="https://canirun.ai">canirun.ai</a>. Este sitio analiza tus especificaciones técnicas (especialmente la memoria RAM y la VRAM de tu tarjeta gráfica) y te indica qué modelos podrás ejecutar.</p>
<h2 id="2-el-equilibrio-entre-potencia-y-ligereza">2. El equilibrio entre potencia y ligereza</h2>
<p>Para tareas de automatización y ayuda en el código, la regla general es que un modelo a partir de <strong>7 mil millones de parámetros (7B)</strong> suele ser el punto de equilibrio ideal. Son lo suficientemente inteligentes para entender lógica de programación y lo bastante ligeros para correr rápido en hardware doméstico moderno.</p>
<p>En mi caso personal, estoy utilizando actualmente <strong>Qwen 2.5 Coder 7B</strong>. Para generar pequeños scripts de Python o comparar ficheros JSON de gran tamaño, su respuesta es casi instantánea y la precisión es sorprendente. No obstante, dependiendo de tu hardware (si tienes más de 32GB de RAM o una GPU potente), podrías escalar a modelos de 14B o 32B si buscas un razonamiento más profundo.</p>
<h2 id="3-configuracion-simplificada-el-comando-magico">3. Configuración simplificada: El comando "mágico"</h2>
<p>Aunque OpenCode permite una configuración manual detallada, la forma más rápida y limpia de conectar tus modelos locales con el asistente es utilizar el puente directo que ofrece Ollama.</p>
<p>Una vez tengas instalado Ollama y hayas descargado tu modelo preferido (por ejemplo, con <code>ollama pull qwen2.5-coder</code>), solo tienes que ejecutar:</p>
<div class="codehilite"><pre><span></span><code>ollama<span class="w"> </span>launch<span class="w"> </span>opencode
</code></pre></div>

<p>Este comando es clave porque automatiza toda la configuración: detecta tu instancia local, establece las variables de entorno necesarias y lanza la interfaz de OpenCode lista para trabajar. Evitas así tener que configurar manualmente proveedores externos o servicios en la nube, manteniendo todo el flujo 100% local.</p>
<h2 id="4-casos-de-uso-ideales">4. Casos de uso ideales</h2>
<p>¿Cuándo merece la pena usar este setup en lugar de una IA comercial? Principalmente en tareas de "baja complejidad pero alta fricción":</p>
<ul>
<li><strong>Manipulación de archivos</strong>: "Crea un script que renombre todos los .txt de esta carpeta a .md y les añada la fecha actual".</li>
<li><strong>Comparación lógica</strong>: "Analiza estos dos directorios y dime qué funciones faltan por sincronizar".</li>
<li><strong>Limpieza de datos</strong>: "Tengo este CSV mal formateado, genera un script para normalizar las columnas".</li>
</ul>
<h2 id="conclusion">Conclusión</h2>
<p>La combinación de OpenCode y Ollama no solo es una cuestión de ahorro de costes (que también), sino de agilidad y privacidad. Al delegar las tareas más pesadas y repetitivas a un modelo local de 7B, liberas tu presupuesto de tokens para problemas realmente complejos y mantienes un flujo de trabajo en la terminal mucho más dinámico.</p>]]></description>
        </item>
    
        <item>
            <title>La guía de la IA</title>
            <link>https://psanxiao.com/posts/2026-03-07-la-guia-de-la-IA.html</link>
            <guid isPermaLink="true">https://psanxiao.com/posts/2026-03-07-la-guia-de-la-IA.html</guid>
            <pubDate>Sat, 07 Mar 2026 00:00:00 +0000</pubDate>
            <description><![CDATA[<p>Dedicándome a la tecnología, trabajando con software y manteniendo un blog donde me gusta escribir y lo hago menos de lo que me gustaría, resulta casi imposible no hablar de la Inteligencia Artificial. Durante el último año, la IA ha pasado de ser casi ciencia ficción a inundarlo todo.</p>
<p>Este post, más largo de lo habitual, nace de una necesidad personal: la de poner en orden mis propias notas, investigaciones y aprendizajes. Al final, escribir es la mejor forma que conozco de estructurar las ideas y separar el grano de la paja. Mi intención con esta serie no es venderte la última herramienta de moda, sino bajar a tierra lo que está pasando y cómo podemos entender esta tecnología sin que nos explote la cabeza.</p>
<div class="toc"><span class="toctitle">Índice de contenidos</span><ul>
<li><a href="#ni-magia-ni-robots-solo-computacion-y-patrones">Ni magia, ni robots: Solo computación y patrones</a></li>
<li><a href="#por-que-ahora">¿Por qué ahora?</a></li>
<li><a href="#que-es-un-llm-en-realidad">¿Qué es un LLM en realidad?</a><ul>
<li><a href="#los-tres-pilares-de-lo-que-puede-generar">Los tres pilares de lo que puede generar:</a></li>
</ul>
</li>
<li><a href="#que-no-es-la-ia">¿Qué NO es la IA?</a><ul>
<li><a href="#como-aprende-una-maquina">Cómo aprende una máquina?</a></li>
</ul>
</li>
<li><a href="#el-espejo-humano-el-sesgo-que-nos-complica-la-vida">El espejo humano: El sesgo que nos complica la vida</a></li>
<li><a href="#capitulo-2-el-diccionario-de-supervivencia">Capítulo 2. El Diccionario de Supervivencia</a><ul>
<li><a href="#1-el-token-la-unidad-de-medida">1. El Token: La unidad de medida</a></li>
<li><a href="#2-la-ventana-de-contexto-no-es-un-pozo-sin-fondo">2. La Ventana de Contexto: No es un pozo sin fondo</a></li>
<li><a href="#3-embeddings-el-carnet-de-identidad-de-tus-datos">3. Embeddings: El carnet de identidad de tus datos</a></li>
<li><a href="#4-alucinaciones-cuando-la-probabilidad-falla">4. Alucinaciones: Cuando la probabilidad falla</a></li>
<li><a href="#5-parametros-el-tamano-del-cerebro">5. Parámetros: El tamaño del "cerebro"</a></li>
<li><a href="#6-temperatura-robot-o-artista">6. Temperatura: ¿Robot o Artista?</a></li>
<li><a href="#7-el-rag-como-aprende-la-ia-sobre-tus-datos">7. El RAG: ¿Cómo aprende la IA sobre tus datos?</a><ul>
<li><a href="#como-funciona-el-rag-por-dentro">¿Cómo funciona el RAG por dentro?</a></li>
<li><a href="#por-que-herramientas-como-notebooklm-parecen-acotarse-tanto">¿Por qué herramientas como NotebookLM parecen "acotarse" tanto?</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#capitulo-3-el-traductor-universal">Capítulo 3. El Traductor Universal</a><ul>
<li><a href="#1-el-codigo-como-arquitectura-logica">1. El código como arquitectura lógica</a></li>
<li><a href="#2-por-que-algunos-modelos-programan-mejor-que-otros">2. ¿Por qué algunos modelos programan mejor que otros?</a></li>
<li><a href="#3-imagenes-y-video-traduciendo-palabras-a-pixeles">3. Imágenes y Vídeo: Traduciendo palabras a píxeles</a></li>
<li><a href="#4-la-trampa-del-dibujo-por-que-la-ia-no-sabe-cuantos-dedos-tiene-una-mano">4. La trampa del dibujo: ¿Por qué la IA no sabe cuántos dedos tiene una mano?</a></li>
<li><a href="#5-por-que-unos-modelos-son-mas-rapidos-que-otros">5. ¿Por qué unos modelos son más rápidos que otros?</a></li>
</ul>
</li>
<li><a href="#capitulo-4-instrucciones-operativas-prompting">Capítulo 4. Instrucciones operativas (Prompting)</a><ul>
<li><a href="#1-rompe-el-espejo-de-la-conversacion-a-la-instruccion">1. Rompe el espejo: De la conversación a la instrucción</a></li>
<li><a href="#2-la-anatomia-de-un-prompt-profesional">2. La anatomía de un prompt profesional</a></li>
<li><a href="#3-la-tecnica-de-la-cadena-de-pensamiento">3. La técnica de la "Cadena de Pensamiento"</a></li>
<li><a href="#4-el-few-shot-ensenar-con-ejemplos">4. El "Few-Shot": Enseñar con ejemplos</a></li>
<li><a href="#5-itera-el-prompt-perfecto-no-existe-a-la-primera">5. Itera: El prompt perfecto no existe a la primera</a></li>
</ul>
</li>
<li><a href="#capitulo-5-las-herramientas-de-desarrollo-de-software">Capítulo 5. Las Herramientas de Desarrollo de Software</a><ul>
<li><a href="#el-agente-el-cerebro-con-iniciativa">El Agente: El "Cerebro" con iniciativa</a></li>
<li><a href="#mcp-el-protocolo-que-lo-cambia-todo">MCP: El protocolo que lo cambia todo</a></li>
<li><a href="#las-skills-herramientas-las-manos-del-agente">Las Skills (Herramientas): Las "Manos" del agente</a><ul>
<li><a href="#1-skills-nativas-las-que-ya-vienen-instaladas">1. Skills Nativas (Las que ya vienen "instaladas")</a></li>
<li><a href="#2-skills-de-ecosistema-marketplace-de-conectores">2. Skills de Ecosistema (Marketplace de conectores)</a></li>
<li><a href="#3-skills-a-medida-las-que-tu-programas">3. Skills a medida (Las que tú programas)</a></li>
</ul>
</li>
<li><a href="#como-encaja-todo-esto-ejemplo-practico">¿Cómo encaja todo esto? (Ejemplo práctico)</a></li>
<li><a href="#instrucciones-persistentes-los-archivos-md-y-ficheros-de-configuracion">Instrucciones persistentes: Los archivos .md y ficheros de configuración</a></li>
</ul>
</li>
<li><a href="#capitulo-6-estrategia-de-modelos-a-quien-le-pides-cada-tarea">Capítulo 6. Estrategia de modelos: ¿A quién le pides cada tarea?</a><ul>
<li><a href="#1-modelos-de-razonamiento-o-series-deep-thinking">1. Modelos de Razonamiento (O-series / "Deep Thinking")</a></li>
<li><a href="#2-modelos-de-desarrollo-coding-assistants">2. Modelos de Desarrollo (Coding Assistants)</a></li>
<li><a href="#3-modelos-flash-o-ligeros-velocidad-y-eficiencia">3. Modelos "Flash" o Ligeros (Velocidad y Eficiencia)</a></li>
<li><a href="#4-modelos-de-gran-contexto-la-memoria-infinita">4. Modelos de Gran Contexto (La memoria infinita)</a></li>
</ul>
</li>
<li><a href="#capitulo-7-conclusiones-y-el-camino-a-seguir">Capítulo 7. Conclusiones y el camino a seguir</a><ul>
<li><a href="#1-la-comprension-es-tu-mejor-herramienta">1. La comprensión es tu mejor herramienta</a></li>
<li><a href="#2-solo-estamos-rascando-la-superficie">2. Solo estamos rascando la superficie</a></li>
<li><a href="#3-orquestacion-frente-a-monopolio">3. Orquestación frente a monopolio</a></li>
<li><a href="#reflexion-final">Reflexión final</a></li>
</ul>
</li>
</ul>
</div>
<h2 id="ni-magia-ni-robots-solo-computacion-y-patrones">Ni magia, ni robots: Solo computación y patrones</h2>
<p>A veces parece que hay una especie de "niebla" mental sobre la IA. Parece que estamos ante un oráculo o un cerebro consciente. Lo que veíamos en las películas se ha hecho realidad, y tenemos entre nosotros un <em>Skynet</em> o <em><a href="https://es.wikipedia.org/wiki/HAL_9000">HAL</a></em> de verdad. Pero si abrimos el capó y miramos los engranajes, la realidad es mucho más terrenal.</p>
<p>En esencia, la IA es software diseñado para imitar capacidades humanas, pero con una diferencia fundamental respecto al desarrollo tradicional. Si un programa clásico es una receta rígida donde yo, como programador, defino que <em>"si pasa A, haz B"</em>, la IA es un <strong>aprendiz de patrones</strong>. No necesita que le dé todas las reglas por escrito; ella misma encuentra las regularidades en los datos. Pero ojo, no "piensa". Es pura estadística aplicada a una escala masiva.</p>
<h2 id="por-que-ahora">¿Por qué ahora?</h2>
<p>Algo que solemos ignorar es que las <strong><a href="https://es.wikipedia.org/wiki/Red_neuronal_artificial">redes neuronales</a></strong> no son una novedad de hace dos años. La idea de imitar las conexiones sinápticas para procesar datos es una teoría que ya se manejaba en los años 50. Entonces, ¿por qué no tuvimos este "boom" hace décadas?</p>
<p>Para que estas redes neuronales "despertaran", han necesitado dos ingredientes que solo coinciden ahora:</p>
<ul>
<li><strong>Datos masivos:</strong> Internet se ha convertido en el campo de entrenamiento más grande de la historia.</li>
<li><strong>Hardware (GPUs):</strong> Necesitábamos chips capaces de hacer billones de operaciones por segundo. La receta ya estaba escrita, pero hasta hace poco no teníamos el horno lo bastante potente para cocinarla.</li>
</ul>
<h2 id="que-es-un-llm-en-realidad">¿Qué es un LLM en realidad?</h2>
<p>Los modelos de lenguaje que usamos hoy son redes neuronales gigantescas entrenadas para una sola tarea: <strong>adivinar la siguiente palabra.</strong></p>
<ul>
<li>Si escribes "El cielo está...", la red neuronal ha procesado tanta información que sabe que la palabra más probable es "azul".</li>
<li>No es que "entienda" el concepto de cielo, es que es un maestro de la probabilidad estadística gracias a esa capacidad de computación masiva.</li>
</ul>
<p>Hasta hace nada, la IA se dedicaba a clasificar: te decía si un correo era spam o si en una foto aparecía un gato. Era IA discriminativa. Lo que ha cambiado las reglas del juego es la <strong>IA Generativa</strong>.</p>
<p>Como su nombre indica, su superpoder es crear contenido nuevo. Ha pasado de organizar carpetas a ser capaz de redactar un texto, generar una imagen desde cero o, lo que a muchos nos interesa, escribir una función de código que funciona a la primera. No es que "sepa" lo que hace, es que sabe qué palabra o píxel tiene más probabilidades de ir después del anterior.</p>
<h3 id="los-tres-pilares-de-lo-que-puede-generar">Los tres pilares de lo que puede generar:</h3>
<ol>
<li><strong>Texto (LLMs):</strong> Como ya vimos, son redes neuronales maestras en la probabilidad. No solo escriben poemas; pueden programar software, resumir libros o traducir idiomas captando los matices culturales, no solo palabra por palabra.</li>
<li><strong>Imagen y Arte Visual:</strong> Modelos como Midjourney o DALL-E han aprendido la relación entre las palabras y los píxeles. Saben qué aspecto tiene la "luz de atardecer" o el "estilo cyberpunk" y pueden construir una imagen desde cero (píxel a píxel) a partir de una frase.</li>
<li><strong>Multimodalidad (El presente):</strong> Esto es lo más nuevo y potente. Es la capacidad de la IA para saltar entre formatos. Por ejemplo, una IA que puede "ver" una foto de tu nevera y "escribir" una receta con lo que hay dentro, o una IA que "escucha" un audio y lo convierte en un vídeo.</li>
</ol>
<h2 id="que-no-es-la-ia">¿Qué NO es la IA?</h2>
<ul>
<li><strong>No es un buscador como Google:</strong> Un buscador te lleva a una web; una IA genera una respuesta nueva combinando lo que sabe.</li>
<li><strong>No tiene sentimientos:</strong> Si te pide perdón por un error, no es porque esté arrepentida, es porque ha aprendido que "lo siento" es la respuesta socialmente correcta en esa situación.</li>
<li><strong>No es infalible:</strong> Puede equivocarse (y mucho). Es una herramienta de apoyo, no una verdad absoluta.</li>
</ul>
<h3 id="como-aprende-una-maquina">Cómo aprende una máquina?</h3>
<p>Imagina que quieres enseñarle a un niño a distinguir entre un perro y un gato. No le das un manual de 500 páginas sobre anatomía felina; simplemente ve un perro y le dices: "Esto es un perro", ve un gato y le dices "Esto es un gato". Aprende por repetición y asociación.</p>
<p>Con la IA pasa exactamente lo mismo:</p>
<ol>
<li><strong>Entrenamiento:</strong> Le "alimentamos" con millones de ejemplos (textos, fotos, códigos).</li>
<li><strong>Patrones:</strong> La máquina nota que los gatos suelen tener orejas puntiagudas y los perros hocicos más largos.</li>
<li><strong>Predicción:</strong> Cuando le enseñas una foto nueva, no "sabe" qué es, pero dice: "Hay un <strong>98%</strong> de probabilidades de que esto sea un gato".</li>
</ol>
<h2 id="el-espejo-humano-el-sesgo-que-nos-complica-la-vida">El espejo humano: El sesgo que nos complica la vida</h2>
<p>Aquí entra un tema que me parece clave para entender por qué a veces nos frustramos con estas herramientas. En los años 60, Joseph Weizenbaum creó <strong><a href="https://es.wikipedia.org/wiki/ELIZA">ELIZA</a></strong>, un chatbot básico que imitaba a un terapeuta. Weizenbaum lo programó para imitar a un <strong>terapeuta rogeriano</strong> (una técnica de psicología que consiste en devolverle al paciente sus propias palabras).</p>
<ul>
<li><strong>Si tú decías:</strong> "Me duele la cabeza".</li>
<li><strong>ELIZA buscaba las palabras clave y respondía:</strong> "¿Por qué dices que te duele la cabeza?".</li>
</ul>
<p>Se quedó horrorizado al ver que la gente se abría emocionalmente con la máquina, creyendo que los entendía.</p>
<p>Tenemos una tendencia innata a humanizar la tecnología (<strong><a href="https://es.wikipedia.org/wiki/Antropomorfismo">antropomorfismo</a></strong>). Si algo nos responde con lenguaje fluido, nuestro cerebro asume que "hay alguien ahí". Nos enfadamos con el chat o le hablamos con rodeos. El problema es que, al tratarla como a un humano, perdemos la <strong>especificidad operativa</strong>. Olvidamos que estamos ante una herramienta de cálculo y no ante un oráculo.</p>
<p>Para sacarle partido de verdad a la IA, el primer paso es romper ese espejo: menos charla y más instrucciones precisas. No pienses que "entiende" tu problema, cuando en realidad sólo está haciendo estadística avanzada para darte la respuesta más probable. Entender esto es la clave para saber como sacarle partido, tratándola como lo que es: una herramienta.</p>
<h1 id="capitulo-2-el-diccionario-de-supervivencia">Capítulo 2. El Diccionario de Supervivencia</h1>
<p>En el post anterior hablábamos de que la IA no es magia, sino computación y patrones. Pero para movernos con soltura en este ecosistema y, sobre todo, para no frustrarnos cuando las cosas no salen como esperamos, necesitamos hablar el mismo idioma que la máquina.</p>
<p>Para manejar la IA con criterio, no basta con saber qué botones pulsar. Necesitas entender los mecanismos que definen su comportamiento. Si ignoras estos conceptos, estarás usando la herramienta a ciegas.</p>
<h3 id="1-el-token-la-unidad-de-medida">1. El Token: La unidad de medida</h3>
<p>La IA no procesa palabras completas como nosotros; <a href="https://platform.openai.com/tokenizer">trocea el texto en <strong>tokens</strong></a>. Un token puede ser una palabra corta, una sílaba o incluso un signo de puntuación.</p>
<p><strong>¿Por qué te importa?</strong> Porque todo en este mundo se mide en tokens: desde la capacidad de respuesta hasta lo que te cobran por las APIs. Si entiendes que la IA "trocea" la información, entenderás por qué a veces le cuesta tanto contar letras de una palabra o hacer rimas perfectas: ella no ve letras individuales, ve bloques de datos.</p>
<h3 id="2-la-ventana-de-contexto-no-es-un-pozo-sin-fondo">2. La Ventana de Contexto: No es un pozo sin fondo</h3>
<p>Este es, probablemente, el concepto más crítico. La <strong>Ventana de Contexto</strong> es la "memoria de trabajo" o la capacidad de atención de la IA en una sola sesión. Imagina que es una mesa de escritorio: cuando la mesa se llena, para poner un papel nuevo tienes que tirar uno de los que ya estaban.</p>
<ul>
<li>
<p><strong>El peligro del truncamiento:</strong> Si le pasas un PDF de 200 páginas a una IA que solo tiene capacidad para 50, la IA no te avisará de que no llega. Simplemente <strong>cortará el texto sobrante</strong>. Tú creerás que ha analizado todo el archivo, pero la información del final (donde quizás estaba la conclusión o la cláusula clave) será invisible para ella.</p>
</li>
<li>
<p><strong>La pérdida en el medio:</strong> Incluso si el texto cabe, se ha demostrado que la IA recuerda mejor el principio y el final de lo que lee. Los detalles que quedan enterrados en el centro del documento suelen diluirse.</p>
</li>
</ul>
<h3 id="3-embeddings-el-carnet-de-identidad-de-tus-datos">3. Embeddings: El carnet de identidad de tus datos</h3>
<p>Si la Ventana de Contexto es nuestra "mesa de trabajo", los <strong><a href="https://en.wikipedia.org/wiki/Word_embedding">Embeddings</a></strong> son el sistema de archivado inteligente que nos permite no saturarla.</p>
<p>Para que una IA entienda la relación entre las ideas, utiliza estos "embeddings": una lista de números (vectores) que sitúa cada frase en un mapa gigante de miles de dimensiones. Para entenderlo como desarrolladores, bajémoslo a un ejemplo de solo 3 coordenadas: <strong>[¿Es software?, ¿Es una base de datos?, ¿Es un animal?]</strong></p>
<ul>
<li><strong>"PostgreSQL"</strong> → <code>[0.9, 0.9, 0.0]</code> (Mucho software, mucha DB, nada animal).</li>
<li><strong>"MySQL"</strong> → <code>[0.9, 0.8, 0.0]</code> (Coordenadas casi idénticas; están "pegados" en el mapa).</li>
<li><strong>"Delfín"</strong> → <code>[0.0, 0.0, 0.9]</code> (Vive en un barrio matemático totalmente distinto).</li>
</ul>
<p><strong>¿Por qué te importa esto en la práctica?</strong> Piensa en cuando subes un PDF a una IA. La IA no "lee" las 200 páginas cada vez que le preguntas algo. Lo que hace es:</p>
<ol>
<li><strong>Trocear</strong> el PDF en párrafos.</li>
<li>Generar el <strong>embedding</strong> (las coordenadas) de cada párrafo.</li>
<li>Cuando preguntas algo, busca qué trozos del PDF tienen las coordenadas más parecidas a tu duda y <strong>solo le pasa esos trozos</strong> a la Ventana de Contexto.</li>
</ol>
<p>Es la base de lo que llamamos <strong>RAG</strong>: buscar por "sentido común" matemático para ahorrar tokens y ganar precisión.</p>
<h3 id="4-alucinaciones-cuando-la-probabilidad-falla">4. Alucinaciones: Cuando la probabilidad falla</h3>
<p>La IA Generativa es una máquina de predecir la siguiente palabra más probable. No tiene una base de datos de "verdades".</p>
<ul>
<li>
<p><strong>El riesgo:</strong> Como está diseñada para ser coherente y elocuente, cuando no sabe algo, "rellena" el vacío con información que suena muy profesional pero que es <strong>falsa</strong>. No te miente por maldad; simplemente está cumpliendo su función de completar texto basándose en patrones.</p>
</li>
<li>
<p><strong>Mi consejo:</strong> Nunca la uses como fuente primaria para datos biográficos, leyes o fechas. Úsala para procesar datos que tú le entregues, no para que ella te los traiga de fuera.</p>
</li>
</ul>
<h3 id="5-parametros-el-tamano-del-cerebro">5. Parámetros: El tamaño del "cerebro"</h3>
<p>Seguro que has oído que X modelo tiene "billones de parámetros". Un parámetro es, básicamente, una conexión ajustable en la red neuronal (como los cables en un cuadro de conexiones).</p>
<ul>
<li>
<p><strong>Más parámetros</strong> suelen significar más matices, mejor comprensión de la ironía y más "sabiduría" generalista.</p>
</li>
<li>
<p>Sin embargo, un modelo con menos parámetros pero <strong>mejor entrenado</strong> (con datos de mayor calidad) puede ser mucho más eficaz para tareas específicas, como programar, y además será mucho más rápido y barato.</p>
</li>
</ul>
<h3 id="6-temperatura-robot-o-artista">6. Temperatura: ¿Robot o Artista?</h3>
<p>Aunque a veces está oculto en la interfaz, la mayoría de los modelos tienen un ajuste de <strong>Temperatura</strong>.</p>
<ul>
<li>
<p><strong>Temperatura baja (Cercana a 0):</strong> La IA es conservadora y previsible. Es el modo "ingeniero": ideal para código, resúmenes técnicos o datos.</p>
</li>
<li>
<p><strong>Temperatura alta (Cercana a 1):</strong> La IA se arriesga más, elige palabras menos probables. Es el modo "creativo": ideal para lluvia de ideas o escribir ficción.</p>
</li>
</ul>
<h3 id="7-el-rag-como-aprende-la-ia-sobre-tus-datos">7. El RAG: ¿Cómo aprende la IA sobre tus datos?</h3>
<p>Seguramente has probado herramientas como <strong>NotebookLM</strong> o le has subido un PDF a ChatGPT y te has sorprendido: de repente, la IA sabe todo sobre ese documento técnico de 50 páginas y sus respuestas se "acotan" estrictamente a lo que pone ahí. No se inventa cosas de internet, se centra en <strong>tus</strong> datos.</p>
<p>¿Cómo es posible? ¿Ha "estudiado" la IA tu documento y se lo ha aprendido para siempre? <strong>No.</strong> Lo que estás viendo en acción es una técnica llamada **<a href="https://es.wikipedia.org/wiki/Generación_aumentada_por_recuperación">RAG (Retrieval-Augmented Generation</a></p>
<p>Para entender el RAG, olvida la idea de que la IA "aprende". Imagina que la IA es un <strong>profesor superdotado</strong> que sabe de todo, pero al que le pides que haga un examen sobre un proyecto tuyo que él nunca ha visto.</p>
<ol>
<li>
<p><strong>Sin RAG (Entrenamiento):</strong> Sería como obligar al profesor a memorizar tu proyecto antes del examen. Es costoso, lento y, si mañana cambias una frase del proyecto, el profesor ya tiene información obsoleta en su cabeza.</p>
</li>
<li>
<p><strong>Con RAG:</strong> Es como si el profesor fuera al examen con <strong>tus apuntes en la mano</strong>. No necesita memorizarlos; simplemente, cuando le haces una pregunta, él busca rápidamente en los apuntes, lee el párrafo relevante y te responde usando su inteligencia pero basándose <strong>solo</strong> en lo que pone el papel.</p>
</li>
</ol>
<h4 id="como-funciona-el-rag-por-dentro">¿Cómo funciona el RAG por dentro?</h4>
<p>Cuando subes ese PDF, el sistema hace tres cosas en milisegundos:</p>
<ul>
<li>
<p><strong>Recuperación (Retrieval):</strong> Gracias a los <strong>Embeddings</strong> (los "carnets de identidad numéricos" que vimos antes), el sistema sabe qué párrafos de tu PDF están matemáticamente cerca de tu pregunta. Si preguntas por el "presupuesto", el sistema localiza instantáneamente la página donde están las cifras.</p>
</li>
<li>
<p><strong>Aumento (Augmentation):</strong> El sistema coge esos párrafos y los "pega" en un prompt invisible para ti que dice: <em>"Aquí tienes estos fragmentos de texto. Úsalos como única fuente de verdad"</em>.</p>
</li>
<li>
<p><strong>Generación (Generation):</strong> La IA redacta la respuesta. Sigue usando su "cerebro" (su entrenamiento previo) para entender el lenguaje y redactar bien, pero los datos los saca exclusivamente de los fragmentos que le acabas de pasar.</p>
</li>
</ul>
<h4 id="por-que-herramientas-como-notebooklm-parecen-acotarse-tanto">¿Por qué herramientas como NotebookLM parecen "acotarse" tanto?</h4>
<p>Te habrás fijado en que si le preguntas a <a href="https://notebooklm.google.com/">NotebookLM</a> algo que no está en tus archivos, a veces te dice: <em>"No puedo responder porque esa información no aparece en los documentos"</em>.</p>
<p>Esto no es porque la IA se haya vuelto "tonta" o haya olvidado lo que sabía. Es porque el sistema RAG tiene una <strong>instrucción de seguridad</strong> muy estricta: priorizar siempre el libro que tiene abierto. Esto es lo que nos da la seguridad de que la IA no va a "alucinar" o inventarse datos basándose en información genérica de internet.</p>
<h1 id="capitulo-3-el-traductor-universal">Capítulo 3. El Traductor Universal</h1>
<p>Si has seguido los posts anteriores, ya tienes claro que la IA no es un oráculo, sino un procesador de patrones. Pero aquí es donde la cosa se pone interesante: ¿Cómo es posible que un "Modelo de Lenguaje" (LLM) sea capaz de escribir una función en Python, <a href="https://psanxiao.com/posts/2025-08-15-introduccion-a-leaflet.html">diseñar un mapa web en Leaflet</a> o generar una imagen de un astronauta a caballo?</p>
<p>La respuesta corta es que, para una IA, <strong>todo es lenguaje</strong>. Pero vamos a desglosarlo un poco mejor, porque entender esto es lo que te permite pasar de "jugar" con la IA a usarla como una herramienta de ingeniería real.</p>
<h3 id="1-el-codigo-como-arquitectura-logica">1. El código como arquitectura lógica</h3>
<p>Para nosotros, programar es crear lógica. Para un LLM, aprender a programar no ha sido muy distinto a aprender inglés o gallego. El código tiene una sintaxis, una gramática y, sobre todo, una estructura previsible.</p>
<p>La IA ha sido entrenada con casi todo el código público que existe (pensemos en el volumen de datos de GitHub). Lo que hace no es "entender" informática, sino entender la <strong>secuencia lógica</strong>. Sabe que después de un <code>if</code> suele venir una condición y, tras ella, una indentación con una instrucción. Al ser un entorno tan rígido y con reglas tan claras, a la IA le resulta más fácil ser precisa programando que escribiendo poesía.</p>
<h3 id="2-por-que-algunos-modelos-programan-mejor-que-otros">2. ¿Por qué algunos modelos programan mejor que otros?</h3>
<p>No es que unos sean "más listos" que otros, es una cuestión de <strong>dieta de datos</strong>.</p>
<ul>
<li>
<p><strong>Los generalistas:</strong> Han leído de todo, incluyendo tutoriales obsoletos y código basura de foros olvidados. Por eso a veces te sugieren soluciones que "huelen" a 2010.</p>
</li>
<li>
<p><strong>Los especialistas (Fine-tuning):</strong> Son modelos que han pasado por un "posgrado". Se les ha entrenado específicamente con repositorios de alta calidad y se les ha refinado mediante humanos que les dicen: <em>"Esta solución funciona, pero esta otra es más eficiente y segura"</em>. Es la diferencia entre un aprendiz y un senior.</p>
</li>
</ul>
<h3 id="3-imagenes-y-video-traduciendo-palabras-a-pixeles">3. Imágenes y Vídeo: Traduciendo palabras a píxeles</h3>
<p>Aquí es donde ocurre lo que llamamos <strong>multimodalidad</strong>. Al principio, esto era como un juego de teléfonos descompuestos: tenías un modelo que entendía el texto y le pasaba la orden a otro que generaba la imagen.</p>
<p>Hoy, los modelos más potentes son <strong>nativos multimodales</strong>. Desde el minuto uno de su entrenamiento, han visto imágenes y han leído sus descripciones simultáneamente. Han aprendido que el concepto lingüístico "atardecer" está vinculado a ciertos patrones de color y degradados de píxeles. Ya no "traducen" de un idioma a otro; entienden el mundo en ambos formatos de forma nativa.</p>
<h3 id="4-la-trampa-del-dibujo-por-que-la-ia-no-sabe-cuantos-dedos-tiene-una-mano">4. La trampa del dibujo: ¿Por qué la IA no sabe cuántos dedos tiene una mano?</h3>
<p>Seguro que has visto esas imágenes con manos de seis dedos. Esto explica perfectamente por qué la IA no tiene un modelo mental de la realidad. Ella no sabe qué es una "mano" ni para qué sirve; solo sabe que en las fotos etiquetadas como "mano" suele haber formas alargadas de color carne. Si estadísticamente el patrón es confuso, el resultado visual también lo será.</p>
<h3 id="5-por-que-unos-modelos-son-mas-rapidos-que-otros">5. ¿Por qué unos modelos son más rápidos que otros?</h3>
<p>A veces te encuentras con modelos que parecen un rayo y otros que escriben a paso de tortuga. Esto depende del <strong>número de parámetros</strong> (el tamaño del "cerebro") y del <strong>hardware</strong> que tengan detrás.</p>
<ul>
<li>
<p><strong>Modelos Flash/Mini:</strong> Pocos parámetros, muy rápidos, ideales para tareas sencillas.</p>
</li>
<li>
<p><strong>Modelos de razonamiento:</strong> Son deliberadamente más lentos porque están programados para "pensar antes de hablar", verificando sus propios pasos lógicos internamente.</p>
</li>
</ul>
<p>Entender que la IA es un <strong>motor de razonamiento estadístico</strong> cambia la forma en la que le pides las cosas. Si le pides código, dale la estructura. Si le pides una imagen, describe los patrones. No estás hablando con un artista ni con un colega senior; estás interactuando con un traductor universal de patrones que es tan bueno como la información con la que lo alimentas.</p>
<h1 id="capitulo-4-instrucciones-operativas-prompting">Capítulo 4. Instrucciones operativas (Prompting)</h1>
<p>Si has llegado hasta aquí, ya sabes que la IA no te "entiende" en el sentido humano de la palabra; lo que hace es procesar una instrucción y calcular la respuesta más probable basándose en su entrenamiento. Por eso, el "arte" de dar instrucciones (que algunos llaman pomposamente <em><a href="https://developers.openai.com/api/docs/guides/prompt-engineering">Prompt Engineering</a></em>) no consiste en ser educado, sino en ser <strong>meticuloso</strong>.</p>
<p>En mi día a día con el código o los mapas, si una consulta SQL no devuelve lo que quiero, no me enfado con la base de datos; reviso la sintaxis. Con la IA hay que aplicar la misma mentalidad.</p>
<h3 id="1-rompe-el-espejo-de-la-conversacion-a-la-instruccion">1. Rompe el espejo: De la conversación a la instrucción</h3>
<p>El mayor error es tratar el chat como una charla de café. Escribir <em>"Oye, ¿sería posible que quizás me ayudaras a resumir esto si no es mucha molestia?"</em> solo añade ruido innecesario a la <strong>ventana de contexto</strong>.</p>
<p>La IA no necesita cortesía, necesita <strong>parámetros</strong>. Pasa de la "charla" a la <strong>instrucción operativa</strong>. Sé directo. Usa verbos de acción: "Resume", "Analiza", "Escribe", "Extrae".</p>
<h3 id="2-la-anatomia-de-un-prompt-profesional">2. La anatomía de un prompt profesional</h3>
<p>Para que una instrucción sea robusta, yo suelo usar una estructura de tres capas:</p>
<ul>
<li>
<p><strong>Rol (¿Quién eres?):</strong> Dile a la IA cómo debe comportarse. No es lo mismo pedirle algo a un "asistente genérico" que a un "ingeniero senior experto en PostgreSQL". Definir el rol acota el "espacio" de donde la IA sacará la información.</p>
</li>
<li>
<p><strong>Contexto y Tarea (¿Qué hacemos?):</strong> Sé específico. En lugar de "haz un resumen", usa "resume los 3 puntos clave de este texto técnico para un cliente que no sabe de tecnología".</p>
</li>
<li>
<p><strong>Formato de salida (¿Cómo lo quiero?):</strong> Define el <em>output</em>. ¿Quieres un JSON? ¿Una tabla Markdown? ¿Un párrafo de 50 palabras? Si no lo defines, la IA elegirá por ti, y probablemente no sea lo que necesitas.</p>
</li>
</ul>
<h3 id="3-la-tecnica-de-la-cadena-de-pensamiento">3. La técnica de la "Cadena de Pensamiento"</h3>
<p>Como vimos en el post anterior, algunos modelos programan mejor porque "piensan" antes de hablar. Tú puedes forzar ese comportamiento en cualquier modelo usando una técnica sencilla: <strong><a href="https://www.ibm.com/think/topics/chain-of-thoughts">pídele que razone paso a paso</a></strong>.</p>
<p>Si le lanzas un problema complejo de golpe, es más fácil que alucine. Si le dices: <em>"Analiza el problema, desglósalo en pasos lógicos y, finalmente, dame la solución"</em>, la probabilidad de éxito aumenta drásticamente. Estás obligando a la red neuronal a seguir un camino lógico trazable.</p>
<h3 id="4-el-few-shot-ensenar-con-ejemplos">4. El "Few-Shot": Enseñar con ejemplos</h3>
<p>A veces, una descripción no basta. Si quieres que la IA escriba con un estilo concreto o clasifique datos de una forma específica, <strong>dale ejemplos</strong>.</p>
<ul>
<li><strong>Ejemplo 1:</strong> Entrada -&gt; Salida.</li>
<li><strong>Ejemplo 2:</strong> Entrada -&gt; Salida.</li>
<li><strong>Ahora tú:</strong> Entrada -&gt; ...</li>
</ul>
<p>Es la forma más rápida de que la IA capte el patrón que buscas sin tener que escribir un manual de instrucciones infinito que sature los <strong>tokens</strong> de la sesión.</p>
<h3 id="5-itera-el-prompt-perfecto-no-existe-a-la-primera">5. Itera: El prompt perfecto no existe a la primera</h3>
<p>En el desarrollo de software, raramente algo funciona a la primera versión. Con el prompting es igual.</p>
<ul>
<li>Si el resultado es demasiado largo, pide que lo recorte.</li>
<li>Si es muy técnico, pide que baje el nivel.</li>
<li>Si ha olvidado una parte, es posible que hayas superado la <strong>ventana de contexto</strong> o que la instrucción fuera ambigua.</li>
</ul>
<p>Dominar el prompting no es aprenderse "fórmulas mágicas", es aprender a comunicarte con una máquina de forma estructurada. Es, en esencia, volver a ser un poco <strong>artesano de la palabra</strong> para obtener resultados de <strong>ingeniería.</strong></p>
<h1 id="capitulo-5-las-herramientas-de-desarrollo-de-software">Capítulo 5. Las Herramientas de Desarrollo de Software</h1>
<p>Si eres de los que, como yo, empezó usando la IA solo para preguntarle cómo centrar un <code>div</code> o para que te explicara una función compleja de PostgreSQL, tengo que decirte que te estás quedando en la superficie. En los últimos meses, el ecosistema ha dado un salto gigante: hemos pasado de "hablar con la IA" a "integrar la IA en nuestro entorno de desarrollo".</p>
<p>Como ingeniero, siempre busco soluciones que resuelvan problemas reales. Y aquí el problema real es la fricción: copiar código del chat, pegarlo en el editor, ver que falla porque la IA no conoce mi base de datos, y volver a empezar. Para resolver esto, han aparecido tres conceptos clave: <strong>Agentes, Skills y el protocolo MCP</strong>.</p>
<h3 id="el-agente-el-cerebro-con-iniciativa">El Agente: El "Cerebro" con iniciativa</h3>
<p>Un chat normal es pasivo: tú preguntas, él responde. Un <strong>Agente</strong> es un modelo de lenguaje al que le hemos dado un objetivo y autonomía para usar herramientas.</p>
<ul>
<li>
<p><strong>Diferencia clave:</strong> Si le pides a un chat "arregla este bug", te dará el código. Si se lo pides a un <strong>Agente</strong>, él leerá el archivo, ejecutará el código para ver el error, buscará una solución y aplicará el parche.</p>
</li>
<li>
<p><strong>En resumen:</strong> Es un LLM que puede ejecutar un ciclo de: <em>Pensar -&gt; Actuar -&gt; Observar el resultado</em>.</p>
</li>
</ul>
<h3 id="mcp-el-protocolo-que-lo-cambia-todo">MCP: El protocolo que lo cambia todo</h3>
<p>Hace poco escribí en el blog sobre <a href="https://psanxiao.com/posts/2026-01-14-conectar-postgres-ia-mediante-mcp.html">cómo conectar PostgreSQL a un agente de IA</a> mediante <strong><a href="https://modelcontextprotocol.io/">MCP (Model Context Protocol)</a></strong>. </p>
<ul>
<li>
<p><strong>¿Qué es?</strong> Es un estándar abierto que permite que cualquier IA se conecte de forma segura a tus datos locales.</p>
</li>
<li>
<p><strong>¿Para qué sirve?</strong> En lugar de copiar y pegar el esquema de tu base de datos en el chat (con el riesgo de seguridad y el gasto de <strong>tokens</strong> que eso supone), el agente "habla" directamente con tu base de datos a través de MCP.</p>
</li>
<li><strong>Utilidad real:</strong> Puedes decirle: <em>"Busca en mi tabla de usuarios cuáles no han verificado el email y genera un script para enviarles un recordatorio"</em>. La IA consulta la estructura real, no se la inventa.</li>
</ul>
<p>Los MCP son, en esencia, canales de comunicación entre los modelos de IA y herramientas externas.</p>
<h3 id="las-skills-herramientas-las-manos-del-agente">Las Skills (Herramientas): Las "Manos" del agente</h3>
<p>Una de las dudas más comunes es cómo "aprende" la IA a hacer cosas nuevas. Las Skills (o herramientas) no son algo que la IA traiga de serie (aunque algunas sí) por "saber mucho", sino funciones que tú, como desarrollador, tienes que habilitar o programar.</p>
<p>Podemos dividirlas en tres niveles según su origen:</p>
<h4 id="1-skills-nativas-las-que-ya-vienen-instaladas">1. Skills Nativas (Las que ya vienen "instaladas")</h4>
<p>Muchas herramientas de IA (como ChatGPT, Claude o Gemini) ya traen herramientas de serie. Por ejemplo, el acceso a internet, la ejecución de código Python en un entorno seguro (Code Interpreter) o la generación de imágenes.</p>
<ul>
<li><strong>Cómo se usan:</strong> No tienes que hacer nada. La IA decide usarlas cuando tu petición lo requiere.</li>
</ul>
<h4 id="2-skills-de-ecosistema-marketplace-de-conectores">2. Skills de Ecosistema (Marketplace de conectores)</h4>
<p>Aquí es donde entra lo que comentábamos del <strong>MCP</strong>. Existe ya un ecosistema de servidores MCP creados por la comunidad.</p>
<ul>
<li>
<p><strong>Cómo se instalan:</strong> Es muy parecido a instalar una dependencia en un proyecto. Te descargas un servidor MCP ya hecho (por ejemplo, uno para Google Drive, otro para Slack o el de PostgreSQL que mencioné en mi post ).</p>
</li>
<li>
<p><strong>Configuración:</strong> Simplemente editas un archivo de configuración en tu entorno (como el <code>claude_desktop_config.json</code>) y le dices dónde está ese servidor. A partir de ese momento, la IA "ve" esa nueva habilidad.</p>
</li>
</ul>
<p>Los MCP dotan a los modelos de IA de nuevas Skills.</p>
<h4 id="3-skills-a-medida-las-que-tu-programas">3. Skills a medida (Las que tú programas)</h4>
<p>Si necesitas que la IA haga algo muy específico de tu flujo de trabajo (por ejemplo, "publicar este borrador en mi blog de Hugo"), tienes que crear la Skill tú mismo.</p>
<ul>
<li>
<p><strong>Cómo se hacen:</strong> No es más que una función en Python o TypeScript.</p>
</li>
<li>
<p><strong>El "Contrato":</strong> Lo más importante no es solo el código, sino la <strong>descripción</strong>. Tienes que escribir un pequeño texto explicando a la IA para qué sirve esa función y qué parámetros necesita. La IA lee esa descripción y, cuando ve que tu petición encaja, "llama" a tu función.</p>
</li>
</ul>
<h3 id="como-encaja-todo-esto-ejemplo-practico">¿Cómo encaja todo esto? (Ejemplo práctico)</h3>
<p>Imagina que estás trabajando en un mapa con Leaflet. Quieres añadir marcadores desde una base de datos:</p>
<ol>
<li>El <strong>Agente</strong> es tu interlocutor.</li>
<li>El <strong>MCP</strong> es el puente que le permite leer tu base de datos PostgreSQL local.</li>
<li>La <strong>Skill</strong> es la capacidad que le has dado para "escribir en el archivo <code>mapa.js</code>".</li>
</ol>
<p>Tú solo dices: <em>"Añade al mapa los puntos de la tabla 'eventos' de mi base de datos"</em>. El Agente usa el <strong>MCP</strong> para consultar la tabla, procesa los datos y usa su <strong>Skill</strong> de escritura para actualizar tu código.</p>
<h3 id="instrucciones-persistentes-los-archivos-md-y-ficheros-de-configuracion">Instrucciones persistentes: Los archivos <code>.md</code> y ficheros de configuración</h3>
<p>Si estamos tratando de ser meticulosos y evitar la "charla" innecesaria, no tiene sentido repetirle nuestras preferencias a la IA en cada nuevo chat. Aquí es donde entran los archivos de configuración de contexto, como los <code>ai.md</code>, <code>agents.md</code> o los <code>.cursorrules</code>.</p>
<ul>
<li>
<p><strong>¿Qué son?</strong> Son archivos de texto plano que colocas en la raíz de tu proyecto. En ellos defines las reglas del juego: qué librerías prefieres (por ejemplo, Leaflet sobre OpenLayers ), cómo quieres que se estructure el código o qué estándares de seguridad debe seguir.</p>
</li>
<li>
<p><strong>Instrucciones del modelo (System Prompts):</strong> Muchos modelos permiten ahora adjuntar un archivo de "instrucciones personalizadas". En lugar de un prompt efímero, es un contrato permanente. Si en mi blog hablo de usar PostgreSQL con MCP, en mi archivo de agente definiré que siempre que consulte la base de datos, use ese protocolo específico.</p>
</li>
<li>
<p><strong>La ventaja técnica:</strong> Al tener estas instrucciones en un fichero <code>.md</code>, puedes versionarlas con Git. Si cambias tu forma de trabajar o añades una nueva <strong>Skill</strong> a tu flujo, el Agente se actualiza automáticamente al leer el archivo.</p>
</li>
</ul>
<h1 id="capitulo-6-estrategia-de-modelos-a-quien-le-pides-cada-tarea">Capítulo 6. Estrategia de modelos: ¿A quién le pides cada tarea?</h1>
<p>A estas alturas de la guía, ya tienes el entorno configurado y sabes cómo dar instrucciones. Pero te falta lo más importante para el día a día: saber qué modelo elegir. Hoy en día no hay una sola "IA"; hay un catálogo creciente de modelos con especialidades muy distintas.</p>
<p>Si intentas resolver un problema de lógica compleja con un modelo rápido, te dará una respuesta errónea con mucha seguridad. Si usas un modelo pesado para corregir una falta de ortografía, estás matando moscas a cañonazos. Vamos a poner orden en este ecosistema.</p>
<h3 id="1-modelos-de-razonamiento-o-series-deep-thinking">1. Modelos de Razonamiento (O-series / "Deep Thinking")</h3>
<p>Estos son los modelos "lentos" por diseño (como la serie o1 o los nuevos modelos de pensamiento profundo). Su característica principal es que utilizan una <strong>cadena de pensamiento interna</strong> antes de escribir la primera palabra.</p>
<ul>
<li>
<p><strong>Cuándo usarlos:</strong> Problemas de lógica pura, matemáticas, arquitectura de software compleja o cuando necesitas que la IA verifique su propia respuesta varias veces.</p>
</li>
<li>
<p><strong>La clave:</strong> Úsalos cuando el "qué" es difícil de resolver y requiere un plan previo. No esperes velocidad; espera acierto.</p>
</li>
</ul>
<h3 id="2-modelos-de-desarrollo-coding-assistants">2. Modelos de Desarrollo (Coding Assistants)</h3>
<p>Aunque los generalistas programan bien, existen versiones optimizadas para el código (como los que alimentan a Cursor o versiones específicas "Coder"). Estos modelos han tenido una dieta de entrenamiento basada en repositorios de alta calidad y entienden mejor la <strong>jerarquía de un proyecto</strong>.</p>
<ul>
<li>
<p><strong>Cuándo usarlos:</strong> Para refactorizar código, crear tests unitarios o entender cómo una función en <code>db.py</code> afecta a tu mapa en <code>index.html</code>.</p>
</li>
<li>
<p><strong>El consejo:</strong> Úsalos dentro de tu IDE. Son modelos que brillan cuando tienen acceso a tu sistema de archivos.</p>
</li>
</ul>
<h3 id="3-modelos-flash-o-ligeros-velocidad-y-eficiencia">3. Modelos "Flash" o Ligeros (Velocidad y Eficiencia)</h3>
<p>Son los modelos pequeños (como GPT-4o-mini, Claude Haiku o Gemini Flash). Tienen menos parámetros, lo que los hace increíblemente rápidos y económicos en términos de <strong>tokens</strong>.</p>
<ul>
<li>
<p><strong>Cuándo usarlos:</strong> Tareas mecánicas o de bajo razonamiento. Traducir un texto, resumir un hilo de emails, formatear un JSON o cambiar el tono de un párrafo.</p>
</li>
<li>
<p><strong>La ventaja:</strong> Su latencia es mínima. En el tiempo que un modelo de razonamiento empieza a "pensar", este ya ha terminado la tarea.</p>
</li>
</ul>
<h3 id="4-modelos-de-gran-contexto-la-memoria-infinita">4. Modelos de Gran Contexto (La memoria infinita)</h3>
<p>Hay modelos específicos que destacan por tener una <strong>Ventana de Contexto</strong> gigantesca (capaces de leer libros enteros o repositorios completos de una vez).</p>
<ul>
<li>
<p><strong>Cuándo usarlos:</strong> Cuando tienes que analizar una documentación técnica de 500 páginas, buscar un bug en un proyecto antiguo con cientos de archivos o cruzar datos de múltiples fuentes.</p>
</li>
<li>
<p><strong>Ojo con:</strong> "Lost in the middle". Aunque puedan leer mucho, recuerda lo que vimos en el Capítulo 2: si el dato importante está en la mitad del texto, es mejor usar un modelo de razonamiento para asegurar que no se le pase por alto.</p>
</li>
</ul>
<h1 id="capitulo-7-conclusiones-y-el-camino-a-seguir">Capítulo 7. Conclusiones y el camino a seguir</h1>
<p>Después de haber desgranado desde qué es un token hasta cómo orquestar agentes con MCP, toca hacer una pausa y mirar el mapa completo. Escribir esta serie me ha servido para poner orden a mis investigaciones, pero sobre todo para reafirmar algunas convicciones sobre cómo debemos afrontar este cambio quienes nos dedicamos a la tecnología.</p>
<p>Estas son mis conclusiones tras meses de "cacharrreo", pruebas y algún que otro error de concepto:</p>
<h3 id="1-la-comprension-es-tu-mejor-herramienta">1. La comprensión es tu mejor herramienta</h3>
<p>No basta con saber qué botones pulsar. <strong>Entender cómo funciona la IA por dentro</strong>, su naturaleza estadística, sus límites de contexto y por qué alucina, es lo que marca la diferencia entre quien se frustra y quien sabe trabajar con ella. Como en cualquier otra rama de la ingeniería, conocer los cimientos te permite construir estructuras mucho más sólidas. Si sabes por qué la IA se equivoca, sabrás cómo corregir la instrucción para que no vuelva a suceder.</p>
<h3 id="2-solo-estamos-rascando-la-superficie">2. Solo estamos rascando la superficie</h3>
<p>Lo que hoy nos parece revolucionario, como el Model Context Protocol (MCP) que os comentaba hace unos días, probablemente sea solo el estándar básico de mañana. Estamos en una fase muy temprana de esta tecnología.</p>
<ul>
<li>Mi consejo: mantén la curiosidad activa.</li>
<li>No te conformes con lo que aprendiste el mes pasado.</li>
<li>Investiga, prueba modelos locales, experimenta con nuevos flujos de trabajo y, sobre todo, no te cases con una sola herramienta. La flexibilidad va a ser nuestra mayor ventaja competitiva.</li>
</ul>
<h3 id="3-orquestacion-frente-a-monopolio">3. Orquestación frente a monopolio</h3>
<p>Si algo me preocupa de este "boom" es la tendencia a quedarnos atrapados en un solo ecosistema. Ahora más que nunca, es vital <strong>huir del <em>vendor lock-in</em></strong>.</p>
<ul>
<li>
<p>Usa modelos diferentes según la tarea: razonamiento para la lógica, modelos <em>flash</em> para la velocidad y modelos de código para el desarrollo.</p>
</li>
<li>
<p>Prioriza opciones que te permitan saltar entre proveedores. Herramientas como OpenRouter u OpenCode Zen, o incluso el uso de modelos <em>Open Source</em> en local no son solo una cuestión de preferencia técnica; son una cuestión de <strong><a href="https://es.wikipedia.org/wiki/Soberanía_tecnológica">soberanía tecnológica</a></strong>. En un mundo donde los modelos cambian de la noche a la mañana, tener el control de tus fuentes de inteligencia es fundamental.</p>
</li>
</ul>
<h3 id="reflexion-final">Reflexión final</h3>
<p>La IA no ha venido a sustituir nuestra capacidad de pensar, sino a amplificar nuestra capacidad de hacer. Seguimos siendo artesanos e ingenieros de la tecnología , y nuestra labor sigue siendo la misma: resolver problemas reales con las mejores herramientas a nuestro alcance. La IA es solo un nuevo martillo, muy potente, pero que requiere una mano experta que sepa dónde golpear.</p>
<p>Espero que esta guía te haya servido para despejar un poco la niebla y empezar a usar estas herramientas con más criterio y menos miedo. Nos vemos en los próximos posts, seguramente probando alguna nueva "skill" o integrando mapas en algún flujo de trabajo que hoy ni siquiera imaginamos.</p>]]></description>
        </item>
    
        <item>
            <title>Conectar PostgreSQL a un agente IA mediante MCP. Háblale a tu base de datos.</title>
            <link>https://psanxiao.com/posts/2026-01-14-conectar-postgres-ia-mediante-mcp.html</link>
            <guid isPermaLink="true">https://psanxiao.com/posts/2026-01-14-conectar-postgres-ia-mediante-mcp.html</guid>
            <pubDate>Wed, 14 Jan 2026 00:00:00 +0000</pubDate>
            <description><![CDATA[<h1 id="conectar-postgresql-a-un-agente-ia-mediante-mcp-hablale-a-tu-base-de-datos">Conectar PostgreSQL a un agente IA mediante MCP. Háblale a tu base de datos.</h1>
<p>Me encontraba en una situación que probablemente te suene familiar. Tenía un proyecto antiguo, una aplicación web funcional que lleva años funcionando, alimentada por una base de datos PostgreSQL que... bueno, digamos que ha vivido su propia vida.</p>
<p>Cada cierto tiempo, el cliente pedía información que no se podía ver en la aplicación web, por lo que era necesario consultar directamente la base de datos. Y cada vez, me enfrentaba al mismo ritual:</p>
<ol>
<li>Abrir el cliente de base de datos</li>
<li>Recordar qué tabla almacena qué información (la documentación estaba, sabes, "actualizada")</li>
<li>Escribir consultas SQL que parecían novelas de misterio con múltiples JOINs</li>
<li>Esperar varios minutos mientras la base de datos trabajaba</li>
<li>Formatear los resultados para que fueran comprensibles</li>
</ol>
<p>No era técnicamente difícil, pero sí increíblemente lento y repetitivo. Y no nos engañemos, hacer consultas SQL no es divertido.</p>
<h2 id="primer-intento-de-solucion-ia-ayudame-a-escribir-sql">Primer Intento de solución: "IA, Ayúdame a Escribir SQL"</h2>
<p>Como buen desarrollador del siglo XXI, mi primer pensamiento fue: "¿Puede la IA ayudarme con esto?". Empecé a usar chats con modelos de IA para generar las consultas SQL.</p>
<p>El proceso era algo así:</p>
<p><strong>Yo:</strong> <em>Necesito una consulta que me muestre estos datos.</em></p>
<p><strong>IA:</strong> <em>Aquí tienes la consulta SQL:</em></p>
<p>Funcionaba... hasta que no funcionaba. El problema fundamental: la IA no conocía mi base de datos. Tenía que explicarle constantemente:</p>
<p>"La tabla <code>usuarios</code> se llama realmente <code>clientes</code>"
"Los campos de fecha están en formato timestamp"
"La relación entre clientes y pedidos es a través de <code>cliente_id</code>, no <code>usuario_id</code>"
"Ten cuidado que hay clientes inactivos en la tabla <code>clientes_baja</code>"</p>
<p>Cada consulta me llevaba más tiempo explicando el contexto que escribiendo SQL yo mismo. Era como tener que entrenar a alguien cada vez que quería hacer una pregunta, bueno, de hecho era literalmente así.</p>
<h2 id="el-momento-mcp-cuando-todo-cambio">El Momento MCP: Cuando Todo Cambió</h2>
<p>Investigando sobre cómo las IAs podrían conectarse directamente a bases de datos, apareció <strong>MCP (Model Context Protocol)</strong>. Y fue como si se encendiera una bombilla.</p>
<p>MCP no es un modelo ni una IA nueva, es un protocolo que permite a los modelos interactuar con herramientas externas (bases de datos, APIs, sistemas locales) de forma estructurada.</p>
<p><strong>¿Y si en lugar de explicarle a la IA cómo es mi base de datos, pudiera simplemente dejarla hablar con ella directamente?</strong></p>
<p>Dejé de pensar en "cómo genero mejores consultas SQL" y empecé a pensar en "cómo conecto la IA directamente a mis datos".</p>
<p>La diferencia es sutil pero fundamental:</p>
<ul>
<li><strong>Antes:</strong> yo → IA → texto SQL → yo → base de datos → yo → IA</li>
<li><strong>Ahora:</strong> yo → IA → base de datos → IA</li>
</ul>
<p>Eliminaba el intermediario (yo) y con ello, toda la fricción.</p>
<h2 id="experimento-1-gemini-cli-y-la-via-facil">Experimento #1: Gemini-cli y la Vía Fácil</h2>
<p>Empecé con Gemini-cli porque descubrí que tenía un sistema de extensiones ya maduro. Entre ellas, una extensión oficial para PostgreSQL. </p>
<p><strong>La instalación fue ridiculamente sencilla:</strong></p>
<div class="codehilite"><pre><span></span><code>gemini<span class="w"> </span>extensions<span class="w"> </span>install<span class="w"> </span>https://github.com/gemini-cli-extensions/postgres
</code></pre></div>

<p>Configuré las variables de entorno, y en minutos, tenía una IA que podía consultar mi base de datos real. El momento mágico fue cuando pregunté (ejemplo figurado):</p>
<p><strong>Yo:</strong> <em>"Muéstrame todos los clientes de Madrid"</em></p>
<p><strong>Gemini:</strong> <em>"Tengo 3 clientes en Madrid: Ana García, Carlos López y María Pérez. Ana se registró hace 2 semanas y ya ha hecho un pedido."</em></p>
<p>No me devolvió una consulta SQL, me devolvió una respuesta basada en los datos. La IA había ejecutado la consulta, interpretado los resultados, y me los presentó de forma natural.</p>
<p><strong>Ventajas de este enfoque:</strong> </p>
<ul>
<li>Super rápido de configurar</li>
<li>Extensión oficial y bien mantenida</li>
<li>Integración perfecta con PostgreSQL</li>
<li>Resultados conversacionales inmediatos</li>
</ul>
<h2 id="experimento-2-opencode-y-el-camino-manual">🚀 Experimento #2: Opencode y el Camino Manual</h2>
<p>Como últimamente estoy experimentando bastante con Opencode (una herramienta que me está encantando), quise ver cómo podía lograr lo mismo con ellos. Aquí la historia fue diferente.</p>
<p>Opencode no tiene un sistema de extensiones predefinido como Gemini-cli. En su lugar, utiliza MCP de forma más nativa, pero requiere que configuremos los servicios MCP manualmente.</p>
<p><strong>El proceso fue:</strong></p>
<ol>
<li>Instalar <code>postgres-mcp</code> como paquete npm</li>
<li>Configurar <code>opencode.json</code> para que usara este MCP local</li>
<li>Ajustar las variables de entorno para que coincidieran</li>
</ol>
<p>Aunque fue más manual que el enfoque de Gemini-cli, una vez funcionó, la experiencia fue igual de impresionante. La diferencia principal está en la filosofía:</p>
<ul>
<li><strong>Gemini-cli:</strong> Tiene extensiones prefabricadas para todo</li>
<li><strong>Opencode:</strong> Te da las herramientas para construir tus propias conexiones</li>
</ul>
<h2 id="este-repositorio-no-es-el-proyecto-es-mi-guia">Este Repositorio No Es el Proyecto, Es Mi Guía</h2>
<p>He creado un pequeño <a href="https://github.com/psanxiao/ia-postgresql-mcp-template">repositorio en mi cuenta de GitHub</a> con la configuración y una base de datos de ejemplo para probarlo. Es principalmente una <strong>documentación para mi yo del futuro</strong>. Pero como compartir es vivir, ahí queda, por si a alguien le es útil o quiere hacer pruebas.</p>
<ul>
<li><strong>Tiene una configuración para arrancar un PostgreSQL con Docker</strong></li>
<li><strong>Tiene una base de datos de ejemplo</strong> (una tienda online con datos realistas)</li>
<li><strong>Está todo documentado paso a paso</strong> (para no perder horas configurándolo de nuevo)</li>
<li><strong>Incluye la configuración para ambas soluciones</strong> (Gemini-cli y Opencode)</li>
<li><strong>Funciona out-of-the-box</strong> (para que mi yo del futuro no sufra)</li>
</ul>
<h2 id="conclusiones">Conclusiones</h2>
<p>Si no te gustan, tengo otras. Pero:</p>
<ul>
<li>
<p>La IA es el mayor avance en interfaces de usuario desde que se inventó el ratón. Los modelos pueden ser mejores o peores, acertar más o menos según su entrenamiento y sus capaciades, pero lo que es innegable es que la IA está cambiando la forma de relacionarnos con la tecnología. Ahora podemos hablarle a todo, incluído a una base de datos.</p>
</li>
<li>
<p><strong>"Me fío de todo el mundo pero desconfío del diablo que llevan dentro"</strong>. Esta frase es de la película <em>The Italian Job</em>. Viene que ni al pelo. Los modelos de IA son lo que son, se basan en probabilidad, no hacen magia, recuérdalo siempre. En este ejemplo concreto, si conoces el dominio que estás consultando y algo te huele raro, revisa, es muy probable que la IA haya hecho mal. Coteja la información, revisa la SQL que ha usado, etc. sobre todo si la información es importante.</p>
</li>
</ul>]]></description>
        </item>
    
        <item>
            <title>Leaflet.js avanzado: GeoJSON, WMS y selector de capas</title>
            <link>https://psanxiao.com/posts/2025-08-24-leaflet-avanzado-geojson.html</link>
            <guid isPermaLink="true">https://psanxiao.com/posts/2025-08-24-leaflet-avanzado-geojson.html</guid>
            <pubDate>Sun, 24 Aug 2025 00:00:00 +0000</pubDate>
            <description><![CDATA[<p>En el <a href="/posts/2025-08-15-introduccion-a-leaflet.html">post anterior</a> vimos cómo crear un mapa web básico. Ahora vamos a dar un salto cualitativo añadiendo funcionalidades avanzadas: marcadores personalizados desde GeoJSON, capas WMS y un control para que el usuario pueda elegir qué mapa base visualizar.</p>
<h3 id="definiendo-nuestros-datos-geojson">Definiendo nuestros datos GeoJSON</h3>
<p>Para este ejemplo, definiremos los datos directamente en una variable de JavaScript. En una aplicación real, estos datos vendrían normalmente de una API o un fichero <code>.geojson</code>.</p>
<h3 id="anadiendo-capas-wms">Añadiendo capas WMS</h3>
<p>Además de las capas de teselas habituales (llamadas capas TMS o XYZ), Leaflet puede consumir servicios WMS (Web Map Service). Estos servicios son un estándar muy común en el mundo de los SIG. Para este ejemplo, usaremos el servicio de ortofotos del Plan Nacional de Ortofotografía Aérea (PNOA) de España. Se hace a través de <code>L.tileLayer.wms</code>:</p>
<div class="codehilite"><pre><span></span><code><span class="kd">var</span><span class="w"> </span><span class="nx">pnoa</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">L</span><span class="p">.</span><span class="nx">tileLayer</span><span class="p">.</span><span class="nx">wms</span><span class="p">(</span><span class="s1">&#39;http://www.ign.es/wms-inspire/pnoa-ma&#39;</span><span class="p">,</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="nx">layers</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;OI.OrthoimageCoverage&#39;</span><span class="p">,</span>
<span class="w">    </span><span class="nx">format</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;image/jpeg&#39;</span><span class="p">,</span>
<span class="w">    </span><span class="nx">transparent</span><span class="o">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
<span class="w">    </span><span class="nx">attribution</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;PNOA cedido por © &lt;a href=&quot;http://www.ign.es/ign/main/index.do&quot;&gt;Instituto Geográfico Nacional&lt;/a&gt;&#39;</span>
<span class="p">});</span>
</code></pre></div>

<h3 id="marcadores-personalizados-con-html-css-y-font-awesome">Marcadores personalizados con HTML, CSS y Font Awesome</h3>
<p>Una de las formas más potentes de personalizar los marcadores es usando <code>L.divIcon</code>. A diferencia de <code>L.icon</code> que usa una imagen, <code>divIcon</code> nos permite usar código HTML y CSS, lo que abre la puerta a usar librerías de iconos como <a href="https://fontawesome.com/">Font Awesome</a>.</p>
<p>El proceso combina la lógica de JavaScript con los estilos de CSS.</p>
<p><strong>En la parte de JavaScript</strong>, dentro de la función <code>pointToLayer</code>, decidimos qué icono y qué estilo aplicar a cada punto. Usamos un <code>switch</code> para elegir una clase de Font Awesome (ej: <code>fa-tree</code>) y una clase CSS propia (ej: <code>parque</code>) basándonos en las propiedades de los datos. Luego, pasamos estas clases al <code>divIcon</code>:</p>
<div class="codehilite"><pre><span></span><code><span class="kd">var</span><span class="w"> </span><span class="nx">customIcon</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">L</span><span class="p">.</span><span class="nx">divIcon</span><span class="p">({</span>
<span class="w">    </span><span class="nx">className</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;custom-marker &#39;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">markerColorClass</span><span class="p">,</span><span class="w"> </span><span class="c1">// Clases para el CSS</span>
<span class="w">    </span><span class="nx">html</span><span class="o">:</span><span class="w"> </span><span class="sb">`&lt;i class=&quot;fas </span><span class="si">${</span><span class="nx">iconClass</span><span class="si">}</span><span class="sb">&quot;&gt;&lt;/i&gt;`</span><span class="p">,</span><span class="w">      </span><span class="c1">// HTML con el icono de Font Awesome</span>
<span class="w">    </span><span class="nx">iconSize</span><span class="o">:</span><span class="w"> </span><span class="p">[</span><span class="mf">30</span><span class="p">,</span><span class="w"> </span><span class="mf">30</span><span class="p">]</span>
<span class="p">});</span>
</code></pre></div>

<p><strong>A continuación, en el CSS</strong>, definimos los estilos. Tenemos una clase base, <code>.custom-marker</code>, que crea el círculo contenedor del icono. Luego, las clases específicas (<code>.playa</code>, <code>.parque</code>, <code>.faro</code>) establecen el color tanto del borde del círculo como del propio icono de Font Awesome que contiene.</p>
<div class="codehilite"><pre><span></span><code><span class="p">.</span><span class="nc">custom-marker</span><span class="w"> </span><span class="p">{</span><span class="w"> </span>
<span class="w">    </span><span class="k">background-color</span><span class="p">:</span><span class="w"> </span><span class="nb">rgba</span><span class="p">(</span><span class="mi">255</span><span class="p">,</span><span class="w"> </span><span class="mi">255</span><span class="p">,</span><span class="w"> </span><span class="mi">255</span><span class="p">,</span><span class="w"> </span><span class="mf">0.9</span><span class="p">);</span><span class="w"> </span>
<span class="w">    </span><span class="k">border</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="kt">px</span><span class="w"> </span><span class="kc">solid</span><span class="w"> </span><span class="mh">#333</span><span class="p">;</span><span class="w"> </span>
<span class="w">    </span><span class="k">border-radius</span><span class="p">:</span><span class="w"> </span><span class="mi">50</span><span class="kt">%</span><span class="p">;</span>
<span class="w">    </span><span class="c">/* ... más estilos ... */</span>
<span class="p">}</span>
<span class="p">.</span><span class="nc">custom-marker</span><span class="p">.</span><span class="nc">playa</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">border-color</span><span class="p">:</span><span class="w"> </span><span class="mh">#00aaff</span><span class="p">;</span><span class="w"> </span><span class="k">color</span><span class="p">:</span><span class="w"> </span><span class="mh">#00aaff</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
<span class="p">.</span><span class="nc">custom-marker</span><span class="p">.</span><span class="nc">parque</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">border-color</span><span class="p">:</span><span class="w"> </span><span class="mh">#28a745</span><span class="p">;</span><span class="w"> </span><span class="k">color</span><span class="p">:</span><span class="w"> </span><span class="mh">#28a745</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
</code></pre></div>

<p>De esta forma, el HTML y el CSS trabajan juntos para crear un marcador totalmente personalizado.</p>
<h3 id="creando-un-selector-de-capas">Creando un selector de capas</h3>
<p>Para permitir al usuario cambiar entre diferentes mapas base y activar o desactivar capas de datos, usamos <code>L.control.layers</code>.</p>
<div class="codehilite"><pre><span></span><code><span class="kd">var</span><span class="w"> </span><span class="nx">baseMaps</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="s2">&quot;Mapa&quot;</span><span class="o">:</span><span class="w"> </span><span class="nx">carto</span><span class="p">,</span>
<span class="w">    </span><span class="s2">&quot;Ortofoto&quot;</span><span class="o">:</span><span class="w"> </span><span class="nx">pnoa</span>
<span class="p">};</span>
<span class="kd">var</span><span class="w"> </span><span class="nx">overlayMaps</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="s2">&quot;Puntos de interés&quot;</span><span class="o">:</span><span class="w"> </span><span class="nx">markers</span>
<span class="p">};</span>
<span class="nx">L</span><span class="p">.</span><span class="nx">control</span><span class="p">.</span><span class="nx">layers</span><span class="p">(</span><span class="nx">baseMaps</span><span class="p">,</span><span class="w"> </span><span class="nx">overlayMaps</span><span class="p">).</span><span class="nx">addTo</span><span class="p">(</span><span class="nx">map</span><span class="p">);</span>
</code></pre></div>

<h3 id="ejemplo-completo">Ejemplo completo</h3>
<p>El siguiente código HTML es un ejemplo completo y autocontenido.</p>
<div class="codehilite"><pre><span></span><code><span class="cp">&lt;!DOCTYPE html&gt;</span>
<span class="p">&lt;</span><span class="nt">html</span><span class="p">&gt;</span>
<span class="p">&lt;</span><span class="nt">head</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">title</span><span class="p">&gt;</span>Leaflet Avanzado: WMS y Control de Capas<span class="p">&lt;/</span><span class="nt">title</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">meta</span> <span class="na">charset</span><span class="o">=</span><span class="s">&quot;utf-8&quot;</span> <span class="p">/&gt;</span>
    <span class="p">&lt;</span><span class="nt">meta</span> <span class="na">name</span><span class="o">=</span><span class="s">&quot;viewport&quot;</span> <span class="na">content</span><span class="o">=</span><span class="s">&quot;width=device-width, initial-scale=1.0&quot;</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">&quot;stylesheet&quot;</span> <span class="na">href</span><span class="o">=</span><span class="s">&quot;https://unpkg.com/leaflet/dist/leaflet.css&quot;</span> <span class="p">/&gt;</span>
    <span class="p">&lt;</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">&quot;stylesheet&quot;</span> <span class="na">href</span><span class="o">=</span><span class="s">&quot;https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css&quot;</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">style</span><span class="p">&gt;</span>
<span class="w">        </span><span class="nt">html</span><span class="o">,</span><span class="w"> </span><span class="nt">body</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">height</span><span class="p">:</span><span class="w"> </span><span class="mi">100</span><span class="kt">%</span><span class="p">;</span><span class="w"> </span><span class="k">margin</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="k">padding</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="k">display</span><span class="p">:</span><span class="w"> </span><span class="kc">flex</span><span class="p">;</span><span class="w"> </span><span class="k">flex-direction</span><span class="p">:</span><span class="w"> </span><span class="kc">column</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
<span class="w">        </span><span class="nt">h1</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">text-align</span><span class="p">:</span><span class="w"> </span><span class="kc">center</span><span class="p">;</span><span class="w"> </span><span class="k">padding</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="kt">px</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="k">margin</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
<span class="w">        </span><span class="p">#</span><span class="nn">map</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">flex-grow</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
<span class="w">        </span><span class="p">.</span><span class="nc">custom-marker</span><span class="w"> </span><span class="p">{</span><span class="w"> </span>
<span class="w">            </span><span class="k">background-color</span><span class="p">:</span><span class="w"> </span><span class="nb">rgba</span><span class="p">(</span><span class="mi">255</span><span class="p">,</span><span class="w"> </span><span class="mi">255</span><span class="p">,</span><span class="w"> </span><span class="mi">255</span><span class="p">,</span><span class="w"> </span><span class="mf">0.9</span><span class="p">);</span><span class="w"> </span>
<span class="w">            </span><span class="k">border</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="kt">px</span><span class="w"> </span><span class="kc">solid</span><span class="w"> </span><span class="mh">#333</span><span class="p">;</span><span class="w"> </span>
<span class="w">            </span><span class="k">border-radius</span><span class="p">:</span><span class="w"> </span><span class="mi">50</span><span class="kt">%</span><span class="p">;</span><span class="w"> </span>
<span class="w">            </span><span class="k">width</span><span class="p">:</span><span class="w"> </span><span class="mi">30</span><span class="kt">px</span><span class="p">;</span><span class="w"> </span>
<span class="w">            </span><span class="k">height</span><span class="p">:</span><span class="w"> </span><span class="mi">30</span><span class="kt">px</span><span class="p">;</span><span class="w"> </span>
<span class="w">            </span><span class="k">display</span><span class="p">:</span><span class="w"> </span><span class="kc">flex</span><span class="p">;</span><span class="w"> </span>
<span class="w">            </span><span class="k">justify-content</span><span class="p">:</span><span class="w"> </span><span class="kc">center</span><span class="p">;</span><span class="w"> </span>
<span class="w">            </span><span class="k">align-items</span><span class="p">:</span><span class="w"> </span><span class="kc">center</span><span class="p">;</span><span class="w"> </span>
<span class="w">            </span><span class="k">box-shadow</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">2</span><span class="kt">px</span><span class="w"> </span><span class="mi">5</span><span class="kt">px</span><span class="w"> </span><span class="nb">rgba</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="mi">0</span><span class="p">,</span><span class="mf">0.3</span><span class="p">);</span><span class="w"> </span>
<span class="w">            </span><span class="k">font-size</span><span class="p">:</span><span class="w"> </span><span class="mi">16</span><span class="kt">px</span><span class="p">;</span><span class="w"> </span>
<span class="w">        </span><span class="p">}</span>
<span class="w">        </span><span class="p">.</span><span class="nc">custom-marker</span><span class="p">.</span><span class="nc">playa</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">border-color</span><span class="p">:</span><span class="w"> </span><span class="mh">#00aaff</span><span class="p">;</span><span class="w"> </span><span class="k">color</span><span class="p">:</span><span class="w"> </span><span class="mh">#00aaff</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
<span class="w">        </span><span class="p">.</span><span class="nc">custom-marker</span><span class="p">.</span><span class="nc">parque</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">border-color</span><span class="p">:</span><span class="w"> </span><span class="mh">#28a745</span><span class="p">;</span><span class="w"> </span><span class="k">color</span><span class="p">:</span><span class="w"> </span><span class="mh">#28a745</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
<span class="w">        </span><span class="p">.</span><span class="nc">custom-marker</span><span class="p">.</span><span class="nc">faro</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">border-color</span><span class="p">:</span><span class="w"> </span><span class="mh">#ffc107</span><span class="p">;</span><span class="w"> </span><span class="k">color</span><span class="p">:</span><span class="w"> </span><span class="mh">#ffc107</span><span class="p">;</span><span class="w"> </span><span class="p">}</span>
<span class="w">    </span><span class="p">&lt;/</span><span class="nt">style</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">head</span><span class="p">&gt;</span>
<span class="p">&lt;</span><span class="nt">body</span><span class="p">&gt;</span>

<span class="p">&lt;</span><span class="nt">h1</span><span class="p">&gt;</span>Leaflet Avanzado: WMS y Control de Capas<span class="p">&lt;/</span><span class="nt">h1</span><span class="p">&gt;</span>
<span class="p">&lt;</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">&quot;map&quot;</span><span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>

<span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&quot;https://unpkg.com/leaflet/dist/leaflet.js&quot;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
<span class="p">&lt;</span><span class="nt">script</span><span class="p">&gt;</span>
<span class="w">    </span><span class="c1">// 1. Definir capas base</span>
<span class="w">    </span><span class="kd">var</span><span class="w"> </span><span class="nx">carto</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">L</span><span class="p">.</span><span class="nx">tileLayer</span><span class="p">(</span>
<span class="w">        </span><span class="s1">&#39;https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png&#39;</span><span class="p">,</span><span class="w"> </span>
<span class="w">        </span><span class="p">{</span>
<span class="w">            </span><span class="nx">attribution</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;&amp;copy; &lt;a href=&quot;https://www.openstreetmap.org/copyright&quot;&gt;OpenStreetMap&lt;/a&gt; contributors &amp;copy; &lt;a href=&quot;https://carto.com/attributions&quot;&gt;CARTO&lt;/a&gt;&#39;</span>
<span class="w">        </span><span class="p">}</span>
<span class="w">    </span><span class="p">);</span>

<span class="w">    </span><span class="kd">var</span><span class="w"> </span><span class="nx">pnoa</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">L</span><span class="p">.</span><span class="nx">tileLayer</span><span class="p">.</span><span class="nx">wms</span><span class="p">(</span><span class="s1">&#39;http://www.ign.es/wms-inspire/pnoa-ma&#39;</span><span class="p">,</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="nx">layers</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;OI.OrthoimageCoverage&#39;</span><span class="p">,</span>
<span class="w">        </span><span class="nx">format</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;image/jpeg&#39;</span><span class="p">,</span>
<span class="w">        </span><span class="nx">transparent</span><span class="o">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
<span class="w">        </span><span class="nx">attribution</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;PNOA cedido por © &lt;a href=&quot;http://www.ign.es/ign/main/index.do&quot;&gt;IGN&lt;/a&gt;&#39;</span>
<span class="w">    </span><span class="p">});</span>

<span class="w">    </span><span class="c1">// 2. Inicializar el mapa</span>
<span class="w">    </span><span class="kd">var</span><span class="w"> </span><span class="nx">map</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">L</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="s1">&#39;map&#39;</span><span class="p">,</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="nx">center</span><span class="o">:</span><span class="w"> </span><span class="p">[</span><span class="mf">43.35</span><span class="p">,</span><span class="w"> </span><span class="o">-</span><span class="mf">8.36</span><span class="p">],</span>
<span class="w">        </span><span class="nx">zoom</span><span class="o">:</span><span class="w"> </span><span class="mf">13</span><span class="p">,</span>
<span class="w">        </span><span class="nx">layers</span><span class="o">:</span><span class="w"> </span><span class="p">[</span><span class="nx">carto</span><span class="p">]</span>
<span class="w">    </span><span class="p">});</span>

<span class="w">    </span><span class="c1">// 3. Datos GeoJSON</span>
<span class="w">    </span><span class="kd">var</span><span class="w"> </span><span class="nx">geojsonData</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span>
<span class="w">      </span><span class="s2">&quot;type&quot;</span><span class="o">:</span><span class="w"> </span><span class="s2">&quot;FeatureCollection&quot;</span><span class="p">,</span>
<span class="w">      </span><span class="s2">&quot;name&quot;</span><span class="o">:</span><span class="w"> </span><span class="s2">&quot;oleiros_puntos&quot;</span><span class="p">,</span>
<span class="w">      </span><span class="s2">&quot;features&quot;</span><span class="o">:</span><span class="w"> </span><span class="p">[</span>
<span class="w">        </span><span class="p">{</span>
<span class="w">          </span><span class="s2">&quot;type&quot;</span><span class="o">:</span><span class="w"> </span><span class="s2">&quot;Feature&quot;</span><span class="p">,</span>
<span class="w">          </span><span class="s2">&quot;properties&quot;</span><span class="o">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="s2">&quot;name&quot;</span><span class="o">:</span><span class="w"> </span><span class="s2">&quot;Playa de Santa Cristina&quot;</span><span class="p">,</span><span class="w"> </span><span class="s2">&quot;tipo&quot;</span><span class="o">:</span><span class="w"> </span><span class="s2">&quot;Playa&quot;</span><span class="w"> </span><span class="p">},</span>
<span class="w">          </span><span class="s2">&quot;geometry&quot;</span><span class="o">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="s2">&quot;type&quot;</span><span class="o">:</span><span class="w"> </span><span class="s2">&quot;Point&quot;</span><span class="p">,</span><span class="w"> </span><span class="s2">&quot;coordinates&quot;</span><span class="o">:</span><span class="w"> </span><span class="p">[</span><span class="o">-</span><span class="mf">8.378631</span><span class="p">,</span><span class="w"> </span><span class="mf">43.339596</span><span class="p">]</span><span class="w"> </span><span class="p">}</span>
<span class="w">        </span><span class="p">},</span>
<span class="w">        </span><span class="p">{</span>
<span class="w">          </span><span class="s2">&quot;type&quot;</span><span class="o">:</span><span class="w"> </span><span class="s2">&quot;Feature&quot;</span><span class="p">,</span>
<span class="w">          </span><span class="s2">&quot;properties&quot;</span><span class="o">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="s2">&quot;name&quot;</span><span class="o">:</span><span class="w"> </span><span class="s2">&quot;Parque de José Martí&quot;</span><span class="p">,</span><span class="w"> </span><span class="s2">&quot;tipo&quot;</span><span class="o">:</span><span class="w"> </span><span class="s2">&quot;Parque&quot;</span><span class="w"> </span><span class="p">},</span>
<span class="w">          </span><span class="s2">&quot;geometry&quot;</span><span class="o">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="s2">&quot;type&quot;</span><span class="o">:</span><span class="w"> </span><span class="s2">&quot;Point&quot;</span><span class="p">,</span><span class="w"> </span><span class="s2">&quot;coordinates&quot;</span><span class="o">:</span><span class="w"> </span><span class="p">[</span><span class="o">-</span><span class="mf">8.378567</span><span class="p">,</span><span class="w"> </span><span class="mf">43.336728</span><span class="p">]</span><span class="w"> </span><span class="p">}</span>
<span class="w">        </span><span class="p">},</span>
<span class="w">        </span><span class="p">{</span>
<span class="w">          </span><span class="s2">&quot;type&quot;</span><span class="o">:</span><span class="w"> </span><span class="s2">&quot;Feature&quot;</span><span class="p">,</span>
<span class="w">          </span><span class="s2">&quot;properties&quot;</span><span class="o">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="s2">&quot;name&quot;</span><span class="o">:</span><span class="w"> </span><span class="s2">&quot;Faro de Mera&quot;</span><span class="p">,</span><span class="w"> </span><span class="s2">&quot;tipo&quot;</span><span class="o">:</span><span class="w"> </span><span class="s2">&quot;Faro&quot;</span><span class="w"> </span><span class="p">},</span>
<span class="w">          </span><span class="s2">&quot;geometry&quot;</span><span class="o">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="s2">&quot;type&quot;</span><span class="o">:</span><span class="w"> </span><span class="s2">&quot;Point&quot;</span><span class="p">,</span><span class="w"> </span><span class="s2">&quot;coordinates&quot;</span><span class="o">:</span><span class="w"> </span><span class="p">[</span><span class="o">-</span><span class="mf">8.354416</span><span class="p">,</span><span class="w"> </span><span class="mf">43.383347</span><span class="p">]</span><span class="w"> </span><span class="p">}</span>
<span class="w">        </span><span class="p">},</span>
<span class="w">        </span><span class="p">{</span>
<span class="w">          </span><span class="s2">&quot;type&quot;</span><span class="o">:</span><span class="w"> </span><span class="s2">&quot;Feature&quot;</span><span class="p">,</span>
<span class="w">          </span><span class="s2">&quot;properties&quot;</span><span class="o">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="s2">&quot;name&quot;</span><span class="o">:</span><span class="w"> </span><span class="s2">&quot;Playa de Bastiagueiro&quot;</span><span class="p">,</span><span class="w"> </span><span class="s2">&quot;tipo&quot;</span><span class="o">:</span><span class="w"> </span><span class="s2">&quot;Playa&quot;</span><span class="w"> </span><span class="p">},</span>
<span class="w">          </span><span class="s2">&quot;geometry&quot;</span><span class="o">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="s2">&quot;type&quot;</span><span class="o">:</span><span class="w"> </span><span class="s2">&quot;Point&quot;</span><span class="p">,</span><span class="w"> </span><span class="s2">&quot;coordinates&quot;</span><span class="o">:</span><span class="w"> </span><span class="p">[</span><span class="o">-</span><span class="mf">8.362489</span><span class="p">,</span><span class="w"> </span><span class="mf">43.340981</span><span class="p">]</span><span class="w"> </span><span class="p">}</span>
<span class="w">        </span><span class="p">}</span>
<span class="w">      </span><span class="p">]</span>
<span class="w">    </span><span class="p">};</span>

<span class="w">    </span><span class="c1">// 4. Capa de marcadores personalizados</span>
<span class="w">    </span><span class="kd">var</span><span class="w"> </span><span class="nx">markers</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">L</span><span class="p">.</span><span class="nx">geoJSON</span><span class="p">(</span><span class="nx">geojsonData</span><span class="p">,</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="nx">pointToLayer</span><span class="o">:</span><span class="w"> </span><span class="kd">function</span><span class="w"> </span><span class="p">(</span><span class="nx">feature</span><span class="p">,</span><span class="w"> </span><span class="nx">latlng</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">            </span><span class="kd">var</span><span class="w"> </span><span class="nx">iconClass</span><span class="p">,</span><span class="w"> </span><span class="nx">markerColorClass</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;&#39;</span><span class="p">;</span>
<span class="w">            </span><span class="k">switch</span><span class="w"> </span><span class="p">(</span><span class="nx">feature</span><span class="p">.</span><span class="nx">properties</span><span class="p">.</span><span class="nx">tipo</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">                </span><span class="k">case</span><span class="w"> </span><span class="s1">&#39;Playa&#39;</span><span class="o">:</span><span class="w"> </span>
<span class="w">                    </span><span class="nx">iconClass</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;fa-umbrella-beach&#39;</span><span class="p">;</span><span class="w"> </span>
<span class="w">                    </span><span class="nx">markerColorClass</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;playa&#39;</span><span class="p">;</span><span class="w"> </span>
<span class="w">                    </span><span class="k">break</span><span class="p">;</span>
<span class="w">                </span><span class="k">case</span><span class="w"> </span><span class="s1">&#39;Parque&#39;</span><span class="o">:</span><span class="w"> </span>
<span class="w">                    </span><span class="nx">iconClass</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;fa-tree&#39;</span><span class="p">;</span><span class="w"> </span>
<span class="w">                    </span><span class="nx">markerColorClass</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;parque&#39;</span><span class="p">;</span><span class="w"> </span>
<span class="w">                    </span><span class="k">break</span><span class="p">;</span>
<span class="w">                </span><span class="k">case</span><span class="w"> </span><span class="s1">&#39;Faro&#39;</span><span class="o">:</span><span class="w"> </span>
<span class="w">                    </span><span class="nx">iconClass</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;fa-lightbulb&#39;</span><span class="p">;</span><span class="w"> </span>
<span class="w">                    </span><span class="nx">markerColorClass</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;faro&#39;</span><span class="p">;</span><span class="w"> </span>
<span class="w">                    </span><span class="k">break</span><span class="p">;</span>
<span class="w">                </span><span class="k">default</span><span class="o">:</span><span class="w"> </span>
<span class="w">                    </span><span class="nx">iconClass</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;fa-map-marker-alt&#39;</span><span class="p">;</span><span class="w"> </span>
<span class="w">                    </span><span class="k">break</span><span class="p">;</span>
<span class="w">            </span><span class="p">}</span>
<span class="w">            </span><span class="kd">var</span><span class="w"> </span><span class="nx">customIcon</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">L</span><span class="p">.</span><span class="nx">divIcon</span><span class="p">({</span>
<span class="w">                </span><span class="nx">className</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;custom-marker &#39;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">markerColorClass</span><span class="p">,</span>
<span class="w">                </span><span class="nx">html</span><span class="o">:</span><span class="w"> </span><span class="sb">`&lt;i class=&quot;fas </span><span class="si">${</span><span class="nx">iconClass</span><span class="si">}</span><span class="sb">&quot;&gt;&lt;/i&gt;`</span><span class="p">,</span>
<span class="w">                </span><span class="nx">iconSize</span><span class="o">:</span><span class="w"> </span><span class="p">[</span><span class="mf">30</span><span class="p">,</span><span class="w"> </span><span class="mf">30</span><span class="p">]</span>
<span class="w">            </span><span class="p">});</span>
<span class="w">            </span><span class="kd">var</span><span class="w"> </span><span class="nx">popupText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;&lt;strong&gt;&#39;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">feature</span><span class="p">.</span><span class="nx">properties</span><span class="p">.</span><span class="nx">name</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s1">&#39;&lt;/strong&gt;&#39;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span>
<span class="w">                            </span><span class="s1">&#39;&lt;br&gt;Tipo: &#39;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">feature</span><span class="p">.</span><span class="nx">properties</span><span class="p">.</span><span class="nx">tipo</span><span class="p">;</span>

<span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="nx">L</span><span class="p">.</span><span class="nx">marker</span><span class="p">(</span><span class="nx">latlng</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">icon</span><span class="o">:</span><span class="w"> </span><span class="nx">customIcon</span><span class="w"> </span><span class="p">}).</span><span class="nx">bindPopup</span><span class="p">(</span><span class="nx">popupText</span><span class="p">);</span>
<span class="w">        </span><span class="p">}</span>
<span class="w">    </span><span class="p">}).</span><span class="nx">addTo</span><span class="p">(</span><span class="nx">map</span><span class="p">);</span>

<span class="w">    </span><span class="c1">// 5. Configurar el control de capas</span>
<span class="w">    </span><span class="kd">var</span><span class="w"> </span><span class="nx">baseMaps</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="s2">&quot;Mapa&quot;</span><span class="o">:</span><span class="w"> </span><span class="nx">carto</span><span class="p">,</span>
<span class="w">        </span><span class="s2">&quot;Ortofoto&quot;</span><span class="o">:</span><span class="w"> </span><span class="nx">pnoa</span>
<span class="w">    </span><span class="p">};</span>

<span class="w">    </span><span class="kd">var</span><span class="w"> </span><span class="nx">overlayMaps</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="s2">&quot;Puntos de interés&quot;</span><span class="o">:</span><span class="w"> </span><span class="nx">markers</span>
<span class="w">    </span><span class="p">};</span>

<span class="w">    </span><span class="nx">L</span><span class="p">.</span><span class="nx">control</span><span class="p">.</span><span class="nx">layers</span><span class="p">(</span><span class="nx">baseMaps</span><span class="p">,</span><span class="w"> </span><span class="nx">overlayMaps</span><span class="p">).</span><span class="nx">addTo</span><span class="p">(</span><span class="nx">map</span><span class="p">);</span>

<span class="w">    </span><span class="c1">// 6. Ajustar el zoom a los marcadores</span>
<span class="w">    </span><span class="nx">map</span><span class="p">.</span><span class="nx">fitBounds</span><span class="p">(</span><span class="nx">markers</span><span class="p">.</span><span class="nx">getBounds</span><span class="p">());</span>

<span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>

<span class="p">&lt;/</span><span class="nt">body</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">html</span><span class="p">&gt;</span>
</code></pre></div>

<h3 id="bonus-final-latitud-y-longitud-el-eterno-debate">Bonus final: Latitud y Longitud, el eterno debate</h3>
<p>Un detalle importante a tener en cuenta es el orden de las coordenadas. Si te has dado cuenta el orden de las coordenadas es diferente cuando definimos el centro del mapa en leaflet, respecto de como se definen en el geojson. No existe un estándar único en el mundo de la información geográfica, lo que a menudo causa errores inesperados.</p>
<ul>
<li><strong>Leaflet</strong>, en sus funciones principales como <code>L.mapsetView()</code> o <code>L.marker()</code>, generalmente espera el formato <code>[latitud, longitud]</code>.</li>
<li><strong>El estándar GeoJSON</strong>, sin embargo, especifica el orden inverso: <code>[longitud, latitud]</code>.</li>
</ul>
<p>Esta inconsistencia es habitual entre diferentes herramientas, formatos y estándares del mundo SIG. Afortunadamente, en este caso, la función <code>L.geoJSON()</code> de Leaflet es lo suficientemente inteligente como para leer correctamente las coordenadas en el formato estándar de GeoJSON sin que tengamos que hacer ninguna conversión manual. Para quien quiera profundizar más en este curioso problema, el artículo <a href="https://macwright.com/lonlat/">Lon, lat is the right way</a> de Tom MacWright lo explica de maravilla.</p>]]></description>
        </item>
    
        <item>
            <title>Introducción a Leaflet: mapas interactivos sencillos y potentes</title>
            <link>https://psanxiao.com/posts/2025-08-15-introduccion-a-leaflet.html</link>
            <guid isPermaLink="true">https://psanxiao.com/posts/2025-08-15-introduccion-a-leaflet.html</guid>
            <pubDate>Fri, 15 Aug 2025 00:00:00 +0000</pubDate>
            <description><![CDATA[<p>En el mundo del desarrollo web, <strong><a href="https://leafletjs.com/">Leaflet</a></strong> se ha convertido en una de las librerías JavaScript más populares para crear mapas interactivos. Su diseño está pensado para ser <strong>ligero</strong>, con un núcleo (<em>core</em>) pequeño que ofrece las funciones esenciales para mostrar mapas y elementos básicos de interacción.  </p>
<p>Esta filosofía tiene una ventaja importante: <strong>puedes empezar de forma simple y rápida</strong>, sin tener que aprender una API enorme o cargar funcionalidades que no necesitas. Y si tu proyecto crece, puedes ir ampliándolo con una extensa colección de <a href="https://leafletjs.com/plugins.html">plugins</a> oficiales y de la comunidad que añaden todo tipo de capacidades: desde dibujar polígonos y medir distancias, hasta mostrar datos en 3D o integrar servicios de geocodificación.</p>
<p>En resumen: <strong>Leaflet es pequeño y fácil al principio, pero no se queda corto</strong>. Es como una navaja suiza a la que le puedes ir añadiendo herramientas a medida que las necesitas.</p>
<h2 id="por-que-usar-leaflet">¿Por qué usar Leaflet?</h2>
<ul>
<li><strong>Ligera:</strong> el núcleo pesa menos de 50 KB.</li>
<li><strong>Compatible:</strong> funciona en la mayoría de navegadores y dispositivos móviles.</li>
<li><strong>Extensible:</strong> cuenta con multitud de <a href="https://leafletjs.com/plugins.html">plugins</a> para añadir funcionalidades avanzadas.</li>
<li><strong>Fácil de aprender:</strong> ideal para principiantes y proyectos rápidos.</li>
</ul>
<h2 id="ejemplo-basico-de-mapa-con-leaflet">Ejemplo básico de mapa con Leaflet</h2>
<p>El siguiente ejemplo muestra cómo crear un mapa centrado en unas coordenadas concretas, añadir un marcador y mostrar un popup. Solo necesitas incluir la librería Leaflet y un contenedor para el mapa.</p>
<div class="codehilite"><pre><span></span><code><span class="cp">&lt;!DOCTYPE html&gt;</span>
<span class="p">&lt;</span><span class="nt">html</span><span class="p">&gt;</span>
<span class="p">&lt;</span><span class="nt">head</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">meta</span> <span class="na">charset</span><span class="o">=</span><span class="s">&quot;utf-8&quot;</span> <span class="p">/&gt;</span>
    <span class="p">&lt;</span><span class="nt">title</span><span class="p">&gt;</span>Mapa con Leaflet<span class="p">&lt;/</span><span class="nt">title</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">meta</span> <span class="na">name</span><span class="o">=</span><span class="s">&quot;viewport&quot;</span> <span class="na">content</span><span class="o">=</span><span class="s">&quot;width=device-width, initial-scale=1.0&quot;</span><span class="p">&gt;</span>

    <span class="cm">&lt;!-- CSS de Leaflet --&gt;</span>
    <span class="p">&lt;</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">&quot;stylesheet&quot;</span> <span class="na">href</span><span class="o">=</span><span class="s">&quot;https://unpkg.com/leaflet/dist/leaflet.css&quot;</span> <span class="p">/&gt;</span>

    <span class="p">&lt;</span><span class="nt">style</span><span class="p">&gt;</span>
<span class="w">        </span><span class="p">#</span><span class="nn">map</span><span class="w"> </span><span class="p">{</span>
<span class="w">            </span><span class="k">height</span><span class="p">:</span><span class="w"> </span><span class="mi">500</span><span class="kt">px</span><span class="p">;</span>
<span class="w">        </span><span class="p">}</span>
<span class="w">    </span><span class="p">&lt;/</span><span class="nt">style</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">head</span><span class="p">&gt;</span>
<span class="p">&lt;</span><span class="nt">body</span><span class="p">&gt;</span>

<span class="p">&lt;</span><span class="nt">h1</span><span class="p">&gt;</span>Mi primer mapa con Leaflet<span class="p">&lt;/</span><span class="nt">h1</span><span class="p">&gt;</span>
<span class="p">&lt;</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">&quot;map&quot;</span><span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>

<span class="cm">&lt;!-- JS de Leaflet --&gt;</span>
<span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&quot;https://unpkg.com/leaflet/dist/leaflet.js&quot;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
<span class="p">&lt;</span><span class="nt">script</span><span class="p">&gt;</span>
<span class="w">    </span><span class="c1">// Inicializar el mapa</span>
<span class="w">    </span><span class="kd">var</span><span class="w"> </span><span class="nx">map</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">L</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="s1">&#39;map&#39;</span><span class="p">).</span><span class="nx">setView</span><span class="p">([</span><span class="mf">43.3336</span><span class="p">,</span><span class="w"> </span><span class="o">-</span><span class="mf">8.3174</span><span class="p">],</span><span class="w"> </span><span class="mf">14</span><span class="p">);</span>

<span class="w">    </span><span class="c1">// Añadir capa base de OpenStreetMap</span>
<span class="w">    </span><span class="nx">L</span><span class="p">.</span><span class="nx">tileLayer</span><span class="p">(</span><span class="s1">&#39;https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png&#39;</span><span class="p">,</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="nx">attribution</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;&amp;copy; &lt;a href=&quot;https://www.openstreetmap.org/&quot;&gt;OpenStreetMap&lt;/a&gt; contributors&#39;</span>
<span class="w">    </span><span class="p">}).</span><span class="nx">addTo</span><span class="p">(</span><span class="nx">map</span><span class="p">);</span>

<span class="w">    </span><span class="c1">// Añadir un marcador</span>
<span class="w">    </span><span class="kd">var</span><span class="w"> </span><span class="nx">marker</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">L</span><span class="p">.</span><span class="nx">marker</span><span class="p">([</span><span class="mf">43.3336</span><span class="p">,</span><span class="w"> </span><span class="o">-</span><span class="mf">8.3174</span><span class="p">]).</span><span class="nx">addTo</span><span class="p">(</span><span class="nx">map</span><span class="p">);</span>

<span class="w">    </span><span class="c1">// Popup en el marcador</span>
<span class="w">    </span><span class="nx">marker</span><span class="p">.</span><span class="nx">bindPopup</span><span class="p">(</span><span class="s2">&quot;&lt;b&gt;Hola!&lt;/b&gt;&lt;br&gt;Este es un mapa de Leaflet centrado en Oleiros.&quot;</span><span class="p">).</span><span class="nx">openPopup</span><span class="p">();</span>
<span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>

<span class="p">&lt;/</span><span class="nt">body</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">html</span><span class="p">&gt;</span>
</code></pre></div>

<h2 id="como-funciona-el-codigo">Cómo funciona el código</h2>
<ul>
<li>Importamos Leaflet: a través de sus archivos CSS y JS desde un CDN.</li>
<li>Creamos un contenedor #map: donde se dibujará el mapa.</li>
<li>Inicializamos el mapa: con L.map() y lo centramos en un punto.</li>
<li>Añadimos una capa base: usando L.tileLayer() con teselas de OpenStreetMap.</li>
<li>Colocamos un marcador: y le añadimos un popup.</li>
</ul>]]></description>
        </item>
    
        <item>
            <title>De la Tierra al navegador: entendiendo la base de los mapas web</title>
            <link>https://psanxiao.com/posts/2025-08-14-de-la-tierra-al-navegador.html</link>
            <guid isPermaLink="true">https://psanxiao.com/posts/2025-08-14-de-la-tierra-al-navegador.html</guid>
            <pubDate>Thu, 14 Aug 2025 00:00:00 +0000</pubDate>
            <description><![CDATA[<p>Si estás empezando a trabajar con <strong>mapas en la web</strong>, probablemente tu primer impulso será abrir una librería como <a href="https://openlayers.org/">OpenLayers</a>, <a href="https://leafletjs.com/">Leaflet</a> o <a href="https://maplibre.org/">MapLibre</a> y cargar tus datos. Y está bien. Pero antes de ver nada en pantalla, puede que aparezcan palabras que suenan algo misteriosas: <em>proyección</em>, <em>WGS84</em>, <em>EPSG:3857</em>, <em>coordenadas geográficas</em>, <em>sistema de referencia</em>…</p>
<p>Entender qué significan estos conceptos te ahorrará muchos quebraderos de cabeza. Porque antes de representar datos geográficos en la web, hay que entender una idea clave: <strong>el planeta en el que vivimos no es fácil de convertir en un plano</strong>.</p>
<p>La Tierra no es una esfera perfecta. De hecho, se parece más a una patata: está achatada por los polos, algo abultada por el ecuador y llena de irregularidades causadas por la gravedad, las montañas y los océanos. Esa forma tan real pero complicada se llama <a href="https://es.wikipedia.org/wiki/Geoide"><strong>geoide</strong></a>, y aunque describe bien cómo es el planeta, resulta imposible de usar directamente en los cálculos de un mapa.</p>
<p>Para simplificar las cosas, se utiliza una superficie más regular llamada <a href="https://es.wikipedia.org/wiki/Elipsoide_de_referencia"><strong>elipsoide de referencia</strong></a>. Es una figura matemática que se ajusta al geoide, pero lo bastante sencilla para trabajar con ella. Existen varios elipsoides, pero hoy el más usado es el <strong>WGS84</strong>, el mismo que emplea el GPS y la mayoría de servicios de mapas en línea.</p>
<p>Con el elipsoide definido, necesitamos una forma de localizar puntos sobre él. Para eso se usan las <a href="https://es.wikipedia.org/wiki/Coordenadas_geogr%C3%A1ficas"><strong>coordenadas geográficas</strong></a>, que indican la <strong>latitud</strong> (posición norte-sur) y la <strong>longitud</strong> (posición este-oeste). Por ejemplo, <code>43.333°N, -8.317°O</code> nos lleva a Oleiros, en Galicia. Es un sistema perfecto para referirse a lugares sobre el globo, pero poco práctico para dibujarlos en una pantalla, porque los ordenadores trabajan con coordenadas <strong>planas (x, y)</strong>.</p>
<p>Y aquí llega el paso más importante: <strong>convertir una superficie curva en un plano</strong>. Este proceso se llama <a href="https://es.wikipedia.org/wiki/Proyecci%C3%B3n_cartogr%C3%A1fica"><strong>proyección cartográfica</strong></a>.Imagina intentar aplanar la piel de una naranja sobre una hoja de papel: tendrás que estirar, cortar o deformar algo. Con la Tierra pasa lo mismo. Cada proyección elige qué quiere conservar (las distancias, las áreas, los ángulos…) y qué está dispuesta a deformar. No hay una proyección perfecta, solo la más adecuada según el uso.</p>
<h2 id="la-proyeccion-web-mercator">La proyección Web Mercator</h2>
<p>En el mundo del webmapping, la proyección más común es la <a href="https://es.wikipedia.org/wiki/Pseudo-Mercator"><strong>Web Mercator</strong></a>, una adaptación moderna de la proyección creada por <strong>Gerardus Mercator</strong> en el siglo XVI. Es la que utilizan <strong>Google Maps</strong>, <strong>OpenStreetMap</strong>, <strong>Bing Maps</strong>, y casi todos los visores actuales.  </p>
<p>Su gran ventaja es que permite mostrar todo el planeta como una cuadrícula continua y realizar <strong>zoom progresivo</strong> sin interrupciones, lo que resulta ideal para aplicaciones interactivas. Internamente, Web Mercator transforma las coordenadas de latitud y longitud en metros usando el elipsoide <strong>WGS84</strong>, y “recorta” la Tierra a una latitud máxima de ±85°. Esto evita que las zonas cercanas a los polos se estiren infinitamente.</p>
<p>El inconveniente, claro, es la <strong>distorsión</strong>: los objetos cercanos a los polos se ven mucho más grandes de lo que son. Groenlandia parece casi tan grande como África, cuando en realidad África es unas 14 veces mayor. Por eso, aunque Web Mercator es práctica, no es la proyección adecuada para hacer cálculos de superficie o distancia.</p>
<p>Aun así, su sencillez y compatibilidad la han convertido en el estándar de facto en los mapas web. Cuando ves un mapa en línea, lo más probable es que esté en <strong>EPSG:3857</strong>, el código oficial de esta proyección.</p>
<h2 id="sistemas-de-referencia-el-idioma-de-los-mapas">Sistemas de referencia: el idioma de los mapas</h2>
<p>Cada combinación de elipsoide, proyección y unidad de medida forma lo que se conoce como un <a href="https://es.wikipedia.org/wiki/Sistema_de_referencia_geod%C3%A9sico"><strong>sistema de referencia espacial</strong></a>. Podemos imaginarlo como el “idioma” que usan los datos geográficos para decirle al mapa <strong>dónde</strong> se encuentra cada punto.</p>
<p>Un sistema de referencia se compone, generalmente, de tres partes:</p>
<ol>
<li><strong>Datum o marco de referencia</strong>: define la forma y posición del elipsoide respecto a la Tierra real. Por ejemplo, el <strong>WGS84</strong> es un datum global.  </li>
<li><strong>Proyección cartográfica</strong>: establece cómo se transforma la superficie curva del elipsoide en un plano (por ejemplo, <strong>Mercator</strong>, <strong>UTM</strong>, <strong>Lambert</strong>, etc.).  </li>
<li><strong>Unidades de medida</strong>: indican en qué se expresan las coordenadas (grados, metros, pies…).</li>
</ol>
<p>Estas tres piezas juntas determinan cómo deben interpretarse las coordenadas de un archivo o servicio geográfico. Por ejemplo:</p>
<ul>
<li><strong>EPSG:4326</strong> → usa el datum WGS84, sin proyección (coordenadas en grados de latitud y longitud).  </li>
<li><strong>EPSG:3857</strong> → usa el mismo datum WGS84, pero proyectado con Web Mercator, y las coordenadas se expresan en metros.  </li>
<li><strong>EPSG:25831</strong> → corresponde al sistema UTM huso 31N sobre el datum ETRS89, habitual en España peninsular.</li>
</ul>
<p>Saber en qué sistema están tus datos, y cuál usa tu mapa, es esencial para que todo encaje. Si no coinciden, los puntos aparecerán desplazados, a veces cientos de kilómetros fuera de lugar. Por suerte, la mayoría de librerías modernas permiten gestionar estos sistemas de forma automática.</p>
<h2 id="que-hacen-las-librerias-web-con-todo-esto">¿Qué hacen las librerías web con todo esto?</h2>
<p>Una buena noticia: no necesitas preocuparte demasiado por las transformaciones.<br />
Librerías como <strong>Leaflet</strong>, <strong>OpenLayers</strong> o <strong>MapLibre</strong> están pensadas para que los datos “simplemente funcionen”.</p>
<p>La mayoría de los mapas en la web utilizan <strong>Web Mercator (EPSG:3857)</strong>, pero muchos conjuntos de datos, APIs o servicios de datos abiertos vienen en <strong>EPSG:4326</strong>, es decir, con latitudes y longitudes en grados. ¿Cómo es posible que ambos se entiendan?</p>
<p>La clave está en que <strong>los dos sistemas comparten el mismo elipsoide, WGS84</strong>.<br />
Eso significa que la relación entre coordenadas geográficas (grados) y proyectadas (metros) es directa y fácil de calcular matemáticamente. Cuando una librería como OpenLayers recibe datos en 4326, <strong>no necesita hacer una transformación geodésica compleja</strong>, sino una simple conversión de grados a metros en la proyección Web Mercator.  </p>
<p>En la práctica, es como si el mapa y tus datos hablaran el mismo idioma con distinto acento: basta una pequeña traducción. Por eso puedes pasar coordenadas en 4326 y ver cómo se dibujan correctamente sobre un mapa base en 3857, sin que aparezcan desplazadas ni deformadas.</p>
<p>Ahora bien, cada librería gestiona esto de forma un poco diferente:</p>
<ul>
<li>
<p><strong>Leaflet</strong> trabaja de forma nativa en Web Mercator y no incluye soporte interno para transformar entre sistemas distintos. Si tus datos están en otra proyección, tendrás que convertirlos antes o usar un plugin como <a href="https://github.com/kartena/Proj4Leaflet">Proj4Leaflet</a>.</p>
</li>
<li>
<p><strong>OpenLayers</strong> integra el motor <a href="https://proj4js.org/">Proj4js</a> y puede <strong>transformar coordenadas directamente</strong> entre sistemas definidos por su código EPSG. Esto la hace más flexible para trabajar con datos en diferentes proyecciones sin necesidad de preprocesarlos.</p>
</li>
<li>
<p><strong>MapLibre GL JS</strong> trabaja también en <strong>Web Mercator</strong>, pero con una particularidad: está pensada principalmente para <strong>mapas vectoriales</strong>. Su motor de renderizado asume que las coordenadas de los datos están en EPSG:4326 (grados) y las proyecta internamente a 3857 en tiempo real, aprovechando la GPU del navegador. Es decir, hace la conversión sobre la marcha de forma eficiente y transparente para ti. No incluye herramientas de reproyección entre otros sistemas, pero en la mayoría de los casos no las necesita.</p>
</li>
</ul>
<p>En resumen, las tres librerías coinciden en que <strong>Web Mercator es su lenguaje común</strong>, y todas pueden mostrar datos en 4326 sin problemas. La diferencia está en cuánta ayuda ofrecen si trabajas fuera de ese “idioma estándar”.</p>
<h2 id="en-resumen">En resumen</h2>
<p>Cuando trabajas con mapas web, tus datos siguen un camino que va desde la forma real del planeta hasta el plano del navegador:</p>
<blockquote>
<p><strong>Tierra → Geoide → Elipsoide → Coordenadas → Proyección → Mapa</strong></p>
</blockquote>
<p>Comprender esta secuencia te ayudará a evitar errores y, sobre todo, a entender qué está ocurriendo “debajo del mapa”. </p>
<p>En siguientes artículos veremos cómo llevar estos conceptos a la práctica: cómo cargar datos, elegir proyecciones y crear tus propios mapas interactivos.</p>]]></description>
        </item>
    
        <item>
            <title>Leaflet, OpenLayers y MapLibre en 2025: ¿Cuál librería elegir para iniciarte en la visualización de datos geográficos web?</title>
            <link>https://psanxiao.com/posts/2025-08-12-leaflet-openlayers-y-maplibre-en-2025.html</link>
            <guid isPermaLink="true">https://psanxiao.com/posts/2025-08-12-leaflet-openlayers-y-maplibre-en-2025.html</guid>
            <pubDate>Tue, 12 Aug 2025 00:00:00 +0000</pubDate>
            <description><![CDATA[<p>Si estás dando tus primeros pasos en el mundo de la visualización de datos geográficos en la web, es normal que te preguntes qué herramienta utilizar para crear mapas interactivos. En este artículo voy a hablar de tres librerías muy populares y con mucha comunidad detrás: <em>Leaflet, OpenLayers y MapLibre</em>. Analizaremos sus características, su comunidad de usuarios y desarrolladores, su ritmo de desarrollo y también sus licencias de software libre. Así tendrás toda la información para decidir cuál se ajusta mejor a tu proyecto y nivel.</p>
<h2 id="que-son-leaflet-openlayers-y-maplibre">¿Qué son Leaflet, OpenLayers y MapLibre?</h2>
<p>Para empezar, un poco de contexto. <a href="https://leafletjs.com">Leaflet</a> es una librería nacida en 2011 que se ha ganado la fama por ser ligera, fácil de aprender y con una curva de aprendizaje muy amigable para principiantes. Su foco principal es facilitar la creación de mapas web sencillos, con funcionalidades básicas pero muy bien diseñadas, y una amplia variedad de plugins para ampliar sus capacidades.</p>
<p>Por otro lado, <a href="https://openlayers.org">OpenLayers</a> es un proyecto más antiguo (desde 2006) y mucho más completo y robusto. Está pensado para quienes necesitan hacer proyectos GIS avanzados con análisis espacial, soporte para múltiples formatos, proyecciones y operaciones complejas sobre datos geográficos. Su curva de aprendizaje es más pronunciada, pero a cambio ofrece una potencia y flexibilidad que pocas librerías pueden igualar.</p>
<p>Finalmente, <a href="https://maplibre.org">MapLibre</a> es relativamente más nuevo y nace como un fork abierto de Mapbox GL JS. Su especialidad son los mapas vectoriales renderizados con WebGL, lo que permite crear visualizaciones muy modernas, estilizadas y con gran rendimiento para datos complejos y dinámicos. MapLibre está ganando mucha popularidad especialmente para aplicaciones que buscan estética y rendimiento en mapas web.</p>
<h2 id="comunidad-y-desarrollo-que-libreria-esta-mas-activa">Comunidad y desarrollo: ¿qué librería está más activa?</h2>
<p>Al elegir una librería para tus proyectos, es fundamental conocer qué tan activa y saludable es su comunidad de desarrolladores, así como la frecuencia con la que se actualiza el proyecto. Esto afecta la calidad del software, la rapidez en corregir errores y la disponibilidad de nuevas funcionalidades.</p>
<p>Una métrica comúnmente citada son las estrellas en GitHub, que indican popularidad, pero no siempre reflejan la actividad real de desarrollo. Por eso, también es útil mirar otros datos como el número de contribuidores activos, la frecuencia de lanzamientos y el nivel de interacción en los issues y pull requests.</p>
<p>Leaflet tiene aproximadamente 43,000 estrellas en GitHub, lo que indica una comunidad muy amplia y extendida. El número de contribuidores totales es de más de 800 en total, aunque a lo largo del tiempo no se ha mantenido constante. La frecuencia de actualizaciones es moderada, con la última versión estable importante lanzada hace más de dos años. Aunque la comunidad es grande, el ritmo de desarrollo es algo pausado en la actualidad. Hay que tener en cuenta que el modelo de Leaflet es tener un núcleo pequeño, que es el repositorio del que se muestra la gráfica, y un ecosistema grande de plugins, a través de los cuales se dota de más funcionalidad.</p>
<p><img src="./img/Leaflet-Commits-over-time.png" alt="Leaflet commits from github" style="max-width: 100%;"></p>
<p>OpenLayers cuenta con unas 13,000 estrellas en GitHub y alrededor de 400 contribuidores en total. A diferencia de Leaflet, el equipo de desarrollo lanza actualizaciones regularmente, con versiones nuevas casi cada mes. Además, la comunidad es muy técnica, lo que favorece un desarrollo continuado y la incorporación de funcionalidades complejas, muy valoradas en proyectos GIS profesionales.</p>
<p><img src="./img/Openlayers-Commits-over-time.png" alt="Openlayers commits from github" style="max-width: 100%;"></p>
<p>MapLibre, con cerca de 8,500 estrellas, es la más joven de las tres pero destaca por su crecimiento rápido. Cuenta ya con más de 500 contribuidores en total y una actividad intensa en issues y pull requests, lo que refleja una comunidad vibrante y muy involucrada en mantener la librería actualizada con las últimas tecnologías, especialmente para mapas vectoriales con WebGL.</p>
<p><img src="./img/Maplibre-Commits-over-time.png" alt="Maplibre commits from github" style="max-width: 100%;"></p>
<p>En resumen, si bien Leaflet sigue siendo la librería más popular, OpenLayers y MapLibre tienen comunidades muy activas y desarrollo frecuente, cada una con su enfoque particular.</p>
<h2 id="licencias-la-tranquilidad-de-trabajar-con-software-libre">Licencias: la tranquilidad de trabajar con software libre</h2>
<p>Otro punto que siempre preocupa a quienes empiezan y también a quienes trabajan en proyectos profesionales es la licencia del software. Las tres librerías usan licencias permisivas BSD de 2 cláusulas, lo que significa que puedes usarlas, modificarlas y distribuirlas sin muchas restricciones, incluso en proyectos comerciales. Esto ofrece una gran tranquilidad y flexibilidad para tu trabajo.</p>
<h2 id="cual-libreria-elegir">¿Cuál librería elegir?</h2>
<p>Si estás buscando algo sencillo, que te permita crear mapas rápidamente sin tener que lidiar con complejidades técnicas, Leaflet es sin duda la mejor opción para empezar. Su comunidad enorme y su ecosistema de plugins te facilitarán mucho el aprendizaje y la implementación.</p>
<p>Si tu objetivo es ir más allá y trabajar en proyectos GIS con análisis espaciales, múltiples capas y datos complejos, OpenLayers es la herramienta ideal. Su potencia y flexibilidad se reflejan en el nivel de detalle que puedes manejar en tus mapas.</p>
<p>Por último, si quieres crear mapas modernos, vectoriales y estilizados, que aprovechen WebGL para un rendimiento y estética superior, MapLibre es una opción muy interesante y actualizada, ideal para aplicaciones que buscan impactar visualmente sin perder rendimiento.</p>
<h2 id="conclusion">Conclusión</h2>
<p>En resumen, las tres librerías tienen sus puntos fuertes y la elección depende mucho del tipo de proyecto que quieras hacer y tu nivel de experiencia. Leaflet es ideal para empezar rápido y sencillo, OpenLayers para proyectos GIS más avanzados y MapLibre para quienes buscan mapas vectoriales modernos y estilizados.</p>
<p>Este artículo ofrece una visión general basada en el estado actual de las herramientas y sus comunidades, pero no debe tomarse al pie de la letra. La mejor manera de aprender y decidir es explorando y probando por ti mismo cada una de ellas, ya que cada proyecto tiene necesidades y matices únicos.</p>]]></description>
        </item>
    
        <item>
            <title>El software ya es libre</title>
            <link>https://psanxiao.com/posts/2024-10-07-el-software-ya-es-libre.html</link>
            <guid isPermaLink="true">https://psanxiao.com/posts/2024-10-07-el-software-ya-es-libre.html</guid>
            <pubDate>Mon, 07 Oct 2024 00:00:00 +0000</pubDate>
            <description><![CDATA[<p>Hoy se celebra el día mundial del software libre. Pero por si acaso no lo sabes, ya te lo digo yo, el software es libre.</p>
<p>Cuando comenzó a desarrollarse el mundo de la informática, los ordenadores eran aparatos muy caros que casi no cabían en una habitación. Un par de fabricantes se repartían el mercado de ese hardware, porque en aquel momento el software no valía nada por sí mismo. En ese momento el software era libre.</p>
<p>La cosa siguió avanzando, los ordenadores empezaron a cambiar de tamaño, cada vez eran más pequeños y su capacidad de procesamiento más grande. Llegamos a la década de los 70, aparece el PC, el personal computer, la posibilidad de llevar los ordenadores a los hogares. Al mercado del hardware empiezan a llegar más fabricantes, el software poco a poco empieza a ser compatible entre equipos y entonces se vuelve importante.</p>
<p>De repente alguien piensa que el negocio ya no está tanto en el hardware sino en el software, así que decide cogerlo y meterlo en cajas para venderlo. Junto con unos textos amplios y de tamaño de letra muy pequeña que establecen las condiciones bajo las que, una vez comprado, puedes hacer uso del mismo. Porque lo compraste, pero no es tuyo. Solo tienes una licencia de uso.</p>
<p>Desde ese momento llega una época oscura, en la que el software ya no es libre. Pero igual que en los cómics de Astérix, hay una aldea de irreductibles galos que no están dispuestos a dejarse conquistar por el imperio. Son el movimiento del Software Libre.</p>
<p>Gracias a ellos el software resistió y no todo se volvió privativo. Pero fueron tiempos difíciles, porque el imperio intentó acabar con estos valientes galos de muchas formas.</p>
<p>Finalmente, y como bien sabemos por la historia, todos los grandes imperios acaban cayendo, o cambiando. Hoy en día, esas grandes corporaciones que querían acabar con el software libre, son las que más contribuyen a su desarrollo. <em>Cosas veredes</em>.</p>
<p>Vivimos ahora en un tiempo donde el hardware está en la nube y el software se utiliza para crear servicios, que es donde está el negocio. El software dejó de ser un fin para ser un medio y, por tanto, ya puede ser libre de nuevo. No hay mal que por bien no venga.</p>
<p>Pero si otra cosa nos enseñó la historia es que, de una forma u otra, todo se repite. Cuando un imperio cae, otro ya está empezando a crecer. Por eso ahora que el software vuelve a ser libre, no podemos bajar la guardia. En nuestras manos está lograr que esta situación perdure. No venció la filosofía, sino la conveniencia, y por lo tanto este <em>statu quo</em> es aún frágil.</p>
<p>En nuestras manos está hacer ver que este modelo funciona, que se puede hacer negocio liberando el software, que puede ser igual o incluso más seguro a pesar de que el código fuente sea público, que facilita y potencia mucho más la innovación. En definitiva, que es el modelo que nunca se debió abandonar.</p>
<p>Porque el software siempre fue libre.</p>
<p>-- <br />
<em><a href="https://mancomun.gal/es/novas/el-software-ya-es-libre/">Editorial publicada en el portal Mancomún</a>.</em></p>]]></description>
        </item>
    
        <item>
            <title>La gestión colaborativa de lo público</title>
            <link>https://psanxiao.com/posts/2022-12-30-la-gestion-colaborativa-de-lo-publico.html</link>
            <guid isPermaLink="true">https://psanxiao.com/posts/2022-12-30-la-gestion-colaborativa-de-lo-publico.html</guid>
            <pubDate>Fri, 30 Dec 2022 00:00:00 +0000</pubDate>
            <description><![CDATA[<p>Vivo en un pueblo, de unos 1300 habitantes, dentro de un municipio que tiene alrededor de 35 mil. Es un pueblo pequeño, con la dispersión típica gallega, grupos de casas aislados pero cercanos entre sí. No se puede decir que haya un núcleo urbano como tal.</p>
<p>Las calles o carreteras, aquí es difícil diferenciar una cosa de la otra, no están especialmente bien mantenidas. La política de ya se harán las aceras las vecinas cuando construyan su casa, hace que las calles, o carreteras, tengan formas irregulares, que haya postes de alumbrado o telefonía en medio de los caminos, o que los tramos de acera estén inconexos. A todo eso se le suma un firme, en la mayoría de los casos en bastante mal estado.</p>
<p>No sería un sitio que animase mucho a salir a dar un paseo, salvo por la ventaja del poco tráfico, que al final hace que puedas salir y caminar casi por el medio y medio de las calles, o carreteras. Siempre le he dado vueltas a cual es el motivo de esta falta de mantenimiento de lo público, por parte de un ayuntamiento que en realidad tiene un gran presupuesto. La primera pregunta que me viene a la cabeza es... ¿son conscientes de qué esto esta así?. En realidad es una pregunta bastante ingenua, porque la respuesta es que sí lo son, obviamente. Pero me da pie a hablar de <strong>la gestión de datos cartográficos en los ayuntamientos</strong>, que es de lo que va este post en realidad.</p>
<p>Los ayuntamientos, hablando en general, no suelen hacer un gran uso de la información geográfica como apoyo a sus tareas de gestión. En muchos casos ni siquiera disponen de buenas fuentes de información. En el mejor de los casos tendrán algo de cartografía proveniente del PXOM -el Plan Xeral de Ordenación Municipal- que estará en formato CAD y será una foto fija del momento de su elaboración. Algunos ayuntamientos parten de esa información para generar, e intentar mantener actualizado, un callejero municipal, y poco más.</p>
<p>Para encontrar una fuente de información geográfica municipal el siguiente nivel es buscar en las Diputaciones, que con el encargo de realizar la <a href="https://mpt.gob.es/politica-territorial/local/coop_econom_local_estado_fondos_europeos/eiel.html">EIEL</a> -Encuesta de Infraestructuras y Equipamientos Locales- sí disponen de algo de cartografía a nivel municipal, que se va actualizando, más o menos.</p>
<p><img src="./img/eiel.png" alt="Mapa calles eiel" width="750"/></p>
<p>Buscando información sobre las calles, o carreteras, de mi pueblo, encontré la información correspondiente de la EIEL, que se puede ver en la captura del visor de mapas del Ministerio. Uno de los parámetros de información que se recoge es el estado del pavimento, así que podemos "tematizar" el mapa por él. Los resultados no encajan exactamente con mi experiencia de usuario, demasiado verde para la realidad del estado del pavimento actual. Los datos en teoría están actualizados a diciembre del 21, ya que la EIEL se realiza de forma anual. Por mi experiencia habiendo trabajado durante varios años en varias <em>"eieles"</em>, sé que actualizar todas las infraestructuras, de todos los municipios, todos los años, es inviable con los recursos que se destinan. Y en realidad hacerlo implicaría destinar una cantidad de recursos enorme. Por eso, cada año se priorizan zonas, tipos de infraestructuras, etc. Así que la realidad es que la información es fiable a medias. Habrá casos en los que concuerde con la realidad, pero otros, como el que tenemos delante, que para nada.</p>
<p>Las administraciones públicas sólo conocen, simplificando, una forma de hacer las cosas, los contratos públicos. Para eso hace falta presupuesto, y estos no son infinitos, así que hay que estar haciendo equilibrios entre gastar en una cosa, dejar de hacerlo en otra, sacar de aquí y poner allá. La colaboración ciudadana está poco o nada explorada, al menos en ciertos ámbitos. A quienes siempre habéis vivido en una ciudad, os parecerá raro el hecho de tener que hacer mantenimiento de las infraestructuras municipales, para eso está el ayuntamiento y su personal. Pero en el rural es algo más habitual. Aquí se asumen trabajos que corresponderían al ayuntamiento, pero que no hace, a veces por falta de recursos, otras simplemente por dejadez o porque el rédito electoral es bajo y no compensa. Cierto es que esta colaboración ciudadana se va perdiendo poco a poco, cada vez exigimos más, para eso pagamos nuestros impuestos. En otras épocas era más habitual que las vecinas arrimasen el hombro para tener el pueblo limpio y lustroso. Aquí en Galicia en realidad hay muchos ejemplos de gestión ciudadana de lo común, comunidades de montes, de agua, mantenimiento de caminos vecinales. Hoy en día, en muchos casos, si hay malas hierbas delante de tu casa, las quitas, a riesgo de que te invadan por una parte, y por otra, porque lo que tenemos que ver todos los días, cada vez que entramos y salimos de casa, nos gusta que esté como mínimo curioso. Pero es que los seres humanos somos así, nos gusta identificarnos y hacer nuestro aquello que nos es familiar. Hablamos de "nuestra calle", "nuestro barrio", "nuestro pueblo" o "nuestra ciudad", como si nos perteneciesen. Nos gusta sentir que somos parte de algo y si hay que contribuir lo hacemos.</p>
<p>Ese sentimiento humano en realidad está poco explotado en algunos ámbitos. En el lado de la gestión de lo público las experiencias que se han hecho aquí en España suelen ir por la recopilación de incidencias reportadas por la ciudadanía. Estos son claros ejemplos de mal uso de la tecnología. El problema no es poner una aplicación móvil a servicio de la ciudadanía para que ésta reporte farolas sin luz o baches en las calles y aceras. Eso siempre se hizo llamando al ayuntamiento o en los pueblos diciéndoselo directamente al alcalde o concejales cuando les ves. El problema y el reto está en canalizar la respuesta a esos problemas y ser capaz de solucionarlos. Pero volviendo al caso particular de la información geográfica, tenemos ya un proyecto muy consolidado de cartografía colaborativa, como es <a href="https://osm.org">OpenStreetMap</a>, que podría ser una alternativa para administraciones locales que no cuentan con muchos recursos para generar por sí mismas una cartografía propia y mantenerla. Incluso para entidades que sí los tienen, también es una opción. <a href="https://blog.mapbox.com/new-york-city-and-openstreetmap-collaborating-through-open-data-9441ebdffb24">El ayuntamiento de Nueva York experimentó con ello</a> hace años, uno de sus técnicos <a href="https://www.youtube.com/watch?v=PUw-SaGYmLs">lo explica en este vídeo</a>. ¿Por qué no seguir experimentando por esta vía?, ¿Por qué no darle una oportunidad a la cartografía colaborativa?. Para ciertas actividades de gestión es una fuente de información perfectamente válida, y complementaria a que para otros usos se siga necesitando otro tipo de cartografía más profesionalizada. </p>
<p>Por ejemplo, como ciudadano preocupado por la gestión de lo público, hice el experimento de tratar de conseguir, por mis medios, una cartografía actualizada, y más acorde con la realidad, de las calles de mi pueblo. Gracias a OSM la parte de conseguir la cartografía de las calles fue fácil, ya está ahí, libre para su uso. En su base de datos, con la etiqueta <a href="https://wiki.openstreetmap.org/wiki/Key:smoothness"><em>smoothness</em></a> podemos establecer el estado. Aunque está pensada para definir como son las vías respecto al paso de vehículos, nos vale perfectamente para clasificar el estado del pavimento, desde excelente hasta muy mal, pasando por bueno, regular o malo. Con la parte tecnológica cubierta, conseguir el mapa de esta segunda imagen, me costó un pequeño paseo para revisar alguna calle por la que no paso tan a menudo y pongamos media hora de edición delante del ordenador. </p>
<p><img src="./img/osm.png" alt="Mapa calles OSM" width="750"/></p>
<p>De esta forma es muy sencillo poder visualizar en un mapa el estado de las calles, actualizado por quien todos los días está pasando por ellas. Con una fuente de información cartográfica, que para este tipo de uso, cumple holgadamente en términos de calidad. Aquí sí, el uso de tecnología es un medio para ayudar a resolver un problema. Tecnología libre además, y datos libres, como debería de ser todo lo relacionado con la gestión de lo público. Tenemos una ciudadanía, en su mayoría, sensibilizada y comprometida con la gestión de lo público. Si por parte de las administraciones se hiciesen y se planteasen las cosas bien, con transparencia, se podrían canalizar todas esas ganas de contribuir. El reto no está en conseguir la colaboración ciudadana, sino en que quienes gestionan las administraciones entiendan este tipo de cosas.</p>
<h3 id="bonus-extra">Bonus extra</h3>
<p><a href="https://github.com/psanxiao/osm-python-maps/blob/main/streets-smoothness-map-Inhas.ipynb">Con este pequeño notebook de Jupyter</a>, se visualiza la información actualizada de las calles obteniéndola directamente de la de la base de datos de OSM, a través de la <a href="https://wiki.openstreetmap.org/wiki/Overpass_API">API de Overpass</a>. Puedes adaptarlo para hacer el ejemplo con tu pueblo, barrio, etc. De esa manera contribuirás a mejorar la base de datos de OpenStreetMap.</p>]]></description>
        </item>
    
  </channel>
</rss>