<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>JMFLORESZAZO</title>
	<atom:link href="https://jmfloreszazo.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://jmfloreszazo.com</link>
	<description>Sitio personal de Jose Mar&#237;a Flores Zazo</description>
	<lastBuildDate>Thu, 14 May 2026 08:30:33 +0000</lastBuildDate>
	<language>es</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://jmfloreszazo.com/wp-content/uploads/2022/12/cropped-favicon-1-32x32.png</url>
	<title>JMFLORESZAZO</title>
	<link>https://jmfloreszazo.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>¿HTML en vez de Markdown para hablar con un LLM? Lo que dice el tokenizador, no el marketing</title>
		<link>https://jmfloreszazo.com/html-en-vez-de-markdown-para-hablar-con-un-llm-lo-que-dice-el-tokenizador-no-el-marketing/</link>
					<comments>https://jmfloreszazo.com/html-en-vez-de-markdown-para-hablar-con-un-llm-lo-que-dice-el-tokenizador-no-el-marketing/#respond</comments>
		
		<dc:creator><![CDATA[Jose María Flores Zazo]]></dc:creator>
		<pubDate>Thu, 14 May 2026 08:21:45 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Artículos]]></category>
		<category><![CDATA[LLM]]></category>
		<category><![CDATA[Personal]]></category>
		<guid isPermaLink="false">https://jmfloreszazo.com/?p=5388</guid>

					<description><![CDATA[Últimamente circula una idea por LinkedIn, X y un par de hilos de Hacker News que conviene mirar con calma: que los LLM modernos —Claude en particular— ya no necesitan Markdown para ser eficientes, que mandar HTML "es más elegante", queda más bonito por pantalla, y que el modelo internamente "limpia el ruido" y termina consumiendo prácticamente los mismos tokens. La conclusión que se vende es tentadora: HTML gratis, mejor presentación, mismo coste.]]></description>
										<content:encoded><![CDATA[<div class="et_pb_section et_pb_section_0 et_section_regular" >
				
				
				
				
				
				
				<div class="et_pb_row et_pb_row_0">
				<div class="et_pb_column et_pb_column_4_4 et_pb_column_0  et_pb_css_mix_blend_mode_passthrough et-last-child">
				
				
				
				
				<div class="et_pb_module et_pb_text et_pb_text_0  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;">Últimamente circula una idea por LinkedIn, X y un par de hilos de Hacker News que conviene mirar con calma: que los LLM modernos —Claude en particular— ya <strong>no necesitan Markdown</strong> para ser eficientes, que mandar HTML «es más elegante», queda más bonito por pantalla, y que el modelo internamente «limpia el ruido» y termina consumiendo prácticamente los mismos tokens. La conclusión que se vende es tentadora: <strong>HTML gratis, mejor presentación, mismo coste.</strong></p>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;">No es así. Y como casi todo en este oficio, lo razonable no es opinar, sino medirlo.</p>
<h2 class="text-text-100 mt-3 -mb-1 text-&#091;1.125rem&#093; font-bold">De dónde viene esto</h2>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;">El origen del debate es real y serio. El 8 de mayo de 2026, Thariq Shihipar, <em>engineering lead</em> de Claude Code en Anthropic, publicó un artículo viral llamado <strong>«Using Claude Code: The Unreasonable Effectiveness of HTML»</strong>. Su argumento, resumido sin caricaturas, es que <strong>para outputs destinados a humanos</strong> (planes de proyecto, code reviews, comparativas visuales, dashboards efímeros) HTML aporta densidad informativa, color, tablas con jerarquía, SVG inline e interactividad que Markdown no puede dar. Y eso, dice, justifica el sobrecoste.</p>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;">Hasta ahí, opinión legítima sobre <strong>experiencia de usuario</strong>.</p>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;">El problema empieza cuando ese argumento se simplifica en el camino y acaba como <em>«Claude ya consume los mismos tokens con HTML que con Markdown»</em>. Eso es lo que se ha colado en muchos hilos, y eso es lo que es técnicamente falso.</p>
<h2 class="text-text-100 mt-3 -mb-1 text-&#091;1.125rem&#093; font-bold">La medición, antes que la teoría</h2>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;">Tomé un fragmento idéntico (una intro corta a Azure Functions, ~700 caracteres de contenido real) y lo serialicé en tres formatos. Tokenización con regex GPT-2 + estimación BPE; el número absoluto puede variar según el tokenizador exacto, pero <strong>el ratio entre formatos es robusto</strong>.</p>
<div class="overflow-x-auto w-full px-2 mb-6">
<table class="min-w-full border-collapse text-sm leading-&#091;1.7&#093; whitespace-normal">
<thead class="text-left">
<tr>
<th scope="col" class="text-text-100 border-b-0.5 border-border-300/60 py-2 pr-4 align-top font-bold">Formato</th>
<th scope="col" class="text-text-100 border-b-0.5 border-border-300/60 py-2 pr-4 align-top font-bold">Caracteres</th>
<th scope="col" class="text-text-100 border-b-0.5 border-border-300/60 py-2 pr-4 align-top font-bold">Tokens estimados</th>
<th scope="col" class="text-text-100 border-b-0.5 border-border-300/60 py-2 pr-4 align-top font-bold">vs Markdown</th>
</tr>
</thead>
<tbody>
<tr>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Markdown</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">711</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">178</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">—</td>
</tr>
<tr>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">HTML «limpio» (<code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-&#091;0.4rem&#093; px-1 py-px text-&#091;0.9rem&#093;">&lt;h1&gt;</code>, <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-&#091;0.4rem&#093; px-1 py-px text-&#091;0.9rem&#093;">&lt;ul&gt;</code>, <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-&#091;0.4rem&#093; px-1 py-px text-&#091;0.9rem&#093;">&lt;p&gt;</code>)</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">902</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">273</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">+53%</td>
</tr>
<tr>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">HTML con <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-&#091;0.4rem&#093; px-1 py-px text-&#091;0.9rem&#093;">&lt;style&gt;</code> inline</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">1.624</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">492</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">+176%</td>
</tr>
</tbody>
</table>
</div>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;">Estos números no son una sorpresa ni un invento mío:</p>
<ul class="&#091;li_&amp;&#093;:mb-0 &#091;li_&amp;&#093;:mt-1 &#091;li_&amp;&#093;:gap-1 &#091;&amp;:not(:last-child)_ul&#093;:pb-1 &#091;&amp;:not(:last-child)_ol&#093;:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3">
<li class="font-claude-response-body whitespace-normal break-words pl-2"><strong>Web2MD</strong> publicó un benchmark sobre artículos web reales: un texto que ocupa ~2.800 tokens en Markdown sube a ~8.000 en HTML. Reducción del <strong>67% al pasar HTML → Markdown</strong>.</li>
<li class="font-claude-response-body whitespace-normal break-words pl-2">Sobre 200 páginas mixtas (docs, blogs, Wikipedia, noticias), la conversión a Markdown supone una <strong>reducción media del 87,5% en tokens</strong>.</li>
<li class="font-claude-response-body whitespace-normal break-words pl-2">El propio Thariq lo admite en su post: el HTML limpio cuesta <strong>2-3x más tokens</strong> que el Markdown equivalente, y HTML con CSS y JS reales puede llegar a <strong>8-10x</strong>.</li>
</ul>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;">Es decir, el debate honesto <strong>no es</strong> «¿HTML cuesta lo mismo?». El debate es: «¿compensa pagar entre 2x y 10x más tokens a cambio de mejor experiencia visual?». Y la respuesta depende del caso.</p>
<h2 class="text-text-100 mt-3 -mb-1 text-&#091;1.125rem&#093; font-bold">Por qué la frase «Claude limpia todo y consume lo mismo» no se sostiene</h2>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;">Tres razones técnicas, ordenadas:</p>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;"><strong>1. El modelo no controla los tokens que genera.</strong> Cuando un LLM produce su respuesta, cada <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-&#091;0.4rem&#093; px-1 py-px text-&#091;0.9rem&#093;">&lt;h2&gt;</code>, cada <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-&#091;0.4rem&#093; px-1 py-px text-&#091;0.9rem&#093;">class="..."</code>, cada <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-&#091;0.4rem&#093; px-1 py-px text-&#091;0.9rem&#093;">&lt;/div&gt;</code> pasa por el tokenizador exactamente igual que el contenido. No existe un paso oculto de compresión semántica entre lo que el modelo escribe y lo que se factura. Lo que sale es lo que se cuenta.</p>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;"><strong>2. BPE no está optimizado para markup.</strong> Los tokenizadores BPE (cl100k, el de Claude, los de la familia GPT) están entrenados predominantemente sobre prosa y código. Símbolos densos como <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-&#091;0.4rem&#093; px-1 py-px text-&#091;0.9rem&#093;">&lt;</code>, <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-&#091;0.4rem&#093; px-1 py-px text-&#091;0.9rem&#093;">&gt;</code>, <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-&#091;0.4rem&#093; px-1 py-px text-&#091;0.9rem&#093;">/</code>, <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-&#091;0.4rem&#093; px-1 py-px text-&#091;0.9rem&#093;">="..."</code> suelen partirse en varios tokens. Markdown, al usar caracteres ligeros (<code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-&#091;0.4rem&#093; px-1 py-px text-&#091;0.9rem&#093;">#</code>, <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-&#091;0.4rem&#093; px-1 py-px text-&#091;0.9rem&#093;">-</code>, <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-&#091;0.4rem&#093; px-1 py-px text-&#091;0.9rem&#093;">**</code>, <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-&#091;0.4rem&#093; px-1 py-px text-&#091;0.9rem&#093;">```</code>) consume menos. No es opinión, es cómo está entrenado el vocabulario.</p>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;"><strong>3. «Limpiar el HTML antes de enviarlo» es input, no output.</strong> Sí existe una pipeline legítima <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-&#091;0.4rem&#093; px-1 py-px text-&#091;0.9rem&#093;">web → Markdown → modelo</code>, y es lo que hacen herramientas tipo <em>Jina Reader</em>, <em>Trafilatura</em> o <em>Web2MD</em>. Pero esa pipeline confirma exactamente lo contrario del bulo: el ahorro está en <strong>convertir a Markdown</strong>, no en mandar HTML «limpio».</p>
<h2 class="text-text-100 mt-3 -mb-1 text-&#091;1.125rem&#093; font-bold">Donde quizá nace la confusión: caching ≠ menos tokens</h2>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;">Anthropic, OpenAI y otros proveedores ofrecen <strong>prompt caching</strong>: si reenvías el mismo prefijo de contexto, el coste por token cacheado es menor. Esto reduce el <strong>coste en € por token</strong>, pero <strong>no el número de tokens consumidos</strong>. Son dos métricas distintas y se mezclan a menudo en los hilos divulgativos. Si alguien ha medido el gasto en factura y ha visto bajada, probablemente está viendo efecto del caché, no eficiencia del formato.</p>
<h2 class="text-text-100 mt-3 -mb-1 text-&#091;1.125rem&#093; font-bold">Calidad del output, no solo coste</h2>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;">Más allá del recibo, el formato afecta a la calidad de las respuestas. Benchmark de Web2MD con el mismo contenido en ambos formatos sobre GPT-4, Claude y Gemini:</p>
<div class="overflow-x-auto w-full px-2 mb-6">
<table class="min-w-full border-collapse text-sm leading-&#091;1.7&#093; whitespace-normal">
<thead class="text-left">
<tr>
<th scope="col" class="text-text-100 border-b-0.5 border-border-300/60 py-2 pr-4 align-top font-bold">Tarea</th>
<th scope="col" class="text-text-100 border-b-0.5 border-border-300/60 py-2 pr-4 align-top font-bold">HTML input</th>
<th scope="col" class="text-text-100 border-b-0.5 border-border-300/60 py-2 pr-4 align-top font-bold">Markdown input</th>
<th scope="col" class="text-text-100 border-b-0.5 border-border-300/60 py-2 pr-4 align-top font-bold">Mejora</th>
</tr>
</thead>
<tbody>
<tr>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Resumen</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">6.8</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">8.9</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">+31%</td>
</tr>
<tr>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Q&amp;A (precisión)</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">7.1</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">8.7</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">+23%</td>
</tr>
<tr>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Extracción de puntos clave</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">6.5</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">9.1</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">+40%</td>
</tr>
<tr>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Traducción</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">7.8</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">8.4</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">+8%</td>
</tr>
<tr>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Reescritura de contenido</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">6.2</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">8.6</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">+39%</td>
</tr>
</tbody>
</table>
</div>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;">En tablas, las representaciones Markdown superan a las HTML en precisión de extracción (60,7% vs 53,6% en evaluaciones sobre GPT). Lógica detrás de esto: el corpus de entrenamiento contiene una cantidad gigantesca de Markdown bien formado (README, wikis, docs), y muy poco HTML bien estructurado y libre de ruido.</p>
<h2 class="text-text-100 mt-3 -mb-1 text-&#091;1.125rem&#093; font-bold">¿Y con Claude Opus 4.7? Spoiler: peor</h2>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;">La objeción razonable es legítima: <em>«los benchmarks que citas son sobre GPT-4 o Claude 3.5, modelos de hace un año. ¿Sigue siendo válido con Opus 4.7?»</em>. La respuesta corta es <strong>sí, y además el problema se agrava</strong>. Veamos los datos publicados desde el lanzamiento.</p>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;">Opus 4.7 estrena tokenizer nuevo, y eso cambia el escenario por completo. La propia documentación de Anthropic lo dice sin maquillaje: <em>«This new tokenizer may use roughly 1x to 1.35x as many tokens when processing text compared to previous models (up to ~35% more, varying by content)»</em>. Es decir, <strong>el mismo texto que en Opus 4.6 ocupaba 100 tokens, en 4.7 puede ocupar hasta 135</strong>. Mismo precio por token, mismo cupo en el plan, pero más tokens por prompt.</p>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;">Los números medidos en producción son aún peores que la horquilla oficial:</p>
<div class="overflow-x-auto w-full px-2 mb-6">
<table class="min-w-full border-collapse text-sm leading-&#091;1.7&#093; whitespace-normal">
<thead class="text-left">
<tr>
<th scope="col" class="text-text-100 border-b-0.5 border-border-300/60 py-2 pr-4 align-top font-bold">Fuente</th>
<th scope="col" class="text-text-100 border-b-0.5 border-border-300/60 py-2 pr-4 align-top font-bold">Workload</th>
<th scope="col" class="text-text-100 border-b-0.5 border-border-300/60 py-2 pr-4 align-top font-bold">Inflación medida vs 4.6</th>
</tr>
</thead>
<tbody>
<tr>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Anthropic (docs oficiales)</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Genérico</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">1.0x – 1.35x</td>
</tr>
<tr>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Claude Code Camp (medición)</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Docs técnicos</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top"><strong>1.47x</strong></td>
</tr>
<tr>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Claude Code Camp (medición)</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top"><code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-&#091;0.4rem&#093; px-1 py-px text-&#091;0.9rem&#093;">CLAUDE.md</code> real</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top"><strong>1.45x</strong></td>
</tr>
<tr>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Simon Willison</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">System prompt de Opus 4.7</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top"><strong>1.46x</strong></td>
</tr>
<tr>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">OpenRouter (cohorte de migración)</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Prompts &lt;10K tokens</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top"><strong>1.42x – 1.45x</strong></td>
</tr>
<tr>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">OpenRouter (cohorte de migración)</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Prompts ≥10K tokens</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top"><strong>1.32x – 1.34x</strong></td>
</tr>
<tr>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Korchasa (análisis multi-lenguaje)</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Prosa inglés</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">+31%</td>
</tr>
<tr>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Korchasa (análisis multi-lenguaje)</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Código fuente</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">+22%</td>
</tr>
<tr>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Korchasa (análisis multi-lenguaje)</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Markdown</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">+21%</td>
</tr>
<tr>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Korchasa (análisis multi-lenguaje)</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Identificadores camelCase</td>
<td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">+51%</td>
</tr>
</tbody>
</table>
</div>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;">El análisis de Korchasa sobre 53 idiomas y 12 tipos de datos identifica el motivo: en 4.7 <strong>se han eliminado del vocabulario las cadenas largas de palabras inglesas pre-mergeadas</strong> (BPE merges) que tenía 4.6. Ahora el modelo necesita varios tokens cortos donde antes usaba uno largo. JSON, espacios en blanco y dígitos no cambian; lenguajes no latinos (CJK, árabe, hebreo, hindi, ruso) tampoco. Lo que se ha pagado son las palabras inglesas, que es justo el tipo de contenido que llena un <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-&#091;0.4rem&#093; px-1 py-px text-&#091;0.9rem&#093;">CLAUDE.md</code>, un <em>system prompt</em> o un payload HTML.</p>
<h3 class="text-text-100 mt-2 -mb-1 text-base font-bold">Por qué esto <strong>amplifica</strong> el problema con HTML</h3>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;">Aquí está la conexión con el debate del artículo. Si el tokenizer de 4.7 fragmenta más cualquier texto inglés, <strong>fragmenta todavía más el markup HTML</strong>, que ya partía con desventaja en BPE por sus símbolos densos (<code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-&#091;0.4rem&#093; px-1 py-px text-&#091;0.9rem&#093;">&lt;</code>, <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-&#091;0.4rem&#093; px-1 py-px text-&#091;0.9rem&#093;">&gt;</code>, <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-&#091;0.4rem&#093; px-1 py-px text-&#091;0.9rem&#093;">/</code>, <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-&#091;0.4rem&#093; px-1 py-px text-&#091;0.9rem&#093;">="..."</code>). El gap entre Markdown y HTML, lejos de cerrarse con el «modelo más nuevo y más inteligente», se ensancha:</p>
<ul class="&#091;li_&amp;&#093;:mb-0 &#091;li_&amp;&#093;:mt-1 &#091;li_&amp;&#093;:gap-1 &#091;&amp;:not(:last-child)_ul&#093;:pb-1 &#091;&amp;:not(:last-child)_ol&#093;:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3">
<li class="font-claude-response-body whitespace-normal break-words pl-2">En Opus 4.6, HTML semántico limpio costaba ~1.5x más tokens que su equivalente Markdown.</li>
<li class="font-claude-response-body whitespace-normal break-words pl-2">En Opus 4.7, esa misma penalización <strong>se compone con la inflación general del nuevo tokenizer</strong> (1.3x – 1.45x), porque los símbolos del markup están entre lo que peor encaja con un vocabulario al que le han quitado merges largos.</li>
</ul>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;">OpenRouter, midiendo cohortes reales de usuarios que migraron 4.6 → 4.7, reporta <strong>incrementos de coste del 12% al 27% en producción</strong> después del descuento del 90% por caching. Sin caching, la inflación de tokens es del <strong>32% al 45%</strong> sobre prompts y completaciones.</p>
<h3 class="text-text-100 mt-2 -mb-1 text-base font-bold">Lectura operativa</h3>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;">Esto refuerza, no debilita, el argumento del artículo:</p>
<ol class="&#091;li_&amp;&#093;:mb-0 &#091;li_&amp;&#093;:mt-1 &#091;li_&amp;&#093;:gap-1 &#091;&amp;:not(:last-child)_ul&#093;:pb-1 &#091;&amp;:not(:last-child)_ol&#093;:pb-1 list-decimal flex flex-col gap-1 pl-8 mb-3">
<li class="font-claude-response-body whitespace-normal break-words pl-2"><strong>Si ya estabas dudando entre MD y HTML para input a Claude, en 4.7 la balanza se inclina aún más hacia Markdown.</strong> El sobrecoste de HTML se multiplica por la inflación base del nuevo tokenizer.</li>
<li class="font-claude-response-body whitespace-normal break-words pl-2"><strong>Si tienes pipelines en producción, <em>replay</em> obligatorio antes de migrar 4.6 → 4.7.</strong> La factura no se mantiene aunque el rate card sí: la documentación oficial avisa de que <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-&#091;0.4rem&#093; px-1 py-px text-&#091;0.9rem&#093;">count_tokens</code> devolverá números distintos para el mismo contenido.</li>
<li class="font-claude-response-body whitespace-normal break-words pl-2"><strong>El caching sigue siendo la palanca principal de coste.</strong> El 90% de descuento por cache hit se mantiene en 4.7, pero el prefijo que escribes al caché es 1.3x – 1.45x más grande la primera vez. Es decir, <strong>el cold-start es más caro</strong>, aunque el régimen estacionario apenas se note.</li>
<li class="font-claude-response-body whitespace-normal break-words pl-2"><strong>camelCase paga +51% de inflación.</strong> Si tu RAG corporativo serializa nombres de variables, identificadores Java/C#, paths de Azure (<code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-&#091;0.4rem&#093; px-1 py-px text-&#091;0.9rem&#093;">Microsoft.Storage/storageAccounts</code>), o cualquier código fuente sin formato, ese 51% va directo a tu factura. Es otro argumento para <strong>limpiar el input antes de mandarlo</strong>, no para meter HTML.</li>
</ol>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;">Resumido: el tokenizador nuevo de Opus 4.7 mejora cosas (multilenguaje no latino, soporte de imágenes a alta resolución, <em>instruction-following</em> estricto en +5pp según IFEval), pero <strong>no resuelve la economía del formato</strong>. Si acaso, la acentúa. El consejo no cambia: <strong>mide con el <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-&#091;0.4rem&#093; px-1 py-px text-&#091;0.9rem&#093;">count_tokens</code> oficial del modelo concreto que vas a usar, no con heurísticas de hace un año.</strong></p>
<h2 class="text-text-100 mt-3 -mb-1 text-&#091;1.125rem&#093; font-bold">Cuándo cada uno</h2>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;">Sin dogmatismo. Cada formato tiene su sitio:</p>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;"><strong>Markdown</strong> es la elección por defecto cuando:</p>
<ul class="&#091;li_&amp;&#093;:mb-0 &#091;li_&amp;&#093;:mt-1 &#091;li_&amp;&#093;:gap-1 &#091;&amp;:not(:last-child)_ul&#093;:pb-1 &#091;&amp;:not(:last-child)_ol&#093;:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3">
<li class="font-claude-response-body whitespace-normal break-words pl-2">Cargas contexto al modelo (RAG, ingestión de documentación, <em>system prompts</em> largos).</li>
<li class="font-claude-response-body whitespace-normal break-words pl-2">El output va a otro paso de la pipeline (parser, embedding, log estructurado, otro agente).</li>
<li class="font-claude-response-body whitespace-normal break-words pl-2">El artefacto vive en Git y necesita diffs legibles en <em>pull requests</em>.</li>
<li class="font-claude-response-body whitespace-normal break-words pl-2">Estás en un escenario de alto throughput donde el coste por output domina.</li>
</ul>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;"><strong>HTML</strong> puede compensar cuando:</p>
<ul class="&#091;li_&amp;&#093;:mb-0 &#091;li_&amp;&#093;:mt-1 &#091;li_&amp;&#093;:gap-1 &#091;&amp;:not(:last-child)_ul&#093;:pb-1 &#091;&amp;:not(:last-child)_ol&#093;:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3">
<li class="font-claude-response-body whitespace-normal break-words pl-2">El consumidor final es un humano que va a leer el artefacto <strong>una sola vez</strong>.</li>
<li class="font-claude-response-body whitespace-normal break-words pl-2">Necesitas densidad visual: tablas con color, SVG inline, badges, jerarquía clara.</li>
<li class="font-claude-response-body whitespace-normal break-words pl-2">Estás en <em>Claude Artifacts</em> o un canvas donde el render aporta valor real.</li>
<li class="font-claude-response-body whitespace-normal break-words pl-2">Aceptas pagar 2-3x tokens a cambio de mejor cognición humana, y lo presupuestas.</li>
</ul>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;">Resumido en una frase: <strong>si el lector es un modelo, Markdown. Si el lector es un humano que va a leerlo una vez, HTML puede compensar.</strong></p>
<h2 class="text-text-100 mt-3 -mb-1 text-&#091;1.125rem&#093; font-bold">Aterrizando en arquitectura enterprise</h2>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;">En los escenarios que tocamos a diario —MCP en .NET y Azure, AI Gateway sobre YARP, agentes orquestados, RAG corporativo, control plane de IA con APIM— el flujo dominante es <strong>modelo dentro de pipeline</strong>, no «informe bonito para enseñar a un cliente». En ese terreno, mandar HTML para hablar con el LLM es regalar tokens sin contrapartida.</p>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;">Y en gobierno de coste de IA empresarial, esa diferencia no es cosmética: a 1.000 páginas al mes a precios actuales de Claude Sonnet, el sobrecoste por usar HTML en lugar de Markdown en la entrada se mueve en el orden de cientos de euros por pipeline. Con Opus 4.7 el cálculo es aún más sangrante: <strong>multiplica ese sobrecoste por la inflación del nuevo tokenizer</strong> y multiplícalo por número de pipelines y por entornos (dev, staging, prod). En cualquier organización con varios casos de uso en producción, esto se nota en el FinOps mensual.</p>
<h2 class="text-text-100 mt-3 -mb-1 text-&#091;1.125rem&#093; font-bold">No soy el único que lo ve así</h2>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;"><a href="https://theitsolutionist.com/2026/05/12/markup-is-the-new-markdown/" target="_blank" rel="noopener">Scott S. Nelson, en <em>Markup is the New Markdown</em> (12 mayo 2026)</a>, pone el dedo en la llaga del propio fenómeno viral:</p>
<blockquote class="ml-2 border-l-4 border-border-300/10 pl-4 text-text-300">
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;"><em>«Que se haya viralizado así dice menos del artículo en sí y más de las ganas que tenía la gente de que alguien les diera permiso para hacer lo que ya querían hacer.»</em></p>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;">En el original: <em>«The fact that it spread the way it did says less about the content and more about how hungry people are for permission to do the thing they already half-wanted to do.»</em></p>
</blockquote>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;">Es exactamente lo que ha pasado. El post de Thariq es legítimo en su acotación original —output destinado a humanos, no input a modelos— pero la versión que circula por LinkedIn ha perdido toda esa precisión por el camino. Y eso, en enterprise, cuesta dinero.</p>
<h2 class="text-text-100 mt-3 -mb-1 text-&#091;1.125rem&#093; font-bold">Conclusión</h2>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;">La intuición correcta es la que muchos teníamos: <strong>no, HTML no consume los mismos tokens que Markdown, ni se «limpia mágicamente» por el modelo ni por la tool que el modelo usa</strong>. El argumento real de Thariq es legítimo, pero acotado: pagas más tokens a cambio de mejor experiencia humana, en outputs concretos, no como regla general.</p>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;">Y con Opus 4.7, el argumento se refuerza en lugar de debilitarse. El nuevo tokenizer infla todo el texto inglés entre un 30% y un 45% real medido en producción, lo que <strong>amplifica la penalización del markup denso</strong> del HTML en lugar de mitigarla. Quien esperaba que «los modelos nuevos resuelvan el problema del formato» se ha llevado lo contrario: un tokenizer que paga más por palabra y, por composición, más todavía por etiqueta.</p>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;">Para <em>input</em> al modelo y para flujos agente-a-agente, Markdown sigue siendo objetivamente la elección correcta en 2026. Y la única forma honesta de zanjar el debate en cada caso no es leer hilos, es <strong>medir tokens</strong> con el tokenizador del modelo concreto que usas.</p>
<p class="font-claude-response-body break-words whitespace-normal leading-&#091;1.7&#093;">Antes de cambiar el formato por defecto de tu stack, pasa tu contenido real por <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-&#091;0.4rem&#093; px-1 py-px text-&#091;0.9rem&#093;">count_tokens</code> de Anthropic (o <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-&#091;0.4rem&#093; px-1 py-px text-&#091;0.9rem&#093;">tiktoken</code> de OpenAI) <strong>especificando el modelo destino</strong>, no el genérico. Probablemente te llevarás la misma sorpresa que me llevé yo.<a class="underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current" href="https://token-calculator.net/"></a></p></div>
			</div><div class="et_pb_module et_pb_divider et_pb_divider_0 et_pb_divider_position_ et_pb_space"><div class="et_pb_divider_internal"></div></div><div class="et_pb_module et_pb_text et_pb_text_1  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h2 class="text-text-100 mt-3 -mb-1 text-&#091;1.125rem&#093; font-bold">Enlaces y referencias</h2></div>
			</div><div class="et_pb_button_module_wrapper et_pb_button_0_wrapper et_pb_button_alignment_center et_pb_module ">
				<a class="et_pb_button et_pb_button_0 et_pb_bg_layout_light" href="https://github.com/jmfloreszazo/report-html-vs-markdown">Un ejemplo de Benchmark</a>
			</div><div class="et_pb_module et_pb_text et_pb_text_2  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h3 class="text-text-100 mt-2 -mb-1 text-base font-bold">El debate original</h3>
<ul class="&#091;li_&amp;&#093;:mb-0 &#091;li_&amp;&#093;:mt-1 &#091;li_&amp;&#093;:gap-1 &#091;&amp;:not(:last-child)_ul&#093;:pb-1 &#091;&amp;:not(:last-child)_ol&#093;:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3">
<li class="font-claude-response-body whitespace-normal break-words pl-2"><strong>Galería oficial de Thariq Shihipar</strong> (20 ejemplos HTML generados por Claude Code) <a class="underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current" href="https://thariqs.github.io/html-effectiveness" target="_blank" rel="noopener">https://thariqs.github.io/html-effectiveness</a></li>
<li class="font-claude-response-body whitespace-normal break-words pl-2"><strong>Simon Willison — cobertura del post de Thariq</strong> (fecha de referencia: 8 mayo 2026) <a class="underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current" href="https://simonwillison.net/2026/May/8/unreasonable-effectiveness-of-html/" target="_blank" rel="noopener">https://simonwillison.net/2026/May/8/unreasonable-effectiveness-of-html/</a></li>
<li class="font-claude-response-body whitespace-normal break-words pl-2"><strong>Scott S. Nelson — <em>Markup is the New Markdown</em></strong> (réplica matizada, 12 mayo 2026) <a class="underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current" href="https://theitsolutionist.com/2026/05/12/markup-is-the-new-markdown/" target="_blank" rel="noopener">https://theitsolutionist.com/2026/05/12/markup-is-the-new-markdown/</a></li>
<li class="font-claude-response-body whitespace-normal break-words pl-2"><strong>Kurtis Redux — <em>The Unreasonable Ineffectiveness of HTML</em></strong> (contraréplica directa) <a class="underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current" href="https://kurtis-redux.medium.com/the-unreasonable-ineffectiveness-of-html-5bd01ae1e879" target="_blank" rel="noopener">https://kurtis-redux.medium.com/the-unreasonable-ineffectiveness-of-html-5bd01ae1e879</a></li>
</ul>
<h3 class="text-text-100 mt-2 -mb-1 text-base font-bold">Datos empíricos sobre tokens, Markdown y HTML</h3>
<ul class="&#091;li_&amp;&#093;:mb-0 &#091;li_&amp;&#093;:mt-1 &#091;li_&amp;&#093;:gap-1 &#091;&amp;:not(:last-child)_ul&#093;:pb-1 &#091;&amp;:not(:last-child)_ol&#093;:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3">
<li class="font-claude-response-body whitespace-normal break-words pl-2"><strong>Web2MD — Markdown vs HTML para LLM</strong> (benchmark de calidad por tarea) <a class="underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current" href="https://web2md.org/blog/markdown-vs-html-for-llm" target="_blank" rel="noopener">https://web2md.org/blog/markdown-vs-html-for-llm</a></li>
<li class="font-claude-response-body whitespace-normal break-words pl-2"><strong>Web2MD — Guía práctica para reducir consumo de tokens</strong> (reducción media del 87,5%) <a class="underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current" href="https://web2md.org/blog/reduce-llm-token-usage-practical-guide" target="_blank" rel="noopener">https://web2md.org/blog/reduce-llm-token-usage-practical-guide</a></li>
<li class="font-claude-response-body whitespace-normal break-words pl-2"><strong>Beam.ai — HTML vs Markdown for AI Agents: Which Format Wins in 2026</strong> <a class="underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current" href="https://beam.ai/agentic-insights/html-vs-markdown-which-format-actually-makes-ai-agents-more-useful" target="_blank" rel="noopener">https://beam.ai/agentic-insights/html-vs-markdown-which-format-actually-makes-ai-agents-more-useful</a></li>
<li class="font-claude-response-body whitespace-normal break-words pl-2"><strong>ReleasePad — The Optimal Format for LLM Content Ingestion</strong> <a class="underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current" href="https://www.releasepad.io/blog/html-vs-markdown-the-optimal-format-for-llm-content-ingestion/" target="_blank" rel="noopener">https://www.releasepad.io/blog/html-vs-markdown-the-optimal-format-for-llm-content-ingestion/</a></li>
<li class="font-claude-response-body whitespace-normal break-words pl-2"><strong>SearchCans — Markdown vs HTML for LLM Context Optimization</strong> <a class="underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current" href="https://www.searchcans.com/blog/markdown-vs-html-llm-context-optimization-2026/" target="_blank" rel="noopener">https://www.searchcans.com/blog/markdown-vs-html-llm-context-optimization-2026/</a></li>
</ul>
<h3 class="text-text-100 mt-2 -mb-1 text-base font-bold">Datos específicos del tokenizer de Opus 4.7</h3>
<ul class="&#091;li_&amp;&#093;:mb-0 &#091;li_&amp;&#093;:mt-1 &#091;li_&amp;&#093;:gap-1 &#091;&amp;:not(:last-child)_ul&#093;:pb-1 &#091;&amp;:not(:last-child)_ol&#093;:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3">
<li class="font-claude-response-body whitespace-normal break-words pl-2"><strong>Anthropic — <em>What&#8217;s new in Claude Opus 4.7</em></strong> (documentación oficial: 1.0x – 1.35x) <a class="underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current" href="https://platform.claude.com/docs/en/about-claude/models/whats-new-claude-4-7" target="_blank" rel="noopener">https://platform.claude.com/docs/en/about-claude/models/whats-new-claude-4-7</a></li>
<li class="font-claude-response-body whitespace-normal break-words pl-2"><strong>Simon Willison — Claude Token Counter con comparativa 4.6 vs 4.7</strong> (medido: 1.46x) <a class="underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current" href="https://simonwillison.net/2026/apr/20/claude-token-counts/" target="_blank" rel="noopener">https://simonwillison.net/2026/apr/20/claude-token-counts/</a></li>
<li class="font-claude-response-body whitespace-normal break-words pl-2"><strong>Claude Code Camp — <em>I Measured Claude 4.7&#8217;s New Tokenizer</em></strong> (medido: 1.45x – 1.47x) <a class="underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current" href="https://www.claudecodecamp.com/p/i-measured-claude-4-7-s-new-tokenizer-here-s-what-it-costs-you" target="_blank" rel="noopener">https://www.claudecodecamp.com/p/i-measured-claude-4-7-s-new-tokenizer-here-s-what-it-costs-you</a></li>
<li class="font-claude-response-body whitespace-normal break-words pl-2"><strong>OpenRouter — <em>Opus 4.7&#8217;s New Tokenizer: What It Actually Costs</em></strong> (cohorte real de migración) <a class="underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current" href="https://openrouter.ai/announcements/opus-47-tokenizer-analysis" target="_blank" rel="noopener">https://openrouter.ai/announcements/opus-47-tokenizer-analysis</a></li>
<li class="font-claude-response-body whitespace-normal break-words pl-2"><strong>Korchasa — <em>opus 4.7: tokenizer changes and rising costs</em></strong> (análisis sobre 53 idiomas y 12 tipos de datos) <a class="underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current" href="https://korchasa.dev/posts/2026_04_17_opus_4_7_tokenizer/" target="_blank" rel="noopener">https://korchasa.dev/posts/2026_04_17_opus_4_7_tokenizer/</a></li>
<li class="font-claude-response-body whitespace-normal break-words pl-2"><strong>Finout — <em>Claude Opus 4.7 Pricing: The Real Cost Story</em></strong> (análisis FinOps del impacto real) <a class="underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current" href="https://www.finout.io/blog/claude-opus-4.7-pricing-the-real-cost-story-behind-the-unchanged-price-tag" target="_blank" rel="noopener">https://www.finout.io/blog/claude-opus-4.7-pricing-the-real-cost-story-behind-the-unchanged-price-tag</a></li>
</ul>
<h3 class="text-text-100 mt-2 -mb-1 text-base font-bold">Herramientas para medir tú mismo</h3>
<ul class="&#091;li_&amp;&#093;:mb-0 &#091;li_&amp;&#093;:mt-1 &#091;li_&amp;&#093;:gap-1 &#091;&amp;:not(:last-child)_ul&#093;:pb-1 &#091;&amp;:not(:last-child)_ol&#093;:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3">
<li class="font-claude-response-body whitespace-normal break-words pl-2"><strong>Anthropic — Endpoint oficial <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-&#091;0.4rem&#093; px-1 py-px text-&#091;0.9rem&#093;">count_tokens</code></strong> (gratuito, sin inferencia) <a class="underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current" href="https://docs.claude.com/en/api/messages-count-tokens" target="_blank" rel="noopener">https://docs.claude.com/en/api/messages-count-tokens</a></li>
<li class="font-claude-response-body whitespace-normal break-words pl-2"><strong>OpenAI — <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-&#091;0.4rem&#093; px-1 py-px text-&#091;0.9rem&#093;">tiktoken</code></strong> (proxy razonable para BPE genérico) <a class="underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current" href="https://github.com/openai/tiktoken" target="_blank" rel="noopener">https://github.com/openai/tiktoken</a></li>
<li class="font-claude-response-body whitespace-normal break-words pl-2"><strong>Token Calculator multi-modelo</strong> (GPT-5.5, Claude Opus 4.7, Gemini 3.1 Pro) <a class="underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current" href="https://token-calculator.net/" target="_blank" rel="noopener">https://token-calculator.net/</a></li>
</ul></div>
			</div>
			</div>
				
				
				
				
			</div>
				
				
			</div>
]]></content:encoded>
					
					<wfw:commentRss>https://jmfloreszazo.com/html-en-vez-de-markdown-para-hablar-con-un-llm-lo-que-dice-el-tokenizador-no-el-marketing/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Cloud soberana, Azure Local e IA: no es volver al datacenter, es recuperar control operativo</title>
		<link>https://jmfloreszazo.com/cloud-soberana-azure-local-e-ia-no-es-volver-al-datacenter-es-recuperar-control-operativo/</link>
					<comments>https://jmfloreszazo.com/cloud-soberana-azure-local-e-ia-no-es-volver-al-datacenter-es-recuperar-control-operativo/#respond</comments>
		
		<dc:creator><![CDATA[Jose María Flores Zazo]]></dc:creator>
		<pubDate>Sat, 09 May 2026 14:22:03 +0000</pubDate>
				<category><![CDATA[Artículos]]></category>
		<category><![CDATA[Desarrollo]]></category>
		<category><![CDATA[Arquitectura]]></category>
		<category><![CDATA[Azure]]></category>
		<category><![CDATA[Software Craftsmanship]]></category>
		<guid isPermaLink="false">https://jmfloreszazo.com/?p=5382</guid>

					<description><![CDATA[La cloud soberana no es una región, ni una landing zone, ni un contrato. Es una arquitectura de control. Azure Local, Microsoft 365 Local, Foundry Local y los modelos de DevOps soberano apuntan a una misma dirección: permitir que las organizaciones reguladas mantengan continuidad, gobierno y trazabilidad sobre sus cargas críticas, incluso en escenarios de desconexión o incertidumbre geopolítica.]]></description>
										<content:encoded><![CDATA[
<div class="et_pb_section et_pb_section_1 et_section_regular" >
				
				
				
				
				
				
				<div class="et_pb_row et_pb_row_1">
				<div class="et_pb_column et_pb_column_4_4 et_pb_column_1  et_pb_css_mix_blend_mode_passthrough et-last-child">
				
				
				
				
				<div class="et_pb_module et_pb_text et_pb_text_3  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p data-start="316" data-end="468">Durante años hemos vendido la nube como una conversación casi binaria: o estás en cloud o estás on-premises. Para mí, ese debate ya se ha quedado corto.</p>
<p data-start="470" data-end="628">La cuestión real ya no es “cloud sí” o “cloud no”. La cuestión relevante para gobiernos, banca, salud, industria, defensa o infraestructuras críticas es otra:</p>
<p data-start="630" data-end="744"><strong data-start="630" data-end="744">¿Qué nivel de control operativo, jurídico, técnico y de continuidad necesito sobre mis sistemas más sensibles?</strong></p>
<p data-start="746" data-end="961">Y aquí es donde empieza a ponerse interesante la conversación sobre <strong data-start="814" data-end="832">cloud soberana</strong>, <strong data-start="834" data-end="849">Azure Local</strong>, <strong data-start="851" data-end="880">operaciones desconectadas</strong>, <strong data-start="882" data-end="905">Microsoft 365 Local</strong>, <strong data-start="907" data-end="924">Foundry Local</strong> y modelos de DevOps más controlados.</p>
<p data-start="963" data-end="1174">Mi tesis es sencilla: <strong data-start="985" data-end="1174">la soberanía digital no consiste en sacar todo de la nube pública. Consiste en diseñar una arquitectura capaz de demostrar control cuando las condiciones normales dejan de ser normales.</strong></p>
<p data-start="1176" data-end="1857"><a href="https://learn.microsoft.com/en-us/azure/azure-sovereign-clouds/microsoft-sovereign-cloud" target="_blank" rel="noopener" title="https://learn.microsoft.com/en-us/azure/azure-sovereign-clouds/microsoft-sovereign-cloud"><span style="text-decoration: underline;">Microsoft ya presenta su estrategia de Sovereign Cloud</span></a> en tres modelos: nube pública soberana, nube privada soberana y nubes nacionales operadas con socios locales. La propia documentación de Microsoft describe la Sovereign Public Cloud como Azure/Microsoft cloud con controles añadidos de residencia, claves gestionadas por cliente, transparencia operativa, Data Guardian, External Key Management y logs resistentes a manipulación; y define la Sovereign Private Cloud como un modelo ejecutado en datacenters controlados por el cliente o por socios, basado en Azure Local y Microsoft 365 Local, orientado a escenarios híbridos o desconectados. <span class="" data-state="closed"></span></p>
<h2 data-section-id="qotlya" data-start="1859" data-end="1902">Soberanía no es solo residencia de datos</h2>
<p data-start="1904" data-end="2031">Uno de los errores habituales es reducir la soberanía a “mis datos están en Europa” o “mis datos están en una región concreta”.</p>
<p data-start="2033" data-end="2071">Eso es importante, pero no suficiente.</p>
<p data-start="2073" data-end="2110">La soberanía real tiene varias capas:</p>
<p data-start="2112" data-end="2218">Primero, <strong data-start="2121" data-end="2143">soberanía del dato</strong>: dónde reside, quién lo cifra, quién puede acceder y bajo qué condiciones.</p>
<p data-start="2220" data-end="2371">Segundo, <strong data-start="2229" data-end="2252">soberanía operativa</strong>: quién administra la plataforma, qué accesos excepcionales existen, cómo se auditan y si puedo demostrar trazabilidad.</p>
<p data-start="2373" data-end="2562">Tercero, <strong data-start="2382" data-end="2407">soberanía tecnológica</strong>: si mis cargas críticas pueden seguir funcionando aunque pierda conectividad, aunque haya una crisis contractual, regulatoria, geopolítica o de proveedor.</p>
<p data-start="2564" data-end="2777">Cuarto, <strong data-start="2572" data-end="2616">soberanía del ciclo de vida del software</strong>: dónde vive el código, dónde se ejecutan los pipelines, dónde están los secretos, quién puede modificar la cadena de suministro y cómo se evidencia cada cambio.</p>
<p data-start="2779" data-end="3096">Para mí, esta última parte se olvida demasiado. En empresas reguladas, el código fuente, los pipelines y los artefactos de despliegue son tan sensibles como los datos de producción. No tiene sentido proteger obsesivamente una base de datos si luego el camino que permite cambiar el software está débilmente gobernado.</p>
<h2 data-section-id="1ln5k0y" data-start="3098" data-end="3177">Azure Local: la nube híbrida empieza a parecerse a una póliza de continuidad</h2>
<p data-start="3179" data-end="3584">Azure Local es especialmente relevante porque deja de plantear el datacenter como un “mundo antiguo” separado de la nube. Microsoft lo describe como una solución de infraestructura distribuida para ejecutar máquinas virtuales, contenedores y determinados servicios Azure en infraestructura propiedad del cliente; además, Azure Stack HCI forma ya parte de Azure Local. <span class="" data-state="closed"></span></p>
<p data-start="3586" data-end="3646">La diferencia importante no es solo técnica. Es estratégica.</p>
<p data-start="3648" data-end="4107"><a href="https://azure.microsoft.com/en-us/products/local" target="_blank" rel="noopener" title="https://azure.microsoft.com/en-us/products/local"><span style="text-decoration: underline;">Azure Local</span></a> puede convertirse en la base para una arquitectura donde ciertas cargas sigan beneficiándose del modelo cloud, pero con más control sobre ubicación, operación y continuidad. En la documentación de Sovereign Private Cloud, Microsoft posiciona Azure Local como la capa base de infraestructura para workloads soberanos, incluyendo cómputo, almacenamiento, red, lifecycle management, VMs y AKS habilitado por Arc. <span class="" data-state="closed"></span></p>
<p data-start="4109" data-end="4380">Esto no significa que Azure Local sea “todo Azure dentro de tu CPD”. Esa lectura sería equivocada y peligrosa. Significa algo más concreto: <strong data-start="4249" data-end="4380">una base local, gestionable con experiencia cloud, sobre la que construir escenarios regulados, híbridos, edge o desconectados.</strong></p>
<p data-start="4382" data-end="4553">Y aquí está el matiz que a mí me parece clave: <a href="https://learn.microsoft.com/en-us/azure/azure-sovereign-clouds/private/overview/sovereign-private-cloud" target="_blank" rel="noopener" title="https://learn.microsoft.com/en-us/azure/azure-sovereign-clouds/private/overview/sovereign-private-cloud"><span style="text-decoration: underline;">Azure Local no debería venderse como nostalgia on-premises. Debería entenderse como <strong data-start="4513" data-end="4552">resiliencia arquitectónica soberana</strong>.</span></a></p>
<h2 data-section-id="1o3dn1v" data-start="4555" data-end="4604">Operaciones desconectadas: útil, pero no magia</h2>
<p data-start="4606" data-end="5078">Las<a href="https://learn.microsoft.com/es-es/azure/azure-local/manage/disconnected-operations-overview?view=azloc-2604" target="_blank" rel="noopener" title="https://learn.microsoft.com/es-es/azure/azure-local/manage/disconnected-operations-overview?view=azloc-2604"> <span style="text-decoration: underline;">operaciones desconectadas</span></a> son probablemente una de las piezas más importantes para sectores regulados. La documentación pública indica que permiten desplegar y administrar instancias de Azure Local sin conexión a la nube pública de Azure, usando un plano de control local y una experiencia familiar de portal y CLI para máquinas virtuales y aplicaciones contenedorizadas mediante servicios seleccionados habilitados por Azure Arc. <span class="" data-state="closed"></span></p>
<p data-start="5080" data-end="5131">Esto es muy potente, pero hay que aterrizarlo bien.</p>
<p data-start="5133" data-end="5309">Desconectado no significa “sin gobierno”.<br data-start="5174" data-end="5177" />Desconectado no significa “sin parches”.<br data-start="5217" data-end="5220" />Desconectado no significa “sin identidad”.<br data-start="5262" data-end="5265" />Desconectado no significa “sin operación”.</p>
<p data-start="5311" data-end="5584">De hecho, cuanto más aislado está un entorno, más disciplina necesita: gestión de versiones, procedimientos de actualización offline, control de identidades locales, backups, observabilidad, evidencias, runbooks, pruebas de conmutación, restauración y validación periódica.</p>
<p data-start="5586" data-end="5742">La cloud soberana no elimina la complejidad. La mueve al terreno donde los arquitectos debemos estar cómodos: <strong data-start="5696" data-end="5741">diseño de riesgos, controles y evidencias</strong>.</p>
<h2 data-section-id="1wys6ql" data-start="5744" data-end="5804">Microsoft 365 Local: útil, pero no confundamos el alcance</h2>
<p data-start="5806" data-end="6267"><span style="text-decoration: underline;"><a href="https://learn.microsoft.com/en-us/azure/azure-sovereign-clouds/private/m365-local/microsoft-365-local-overview" target="_blank" rel="noopener" title="Microsoft 365 Local">Microsoft 365 Local</a></span> es otra pieza interesante, pero conviene explicarla con precisión. Según Microsoft Learn, Microsoft 365 Local permite ejecutar <strong data-start="5953" data-end="6019">Exchange Server, SharePoint Server y Skype for Business Server</strong> sobre infraestructura Azure Local propiedad y gestionada por el cliente, con mayor control de residencia, acceso y cumplimiento. También se indica que soporta escenarios híbridos y completamente desconectados. <span class="" data-state="closed"></span></p>
<p data-start="6269" data-end="6379">Esto es importante para sectores que necesitan productividad local o continuidad en escenarios de aislamiento.</p>
<p data-start="6381" data-end="6533">Pero hay que evitar una confusión: <strong data-start="6416" data-end="6532">Microsoft 365 Local no debe interpretarse automáticamente como “todo Microsoft 365 cloud funcionando localmente”</strong>.</p>
<p data-start="6535" data-end="6895">No estamos hablando, al menos con la información pública actual, de replicar sin más toda la experiencia SaaS moderna de Microsoft 365, Teams, Copilot, Entra ID, Conditional Access y ecosistema cloud en un datacenter local. Es una solución con un alcance concreto, útil para determinados escenarios, pero que debe explicarse sin crear expectativas incorrectas.</p>
<p data-start="6897" data-end="7001">En entornos regulados, vender mal el alcance de una plataforma es casi tan peligroso como diseñarla mal.</p>
<h2 data-section-id="joc744" data-start="7003" data-end="7056">IA soberana: el dato no siempre puede ir al modelo</h2>
<p data-start="7058" data-end="7180">La IA introduce una nueva tensión. Antes nos preocupaba dónde estaba la base de datos. Ahora también debemos preguntarnos:</p>
<p data-start="7182" data-end="7430">¿Dónde se ejecuta la inferencia?<br data-start="7214" data-end="7217" />¿Dónde se procesan los embeddings?<br data-start="7251" data-end="7254" />¿Dónde se guardan los prompts?<br data-start="7284" data-end="7287" />¿Dónde se evalúan los modelos?<br data-start="7317" data-end="7320" />¿Dónde quedan las trazas?<br data-start="7345" data-end="7348" />¿Qué datos pasan por una cadena RAG?<br data-start="7384" data-end="7387" />¿Qué agente puede actuar sobre qué sistema?</p>
<p data-start="7432" data-end="7833">Microsoft ya documenta <a href="https://learn.microsoft.com/en-us/azure/azure-sovereign-clouds/private/foundry-local/deploy-run-first-model?tabs=kubectl" target="_blank" rel="noopener" title="https://learn.microsoft.com/en-us/azure/azure-sovereign-clouds/private/foundry-local/deploy-run-first-model?tabs=kubectl"><span style="text-decoration: underline;">Foundry Local</span></a> sobre Azure Local como una vía para acercar la IA al dato, desplegando y ejecutando modelos dentro del entorno Azure Local. La documentación pública indica que Foundry Local está en preview, que puede desplegarse sobre Kubernetes conectado a Azure Arc o Kubernetes directo, y que requiere acceso de preview para el despliegue. <span class="" data-state="closed"></span></p>
<p data-start="7835" data-end="7996">Para mí, esto apunta a una idea importante: <strong data-start="7879" data-end="7995">la IA empresarial no se va a decidir solo por el modelo más potente, sino por el modelo operativo más gobernable</strong>.</p>
<p data-start="7998" data-end="8314">En banca, salud, administración pública o industria, muchas cargas de IA no podrán moverse alegremente a cualquier endpoint global. Necesitaremos arquitecturas donde los datos sensibles se queden cerca de su dominio de control, donde la inferencia tenga trazabilidad y donde los agentes no puedan actuar sin límites.</p>
<p data-start="8316" data-end="8589">Aquí conecto con algo que llevo tiempo defendiendo: la IA empresarial no puede ser solo productividad individual. Tiene que evolucionar hacia un modelo gobernado de ciclo de vida: intención, especificación viva, controles, ADRs, guardrails, testing, despliegue y auditoría.</p>
<h2 data-section-id="15n487t" data-start="8591" data-end="8641">DevOps soberano: el código también es soberanía</h2>
<p data-start="8643" data-end="8725">Hay un punto que creo que va a ganar peso: <strong data-start="8686" data-end="8724">la soberanía del software delivery</strong>.</p>
<p data-start="8727" data-end="9144">GitHub Enterprise Server ya existe como versión self-hosted de GitHub. <span style="text-decoration: underline;"><a href="https://docs.github.com/en/enterprise-server%403.14/admin/overview/about-github-enterprise-server" target="_blank" rel="noopener" title="https://docs.github.com/en/enterprise-server%403.14/admin/overview/about-github-enterprise-server">La documentación oficial de GitHub lo describe como una plataforma autohospedada</a></span> que se ejecuta en infraestructura propia, con controles de acceso, seguridad, red, IAM, monitorización y VPN definidos por la empresa. También deja claro que el administrador es responsable de mantener la instancia actualizada. <span class="" data-state="closed"></span></p>
<p data-start="9146" data-end="9163">Esto no es menor.</p>
<p data-start="9165" data-end="9283">Si una organización necesita soberanía extrema, no basta con tener producción controlada. Tiene que controlar también:</p>
<ul data-start="9285" data-end="9493">
<li data-section-id="f5yvml" data-start="9285" data-end="9300">repositorios;</li>
<li data-section-id="1owjvck" data-start="9301" data-end="9317">pull requests;</li>
<li data-section-id="8zg2jd" data-start="9318" data-end="9329">secretos;</li>
<li data-section-id="1jjhkg2" data-start="9330" data-end="9342">pipelines;</li>
<li data-section-id="tcnfb1" data-start="9343" data-end="9356">artefactos;</li>
<li data-section-id="e7r8dw" data-start="9357" data-end="9379">políticas de branch;</li>
<li data-section-id="rpelww" data-start="9380" data-end="9405">evidencias de revisión;</li>
<li data-section-id="5psgwj" data-start="9406" data-end="9414">SBOMs;</li>
<li data-section-id="1qxzhwe" data-start="9415" data-end="9438">firmas de artefactos;</li>
<li data-section-id="18gjvuh" data-start="9439" data-end="9465">trazabilidad de cambios;</li>
<li data-section-id="1y6jso1" data-start="9466" data-end="9493">dependencias de terceros.</li>
</ul>
<p data-start="366" data-end="676">Mi punto aquí es claro: GitHub Enterprise Server encaja muy bien conceptualmente en una estrategia de soberanía del software delivery. Pero en arquitectura no deberíamos vender intuiciones como certezas. Hasta que no exista documentación suficiente o una validación real en un entorno controlado, lo responsable es tratarlo como una opción prometedora, no como una capacidad cerrada y universalmente aplicable.</p>
<h2 data-section-id="4gt4gw" data-start="10005" data-end="10078">La nube soberana no es una tecnología: es una arquitectura de decisión</h2>
<p data-start="10080" data-end="10155">Para mí, el mayor error sería convertir esto en otro catálogo de productos.</p>
<p data-start="10157" data-end="10414">Azure Local, Microsoft 365 Local, Foundry Local, Confidential Computing, External Key Management, Data Guardian, Sovereign Landing Zone o Regulated Environment Management son piezas. Algunas ya están disponibles, otras evolucionan, y todas requieren diseño.</p>
<p data-start="10416" data-end="10852">La <span style="text-decoration: underline;"><a href="https://learn.microsoft.com/en-us/azure/azure-sovereign-clouds/public/overview-sovereign-landing-zone?tabs=hubspoke" target="_blank" rel="noopener" title="Sovereign Landing Zone">Sovereign Landing Zone</a></span>, por ejemplo, se plantea como un enfoque de policy-as-code para aplicar controles de localización, cifrado, configuración y computación confidencial en entornos soberanos. Microsoft también documenta REM como una experiencia para configurar, desplegar y monitorizar workloads en soporte de operaciones soberanas, complementando EKM, Data Guardian y Confidential Computing. <span class="" data-state="closed"></span></p>
<p data-start="10854" data-end="10928">Pero ninguna landing zone sustituye a una conversación seria sobre riesgo.</p>
<p data-start="10930" data-end="11063">La pregunta correcta no es: “¿Qué producto compro?”<br data-start="10981" data-end="10984" />La pregunta correcta es: “¿Qué escenarios de pérdida de control debo resistir?”</p>
<p data-start="11065" data-end="11077">Por ejemplo:</p>
<p data-start="11079" data-end="11498">¿Qué pasa si pierdo conectividad durante días?<br data-start="11125" data-end="11128" />¿Qué pasa si un proveedor externo no puede operar mi entorno?<br data-start="11189" data-end="11192" />¿Qué pasa si una carga crítica debe moverse a ejecución local?<br data-start="11254" data-end="11257" />¿Qué pasa si necesito demostrar que ningún operador no autorizado accedió a datos sensibles?<br data-start="11349" data-end="11352" />¿Qué pasa si mi pipeline de software queda comprometido?<br data-start="11408" data-end="11411" />¿Qué pasa si un modelo de IA procesa información regulada fuera del perímetro aceptado?</p>
<p data-start="11500" data-end="11620">Ahí es donde un arquitecto aporta valor. No dibujando cajas, sino convirtiendo incertidumbre en decisiones verificables.</p>
<h2 data-section-id="1geay1" data-start="11622" data-end="11642">Mi punto de vista</h2>
<p data-start="11644" data-end="11810">Mi lectura es que estamos entrando en una etapa donde la cloud híbrida deja de ser una solución de transición y pasa a ser una <strong data-start="11771" data-end="11809">estrategia de resiliencia soberana</strong>.</p>
<p data-start="11812" data-end="12055">Durante años, muchas organizaciones han visto lo híbrido como una deuda: “todavía no hemos migrado todo”. Creo que eso cambia. En los próximos años, lo híbrido bien diseñado puede convertirse en una ventaja competitiva para sectores regulados.</p>
<p data-start="12057" data-end="12114">Pero tiene que ser híbrido de verdad, no un Frankenstein.</p>
<p data-start="12116" data-end="12413">Tiene que tener identidad pensada.<br data-start="12150" data-end="12153" />Tiene que tener red pensada.<br data-start="12181" data-end="12184" />Tiene que tener observabilidad pensada.<br data-start="12223" data-end="12226" />Tiene que tener actualizaciones pensadas.<br data-start="12267" data-end="12270" />Tiene que tener DevSecOps pensado.<br data-start="12304" data-end="12307" />Tiene que tener IA pensada.<br data-start="12334" data-end="12337" />Tiene que tener pruebas reales de desconexión.<br data-start="12383" data-end="12386" />Tiene que tener evidencias.</p>
<p data-start="12415" data-end="12690">La cloud soberana no debería ser una excusa para volver a modelos artesanales, opacos y difíciles de operar. Debería ser justo lo contrario: <strong data-start="12556" data-end="12689">llevar la disciplina cloud, la automatización, el gobierno y la trazabilidad a los entornos donde el control local es obligatorio</strong>.</p>
<h2 data-section-id="8dqre" data-start="12692" data-end="12705">Conclusión</h2>
<p data-start="12707" data-end="12828">No creo que el futuro sea abandonar la nube pública. Tampoco creo que el futuro sea confiar ciegamente en ella para todo.</p>
<p data-start="12830" data-end="12867">Creo que el futuro será más matizado:</p>
<ul data-start="12869" data-end="13268">
<li data-section-id="1plu2h3" data-start="12869" data-end="12936">cloud pública para innovación, elasticidad y servicios avanzados;</li>
<li data-section-id="1mj7mbz" data-start="12937" data-end="13009">cloud soberana pública para cargas reguladas con controles reforzados;</li>
<li data-section-id="82ckf6" data-start="13010" data-end="13108">Azure Local y modelos privados para continuidad, edge, defensa, industria y escenarios críticos;</li>
<li data-section-id="1ib2iqa" data-start="13109" data-end="13184">IA local o híbrida cuando el dato, la latencia o la regulación lo exijan;</li>
<li data-section-id="ijzh7y" data-start="13185" data-end="13268">DevOps soberano cuando el código y la cadena de suministro sean activos críticos.</li>
</ul>
<p data-start="13270" data-end="13394">La soberanía digital no es un botón.<br data-start="13306" data-end="13309" />No es una región.<br data-start="13326" data-end="13329" />No es un contrato.<br data-start="13347" data-end="13350" />No es una landing zone.<br data-start="13373" data-end="13376" />No es un producto.</p>
<p data-start="13396" data-end="13456"><strong data-start="13396" data-end="13456">Es una arquitectura de control, continuidad y evidencia.</strong></p>
<p data-start="13458" data-end="13559">Y esa, sinceramente, es una conversación donde los arquitectos tenemos que estar mucho más presentes.</p></div>
			</div>
			</div>
				
				
				
				
			</div>
				
				
			</div>
]]></content:encoded>
					
					<wfw:commentRss>https://jmfloreszazo.com/cloud-soberana-azure-local-e-ia-no-es-volver-al-datacenter-es-recuperar-control-operativo/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Azure Subscription Snapshot: entiende tu Azure en minutos</title>
		<link>https://jmfloreszazo.com/azure-subscription-snapshot-entiende-tu-azure-en-minutos/</link>
					<comments>https://jmfloreszazo.com/azure-subscription-snapshot-entiende-tu-azure-en-minutos/#respond</comments>
		
		<dc:creator><![CDATA[Jose María Flores Zazo]]></dc:creator>
		<pubDate>Mon, 27 Apr 2026 06:35:01 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Artículos]]></category>
		<category><![CDATA[Desarrollo]]></category>
		<category><![CDATA[Arquitectura]]></category>
		<category><![CDATA[Azure]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Software Craftsmanship]]></category>
		<guid isPermaLink="false">https://jmfloreszazo.com/?p=5373</guid>

					<description><![CDATA[Heredar una suscripcion de Azure suele empezar con una pregunta incomoda: ¿qué hay aquí? y ¿qué se puede romper si toco algo?. Azure Subscription Snapshot nace para responder justo eso, sin magia negra y sin depender de memoria tribal. Ejecutas un comando, esperas a que termine, y obtienes una fotografia completa, ordenada y reutilizable de tu entorno.]]></description>
										<content:encoded><![CDATA[
<div class="et_pb_section et_pb_section_2 et_section_regular" >
				
				
				
				
				
				
				<div class="et_pb_row et_pb_row_2">
				<div class="et_pb_column et_pb_column_4_4 et_pb_column_2  et_pb_css_mix_blend_mode_passthrough et-last-child">
				
				
				
				
				<div class="et_pb_module et_pb_text et_pb_text_4  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><div>
<div><strong>No todas las cosas que te encuentres tienen IaC.</strong></div>
<p></p>
<div><span>Heredar una suscripcion de Azure suele empezar con una pregunta incomoda: </span><span><strong>¿qué hay aquí? y ¿qué se puede romper si toco algo?</strong></span><span>. <span style="color: #0000ff;"><a href="https://github.com/jmfloreszazo/azure-subscription-snapshot" target="_blank" rel="noopener" title="Azure Subscription Snapshot" style="color: #0000ff;"><strong>Azure Subscription Snapshot</strong></a></span> nace para responder justo eso, sin magia negra y sin depender de memoria tribal. Ejecutas un comando, esperas a que termine, y obtienes una fotografia completa, ordenada y reutilizable de tu entorno.</span></div>
<p></p>
<div><span>El proyecto crea un snapshot determinista de una suscripcion o de un resource group: inventario, configuracion, RBAC, identidades, politicas, locks, diagnosticos, despliegues, red, servicios y actividad reciente. Todo queda en JSON estable, con claves ordenadas, para que puedas guardarlo en Git y comparar cambios reales entre ejecuciones. Si nada cambio en Azure, los archivos tampoco cambian.</span></div>
<p></p>
<div><span>La potencia aparece cuando dejas de ver el dump como una simple exportacion. Azure Subscription Snapshot genera un grafo de relaciones (</span><span>`nodes.json`</span><span> y </span><span>`edges.json`</span><span>) para analizar dependencias, detectar recursos huerfanos o entender el radio de impacto de una caida. Tambien produce diagramas Mermaid de arquitectura, red, identidad y tipos de recursos, ademas de un reporte HTML autonomo que puedes abrir y compartir sin servidores ni herramientas externas.</span></div>
<p></p>
<div><span>Para equipos que trabajan con IA, el proyecto ya viene preparado para Copilot Chat y Azure MCP. Los prompts y modos especializados permiten hacer preguntas utiles sobre datos reales: quien tiene Owner, que recursos estan expuestos, que cambio en los ultimos 90 dias, donde puede haber gasto innecesario, o que Bicep inicial se puede reconstruir desde el estado capturado. El agente lee primero los archivos locales y solo consulta Azure cuando necesita verificar o ampliar informacion.</span></div>
<p></p>
<div><span>Eso lo hace especialmente valioso en<strong> escenarios de traspaso, auditoria, seguridad, gobierno y operacion diaria</strong>. En vez de empezar con diez pestañas del portal y varias consultas sueltas, empiezas con una base de conocimiento versionable, auditable y facil de razonar. Puedes crear una linea base hoy, repetir el snapshot la semana que viene y ver el drift con claridad: recursos nuevos, permisos cambiados, politicas modificadas o configuraciones que se desviaron.</span></div>
<p></p>
<div><span>En resumen: <strong>Azure Subscription Snapshot convierte una suscripcion Azure en evidencia navegable</strong>. Te da contexto rapido, reduce incertidumbre, ayuda a priorizar riesgos y permite que humanos y agentes de IA trabajen sobre los mismos hechos. Es una herramienta pequeña en la forma, pero muy potente en el efecto: pasar de «no se que hay» a «se exactamente por donde empezar».</span></div>
</div></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_0">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">az login
az account set --subscription &quot;&lt;sub-id-or-name&gt;&quot;
./Invoke-AzureSnapshot.ps1 -OutputDir .\out -GenerateReport</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_5  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><div>
<div><span>Al terminar, abre </span><span>`out/12-report/report.html`</span><span> y explora el inventario, los diagramas y las relaciones.</span></div>
</div></div>
			</div>
			</div>
				
				
				
				
			</div>
				
				
			</div>
]]></content:encoded>
					
					<wfw:commentRss>https://jmfloreszazo.com/azure-subscription-snapshot-entiende-tu-azure-en-minutos/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>MCP en sistemas enterprise: cómo diseñar, gobernar y escalar en .NET y Azure (I)</title>
		<link>https://jmfloreszazo.com/mcp-en-sistemas-enterprise-como-disenar-gobernar-y-escalar-en-net-y-azure-i/</link>
					<comments>https://jmfloreszazo.com/mcp-en-sistemas-enterprise-como-disenar-gobernar-y-escalar-en-net-y-azure-i/#respond</comments>
		
		<dc:creator><![CDATA[Jose María Flores Zazo]]></dc:creator>
		<pubDate>Fri, 17 Apr 2026 08:13:44 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Artículos]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[Arquitectura]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[MCP]]></category>
		<category><![CDATA[Software Craftsmanship]]></category>
		<guid isPermaLink="false">https://jmfloreszazo.com/?p=5348</guid>

					<description><![CDATA[Parte I — Fundamentos, arquitectura limpia y estrategia de testing]]></description>
										<content:encoded><![CDATA[
<div class="et_pb_section et_pb_section_3 et_section_regular" >
				
				
				
				
				
				
				<div class="et_pb_row et_pb_row_3">
				<div class="et_pb_column et_pb_column_4_4 et_pb_column_3  et_pb_css_mix_blend_mode_passthrough et-last-child">
				
				
				
				
				<div class="et_pb_module et_pb_text et_pb_text_6  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h5 data-section-id="13b5f1q" data-start="173" data-end="188" style="text-align: center;"><span>Fundamentos, arquitectura limpia y estrategia de testing</span></h5>
<h2 data-section-id="13b5f1q" data-start="173" data-end="188"></h2>
<h2 data-section-id="13b5f1q" data-start="173" data-end="188">Introducción</h2>
<p data-start="190" data-end="350">Model Context Protocol (MCP) está emergiendo como el estándar de facto para conectar agentes de IA con capacidades reales de sistemas: APIs, datos, operaciones.</p>
<p data-start="352" data-end="427">El problema es que está entrando en proyectos <strong data-start="398" data-end="426">por la puerta equivocada</strong>.</p>
<p data-start="429" data-end="454">Estoy viendo equipos que:</p>
<ul data-start="456" data-end="615">
<li data-section-id="fs9u5z" data-start="456" data-end="494">diseñan directamente “un MCP Server”</li>
<li data-section-id="2274f4" data-start="495" data-end="525">meten lógica dentro de tools</li>
<li data-section-id="1x3sikp" data-start="526" data-end="555">acoplan dominio a protocolo</li>
<li data-section-id="1fs50hd" data-start="556" data-end="615">y luego se preguntan por qué no pueden testear ni escalar</li>
</ul>
<p data-start="617" data-end="646">Este artículo va a ser claro:</p>
<blockquote data-start="648" data-end="697">
<p data-start="650" data-end="697"><strong data-start="650" data-end="697">MCP no es tu arquitectura. Es un adaptador.</strong></p>
</blockquote>
<p data-start="699" data-end="755">Y si no lo tratas como tal, vas a construir algo frágil.</p></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_7  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h2 data-section-id="ysfqk" data-start="762" data-end="819">1. Qué es MCP (desde el punto de vista arquitectónico)</h2>
<p data-start="821" data-end="844">MCP define un contrato:</p>
<ul data-start="846" data-end="947">
<li data-section-id="18f0kmk" data-start="846" data-end="876">tools (acciones ejecutables)</li>
<li data-section-id="x40uvx" data-start="877" data-end="896">resources (datos)</li>
<li data-section-id="quw7ky" data-start="897" data-end="919">prompts (plantillas)</li>
<li data-section-id="1p8x7vk" data-start="920" data-end="947">comunicación vía JSON-RPC</li>
</ul>
<p data-start="949" data-end="1031">Especificación oficial:<br data-start="972" data-end="975" /><a data-start="975" data-end="1031" class="decorated-link" href="https://modelcontextprotocol.io/specification/2025-03-26" target="_blank" rel="noopener">https://modelcontextprotocol.io/specification/2025-03-26<span aria-hidden="true" class="ms-0.5 inline-block align-middle leading-none"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" aria-hidden="true" data-rtl-flip="" class="block h-&#091;0.75em&#093; w-&#091;0.75em&#093; stroke-current stroke-&#091;0.75&#093;"><use href="/cdn/assets/sprites-core-a066ed1a.svg#304883" fill="currentColor"></use></svg></span></a></p>
<p data-start="1033" data-end="1093">En .NET, ya puedes montar servidores MCP sobre ASP.NET Core:</p>
<ul data-start="1095" data-end="1166">
<li data-section-id="co3vro" data-start="1095" data-end="1105">SDK C#</li>
<li data-section-id="og9z4k" data-start="1106" data-end="1142">integración con HTTP / streaming</li>
<li data-section-id="10ub5vz" data-start="1143" data-end="1166">despliegue en Azure</li>
</ul>
<p data-start="1168" data-end="1281">Ejemplo oficial de Microsoft:<br data-start="1197" data-end="1200" /><a data-start="1200" data-end="1281" rel="noopener" target="_blank" class="decorated-link" href="https://learn.microsoft.com/en-us/azure/container-apps/tutorial-mcp-server-dotnet">https://learn.microsoft.com/en-us/azure/container-apps/tutorial-mcp-server-dotnet</a></p></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_8  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h3 data-section-id="zypvqe" data-start="1288" data-end="1305">Lo importante</h3>
<p data-start="1307" data-end="1311">MCP:</p>
<ul data-start="1313" data-end="1371">
<li data-section-id="1v1mkkv" data-start="1313" data-end="1337"><strong data-start="1315" data-end="1337">expone capacidades</strong></li>
<li data-section-id="1d1ytax" data-start="1338" data-end="1371"><strong data-start="1340" data-end="1371">no define cómo construirlas</strong></li>
</ul></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_9  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h2 data-section-id="n74wa4" data-start="1378" data-end="1415">2. El anti-patrón que debes evitar</h2>
<p data-start="1417" data-end="1452">Este diseño aparece constantemente:</p></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_1">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">MCP Tool
 ├── l&oacute;gica de negocio
 ├── validaciones
 ├── llamadas a Azure
 ├── composici&oacute;n de respuesta
 └── retorno al cliente</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_10  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p data-start="1593" data-end="1603">Problemas:</p>
<ul data-start="1605" data-end="1740">
<li data-section-id="1e1li34" data-start="1605" data-end="1627">acoplamiento extremo</li>
<li data-section-id="16in8nf" data-start="1628" data-end="1662">imposible testear en aislamiento</li>
<li data-section-id="49pu6t" data-start="1663" data-end="1684">difícil evolucionar</li>
<li data-section-id="17txd5v" data-start="1685" data-end="1702">no reutilizable</li>
<li data-section-id="1uivnkw" data-start="1703" data-end="1740">sin separación de responsabilidades</li>
</ul>
<p data-start="1742" data-end="1757">Esto no escala.</p></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_11  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h2 data-section-id="1cbmj5x" data-start="1764" data-end="1824">3. Patrón correcto: Clean Architecture + MCP como adapter</h2>
<p data-start="1826" data-end="1863">La única forma sostenible es separar:</p></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_2">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">[MCP Adapter]
      │
[Application Layer]
      │
[Domain]
      │
[Infrastructure]</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_12  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h3 data-section-id="1yzffdy" data-start="1962" data-end="1977">Regla clave</h3>
<blockquote data-start="1979" data-end="2045">
<p data-start="1981" data-end="2045"><strong data-start="1981" data-end="2045">Cada tool MCP debe ser un wrapper fino sobre un caso de uso.</strong></p>
</blockquote>
<p data-start="2047" data-end="2074">Ejemplo conceptual en .NET:</p></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_3">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">public class GetCostTool
{
    private readonly IGetCostUseCase _useCase;

    public async Task&lt;GetCostResult&gt; Execute(GetCostInput input)
    {
        return await _useCase.Execute(input);
    }
}</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_13  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p>Nada de lógica en el tool.</p></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_14  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h2 data-section-id="dqenm6" data-start="2324" data-end="2370">4. Por qué no debes diseñar “directo a MCP”</h2>
<p data-start="2372" data-end="2407">Hay razones técnicas, no opiniones.</p>
<h3 data-section-id="lg3za9" data-start="2409" data-end="2452">4.1 El protocolo aún está evolucionando</h3>
<p data-start="2454" data-end="2484">El propio roadmap lo reconoce:</p>
<ul data-start="2486" data-end="2558">
<li data-section-id="1k3gyb8" data-start="2486" data-end="2514">retos en transporte remoto</li>
<li data-section-id="13ih1s1" data-start="2515" data-end="2525">balanceo</li>
<li data-section-id="m5jdig" data-start="2526" data-end="2547">operación stateless</li>
<li data-section-id="19y3fhv" data-start="2548" data-end="2558">sesiones</li>
</ul>
<p>Información adicional: <a data-start="2560" data-end="2611" rel="noopener" target="_blank" class="decorated-link" href="https://modelcontextprotocol.io/development/roadmap">https://modelcontextprotocol.io/development/roadmap</a></p></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_15  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h3 data-section-id="1l91ky" data-start="2618" data-end="2661">4.2 MCP no cubre necesidades operativas</h3>
<p data-start="2663" data-end="2675">No resuelve:</p>
<ul data-start="2677" data-end="2748">
<li data-section-id="16ra8e9" data-start="2677" data-end="2692">health checks</li>
<li data-section-id="17mmyjn" data-start="2693" data-end="2718">observabilidad estándar</li>
<li data-section-id="jktwko" data-start="2719" data-end="2748">control de errores avanzado</li>
</ul>
<p data-start="2750" data-end="2803">De hecho, Microsoft recomienda endpoints adicionales:</p></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_4">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">/mcp
/health</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_16  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p>Ir al tutorial: <a href="https://learn.microsoft.com/en-us/azure/container-apps/tutorial-mcp-server-dotnet" target="_blank" rel="noopener">https://learn.microsoft.com/en-us/azure/container-apps/tutorial-mcp-server-dotnet</a></p></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_17  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h3 data-section-id="1p18kvt" data-start="2919" data-end="2955">4.3 Te ata a un canal de entrada</h3>
<p data-start="2957" data-end="2984">Si todo está dentro de MCP:</p>
<ul data-start="2986" data-end="3087">
<li data-section-id="gl61hi" data-start="2986" data-end="3021">no puedes exponer REST fácilmente</li>
<li data-section-id="1mfms63" data-start="3022" data-end="3051">no puedes reutilizar lógica</li>
<li data-section-id="f1glg9" data-start="3052" data-end="3087">no puedes integrar otros sistemas</li>
</ul></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_18  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h2 data-section-id="nyl4gk" data-start="3094" data-end="3153">5. Testing: separar lo determinista de lo probabilístico</h2>
<p data-start="3155" data-end="3196">Aquí está la clave que suele confundirse.</p></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_19  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h2 data-section-id="1boxugx" data-start="3203" data-end="3250">5.1 Qué puedes testear de forma determinista</h2>
<h3 data-section-id="gakrfd" data-start="3252" data-end="3276"></h3>
<h3 data-section-id="gakrfd" data-start="3252" data-end="3276">Dominio y aplicación</h3>
<ul data-start="3278" data-end="3324">
<li data-section-id="110e03q" data-start="3278" data-end="3304">tests unitarios clásicos</li>
<li data-section-id="1ot6rfm" data-start="3305" data-end="3314">sin MCP</li>
<li data-section-id="1ot6qs1" data-start="3315" data-end="3324">sin LLM</li>
</ul>
<p data-start="3326" data-end="3345">100% determinista</p>
<h3 data-section-id="wsk6n3" data-start="3352" data-end="3380">Adaptador MCP (contrato)</h3>
<p data-start="3382" data-end="3397">Puedes validar:</p>
<ul data-start="3399" data-end="3478">
<li data-section-id="daoezo" data-start="3399" data-end="3416">nombre del tool</li>
<li data-section-id="10afxak" data-start="3417" data-end="3443">schema de entrada/salida</li>
<li data-section-id="1wxi9r4" data-start="3444" data-end="3468">mapping a casos de uso</li>
<li data-section-id="1banih2" data-start="3469" data-end="3478">errores</li>
</ul>
<h3 data-section-id="3ddkvv" data-start="3485" data-end="3501">Endpoint MCP</h3>
<p data-start="3503" data-end="3518">Puedes testear:</p>
<ul data-start="3520" data-end="3592">
<li data-section-id="m8ef71" data-start="3520" data-end="3539">JSON-RPC correcto</li>
<li data-section-id="1uuh273" data-start="3540" data-end="3565">estructura de respuesta</li>
<li data-section-id="1rb2q8m" data-start="3566" data-end="3580">códigos HTTP</li>
<li data-section-id="1td4pn8" data-start="3581" data-end="3592">streaming</li>
</ul>
<p data-start="3594" data-end="3640">El protocolo está definido para permitir esto: <a data-start="3642" data-end="3698" rel="noopener" target="_blank" class="decorated-link" href="https://modelcontextprotocol.io/specification/2025-03-26">https://modelcontextprotocol.io/specification/2025-03-26</a></p></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_20  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h2 data-section-id="1pczqdt" data-start="3705" data-end="3734">5.2 Qué NO es determinista</h2>
<p data-start="3736" data-end="3768">La respuesta final de un agente:</p></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_5">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">Usuario &rarr; LLM &rarr; decide tool &rarr; ejecuta &rarr; responde</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_21  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p data-start="3832" data-end="3844">Aquí entran:</p>
<ul data-start="3846" data-end="3894">
<li data-section-id="xkxsz" data-start="3846" data-end="3869">decisiones del modelo</li>
<li data-section-id="1pzbdrs" data-start="3870" data-end="3880">contexto</li>
<li data-section-id="s1c49s" data-start="3881" data-end="3894">temperatura</li>
</ul>
<p data-start="3896" data-end="3927">Esto no es testing clásico. Es:</p>
<ul data-start="3929" data-end="3969">
<li data-section-id="n146ad" data-start="3929" data-end="3941">evaluación</li>
<li data-section-id="1hqprrm" data-start="3942" data-end="3952">métricas</li>
<li data-section-id="1r1nit5" data-start="3953" data-end="3969">observabilidad</li>
</ul></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_22  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h2 data-section-id="1y8lxd8" data-start="3976" data-end="4020">5.3 ¿Necesitas una API REST para testear?</h2>
<p data-start="4022" data-end="4025">No.</p>
<p data-start="4027" data-end="4059">Puedes testear directamente MCP:</p>
<ul data-start="4061" data-end="4119">
<li data-section-id="byuewv" data-start="4061" data-end="4085">llamadas HTTP JSON-RPC</li>
<li data-section-id="5vm8vq" data-start="4086" data-end="4119">herramientas como MCP Inspector</li>
</ul>
<p data-start="4121" data-end="4173"><a data-start="4121" data-end="4173" rel="noopener" target="_blank" class="decorated-link" href="https://modelcontextprotocol.io/docs/tools/inspector">https://modelcontextprotocol.io/docs/tools/inspector<span aria-hidden="true" class="ms-0.5 inline-block align-middle leading-none"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" aria-hidden="true" data-rtl-flip="" class="block h-&#091;0.75em&#093; w-&#091;0.75em&#093; stroke-current stroke-&#091;0.75&#093;"><use href="/cdn/assets/sprites-core-a066ed1a.svg#304883" fill="currentColor"></use></svg></span></a></p>
<p data-start="4175" data-end="4209">REST es útil, pero no obligatorio.</p></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_23  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h2 data-section-id="1hu291f" data-start="4216" data-end="4253">6. Diseño de un MCP Server en .NET</h2>
<p data-start="4255" data-end="4268">Stack típico:</p>
<ul data-start="4270" data-end="4339">
<li data-section-id="o2m6yo" data-start="4270" data-end="4284">ASP.NET Core</li>
<li data-section-id="4y6hs6" data-start="4285" data-end="4320"><code data-start="4287" data-end="4320">ModelContextProtocol.AspNetCore</code></li>
<li data-section-id="1li502i" data-start="4321" data-end="4339">hosting en Azure</li>
</ul>
<p data-start="4341" data-end="4364">Opciones de despliegue:</p>
<ul data-start="4366" data-end="4441">
<li data-section-id="xedcuc" data-start="4366" data-end="4390">Azure Container Apps</li>
<li data-section-id="1c5g4dp" data-start="4391" data-end="4412">Azure App Service</li>
<li data-section-id="1rwamq6" data-start="4413" data-end="4441">Azure Kubernetes Service</li>
</ul>
<p data-start="4443" data-end="4546">Referencia oficial: <a data-start="4465" data-end="4546" rel="noopener" target="_blank" class="decorated-link" href="https://learn.microsoft.com/en-us/azure/container-apps/tutorial-mcp-server-dotnet">https://learn.microsoft.com/en-us/azure/container-apps/tutorial-mcp-server-dotnet</a></p></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_24  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p><strong>Endpoints recomendados</strong></p></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_6">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">/mcp      &rarr; protocolo MCP
/health   &rarr; health checks</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_25  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p>Nunca uses <code data-start="4657" data-end="4663">/mcp</code> como health endpoint.</p>
<p><em>Nota del autor: siento repetir esto, pero cuando lo ves, lo explicas 1 vez, 2 veces, &#8230;, pues insistes otra vez.</em></p></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_26  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h2 data-section-id="ne886i" data-start="4692" data-end="4728">7. Problemas reales en producción</h2>
<p data-start="4730" data-end="4778">Cuando sales de local aparecen problemas serios.</p>
<p data-start="4730" data-end="4778">
<h3 data-section-id="151e1hk" data-start="4785" data-end="4803">7.1 Transporte</h3>
<ul data-start="4805" data-end="4876">
<li data-section-id="11c6bqk" data-start="4805" data-end="4821">streaming HTTP</li>
<li data-section-id="1h9csy1" data-start="4822" data-end="4853">proxies que rompen conexiones</li>
<li data-section-id="vfo7mc" data-start="4854" data-end="4876">buffering inesperado</li>
</ul>
<h3 data-section-id="lzbwf3" data-start="4883" data-end="4899">7.2 Escalado</h3>
<ul data-start="4901" data-end="4974">
<li data-section-id="1yodasq" data-start="4901" data-end="4922">sesiones implícitas</li>
<li data-section-id="96fdoc" data-start="4923" data-end="4952">balanceadores no preparados</li>
<li data-section-id="hpyuje" data-start="4953" data-end="4974">pérdida de contexto</li>
</ul>
<h3 data-section-id="mvhfpu" data-start="4981" data-end="4998">7.3 Seguridad</h3>
<ul data-start="5000" data-end="5059">
<li data-section-id="1k3pfvv" data-start="5000" data-end="5015">autenticación</li>
<li data-section-id="qg0kzx" data-start="5016" data-end="5037">exposición de tools</li>
<li data-section-id="4fbmnc" data-start="5038" data-end="5059">ejecución indirecta</li>
</ul>
<h3 data-section-id="ywf2hm" data-start="5066" data-end="5088">7.4 Observabilidad</h3>
<ul data-start="5090" data-end="5149">
<li data-section-id="vw7ui" data-start="5090" data-end="5120">correlación entre tool calls</li>
<li data-section-id="6qf398" data-start="5121" data-end="5149">trazabilidad de decisiones</li>
</ul></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_27  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h2 data-section-id="rvb1ba" data-start="5156" data-end="5194">8. Insight clave</h2>
<p data-start="5196" data-end="5236">
<p data-start="5196" data-end="5236">Este es el punto que debes interiorizar:</p>
<blockquote data-start="5238" data-end="5316">
<p data-start="5240" data-end="5316"><strong data-start="5240" data-end="5316">MCP introduce ejecución remota de capacidades controladas por un modelo.</strong></p>
</blockquote>
<p data-start="5318" data-end="5343">Eso cambia completamente:</p>
<ul data-start="5345" data-end="5420">
<li data-section-id="129inpa" data-start="5345" data-end="5369">el modelo de seguridad</li>
<li data-section-id="17oliae" data-start="5370" data-end="5392">el modelo de testing</li>
<li data-section-id="1rzaiam" data-start="5393" data-end="5420">el modelo de arquitectura</li>
</ul></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_28  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h2 data-section-id="hyb4sr" data-start="5427" data-end="5451">9. Patrón recomendado</h2>
<h3 data-section-id="19055r5" data-start="5453" data-end="5479"></h3>
<h3 data-section-id="19055r5" data-start="5453" data-end="5479">Estructura de solución</h3></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_7">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">/src
 ├── Domain
 ├── Application
 ├── Infrastructure
 ├── Adapters.Mcp
 └── Adapters.Rest (opcional)</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_29  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h3 data-section-id="wt4swc" data-start="5601" data-end="5615">Principios</h3>
<ul data-start="5617" data-end="5740">
<li data-section-id="n04216" data-start="5617" data-end="5641">MCP = adapter, no core</li>
<li data-section-id="z11mv9" data-start="5642" data-end="5665">dominio independiente</li>
<li data-section-id="1lha3if" data-start="5666" data-end="5691">casos de uso testeables</li>
<li data-section-id="dz7fjd" data-start="5692" data-end="5721">infraestructura desacoplada</li>
<li data-section-id="782ww4" data-start="5722" data-end="5740">tools sin lógica</li>
</ul></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_30  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h2 data-section-id="1q5yeol" data-start="5747" data-end="5764">Para terminar</h2>
<p data-start="5766" data-end="5827">
<p data-start="5766" data-end="5827">MCP es una pieza clave en la arquitectura de sistemas con IA.</p>
<p data-start="5829" data-end="5851">Pero no es el sistema.</p>
<p data-start="5853" data-end="5894">Si diseñas directamente alrededor de MCP:</p>
<ul data-start="5896" data-end="5969">
<li data-section-id="fh4ato" data-start="5896" data-end="5918">pierdes testabilidad</li>
<li data-section-id="7ubl03" data-start="5919" data-end="5936">pierdes control</li>
<li data-section-id="1aqt94v" data-start="5937" data-end="5969">pierdes capacidad de evolución</li>
</ul>
<p data-start="5971" data-end="5996">Si lo usas correctamente:</p>
<ul data-start="5998" data-end="6093">
<li data-section-id="l0d7fh" data-start="5998" data-end="6035">ganas interoperabilidad con agentes</li>
<li data-section-id="1ebju6s" data-start="6036" data-end="6067">mantienes arquitectura sólida</li>
<li data-section-id="1q34e4j" data-start="6068" data-end="6093">puedes escalar en Azure</li>
</ul>
<blockquote>
<p><strong>No construyas un MCP Server.</strong><br data-start="6148" data-end="6151" /><strong>Construye capacidades bien diseñadas y expónlas vía MCP.</strong></p>
</blockquote></div>
			</div><div class="et_pb_module et_pb_divider et_pb_divider_1 et_pb_divider_position_ et_pb_space"><div class="et_pb_divider_internal"></div></div><div class="et_pb_module et_pb_text et_pb_text_31  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p><strong>Nota del autor</strong></p>
<p>Una observación recurrente en proyectos recientes es que muchas de las malas prácticas en torno a MCP aparecen en desarrollos realizados en Python.</p>
<p>Es importante matizar que esto <strong>no es un problema del lenguaje en sí</strong> —aunque, como cualquier stack, tiene sus limitaciones—, sino principalmente una cuestión de <strong>falta de disciplina arquitectónica</strong>.</p>
<p>En muchos casos se detecta:</p>
<ul>
<li>
<p>ausencia de separación clara entre dominio, aplicación e infraestructura</p>
</li>
<li>
<p>lógica de negocio incrustada en handlers o tools</p>
</li>
<li>
<p>falta de contratos explícitos</p>
</li>
<li>
<p>baja testabilidad desde el diseño</p>
</li>
<li>falta de testing (derivado de lo anterior)</li>
</ul>
<p>Python facilita iterar rápido, pero también facilita <strong>saltarse estructuras formales</strong> si no se imponen desde el inicio. En entornos MCP esto se agrava, porque el protocolo introduce una capa adicional de complejidad (ejecución indirecta, tools, agentes, etc.).</p>
<p>La conclusión no es “evitar Python”, sino:</p>
<blockquote>
<p><strong>sin un modelo de arquitectura limpia, cualquier lenguaje deriva rápidamente en sistemas difíciles de mantener, testear y escalar.</strong></p>
</blockquote>
<p>En contextos enterprise —especialmente cuando se integran agentes y MCP—, la disciplina arquitectónica deja de ser opcional.</p></div>
			</div>
			</div>
				
				
				
				
			</div>
				
				
			</div>
]]></content:encoded>
					
					<wfw:commentRss>https://jmfloreszazo.com/mcp-en-sistemas-enterprise-como-disenar-gobernar-y-escalar-en-net-y-azure-i/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>MCP en sistemas enterprise: cómo diseñar, gobernar y escalar en .NET y Azure (II)</title>
		<link>https://jmfloreszazo.com/mcp-en-sistemas-enterprise-como-disenar-gobernar-y-escalar-en-net-y-azure-ii/</link>
					<comments>https://jmfloreszazo.com/mcp-en-sistemas-enterprise-como-disenar-gobernar-y-escalar-en-net-y-azure-ii/#respond</comments>
		
		<dc:creator><![CDATA[Jose María Flores Zazo]]></dc:creator>
		<pubDate>Fri, 17 Apr 2026 08:13:31 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Artículos]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[Arquitectura]]></category>
		<category><![CDATA[Azure]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[MCP]]></category>
		<category><![CDATA[Software Craftsmanship]]></category>
		<guid isPermaLink="false">https://jmfloreszazo.com/?p=5364</guid>

					<description><![CDATA[Parte II — Control plane de IA: APIM, seguridad y escalado real en Azure]]></description>
										<content:encoded><![CDATA[
<div class="et_pb_section et_pb_section_4 et_section_regular" >
				
				
				
				
				
				
				<div class="et_pb_row et_pb_row_4">
				<div class="et_pb_column et_pb_column_4_4 et_pb_column_4  et_pb_css_mix_blend_mode_passthrough et-last-child">
				
				
				
				
				<div class="et_pb_module et_pb_text et_pb_text_32  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h5 data-section-id="13b5f1q" data-start="393" data-end="408" style="text-align: center;">Control plane de IA: APIM, seguridad y escalado real en Azure</h5>
<h2 data-section-id="13b5f1q" data-start="393" data-end="408"></h2>
<h2 data-section-id="13b5f1q" data-start="393" data-end="408">Introducción</h2>
<p data-start="410" data-end="466">En la primera parte hemos dejado claro algo fundamental:</p>
<blockquote data-start="468" data-end="513">
<p data-start="470" data-end="513">MCP no es tu arquitectura. Es un adaptador.</p>
</blockquote>
<p data-start="515" data-end="550">Pero eso abre una pregunta crítica:</p>
<blockquote data-start="552" data-end="622">
<p data-start="554" data-end="622">¿Qué controla realmente lo que un agente puede hacer cuando usa MCP?</p>
</blockquote>
<p data-start="624" data-end="656">Porque aquí cambia el paradigma.</p>
<p data-start="658" data-end="676">Ya no hablamos de:</p>
<ul data-start="677" data-end="750">
<li data-section-id="1bjbv8n" data-start="677" data-end="705">APIs invocadas por humanos</li>
<li data-section-id="vdri8k" data-start="706" data-end="750">o sistemas integrándose de forma explícita</li>
</ul>
<p data-start="752" data-end="770">Ahora hablamos de:</p>
<blockquote data-start="772" data-end="852">
<p data-start="774" data-end="852"><strong data-start="774" data-end="852">un modelo tomando decisiones y ejecutando capacidades reales en tu sistema</strong></p>
</blockquote>
<p data-start="854" data-end="892">Y eso tiene implicaciones directas en:</p>
<ul data-start="894" data-end="940">
<li data-section-id="1th9z4m" data-start="894" data-end="905">seguridad</li>
<li data-section-id="8f75h9" data-start="906" data-end="916">gobierno</li>
<li data-section-id="16c0c2u" data-start="917" data-end="924">coste</li>
<li data-section-id="1559vby" data-start="925" data-end="940">escalabilidad</li>
</ul>
<p data-start="942" data-end="973">La respuesta en Azure es clara:</p>
<blockquote data-start="975" data-end="1027">
<p data-start="977" data-end="1027"><strong data-start="977" data-end="1027">API Management como AI Gateway (control plane)</strong></p>
</blockquote></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_33  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h2 data-section-id="189aect" data-start="1034" data-end="1101">1. El problema real: MCP sin control es una superficie de ataque</h2>
<p data-start="1103" data-end="1140">Cuando expones MCP, estás exponiendo:</p>
<ul data-start="1142" data-end="1199">
<li data-section-id="1yg3hbm" data-start="1142" data-end="1161">tools ejecutables</li>
<li data-section-id="1hwjqu4" data-start="1162" data-end="1178">acceso a datos</li>
<li data-section-id="dh7r2o" data-start="1179" data-end="1199">operaciones reales</li>
</ul>
<p data-start="1201" data-end="1241">Pero además introduces un intermediario:</p></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_8">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">Usuario &rarr; LLM &rarr; decide &rarr; ejecuta tool &rarr; sistema</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_34  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p data-start="1304" data-end="1331">Esto genera nuevos riesgos:</p>
<h3 data-section-id="86nnd6" data-start="1333" data-end="1356">1. Prompt Injection</h3>
<p data-start="1357" data-end="1430">El modelo recibe instrucciones maliciosas y ejecuta acciones no deseadas.</p>
<h3 data-section-id="kqmbh2" data-start="1432" data-end="1450">2. Tool Misuse</h3>
<p data-start="1451" data-end="1504">Uso indebido de tools válidas pero fuera de contexto.</p>
<h3 data-section-id="xza69a" data-start="1506" data-end="1530">3. Data Exfiltration</h3>
<p data-start="1531" data-end="1561">Extracción de datos vía tools.</p>
<h3 data-section-id="1u1325e" data-start="1563" data-end="1584">4. Cost Explosion</h3>
<p data-start="1585" data-end="1649">Uso descontrolado de tokens o ejecución de operaciones costosas.</p></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_35  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h3 data-section-id="539oxp" data-start="1656" data-end="1673">Insight clave</h3>
<blockquote data-start="1675" data-end="1752">
<p data-start="1677" data-end="1752">MCP convierte un problema de input en un problema de ejecución distribuida.</p>
</blockquote></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_36  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h2 data-section-id="aeoj9n" data-start="1759" data-end="1807">2. APIM como AI Gateway (control plane de IA)</h2>
<p data-start="1809" data-end="1858">Microsoft introduce explícitamente este concepto:</p>
<p data-start="1860" data-end="1944"><a data-start="1863" data-end="1944" rel="noopener" target="_blank" class="decorated-link" href="https://learn.microsoft.com/en-us/azure/api-management/genai-gateway-capabilities">https://learn.microsoft.com/en-us/azure/api-management/genai-gateway-capabilities<span aria-hidden="true" class="ms-0.5 inline-block align-middle leading-none"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" aria-hidden="true" data-rtl-flip="" class="block h-&#091;0.75em&#093; w-&#091;0.75em&#093; stroke-current stroke-&#091;0.75&#093;"><use href="/cdn/assets/sprites-core-a066ed1a.svg#304883" fill="currentColor"></use></svg></span></a></p>
<p data-start="1946" data-end="1996">APIM deja de ser solo un API Gateway y pasa a ser:</p>
<blockquote data-start="1998" data-end="2059">
<p data-start="2000" data-end="2059"><strong data-start="2000" data-end="2059">un punto de control para modelos, agentes y MCP servers</strong></p>
</blockquote></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_37  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h2 data-section-id="1dzg1o9" data-start="2066" data-end="2115">3. Qué aporta realmente APIM en escenarios MCP</h2>
<p data-start="2117" data-end="2155">No es solo “poner un gateway delante”.</p></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_38  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h2 data-section-id="8kmokt" data-start="2162" data-end="2202">3.1 Control de acceso y autenticación</h2>
<ul data-start="2204" data-end="2276">
<li data-section-id="jawx7i" data-start="2204" data-end="2230">integración con Entra ID</li>
<li data-section-id="ut48yq" data-start="2231" data-end="2251">OAuth para agentes</li>
<li data-section-id="fj92or" data-start="2252" data-end="2276">control por consumidor</li>
</ul>
<p data-start="2278" data-end="2290">Sin esto:</p>
<ul data-start="2292" data-end="2351">
<li data-section-id="148dzl8" data-start="2292" data-end="2324">cualquiera puede invocar tools</li>
<li data-section-id="i0cxo0" data-start="2325" data-end="2351">no hay trazabilidad real</li>
</ul></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_39  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h2 data-section-id="16v5xkj" data-start="2739" data-end="2777">3.3 Control de tokens (FinOps real)</h2>
<p data-start="2779" data-end="2800">En sistemas clásicos:</p>
<ul data-start="2802" data-end="2822">
<li data-section-id="16veb3z" data-start="2802" data-end="2822">controlas requests</li>
</ul>
<p data-start="2824" data-end="2830">En IA:</p>
<ul data-start="2832" data-end="2850">
<li data-section-id="15qmcr9" data-start="2832" data-end="2850">controlas tokens</li>
</ul>
<p data-start="2852" data-end="2865">APIM permite:</p>
<ul data-start="2867" data-end="2952">
<li data-section-id="vuld9" data-start="2867" data-end="2895">limitar tokens por usuario</li>
<li data-section-id="qt4r76" data-start="2896" data-end="2931">cortar ejecuciones antes de coste</li>
<li data-section-id="1v38jn" data-start="2932" data-end="2952">medir consumo real</li>
</ul>
<p data-start="2954" data-end="3038"><a data-start="2957" data-end="3038" rel="noopener" target="_blank" class="decorated-link" href="https://learn.microsoft.com/en-us/azure/api-management/genai-gateway-capabilities">https://learn.microsoft.com/en-us/azure/api-management/genai-gateway-capabilities<span aria-hidden="true" class="ms-0.5 inline-block align-middle leading-none"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" aria-hidden="true" data-rtl-flip="" class="block h-&#091;0.75em&#093; w-&#091;0.75em&#093; stroke-current stroke-&#091;0.75&#093;"><use href="/cdn/assets/sprites-core-a066ed1a.svg#304883" fill="currentColor"></use></svg></span></a></p></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_40  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h3 data-section-id="1h4s2sr" data-start="2649" data-end="2664">Punto clave</h3>
<blockquote data-start="2666" data-end="2732">
<p data-start="2668" data-end="2732">El control debe hacerse antes de que el prompt llegue al modelo.</p>
</blockquote></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_41  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h2 data-section-id="16ukbyu" data-start="3045" data-end="3076">3.4 Observabilidad semántica</h2>
<p data-start="3078" data-end="3097">No es logging HTTP.</p>
<p data-start="3099" data-end="3102">Es:</p>
<ul data-start="3104" data-end="3151">
<li data-section-id="j36j7j" data-start="3104" data-end="3113">prompts</li>
<li data-section-id="1x2vd33" data-start="3114" data-end="3127">completions</li>
<li data-section-id="1l70340" data-start="3128" data-end="3136">tokens</li>
<li data-section-id="1iz805n" data-start="3137" data-end="3151">uso de tools</li>
</ul>
<p data-start="3153" data-end="3166">Esto permite:</p>
<ul data-start="3168" data-end="3235">
<li data-section-id="18l4moz" data-start="3168" data-end="3188">detectar anomalías</li>
<li data-section-id="1maugmz" data-start="3189" data-end="3213">auditar comportamiento</li>
<li data-section-id="1k7v295" data-start="3214" data-end="3235">entender decisiones</li>
</ul></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_42  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h2 data-section-id="tdvke" data-start="3242" data-end="3282">3.5 Routing inteligente y resiliencia</h2>
<p data-start="3284" data-end="3297">APIM permite:</p>
<ul data-start="3299" data-end="3368">
<li data-section-id="8o2zcv" data-start="3299" data-end="3323">fallback entre modelos</li>
<li data-section-id="1qrh1h5" data-start="3324" data-end="3350">balanceo entre endpoints</li>
<li data-section-id="gx925v" data-start="3351" data-end="3368">circuit breaker</li>
</ul>
<p data-start="3370" data-end="3413">Esto es crítico cuando dependes de LLMs.</p></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_43  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h2 data-section-id="m2d0h9" data-start="3420" data-end="3443">3.6 Semantic caching</h2>
<ul data-start="3445" data-end="3518">
<li data-section-id="9a97ex" data-start="3445" data-end="3473">cache basado en embeddings</li>
<li data-section-id="3io3m" data-start="3474" data-end="3497">reducción de latencia</li>
<li data-section-id="ipj6j9" data-start="3498" data-end="3518">reducción de coste</li>
</ul></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_44  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h2 data-section-id="tlxnsu" data-start="3525" data-end="3558">4. MCP + APIM: patrón correcto</h2></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_9">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">[Agent / Copilot]
        │
        ▼
[APIM - AI Gateway]
        │
        ├── Auth
        ├── Prompt filtering
        ├── Token control
        ├── Observability
        │
        ▼
[MCP Server]
        │
        ▼
[Application / Domain]</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_45  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h2 data-section-id="3xkdwf" data-start="3820" data-end="3878">5. Seguridad: cómo se mitiga realmente prompt injection</h2>
<p data-start="3880" data-end="3893">
<p data-start="3880" data-end="3893">Error típico:</p>
<blockquote data-start="3895" data-end="3920">
<p data-start="3897" data-end="3920">“el modelo lo gestiona”</p>
</blockquote>
<p data-start="3922" data-end="3925">No.</p></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_46  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h2 data-section-id="xdheut" data-start="3932" data-end="3954">Estrategia correcta</h2>
<h3 data-section-id="6ywebx" data-start="3956" data-end="3981"></h3>
<h3 data-section-id="6ywebx" data-start="3956" data-end="3981">1. Pre-filter en APIM</h3>
<ul data-start="3982" data-end="4010">
<li data-section-id="m6qme5" data-start="3982" data-end="4010">bloquear inputs maliciosos</li>
</ul>
<h3 data-section-id="l4iykg" data-start="4012" data-end="4045">2. Validación estricta en MCP</h3>
<ul data-start="4046" data-end="4086">
<li data-section-id="1euhf5k" data-start="4046" data-end="4066">schema obligatorio</li>
<li data-section-id="1sb1dwa" data-start="4067" data-end="4086">sin inputs libres</li>
</ul>
<h3 data-section-id="10t18jp" data-start="4088" data-end="4112">3. Dominio protegido</h3>
<ul data-start="4113" data-end="4141">
<li data-section-id="1wcntbi" data-start="4113" data-end="4126">invariantes</li>
<li data-section-id="q04feq" data-start="4127" data-end="4141">validaciones</li>
</ul>
<h3 data-section-id="reudu5" data-start="4143" data-end="4164">4. Observabilidad</h3>
<ul data-start="4165" data-end="4197">
<li data-section-id="1edercd" data-start="4165" data-end="4197">detección de patrones anómalos</li>
</ul></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_47  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h3 data-section-id="459bmw" data-start="4204" data-end="4220">Regla de oro</h3>
<blockquote data-start="4222" data-end="4262">
<p data-start="4224" data-end="4262">Cada tool es potencialmente peligrosa.</p>
</blockquote></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_48  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h2 data-section-id="1gavggg" data-start="4269" data-end="4335">6. Escalado de MCP en Azure (lo que diferencia a un arquitecto)</h2>
<p data-start="4337" data-end="4361">
<p data-start="4337" data-end="4361">Aquí está el valor real.</p></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_49  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h2 data-section-id="1wml802" data-start="4368" data-end="4401">6.1 Azure Container Apps (ACA)</h2>
<h3 data-section-id="1dde1he" data-start="4403" data-end="4420"></h3>
<h3 data-section-id="1dde1he" data-start="4403" data-end="4420">Cuándo usarlo</h3>
<ul data-start="4422" data-end="4492">
<li data-section-id="1a0zxy0" data-start="4422" data-end="4440">cargas variables</li>
<li data-section-id="1wf36j9" data-start="4441" data-end="4468">arquitectura event-driven</li>
<li data-section-id="9hyp8i" data-start="4469" data-end="4492">simplicidad operativa</li>
</ul>
<h3 data-section-id="tf75cc" data-start="4494" data-end="4504">Patrón</h3></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_10">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">Client &rarr; APIM &rarr; ACA (MCP Server) &rarr; Backend</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_50  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h3 data-section-id="ercfpk" data-start="4562" data-end="4572">Claves</h3>
<ul data-start="4574" data-end="4641">
<li data-section-id="l3p186" data-start="4574" data-end="4585">stateless</li>
<li data-section-id="mj9elf" data-start="4586" data-end="4616">autoscaling por concurrencia</li>
<li data-section-id="k8sozj" data-start="4617" data-end="4641">cuidado con cold start</li>
</ul></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_51  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h2 data-section-id="frzwxd" data-start="4648" data-end="4685">6.2 Azure Kubernetes Service (AKS)</h2>
<h3 data-section-id="1dde1he" data-start="4687" data-end="4704"></h3>
<h3 data-section-id="1dde1he" data-start="4687" data-end="4704">Cuándo usarlo</h3>
<ul data-start="4706" data-end="4757">
<li data-section-id="geaohy" data-start="4706" data-end="4718">alta carga</li>
<li data-section-id="aairk1" data-start="4719" data-end="4733">control fino</li>
<li data-section-id="moehb9" data-start="4734" data-end="4757">escenarios enterprise</li>
</ul>
<h3 data-section-id="tf75cc" data-start="4759" data-end="4769">Patrón</h3></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_11">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">Client &rarr; APIM &rarr; Ingress &rarr; Pods MCP &rarr; Backend</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_52  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h3 data-section-id="ercfpk" data-start="4829" data-end="4839">Claves</h3>
<ul data-start="4841" data-end="4961">
<li data-section-id="174cdd3" data-start="4841" data-end="4867">estado fuera (Redis, DB)</li>
<li data-section-id="3pqal7" data-start="4868" data-end="4894">affinity si hay sesiones</li>
<li data-section-id="1mxir6y" data-start="4895" data-end="4929">configuración de streaming (SSE)</li>
<li data-section-id="13ywwgm" data-start="4930" data-end="4961">HPA basado en métricas reales</li>
</ul></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_53  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h2 data-section-id="1akw4ek" data-start="4968" data-end="4990">Problema importante</h2>
<blockquote data-start="4992" data-end="5027">
<p data-start="4994" data-end="5027">MCP no es naturalmente stateless.</p>
</blockquote>
<p data-start="5029" data-end="5059">Tienes que diseñarlo como tal.</p></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_54  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h2 data-section-id="1hp9ko8" data-start="5066" data-end="5099">6.3 Azure MCP Server (managed)</h2>
<p data-start="5101" data-end="5179"><a data-start="5104" data-end="5179" rel="noopener" target="_blank" class="decorated-link" href="https://learn.microsoft.com/en-us/azure/developer/azure-mcp-server/overview">https://learn.microsoft.com/en-us/azure/developer/azure-mcp-server/overview<span aria-hidden="true" class="ms-0.5 inline-block align-middle leading-none"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" aria-hidden="true" data-rtl-flip="" class="block h-&#091;0.75em&#093; w-&#091;0.75em&#093; stroke-current stroke-&#091;0.75&#093;"><use href="/cdn/assets/sprites-core-a066ed1a.svg#304883" fill="currentColor"></use></svg></span></a></p>
<h3 data-section-id="sjtonx" data-start="5181" data-end="5191"></h3>
<h3 data-section-id="sjtonx" data-start="5181" data-end="5191">Qué es</h3>
<ul data-start="5193" data-end="5251">
<li data-section-id="h37o7l" data-start="5193" data-end="5214">servicio gestionado</li>
<li data-section-id="11zc65e" data-start="5215" data-end="5251">acceso a capacidades Azure vía MCP</li>
</ul>
<h3 data-section-id="1dde1he" data-start="5253" data-end="5270">Cuándo usarlo</h3>
<ul data-start="5272" data-end="5321">
<li data-section-id="9n97eg" data-start="5272" data-end="5296">acelerar integraciones</li>
<li data-section-id="17l9dn7" data-start="5297" data-end="5321">evitar infraestructura</li>
</ul>
<h3 data-section-id="134oyx1" data-start="5323" data-end="5337">Limitación</h3>
<ul data-start="5339" data-end="5364">
<li data-section-id="awhkux" data-start="5339" data-end="5364">no sustituye tu backend</li>
</ul></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_55  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h2 data-section-id="53vr9k" data-start="5371" data-end="5412">7. Arquitectura de referencia completa</h2>
<div class="relative w-full mt-4 mb-1">
<div class="">
<div class="relative">
<div class="h-full min-h-0 min-w-0">
<div class="h-full min-h-0 min-w-0">
<div class="border border-token-border-light border-radius-3xl corner-superellipse/1.1 rounded-3xl">
<div class="h-full w-full border-radius-3xl bg-token-bg-elevated-secondary corner-superellipse/1.1 overflow-clip rounded-3xl lxnfua_clipPathFallback">
<div class="pointer-events-none absolute end-1.5 top-1 z-2 md:end-2 md:top-1"></div>
<div class="relative">
<div class="pe-11 pt-3">
<div class="relative z-0 flex max-w-full">
<div id="code-block-viewer" dir="ltr" class="q9tKkq_viewer cm-editor z-10 light:cm-light dark:cm-light flex h-full w-full flex-col items-stretch ͼd ͼr">
<div class="cm-scroller">
<div class="cm-content q9tKkq_readonly"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_12">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">                [Agents]
                    │
                    ▼
           [APIM - AI Gateway]
                    │
        ┌───────────┴───────────┐
        ▼                       ▼
[MCP Server Layer]     [Other APIs]
        │
        ├── Redis
        ├── Service Bus
        └── Databases
        │
        ▼
[Application / Domain]</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_56  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h2 data-section-id="uzzya9" data-start="5775" data-end="5804">8. Decisión arquitectónica</h2>
<p data-start="5806" data-end="5815">Sin APIM:</p>
<ul data-start="5817" data-end="5875">
<li data-section-id="1ay4yhq" data-start="5817" data-end="5833">no hay control</li>
<li data-section-id="4qr7rg" data-start="5834" data-end="5851">no hay gobierno</li>
<li data-section-id="pjklh9" data-start="5852" data-end="5875">no hay seguridad real</li>
</ul>
<p data-start="5877" data-end="5886">Con APIM:</p>
<ul data-start="5888" data-end="5943">
<li data-section-id="17gczjt" data-start="5888" data-end="5910">tienes control plane</li>
<li data-section-id="gd7e8h" data-start="5911" data-end="5927">puedes escalar</li>
<li data-section-id="1ti3hsx" data-start="5928" data-end="5943">puedes operar</li>
</ul></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_57  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><blockquote>
<p>MCP define cómo ejecutar.<br data-start="5995" data-end="5998" />APIM define qué está permitido ejecutar.</p>
</blockquote></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_58  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h2 data-section-id="jvltp9" data-start="6047" data-end="6063">Para terminar</h2>
<p data-start="6065" data-end="6084">En sistemas con IA:</p>
<ul data-start="6086" data-end="6140">
<li data-section-id="2rbc7f" data-start="6086" data-end="6104">el modelo decide</li>
<li data-section-id="1eka5bz" data-start="6105" data-end="6118">MCP ejecuta</li>
<li data-section-id="1o3jn1l" data-start="6119" data-end="6140">tu sistema responde</li>
</ul>
<p data-start="6142" data-end="6184">Pero alguien tiene que gobernar ese flujo.</p>
<p data-start="6186" data-end="6213">En Azure, ese “alguien” es:</p>
<blockquote data-start="6215" data-end="6251">
<p data-start="6217" data-end="6251"><strong data-start="6217" data-end="6251">API Management como AI Gateway</strong></p>
</blockquote></div>
			</div><div class="et_pb_module et_pb_divider et_pb_divider_2 et_pb_divider_position_ et_pb_space"><div class="et_pb_divider_internal"></div></div><div class="et_pb_module et_pb_text et_pb_text_59  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p><strong>Nota:</strong></p>
<blockquote>
<p>No expongas capacidades a un agente sin un control plane.<br data-start="6335" data-end="6338" />Porque en ese momento, ya no controlas el sistema tú.</p>
</blockquote></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_60  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p class="isSelectedEnd"><span>Si revisas mis repositorios y artículos anteriores, verás que esta preocupación no es nueva.</span></p>
<ul data-spread="false">
<li><span>GitHub: </span><a href="https://github.com/jmfloreszazo"><span>https://github.com/jmfloreszazo</span></a></li>
<li><span>Blog: </span><a href="https://jmfloreszazo.com"><span>https://jmfloreszazo.com</span></a></li>
</ul>
<p class="isSelectedEnd"><span>En particular, llevo tiempo trabajando y escribiendo sobre piezas que hoy encajan directamente en este problema:</span></p>
<ul data-spread="false">
<li><span>Reverse proxy y control de tráfico con YARP</span></li>
<li><span>Exposición y gobierno de capacidades (antes APIs, ahora MCP)</span></li>
<li><span>Evolución hacia modelos más dinámicos y agentic</span></li>
</ul>
<p class="isSelectedEnd"><span>Si conectas esos puntos, verás que el patrón es el mismo:</span></p>
<blockquote>
<p class="isSelectedEnd"><span>control del flujo, control de la ejecución y desacoplo de responsabilidades</span></p>
</blockquote>
<p class="isSelectedEnd"><span>Lo único que ha cambiado es el actor que está en medio.</span></p>
<p class="isSelectedEnd"><span>Antes era un cliente.</span><br /><span>Ahora es un modelo.</span></p>
<p class="isSelectedEnd"><span>Y quizá ahora se entienda mejor por qué esta preocupación surgió en cuanto MCP empezó a hacerse mainstream: fue entonces cuando se hicieron evidentes, de forma bastante clara, muchas de sus carencias en escenarios reales.</span></p>
<p class="isSelectedEnd"><span>Porque al final, el problema nunca ha sido la tecnología concreta.</span></p>
<p><span>El problema es no tener control sobre cómo se utiliza.</span></p></div>
			</div>
			</div>
				
				
				
				
			</div>
				
				
			</div>
]]></content:encoded>
					
					<wfw:commentRss>https://jmfloreszazo.com/mcp-en-sistemas-enterprise-como-disenar-gobernar-y-escalar-en-net-y-azure-ii/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>AÑADIENDO INTELIGENCIA A TUS APLICACIONES CON GITHUB COPILOT CLI</title>
		<link>https://jmfloreszazo.com/anadiendo-inteligencia-a-tus-aplicaciones-con-github-copilot-cli/</link>
					<comments>https://jmfloreszazo.com/anadiendo-inteligencia-a-tus-aplicaciones-con-github-copilot-cli/#respond</comments>
		
		<dc:creator><![CDATA[Jose María Flores Zazo]]></dc:creator>
		<pubDate>Fri, 10 Apr 2026 15:10:17 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Artículos]]></category>
		<category><![CDATA[CLI]]></category>
		<category><![CDATA[Copilot]]></category>
		<category><![CDATA[GITHUB]]></category>
		<category><![CDATA[NodeJS]]></category>
		<guid isPermaLink="false">https://jmfloreszazo.com/?p=5332</guid>

					<description><![CDATA[Cuando hablamos de añadir IA a una aplicación, lo primero que viene a la cabeza es desplegar un modelo. Azure AI Foundry, OpenAI, un endpoint de inferencia… y empezar a hacer llamadas HTTP. Funciona. Pero hay otro camino que a veces olvidamos.]]></description>
										<content:encoded><![CDATA[<p><div class="et_pb_section et_pb_section_5 et_section_regular" >
				
				
				
				
				
				
				<div class="et_pb_row et_pb_row_5">
				<div class="et_pb_column et_pb_column_4_4 et_pb_column_5  et_pb_css_mix_blend_mode_passthrough et-last-child">
				
				
				
				
				<div class="et_pb_module et_pb_text et_pb_text_61  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><div>
<div><span>Cuando hablamos de añadir IA a una aplicación, lo primero que viene a la cabeza es desplegar un modelo. Azure AI Foundry, OpenAI, un endpoint de inferencia… y empezar a hacer llamadas HTTP.</span></div>
</p>
<div><span>Funciona. Pero hay otro camino que a veces olvidamos.</span></div>
</p>
<div><span>La línea de comandos (<a href="http://es.wikipedia.org/wiki/En_el_principio_fue_la_l%C3%ADnea_de_comandos" target="_blank" rel="noopener" title="En el principio fue la línea de comandos">ya lo he recomendado varias veces, <em>pero creo que este libro lo tienes que leer</em></a>).</span></div>
</p>
<div><span>Quienes llevamos tiempo en esto sabemos que la CLI siempre fue el sitio donde las cosas pasan de verdad. Antes de que existieran los dashboards, los portales y las interfaces gráficas, estaba el terminal. Y sigue estando.</span></div>
</p>
<div><span>GitHub Copilot CLI es exactamente eso: inteligencia artificial accesible desde la línea de comandos. Sin desplegar modelos, sin configurar endpoints, sin gestionar claves de API de un servicio en la nube.</span></div>
</p>
<div><span>Y lo interesante es que puedes integrarlo directamente en tus aplicaciones.</span></div>
</div></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_62  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h3>LA IDEA</h3>
<p>En lugar de montar una infraestructura de IA completa, ¿por qué no usar lo que ya tienes instalado?</p>
<p>Si tienes GitHub CLI con la extensión Copilot, ya tienes un LLM disponible en tu máquina. Solo necesitas un proceso hijo que lo invoque.</p>
<p>La arquitectura es deliberadamente simple:</p></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_13">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">[Tu aplicaci&oacute;n] &rarr; [Servidor Node.js] &rarr; [Copilot CLI] &rarr; [Respuesta IA]</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_63  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p>Sin Azure AI Foundry. Sin claves de API rotando. Sin costes por token.</p>
<blockquote>
<p>Copilot CLI como backend de IA para prototipado rápido y herramientas internas.</p>
</blockquote>
<p>Obviamente, para producción a escala, querrás un servicio gestionado. Pero para herramientas internas, demos, prototipos y flujos de trabajo de equipo, este enfoque es sorprendentemente potente.</p></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_64  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h3>EL CASO PRÁCTICO: UN PLUGIN DE POWERPOINT</h3>
<p>Para demostrarlo, he construido un Add-in de PowerPoint que genera presentaciones a partir de Markdown, con Copilot CLI como motor de generación de contenido.</p>
<p>El flujo es:</p>
<ol>
<li>El usuario escribe un prompt: «5 diapositivas sobre computación cuántica»</li>
<li>El servidor invoca `copilot.exe` con ese prompt</li>
<li>Copilot devuelve Markdown estructurado</li>
<li>El plugin parsea el Markdown y genera las slides</li>
</ol>
<p>Pero lo interesante no es el plugin en sí. Es cómo se integra Copilot CLI en el backend.</p></div>
			</div><div class="et_pb_module et_pb_image et_pb_image_0">
				
				
				
				
				<span class="et_pb_image_wrap "><img fetchpriority="high" decoding="async" width="1735" height="1407" src="https://jmfloreszazo.com/wp-content/uploads/2026/04/ghcli_pptx_00.jpg" alt="" title="ghcli_pptx_00" srcset="https://jmfloreszazo.com/wp-content/uploads/2026/04/ghcli_pptx_00.jpg 1735w, https://jmfloreszazo.com/wp-content/uploads/2026/04/ghcli_pptx_00-300x243.jpg 300w, https://jmfloreszazo.com/wp-content/uploads/2026/04/ghcli_pptx_00-1024x830.jpg 1024w, https://jmfloreszazo.com/wp-content/uploads/2026/04/ghcli_pptx_00-768x623.jpg 768w, https://jmfloreszazo.com/wp-content/uploads/2026/04/ghcli_pptx_00-1536x1246.jpg 1536w, https://jmfloreszazo.com/wp-content/uploads/2026/04/ghcli_pptx_00-1080x876.jpg 1080w" sizes="(max-width: 1735px) 100vw, 1735px" class="wp-image-5339" /></span>
			</div><div class="et_pb_module et_pb_image et_pb_image_1">
				
				
				
				
				<span class="et_pb_image_wrap "><img loading="lazy" decoding="async" width="1735" height="1407" src="https://jmfloreszazo.com/wp-content/uploads/2026/04/ghcli_pptx_01.jpg" alt="" title="ghcli_pptx_01" srcset="https://jmfloreszazo.com/wp-content/uploads/2026/04/ghcli_pptx_01.jpg 1735w, https://jmfloreszazo.com/wp-content/uploads/2026/04/ghcli_pptx_01-300x243.jpg 300w, https://jmfloreszazo.com/wp-content/uploads/2026/04/ghcli_pptx_01-1024x830.jpg 1024w, https://jmfloreszazo.com/wp-content/uploads/2026/04/ghcli_pptx_01-768x623.jpg 768w, https://jmfloreszazo.com/wp-content/uploads/2026/04/ghcli_pptx_01-1536x1246.jpg 1536w, https://jmfloreszazo.com/wp-content/uploads/2026/04/ghcli_pptx_01-1080x876.jpg 1080w" sizes="(max-width: 1735px) 100vw, 1735px" class="wp-image-5340" /></span>
			</div><div class="et_pb_module et_pb_text et_pb_text_65  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><div>
<h3><span>INVOCANDO COPILOT CLI DESDE NODE.JS</span></h3>
</p>
<div><span>La pieza central es un servidor Express que actúa como proxy hacia el binario de Copilot:</span></div>
</div></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_14">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">const { execFile } = require(&quot;child_process&quot;);
const path = require(&quot;path&quot;);
const os = require(&quot;os&quot;);

let cachedGhToken = null;

function getGhToken() {
  return new Promise((resolve, reject) =&gt; {
    const ghPath = &quot;C:\\Program Files\\GitHub CLI\\gh.exe&quot;;
    execFile(ghPath, [&quot;auth&quot;, &quot;token&quot;], { timeout: 10000 }, (err, stdout) =&gt; {
      if (err) {
        reject(new Error(&quot;Ejecuta &#039;gh auth login&#039; primero.&quot;));
        return;
      }
      resolve(stdout.trim());
    });
  });
}

function runCopilotCli(prompt) {
  return new Promise(async (resolve, reject) =&gt; {
    if (!cachedGhToken) {
      cachedGhToken = await getGhToken();
    }

    const copilotPath = path.join(
      os.homedir(),
      &quot;AppData&quot;, &quot;Local&quot;, &quot;GitHub CLI&quot;, &quot;copilot&quot;, &quot;copilot.exe&quot;
    );

    execFile(
      copilotPath,
      [&quot;-p&quot;, prompt, &quot;-s&quot;, &quot;--no-ask-user&quot;],
      {
        timeout: 120000,
        maxBuffer: 1024 * 1024,
        env: { ...process.env, GH_TOKEN: cachedGhToken },
      },
      (error, stdout, stderr) =&gt; {
        if (error) {
          reject(new Error(stderr || error.message));
          return;
        }
        resolve(stdout.trim());
      }
    );
  });
}</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_66  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p>Puntos clave:</p>
<p>&#8211; No usamos `gh copilot`. Invocamos directamente el binario `copilot.exe` que la extensión instala en `AppData/Local/GitHub CLI/copilot/`.<br />&#8211; El token se obtiene de `gh auth token` y se cachea. El binario necesita `GH_TOKEN` como variable de entorno para autenticarse.<br />&#8211; Flags: `-p` para modo no interactivo, `-s` para salida limpia (solo la respuesta), `&#8211;no-ask-user` para que no haga preguntas de vuelta.</p>
<h3>AGENTS Y SKILLS: DANDO PERSONALIDAD A LA IA</h3>
<p>Tener un LLM disponible está bien. Pero la diferencia entre una respuesta genérica y una respuesta útil es el contexto.</p>
<p>Aquí es donde entra el concepto de <strong>Agents</strong> y <strong>Skills</strong>.</p>
<p>Un Agent es una personalidad predefinida con instrucciones de sistema. Un Skill es una capacidad concreta que se inyecta en el prompt.</p>
<p>En el servidor, se definen así:</p></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_15">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">const AGENTS = {
  &quot;profesor-secundaria&quot;: {
    name: &quot;🎓 Profesor de Secundaria&quot;,
    systemPrompt: `Eres un profesor de secundaria carism&aacute;tico y did&aacute;ctico. 
Tu objetivo es que los alumnos de 12 a 16 a&ntilde;os entiendan cualquier tema.
- Usa lenguaje sencillo y cercano
- Incluye ejemplos del d&iacute;a a d&iacute;a
- Usa analog&iacute;as divertidas y memorables
- A&ntilde;ade datos curiosos: &quot;&iquest;Sab&iacute;as que...?&quot;
- Incluye preguntas ret&oacute;ricas para fomentar la reflexi&oacute;n`,
  },
  &quot;storyteller&quot;: {
    name: &quot;📖 Storyteller&quot;,
    systemPrompt: `Eres un narrador que convierte cualquier tema en una historia.
Usa arcos narrativos: problema, desarrollo, conclusi&oacute;n.
Haz que cada diapositiva sea un cap&iacute;tulo.`,
  },
};

const SKILLS = {
  &quot;analogias-cotidianas&quot;: {
    name: &quot;🔄 Analog&iacute;as Cotidianas&quot;,
    instruction: `Para cada concepto, a&ntilde;ade una analog&iacute;a cotidiana.
Ejemplo: &quot;La CPU es como el cerebro: procesa todas las decisiones&quot;.
Usa objetos que un adolescente conoce: m&oacute;viles, videojuegos, redes sociales.`,
  },
  &quot;quiz-interactivo&quot;: {
    name: &quot;❓ Quiz Interactivo&quot;,
    instruction: `Al final, a&ntilde;ade una slide de &quot;Quiz R&aacute;pido&quot; con 3-5 preguntas.
Incluye verdadero/falso, opci&oacute;n m&uacute;ltiple, completar la frase.`,
  },
  &quot;datos-curiosos&quot;: {
    name: &quot;🤯 Datos Curiosos&quot;,
    instruction: `Incluye al menos un dato sorprendente por diapositiva.
Formato: &quot;🤯 &iquest;Sab&iacute;as que...?&quot; &mdash; datos verificables que generen &quot;wow&quot;.`,
  },
};</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_67  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner">Cuando el usuario hace una petición a Copilot, el servidor construye el prompt completo inyectando el agente activo y los skills seleccionados:</div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_16">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">function buildSystemContext() {
  const agent = AGENTS[activeAgentId];
  let context = &quot;&quot;;

  if (agent) {
    context += `=== AGENTE: ${agent.name} ===\n${agent.systemPrompt}\n\n`;
  }

  for (const skillId of activeSkillIds) {
    const skill = SKILLS[skillId];
    if (skill) {
      context += `[${skill.name}]\n${skill.instruction}\n`;
    }
  }

  return context;
}</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_68  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><div></p>
<div><span>El prompt final que llega a Copilot CLI tiene esta estructura:</span></div>
</div></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_17">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">=== AGENTE: 🎓 Profesor de Secundaria ===
Eres un profesor de secundaria carism&aacute;tico y did&aacute;ctico...

[🔄 Analog&iacute;as Cotidianas]
Para cada concepto, a&ntilde;ade una analog&iacute;a cotidiana...

[🤯 Datos Curiosos]
Incluye al menos un dato sorprendente por diapositiva...

=== INSTRUCCIONES DE FORMATO ===
Genera contenido en formato Markdown para una presentaci&oacute;n.
Usa # para t&iacute;tulo, ## para diapositivas, - para puntos.

Solicitud del usuario: 5 diapositivas sobre computaci&oacute;n cu&aacute;ntica</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_69  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p><span>El resultado: el mismo prompt del usuario genera contenido completamente diferente según el agente y los skills activos.</span></p>
<blockquote>
<p>Un prompt sin contexto es una pregunta al aire. Un prompt con agente y skills es una instrucción precisa.</p>
</blockquote>
<h3>LA INTERFAZ: SELECCIÓN EN TIEMPO REAL</h3>
<p>En el panel lateral del Add-in (aquí volvemos a ver como el UI ya cambia con respecto a los <em>clic-clic</em> habituales), el usuario selecciona el agente con un dropdown y activa/desactiva skills con chips interactivos. La configuración se sincroniza con el servidor en tiempo real:</p></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_18">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">async function syncConfig() {
  const agentId = document.getElementById(&quot;agent-select&quot;).value;
  const skillIds = Array.from(
    document.querySelectorAll(&quot;.skill-chip.active&quot;)
  ).map((el) =&gt; el.dataset.skillId);

  await fetch(`${SERVER_URL}/api/config`, {
    method: &quot;POST&quot;,
    headers: { &quot;Content-Type&quot;: &quot;application/json&quot; },
    body: JSON.stringify({ agentId, skillIds }),
  });
}</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_image et_pb_image_2">
				
				
				
				
				<span class="et_pb_image_wrap "><img loading="lazy" decoding="async" width="1150" height="946" src="https://jmfloreszazo.com/wp-content/uploads/2026/04/ghcli_pptx_02.jpg" alt="" title="ghcli_pptx_02" srcset="https://jmfloreszazo.com/wp-content/uploads/2026/04/ghcli_pptx_02.jpg 1150w, https://jmfloreszazo.com/wp-content/uploads/2026/04/ghcli_pptx_02-300x247.jpg 300w, https://jmfloreszazo.com/wp-content/uploads/2026/04/ghcli_pptx_02-1024x842.jpg 1024w, https://jmfloreszazo.com/wp-content/uploads/2026/04/ghcli_pptx_02-768x632.jpg 768w, https://jmfloreszazo.com/wp-content/uploads/2026/04/ghcli_pptx_02-1080x888.jpg 1080w" sizes="(max-width: 1150px) 100vw, 1150px" class="wp-image-5338" /></span>
			</div><div class="et_pb_module et_pb_text et_pb_text_70  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p>Cada vez que el usuario cambia de agente o toca un skill, la siguiente petición a Copilot CLI ya lleva el nuevo contexto.</p>
<p>No hay página de configuración separada. No hay guardado manual. Cambias, generas, ves el resultado.</p>
<h3>CÓMO PROBARLO</h3>
<p>Requisitos:</p>
<ul>
<li>Node.js 18+</li>
<li>GitHub CLI con la extensión Copilot: `gh extension install github/gh-copilot`</li>
<li>PowerPoint desktop (Windows)</li>
<li>Y este repositorio: <a href="%20https://github.com/jmfloreszazo/pptx_maker_with_gc_cli" target="_blank" rel="noopener" title=" https://github.com/jmfloreszazo/pptx_maker_with_gc_cli"></a></li>
</ul></div>
			</div><div class="et_pb_button_module_wrapper et_pb_button_1_wrapper et_pb_button_alignment_center et_pb_module ">
				<a class="et_pb_button et_pb_button_1 et_pb_bg_layout_light" href="https://github.com/jmfloreszazo/pptx_maker_with_gc_cli">Ejemplo</a>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_19">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs"># Clonar e instalar
git clone &lt;repositorio&gt;
cd pptx_maker_with_gc_cli
npm install

# Levantar servidores (webpack + backend)
npm run dev

# Cargar el Add-in en PowerPoint
npx office-addin-debugging start manifest.xml</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_71  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p>Ese último comando es clave. Abre PowerPoint directamente con el Add-in cargado. Sin buscar menús de sideloading, sin copiar manifiestos a carpetas compartidas.</p>
<p>Una vez abierto:</p>
<ol>
<li>Selecciona un agente (Profesor de Secundaria viene por defecto)</li>
<li>Activa los skills que quieras</li>
<li>Escribe un prompt: «Explica el ciclo del agua para mi clase de 2º ESO»</li>
<li>Click en «Generar con Copilot CLI»</li>
<li>Las slides aparecen en la vista previa</li>
<li>Click en «Crear Presentación» para generarlas en PowerPoint</li>
</ol>
<h3>POR QUÉ ESTE ENFOQUE ES INTERESANTE</h3>
<p><a href="https://azure.microsoft.com/es-es/products/ai-foundry" target="_blank" rel="noopener" title="Azure AI Foundry">Azure AI Foundry</a> es la opción correcta para producción. No hay discusión.</p>
<p>Pero no siempre estás en producción.</p>
<p>A veces necesitas:</p>
<ul>
<li>una demo rápida que funcione sin infraestructura cloud</li>
<li>una herramienta interna para un equipo pequeño</li>
<li>un prototipo para validar una idea antes de invertir en servicios</li>
<li>enseñar a alguien cómo funciona la IA aplicada, sin que tenga que crear una cuenta en Azure</li>
<li>y saliendo a cosa más serias:
<ul>
<li>podrás crear una presentación con los issues asociados a tu proyecto de GitHub sin tener que conectarte a las apis.</li>
<li>listar todos los commits y llevarlos a la presentación de power point.</li>
<li>la imaginación es tu límite.</li>
</ul>
</li>
</ul>
<p>Copilot CLI cubre todos esos escenarios.</p>
<p>Y el patrón de Agents + Skills es reutilizable. Hoy genera presentaciones. Mañana puede generar informes, emails, documentación técnica o cualquier otro contenido que necesite contexto y personalidad.</p>
<blockquote>
<p>La inteligencia no está solo en el modelo. Está en cómo lo invocas.</p>
</blockquote>
<p><strong>LO QUE SIEMPRE SUPIMOS</strong></p>
<p><strong>La línea de comandos</strong> fue el primer lugar donde automatizamos cosas. Pipes, scripts, procesos encadenados.</p>
<p>Ahora la línea de comandos tiene IA dentro.</p>
<p>Y la forma de integrarla en tus aplicaciones es exactamente la misma que siempre usamos: un `exec`, unas flags, y un resultado por stdout.</p>
<p>No es <em>glamuroso</em>. Pero funciona.</p>
<p>Y a veces, eso es todo lo que necesitas.</p></div>
			</div>
			</div>
				
				
				
				
			</div>
				
				
			</div></p>
]]></content:encoded>
					
					<wfw:commentRss>https://jmfloreszazo.com/anadiendo-inteligencia-a-tus-aplicaciones-con-github-copilot-cli/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Construyendo un AI Gateway sobre YARP en .NET</title>
		<link>https://jmfloreszazo.com/construyendo-un-ai-gateway-sobre-yarp-en-net/</link>
					<comments>https://jmfloreszazo.com/construyendo-un-ai-gateway-sobre-yarp-en-net/#respond</comments>
		
		<dc:creator><![CDATA[Jose María Flores Zazo]]></dc:creator>
		<pubDate>Tue, 17 Mar 2026 10:39:34 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Artículos]]></category>
		<category><![CDATA[Desarrollo]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[Arquitectura]]></category>
		<category><![CDATA[Software Craftsmanship]]></category>
		<category><![CDATA[YARP]]></category>
		<guid isPermaLink="false">https://jmfloreszazo.com/?p=5320</guid>

					<description><![CDATA[Cada vez más aplicaciones empiezan a usar LLMs. Integrar un modelo es sencillo: haces una llamada HTTP y listo.
El problema aparece cuando la IA pasa a producción.
Entonces necesitas ... ]]></description>
										<content:encoded><![CDATA[<p><div class="et_pb_section et_pb_section_6 et_section_regular" >
				
				
				
				
				
				
				<div class="et_pb_row et_pb_row_6">
				<div class="et_pb_column et_pb_column_4_4 et_pb_column_6  et_pb_css_mix_blend_mode_passthrough et-last-child">
				
				
				
				
				<div class="et_pb_module et_pb_text et_pb_text_72  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p data-start="228" data-end="339">Cada vez más aplicaciones empiezan a usar LLMs. Integrar un modelo es sencillo: haces una llamada HTTP y listo.</p>
<p data-start="341" data-end="392">El problema aparece cuando la IA pasa a producción.</p>
<p data-start="394" data-end="413">Entonces necesitas:</p>
<ul data-start="415" data-end="550">
<li data-section-id="1qguh1k" data-start="415" data-end="427">
<p data-start="417" data-end="427">guardrails</p>
</li>
<li data-section-id="1uk8weu" data-start="428" data-end="447">
<p data-start="430" data-end="447">control de acceso</p>
</li>
<li data-section-id="1nybjqu" data-start="448" data-end="479">
<p data-start="450" data-end="479">protección de datos sensibles</p>
</li>
<li data-section-id="umy1cf" data-start="480" data-end="503">
<p data-start="482" data-end="503">routing entre modelos</p>
</li>
<li data-section-id="3jxjo" data-start="504" data-end="515">
<p data-start="506" data-end="515">auditoría</p>
</li>
<li data-section-id="rq5lwg" data-start="516" data-end="534">
<p data-start="518" data-end="534">control de coste</p>
</li>
<li data-section-id="97h27z" data-start="535" data-end="550">
<p data-start="537" data-end="550">rate limiting</p>
</li>
</ul>
<p data-start="552" data-end="644">Muchas organizaciones resuelven esto introduciendo un <strong data-start="606" data-end="620">AI Gateway</strong> delante de los modelos.</p>
<p data-start="646" data-end="766">Pero si trabajas en .NET, ya tienes una pieza muy potente para construirlo:<br data-start="721" data-end="724" /><strong data-start="724" data-end="765"><span class="hover:entity-accent entity-underline inline cursor-pointer align-baseline"><span class="whitespace-normal">YARP (Yet Another Reverse Proxy)</span></span></strong>.</p>
<p data-start="768" data-end="799">De ahí nace <strong data-start="780" data-end="798">Yarp.AiGateway</strong>.</p></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_73  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h1 data-section-id="7tz2bm" data-start="806" data-end="815">La idea</h1>
<p data-start="817" data-end="960">En lugar de crear un gateway desde cero, el proyecto <strong data-start="870" data-end="887">extiende YARP</strong> e introduce capacidades específicas de IA dentro del pipeline del proxy.</p>
<p data-start="962" data-end="988">La arquitectura queda así:</p></div>
			</div><div class="et_pb_module et_pb_image et_pb_image_3">
				
				
				
				
				<span class="et_pb_image_wrap "><img loading="lazy" decoding="async" width="834" height="114" src="https://jmfloreszazo.com/wp-content/uploads/2026/03/diagrama.jpg" alt="" title="diagrama" srcset="https://jmfloreszazo.com/wp-content/uploads/2026/03/diagrama.jpg 834w, https://jmfloreszazo.com/wp-content/uploads/2026/03/diagrama-300x41.jpg 300w, https://jmfloreszazo.com/wp-content/uploads/2026/03/diagrama-768x105.jpg 768w" sizes="(max-width: 834px) 100vw, 834px" class="wp-image-5323" /></span>
			</div><div class="et_pb_module et_pb_text et_pb_text_74  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><ul data-start="1082" data-end="1230">
<li data-section-id="spzdhg" data-start="1082" data-end="1144">
<p data-start="1084" data-end="1144"><strong data-start="1084" data-end="1092">YARP</strong> se encarga del reverse proxy, routing y forwarding.</p>
</li>
<li data-section-id="katq41" data-start="1145" data-end="1230">
<p data-start="1147" data-end="1230"><strong data-start="1147" data-end="1161">AI Gateway</strong> intercepta requests y responses para aplicar políticas de seguridad.</p>
</li>
</ul>
<p data-start="1232" data-end="1274">Es un enfoque muy simple pero muy potente.</p></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_75  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h1 data-section-id="yeiy9u" data-start="1281" data-end="1306">Qué añade el AI Gateway</h1>
<p data-start="1308" data-end="1388">El middleware introduce capacidades específicas para trabajar con modelos de IA:</p>
<ul data-start="1390" data-end="1647">
<li data-section-id="7k5k4e" data-start="1390" data-end="1421">
<p data-start="1392" data-end="1421">detección de prompt injection</p>
</li>
<li data-section-id="1l65nem" data-start="1422" data-end="1482">
<p data-start="1424" data-end="1482">redacción automática de PII (emails, DNI, IBAN, tarjetas…)</p>
</li>
<li data-section-id="14u6rjl" data-start="1483" data-end="1516">
<p data-start="1485" data-end="1516">análisis semántico de contenido</p>
</li>
<li data-section-id="1ftf000" data-start="1517" data-end="1549">
<p data-start="1519" data-end="1549">detección de fugas de secretos</p>
</li>
<li data-section-id="1pcinb3" data-start="1550" data-end="1586">
<p data-start="1552" data-end="1586">rate limiting por usuario o tenant</p>
</li>
<li data-section-id="9qnyn8" data-start="1587" data-end="1624">
<p data-start="1589" data-end="1624">routing entre múltiples proveedores</p>
</li>
<li data-section-id="18nobp3" data-start="1625" data-end="1647">
<p data-start="1627" data-end="1647">auditoría y métricas</p>
</li>
</ul>
<p data-start="1649" data-end="1687">Todo configurado con un único archivo:</p></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_20">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">aigateway.json</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_76  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p>Ejemplo de integración:</p></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_21">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">builder.Services.AddReverseProxy()
    .LoadFromConfig(builder.Configuration.GetSection(&quot;ReverseProxy&quot;))
    .AddTransforms(context =&gt;
    {
        context.AddRequestTransform(tc =&gt;
        {
            var apiKey = Environment.GetEnvironmentVariable(&quot;AZURE_OPENAI_API_KEY&quot;) ?? &quot;&quot;;
            tc.ProxyRequest.Headers.TryAddWithoutValidation(&quot;api-key&quot;, apiKey);
            return ValueTask.CompletedTask;
        });
    });

builder.Services.AddAiGatewayFromJson(&quot;aigateway.json&quot;);

app.MapReverseProxy(proxyPipeline =&gt;
{
    proxyPipeline.UseAiGatewayGuardrails();
});</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_77  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p data-start="2331" data-end="2360">YARP sigue haciendo el proxy.</p>
<p data-start="2362" data-end="2435">El AI Gateway simplemente <strong data-start="2388" data-end="2434">añade seguridad e inteligencia al pipeline</strong>.</p></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_78  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h1 data-section-id="1y6ev9j" data-start="2442" data-end="2463">Por qué hacerlo así</h1>
<p data-start="2465" data-end="2532">Muchos proyectos intentan construir un gateway completo desde cero.</p>
<p data-start="2534" data-end="2572">Este proyecto toma un camino distinto:</p>
<p data-start="2574" data-end="2634"><strong data-start="2574" data-end="2633">aprovechar YARP como base y añadir la capa de IA encima</strong>.</p>
<p data-start="2636" data-end="2663">Esto tiene varias ventajas:</p>
<ul data-start="2665" data-end="2786">
<li data-section-id="lnp4rv" data-start="2665" data-end="2708">
<p data-start="2667" data-end="2708">reutilizas un proxy probado en producción</p>
</li>
<li data-section-id="1ua81pd" data-start="2709" data-end="2730">
<p data-start="2711" data-end="2730">reduces complejidad</p>
</li>
<li data-section-id="1r9zgmk" data-start="2731" data-end="2755">
<p data-start="2733" data-end="2755">mejoras el rendimiento</p>
</li>
<li data-section-id="v9pzzx" data-start="2756" data-end="2786">
<p data-start="2758" data-end="2786">simplificas el mantenimiento</p>
</li>
</ul>
<p data-start="2788" data-end="2806">En otras palabras:</p>
<blockquote>
<p data-start="2788" data-end="2806">YARP = infraestructura<br />AI Gateway = políticas y seguridad</p>
</blockquote></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_79  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h1 data-section-id="1860llg" data-start="2880" data-end="2901">Un ejemplo sencillo</h1>
<p>Imagina una API que permite consultas sobre clima.</p>
<p>Petición válida:</p>
<p><em>Madrid está a 28°C. ¿Es buen clima para eventos al aire libre?</em></p>
<p>Respuesta:</p>
<p><em>200 OK</em></p>
<p>Pero si el prompt intenta algo fuera de política:</p>
<p><em>Madrid está a 28°C. ¿Cómo afectaría esto a un partido de fútbol?</em></p>
<p>El gateway puede bloquearlo antes de que llegue al modelo:</p>
<p><em>422 Prompt blocked by policy</em></p>
<p>El modelo nunca llega a ejecutarse.</p></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_80  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h1 data-section-id="12qef2c" data-start="3338" data-end="3380">Protección automática de datos sensibles</h1>
<p>Otra ventaja es la sanitización automática.</p>
<p>Entrada enviada por el desarrollador:</p>
<p><em>Paciente Pepito García, MRN-2024-12345, pepito@hospital.com</em></p>
<p>El modelo recibe realmente:</p>
<p><em>[REDACTED_PATIENT], [REDACTED_MRN], [REDACTED_EMAIL]</em></p>
<p>Esto permite cumplir normativas como <strong>GDPR o HIPAA</strong> sin depender únicamente del código de las aplicaciones.</p>
<p>La protección se aplica en la <strong>capa de infraestructura</strong>.</p></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_81  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h1 data-section-id="5hy1p4" data-start="3802" data-end="3839">Por qué este enfoque es interesante</h1>
<p data-start="3841" data-end="3869">La idea principal es simple:</p>
<blockquote data-start="3871" data-end="3925">
<p data-start="3873" data-end="3925">la gobernanza de IA puede vivir en la capa de proxy.</p>
</blockquote>
<p data-start="3927" data-end="3996">Y si ya usas YARP, no necesitas introducir otra plataforma adicional.</p>
<p data-start="3998" data-end="4025">Solo extiendes el pipeline.</p></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_82  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h1 data-section-id="19t7i27" data-start="4032" data-end="4056">Explora el repositorio</h1>
<p data-start="4058" data-end="4078">El proyecto incluye:</p>
<ul data-start="4080" data-end="4255">
<li data-section-id="9gcurs" data-start="4080" data-end="4111">
<p data-start="4082" data-end="4111">integración completa con YARP</p>
</li>
<li data-section-id="1omaj4s" data-start="4112" data-end="4142">
<p data-start="4114" data-end="4142">múltiples proveedores de LLM</p>
</li>
<li data-section-id="1qep4ak" data-start="4143" data-end="4169">
<p data-start="4145" data-end="4169">guardrails configurables</p>
</li>
<li data-section-id="1aj33r3" data-start="4170" data-end="4190">
<p data-start="4172" data-end="4190">ejemplos completos</p>
</li>
<li data-section-id="1ltrow" data-start="4191" data-end="4212">
<p data-start="4193" data-end="4212">tests automatizados</p>
</li>
<li data-section-id="1vfoyhv" data-start="4213" data-end="4255">
<p data-start="4215" data-end="4255">archivos <code data-start="4224" data-end="4231">.http</code> para probar rápidamente</p>
</li>
</ul>
<p data-start="4257" data-end="4269">Repositorio:</p></div>
			</div><div class="et_pb_button_module_wrapper et_pb_button_2_wrapper et_pb_button_alignment_center et_pb_module ">
				<a class="et_pb_button et_pb_button_2 et_pb_bg_layout_light" href="https://github.com/jmfloreszazo/yarp_aigateway" target="_blank">Repositorio en GitHub</a>
			</div><div class="et_pb_module et_pb_text et_pb_text_83  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p>Si estás construyendo sistemas con IA en .NET, puede ser una forma interesante de añadir <strong data-start="4384" data-end="4419">seguridad, control y gobernanza</strong> sin añadir demasiada complejidad.</p></div>
			</div>
			</div>
				
				
				
				
			</div>
				
				
			</div></p>
]]></content:encoded>
					
					<wfw:commentRss>https://jmfloreszazo.com/construyendo-un-ai-gateway-sobre-yarp-en-net/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Azure Artifact Signing — la identidad del software en la era de la supply chain</title>
		<link>https://jmfloreszazo.com/azure-artifact-signing-la-identidad-del-software-en-la-era-de-la-supply-chain/</link>
					<comments>https://jmfloreszazo.com/azure-artifact-signing-la-identidad-del-software-en-la-era-de-la-supply-chain/#respond</comments>
		
		<dc:creator><![CDATA[Jose María Flores Zazo]]></dc:creator>
		<pubDate>Thu, 12 Mar 2026 07:00:00 +0000</pubDate>
				<category><![CDATA[Desarrollo]]></category>
		<guid isPermaLink="false">https://jmfloreszazo.com/?p=5302</guid>

					<description><![CDATA[Durante muchos años en arquitectura de software hemos hablado de: CI/CD, DevSecOps, SBOM, supply chain security, SLSA, ...
Pero hay una pregunta que durante demasiado tiempo ha quedado en segundo plano: ¿Cómo sabemos que el binario que estamos ejecutando es realmente el que generó nuestro pipeline? No el código, el binario final.
Ahí es donde entra Azure Artifact Signing. Veamos como.]]></description>
										<content:encoded><![CDATA[
<div class="et_pb_section et_pb_section_7 et_section_regular" >
				
				
				
				
				
				
				<div class="et_pb_row et_pb_row_7">
				<div class="et_pb_column et_pb_column_4_4 et_pb_column_7  et_pb_css_mix_blend_mode_passthrough et-last-child">
				
				
				
				
				<div class="et_pb_module et_pb_text et_pb_text_84  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p data-start="114" data-end="179">Durante muchos años en arquitectura de software hemos hablado de:</p>
<ul data-start="181" data-end="258">
<li data-section-id="iw8slm" data-start="181" data-end="192">
<p data-start="183" data-end="192"><strong data-start="183" data-end="192">CI/CD</strong></p>
</li>
<li data-section-id="1hvz13a" data-start="193" data-end="208">
<p data-start="195" data-end="208"><strong data-start="195" data-end="208">DevSecOps</strong></p>
</li>
<li data-section-id="jlyo3f" data-start="209" data-end="219">
<p data-start="211" data-end="219"><strong data-start="211" data-end="219">SBOM</strong></p>
</li>
<li data-section-id="1tgf39c" data-start="220" data-end="247">
<p data-start="222" data-end="247"><strong data-start="222" data-end="247">supply chain security</strong></p>
</li>
<li data-section-id="jmy0rp" data-start="248" data-end="258">
<p data-start="250" data-end="258"><strong data-start="250" data-end="258">SLSA</strong></p>
</li>
</ul>
<p data-start="260" data-end="339">Pero hay una pregunta que durante demasiado tiempo ha quedado en segundo plano:</p>
<blockquote data-start="341" data-end="443">
<p data-start="343" data-end="443"><strong data-start="343" data-end="443">¿Cómo sabemos que el binario que estamos ejecutando es realmente el que generó nuestro pipeline?</strong></p>
</blockquote>
<p data-start="445" data-end="458">No el código.</p>
<p data-start="460" data-end="481">El <strong data-start="463" data-end="480">binario final</strong>.</p>
<p data-start="483" data-end="529">Ahí es donde entra <a href="https://azure.microsoft.com/en-us/products/artifact-signing" target="_blank" rel="noopener" title="Azure Artifact Signing"><strong data-start="502" data-end="528">Azure Artifact Signing</strong></a>.</p></div>
			</div><div class="et_pb_module et_pb_image et_pb_image_4">
				
				
				
				
				<span class="et_pb_image_wrap "><img loading="lazy" decoding="async" width="723" height="650" src="https://jmfloreszazo.com/wp-content/uploads/2026/03/az-artsign01.png" alt="" title="az-artsign01" srcset="https://jmfloreszazo.com/wp-content/uploads/2026/03/az-artsign01.png 723w, https://jmfloreszazo.com/wp-content/uploads/2026/03/az-artsign01-300x270.png 300w" sizes="(max-width: 723px) 100vw, 723px" class="wp-image-5314" /></span>
			</div><div class="et_pb_module et_pb_text et_pb_text_85  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h1 data-section-id="jqarm1" data-start="536" data-end="570">El problema: confiar en binarios</h1>
<p data-start="572" data-end="624">Un pipeline moderno genera artefactos continuamente:</p>
<ul data-start="626" data-end="677">
<li data-section-id="1ytwtqx" data-start="626" data-end="639">
<p data-start="628" data-end="639">ejecutables</p>
</li>
<li data-section-id="1nhec0l" data-start="640" data-end="651">
<p data-start="642" data-end="651">librerías</p>
</li>
<li data-section-id="1ounz6i" data-start="652" data-end="662">
<p data-start="654" data-end="662">paquetes</p>
</li>
<li data-section-id="12x4k0v" data-start="663" data-end="677">
<p data-start="665" data-end="677">contenedores</p>
</li>
</ul>
<p data-start="679" data-end="702">Ejemplo típico en .NET:</p></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_22">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">InventoryService.exe
InventoryService.dll
InventoryService.pdb</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_86  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p data-start="780" data-end="825">Esos archivos son <strong data-start="798" data-end="824">artefactos de software</strong>.</p>
<p data-start="827" data-end="859">Pero surge una cuestión crítica:</p>
<ul data-start="861" data-end="949">
<li data-section-id="12g5ozt" data-start="861" data-end="889">
<p data-start="863" data-end="889">¿quién generó ese binario?</p>
</li>
<li data-section-id="x3o4u1" data-start="890" data-end="912">
<p data-start="892" data-end="912">¿ha sido modificado?</p>
</li>
<li data-section-id="1qsbs0g" data-start="913" data-end="949">
<p data-start="915" data-end="949">¿proviene de un pipeline legítimo?</p>
</li>
</ul>
<p data-start="951" data-end="992">La respuesta clásica es <strong data-start="975" data-end="991">code signing</strong>.</p>
<p data-start="994" data-end="1055">Una firma criptográfica que permite verificar que el binario:</p>
<ol data-start="1057" data-end="1168">
<li data-section-id="1ut4fic" data-start="1057" data-end="1096">
<p data-start="1060" data-end="1096">proviene de una identidad concreta</p>
</li>
<li data-section-id="1xgmf8p" data-start="1097" data-end="1123">
<p data-start="1100" data-end="1123">no ha sido modificado</p>
</li>
<li data-section-id="1rn4luk" data-start="1124" data-end="1168">
<p data-start="1127" data-end="1168">fue emitido por una autoridad confiable</p>
</li>
</ol>
<p>Revisa este documento: <a href="https://learn.microsoft.com/en-us/azure/artifact-signing/how-to-signing-integrations" target="_blank" rel="noopener" title="https://learn.microsoft.com/en-us/azure/artifact-signing/how-to-signing-integrations">https://learn.microsoft.com/en-us/azure/artifact-signing/how-to-signing-integrations</a></p></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_87  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h1 data-section-id="7lv38a" data-start="1175" data-end="1217">El problema del Code Signing tradicional</h1>
<p data-start="1219" data-end="1260">Históricamente firmar software implicaba:</p>
<ul data-start="1262" data-end="1378">
<li data-section-id="vpzxtw" data-start="1262" data-end="1284">
<p data-start="1264" data-end="1284">comprar certificados</p>
</li>
<li data-section-id="k1bngg" data-start="1285" data-end="1311">
<p data-start="1287" data-end="1311">proteger claves privadas</p>
</li>
<li data-section-id="bayjq6" data-start="1312" data-end="1329">
<p data-start="1314" data-end="1329">tokens hardware</p>
</li>
<li data-section-id="n401a8" data-start="1330" data-end="1349">
<p data-start="1332" data-end="1349">procesos manuales</p>
</li>
<li data-section-id="iex8bn" data-start="1350" data-end="1378">
<p data-start="1352" data-end="1378">renovación de certificados</p>
</li>
</ul>
<p data-start="1380" data-end="1415">En muchos equipos esto acababa así:</p></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_23">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">certificado.pfx
password.txt</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_88  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h1 data-section-id="9gn69h" data-start="1573" data-end="1613">La propuesta de Azure Artifact Signing</h1>
<p data-start="1615" data-end="1655">Azure Artifact Signing cambia el modelo:</p>
<blockquote data-start="1657" data-end="1721">
<p data-start="1659" data-end="1721"><strong data-start="1659" data-end="1721">la firma se convierte en un servicio gestionado en la nube</strong></p>
</blockquote>
<p data-start="1723" data-end="1789">Las claves privadas se almacenan en <strong data-start="1759" data-end="1788">HSM gestionados por Azure</strong>.</p>
<p data-start="1791" data-end="1854">Los desarrolladores <strong data-start="1811" data-end="1853">no gestionan certificados directamente</strong>.</p>
<p data-start="1856" data-end="1892">El flujo se integra con el pipeline.</p>
<p data-start="1894" data-end="1920">Arquitectura simplificada:</p></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_24">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">Developer Commit
        │
        ▼
CI/CD Pipeline
        │
        ▼
Artifact Signing (Azure)
        │
        ▼
Signed Artifact
        │
        ▼
Deployment</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_89  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p>El resultado es un artefacto que contiene <strong data-start="2137" data-end="2166">una identidad verificable</strong>.</p></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_90  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h1 data-section-id="1vkpvj9" data-start="2174" data-end="2206">Qué es exactamente un Artifact</h1>
<p data-start="2208" data-end="2252">Un artifact es <strong data-start="2223" data-end="2251">el resultado de un build</strong>.</p>
<p data-start="2254" data-end="2271">Ejemplos comunes:</p>
<div class="TyagGW_tableContainer">
<div class="group TyagGW_tableWrapper flex flex-col-reverse w-fit" tabindex="-1">
<table data-start="2273" data-end="2396" class="w-fit min-w-(--thread-content-width)">
<thead data-start="2273" data-end="2298">
<tr data-start="2273" data-end="2298">
<th data-start="2273" data-end="2286" data-col-size="sm" class="">Tecnología</th>
<th data-start="2286" data-end="2298" data-col-size="sm" class="">Artifact</th>
</tr>
</thead>
<tbody data-start="2309" data-end="2396">
<tr data-start="2309" data-end="2334">
<td data-start="2309" data-end="2316" data-col-size="sm">.NET</td>
<td data-start="2316" data-end="2334" data-col-size="sm"><code data-start="2318" data-end="2324">.dll</code>, <code data-start="2326" data-end="2332">.exe</code></td>
</tr>
<tr data-start="2335" data-end="2358">
<td data-start="2335" data-end="2344" data-col-size="sm">Docker</td>
<td data-start="2344" data-end="2358" data-col-size="sm">imagen OCI</td>
</tr>
<tr data-start="2359" data-end="2375">
<td data-start="2359" data-end="2365" data-col-size="sm">npm</td>
<td data-start="2365" data-end="2375" data-col-size="sm"><code data-start="2367" data-end="2373">.tgz</code></td>
</tr>
<tr data-start="2376" data-end="2396">
<td data-start="2376" data-end="2384" data-col-size="sm">NuGet</td>
<td data-start="2384" data-end="2396" data-col-size="sm"><code data-start="2386" data-end="2394">.nupkg</code></td>
</tr>
</tbody>
</table>
</div>
</div>
<p data-start="2398" data-end="2411">Ejemplo real:</p></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_25">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">publish/InventoryService.exe</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_91  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p>Ese ejecutable es el artifact que se firmará.</p></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_92  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h1 data-section-id="14hgyd5" data-start="2507" data-end="2533">Ejemplo práctico en .NET</h1>
<h2 data-section-id="t9ktt" data-start="2584" data-end="2609">1. Crear la aplicación</h2></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_26">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">dotnet new console -n SigningDemo
cd SigningDemo</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_93  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p>Programa simple:</p></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_27">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">Console.WriteLine(&quot;Azure Artifact Signing Demo&quot;);</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_94  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p>Compilamos:</p></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_28">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">dotnet publish -c Release -r win-x64</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_95  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p>Se genera:</p></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_29">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">bin/Release/net8.0/win-x64/publish/SigningDemo.exe</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_96  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p>Ese es el <strong data-start="2905" data-end="2917">artifact</strong>.</p></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_97  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h1 data-section-id="ascw5h" data-start="2925" data-end="2958">Preparar Azure Artifact Signing</h1>
<p data-start="2960" data-end="2996">En Azure necesitamos tres elementos:</p>
<p data-start="2998" data-end="3030"><strong data-start="3002" data-end="3030">1. Artifact Signing Account</strong></p></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_30">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">asign-demo</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_98  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p><strong data-start="3056" data-end="3079">2. Identity validation</strong></p>
<p>Ejemplo:</p></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_31">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">cp-public-code</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_99  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p>Este profile define el certificado usado para firmar.</p></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_100  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h1 data-section-id="g3he9n" data-start="3251" data-end="3270">Metadata de firma</h1>
<p data-start="3272" data-end="3340">Azure necesita un pequeño archivo JSON que describe qué perfil usar.</p>
<p data-start="3342" data-end="3357"><code data-start="3342" data-end="3357">metadata.json</code></p></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_32">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">{
  &quot;Endpoint&quot;: &quot;https://weu.codesigning.azure.net&quot;,
  &quot;CodeSigningAccountName&quot;: &quot;asign-demo&quot;,
  &quot;CertificateProfileName&quot;: &quot;cp-public-code&quot;,
  &quot;CorrelationId&quot;: &quot;build-demo&quot;
}</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_101  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h1 data-section-id="1v1j0l4" data-start="3552" data-end="3574">Firma del ejecutable</h1>
<p data-start="3576" data-end="3624">Usamos <strong data-start="3583" data-end="3623">SignTool + Azure CodeSigning library</strong>.</p>
<p data-start="3626" data-end="3634">Ejemplo:</p></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_33">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">signtool sign `
 /v `
 /fd SHA256 `
 /tr http://timestamp.acs.microsoft.com `
 /td SHA256 `
 /dlib Azure.CodeSigning.Dlib.dll `
 /dmdf metadata.json `
 SigningDemo.exe</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_102  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p data-start="3823" data-end="3839">Qué ocurre aquí:</p>
<ol data-start="3841" data-end="3975">
<li data-section-id="1ux04xu" data-start="3841" data-end="3882">
<p data-start="3844" data-end="3882">SignTool calcula el hash del binario</p>
</li>
<li data-section-id="1otgzzx" data-start="3883" data-end="3925">
<p data-start="3886" data-end="3925">Azure Artifact Signing firma ese hash</p>
</li>
<li data-section-id="k98f6a" data-start="3926" data-end="3975">
<p data-start="3929" data-end="3975">se adjunta una firma Authenticode al archivo</p>
</li>
</ol>
<p data-start="3977" data-end="3987">Resultado:</p></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_34">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">SigningDemo.exe (signed)</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_103  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h1 data-section-id="nudm0d" data-start="4028" data-end="4048">Verificar la firma</h1>
<p data-start="4050" data-end="4074">Podemos comprobarlo con:</p></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_35">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">signtool verify /pa SigningDemo.exe</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_104  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p>Resultado esperado:</p></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_36">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">Successfully verified
Publisher: Your Organization</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_105  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p>Windows ahora reconoce ese binario como software firmado.</p></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_106  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h1 data-section-id="m85bu" data-start="4276" data-end="4298">Integración en CI/CD</h1>
<p data-start="4300" data-end="4361">Lo interesante aparece cuando esto se integra en el pipeline.</p>
<p data-start="4363" data-end="4382">Ejemplo conceptual:</p></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_37">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">GitHub
   │
   ▼
GitHub Actions
   │
   ├ Build .NET
   ├ Generate SBOM
   ├ Artifact Signing
   │
   ▼
Signed Release</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_107  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p data-start="4512" data-end="4537">Ahora cada release tiene:</p>
<ul data-start="4539" data-end="4612">
<li data-section-id="1u8nau4" data-start="4539" data-end="4566">
<p data-start="4541" data-end="4566"><strong data-start="4541" data-end="4566">identidad verificable</strong></p>
</li>
<li data-section-id="1xcayau" data-start="4567" data-end="4582">
<p data-start="4569" data-end="4582"><strong data-start="4569" data-end="4582">timestamp</strong></p>
</li>
<li data-section-id="1mpzoqm" data-start="4583" data-end="4612">
<p data-start="4585" data-end="4612"><strong data-start="4585" data-end="4612">provenance del pipeline</strong></p>
</li>
</ul></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_108  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h1 data-section-id="zerx3t" data-start="4619" data-end="4648">Más allá de los ejecutables</h1>
<p data-start="4650" data-end="4689">Artifact Signing no se limita a <code data-start="4682" data-end="4688">.exe</code>.</p>
<p data-start="4691" data-end="4717">También puede aplicarse a:</p>
<ul data-start="4719" data-end="4796">
<li data-section-id="1j4gyzb" data-start="4719" data-end="4735">
<p data-start="4721" data-end="4735">paquetes NuGet</p>
</li>
<li data-section-id="1vhpdl1" data-start="4736" data-end="4745">
<p data-start="4738" data-end="4745">drivers</p>
</li>
<li data-section-id="g5jeex" data-start="4746" data-end="4766">
<p data-start="4748" data-end="4766">artefactos Windows</p>
</li>
<li data-section-id="1b6yssp" data-start="4767" data-end="4796">
<p data-start="4769" data-end="4796">otros binarios distribuidos</p>
</li>
</ul>
<p data-start="4798" data-end="4858">Esto empieza a ser crítico en modelos modernos de seguridad.</p></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_109  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h1 data-section-id="2jefqm" data-start="4865" data-end="4888">Supply Chain Security</h1>
<p data-start="4890" data-end="4943">Los ataques recientes han demostrado algo importante:</p>
<p data-start="4945" data-end="5011">El objetivo de los atacantes ya no es solo comprometer servidores.</p>
<p data-start="5013" data-end="5054">El objetivo es <strong data-start="5028" data-end="5053">comprometer pipelines</strong>.</p>
<p data-start="5056" data-end="5075">Ejemplos conocidos:</p>
<ul data-start="5077" data-end="5141">
<li data-section-id="12em6y4" data-start="5077" data-end="5089">
<p data-start="5079" data-end="5089">SolarWinds</p>
</li>
<li data-section-id="1y4pf2q" data-start="5090" data-end="5118">
<p data-start="5092" data-end="5118">ataques a repositorios npm</p>
</li>
<li data-section-id="1nktrp3" data-start="5119" data-end="5141">
<p data-start="5121" data-end="5141">supply chain malware</p>
</li>
</ul>
<p data-start="5143" data-end="5226">Si un atacante compromete el pipeline, el software generado puede parecer legítimo.</p>
<p data-start="5228" data-end="5258">La firma añade una capa clave:</p>
<blockquote data-start="5260" data-end="5337">
<p data-start="5262" data-end="5337"><strong data-start="5262" data-end="5337">solo artefactos generados por identidades verificadas pueden ejecutarse</strong></p>
</blockquote></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_110  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h1 data-section-id="urrwil" data-start="5344" data-end="5382">El detalle arquitectónico importante</h1>
<p data-start="5384" data-end="5443">Artifact Signing introduce algo que hasta ahora era difuso: <strong data-start="5445" data-end="5475">la identidad del software.</strong></p>
<p data-start="5477" data-end="5493">No solo sabemos:</p>
<ul data-start="5495" data-end="5536">
<li data-section-id="1rfleio" data-start="5495" data-end="5511">
<p data-start="5497" data-end="5511">qué código hay</p>
</li>
<li data-section-id="9uaqkb" data-start="5512" data-end="5536">
<p data-start="5514" data-end="5536">qué dependencias tiene</p>
</li>
</ul>
<p data-start="5538" data-end="5554">También sabemos:</p>
<ul data-start="5556" data-end="5625">
<li data-section-id="dadyid" data-start="5556" data-end="5583">
<p data-start="5558" data-end="5583">qué organización lo firmó</p>
</li>
<li data-section-id="zse4bw" data-start="5584" data-end="5608">
<p data-start="5586" data-end="5608">qué pipeline lo generó</p>
</li>
<li data-section-id="95ft6z" data-start="5609" data-end="5625">
<p data-start="5611" data-end="5625">cuándo se creó</p>
</li>
</ul>
<p data-start="5627" data-end="5665">Esto permite construir controles como:</p>
<ul data-start="5667" data-end="5779">
<li data-section-id="8grc1o" data-start="5667" data-end="5691">
<p data-start="5669" data-end="5691"><strong data-start="5669" data-end="5691">execution policies</strong></p>
</li>
<li data-section-id="vqb8ec" data-start="5692" data-end="5726">
<p data-start="5694" data-end="5726"><strong data-start="5694" data-end="5726">Kubernetes admission control</strong></p>
</li>
<li data-section-id="73of7i" data-start="5727" data-end="5752">
<p data-start="5729" data-end="5752"><strong data-start="5729" data-end="5752">software provenance</strong></p>
</li>
<li data-section-id="1hyact" data-start="5753" data-end="5779">
<p data-start="5755" data-end="5779"><strong data-start="5755" data-end="5779">runtime verification</strong></p>
</li>
</ul></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_111  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h1 data-section-id="1xzcg6l" data-start="5786" data-end="5816">Hacia pipelines de confianza</h1>
<p data-start="5818" data-end="5881">El modelo que está emergiendo en muchas organizaciones es este:</p></div>
			</div><div class="et_pb_module et_pb_dmb_code_snippet et_pb_dmb_code_snippet_38">
				
				
				
				
				
				
				<div class="et_pb_module_inner">
					<pre class="default"><code class="hljs">Source Code
     │
     ▼
Build Pipeline
     │
     ▼
SBOM generation
     │
     ▼
Artifact Signing
     │
     ▼
Artifact Registry
     │
     ▼
Deployment</code></pre >
				</div>
			</div><div class="et_pb_module et_pb_text et_pb_text_112  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><p data-start="6051" data-end="6093">Cada paso añade <strong data-start="6067" data-end="6092">confianza verificable</strong>.</p></div>
			</div><div class="et_pb_module et_pb_text et_pb_text_113  et_pb_text_align_left et_pb_bg_layout_light">
				
				
				
				
				<div class="et_pb_text_inner"><h1 data-section-id="fsbaih" data-start="6100" data-end="6112">Y para terminar</h1>
<p data-start="6114" data-end="6194">Durante años hemos tratado la seguridad del software como un problema de código.</p>
<p data-start="6196" data-end="6265">Pero el verdadero punto crítico suele aparecer <strong data-start="6243" data-end="6264">después del build</strong>.</p>
<p data-start="6267" data-end="6296">La pregunta clave no es solo:</p>
<blockquote data-start="6298" data-end="6321">
<p data-start="6300" data-end="6321">¿El código es seguro?</p>
</blockquote>
<p data-start="6323" data-end="6343">La pregunta real es:</p>
<blockquote data-start="6345" data-end="6405">
<p data-start="6347" data-end="6405"><strong data-start="6347" data-end="6405">¿Podemos confiar en el binario que estamos ejecutando?</strong></p>
</blockquote>
<p data-start="6407" data-end="6464">Azure Artifact Signing intenta resolver precisamente eso.</p>
<p data-start="6466" data-end="6504">No es simplemente una herramienta más.</p>
<p data-start="6506" data-end="6541">Es un paso hacia algo más profundo:</p>
<blockquote data-start="6543" data-end="6584">
<p data-start="6545" data-end="6584"><strong data-start="6545" data-end="6584">software con identidad verificable.</strong></p>
</blockquote>
<p data-start="6586" data-end="6709">Y en un mundo donde la <strong data-start="6609" data-end="6663">supply chain del software es cada vez más compleja</strong>, eso empieza a ser absolutamente fundamental.</p></div>
			</div>
			</div>
				
				
				
				
			</div>
				
				
			</div>
]]></content:encoded>
					
					<wfw:commentRss>https://jmfloreszazo.com/azure-artifact-signing-la-identidad-del-software-en-la-era-de-la-supply-chain/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
