<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Jonathan Muszkat — Blog (ES)</title>
    <link>https://me.jonymusky.com/es/blog</link>
    <description>Notas sobre Next.js, agentes de AI y cómo es llevar ingeniería en una startup.</description>
    <language>es</language>
    <atom:link href="https://me.jonymusky.com/es/feed.xml" rel="self" type="application/rss+xml" />
    <item>
      <title><![CDATA[El interés compuesto de un ATS: tu próxima búsqueda no debería arrancar de cero]]></title>
      <link>https://me.jonymusky.com/es/blog/ats-compound-interest-talent-base</link>
      <guid isPermaLink="true">https://me.jonymusky.com/es/blog/ats-compound-interest-talent-base</guid>
      <pubDate>Mon, 05 Oct 2026 12:00:00 GMT</pubDate>
      <description><![CDATA[Si hace cinco años que contratás, deberías tener cinco años de ventaja acumulada. La mayoría de los equipos no la tiene, porque usa su ATS como un tablero en lugar de una base de talento. Dónde se pierde el conocimiento, qué están haciendo Ashby, Greenhouse, Manatal, Spott y Selenios al respecto, y los hábitos que hacen que cada búsqueda sea más fácil que la anterior.]]></description>
      <category>recruiting</category>
      <category>ai-agents</category>
      <category>leadership</category>
      <content:encoded><![CDATA[<p>La mayoría de los equipos que pasan de una planilla a un ATS lo siguen usando como una planilla. Un tablero con columnas, un candidato por tarjeta, tarjetas que se mueven a la derecha hasta que alguien queda contratado y todos los demás quedan archivados. Funciona. Las posiciones se cubren. El problema no es lo que la herramienta hace por vos durante una búsqueda. El problema es todo lo que estás perdiendo mientras la usás de esa forma.</p>

<p>Cada búsqueda debería hacer más fácil la siguiente.</p>

<ul>
<li>Cada candidato que entrevistaste.</li>
<li>Cada screening.</li>
<li>Cada evaluación.</li>
<li>Cada conversación.</li>
<li>Cada motivo por el que alguien avanzó, o no.</li>
<li>Cada persona que era excelente, solo que no para ese rol.</li>
<li>Cada candidato que no estaba disponible hace ocho meses y hoy quizás sí.</li>
</ul>

<p>Todo eso debería convertirse en un activo de la empresa. Si hace cinco años que contratás, deberías tener cinco años de ventaja acumulada.</p>

<h2>Cinco años contratando, cinco años de ventaja</h2>

<p>El recruiting tiene la misma forma que el interés compuesto. Una sola búsqueda produce un poco de conocimiento reutilizable: una docena de personas a las que volverías a llamar sin dudarlo, algunas notas sobre por qué el mercado rechazó tu banda salarial, una evaluación que te dice qué significaba realmente "senior" para ese equipo. Por sí sola, parece nada. Si la guardás y la sumás a la siguiente búsqueda, y a la que viene después, deja de parecer nada.</p>

<figure>
<svg viewBox="0 0 750 340" width="100%" role="img" aria-labelledby="ats-c1-t ats-c1-d" style="display:block;height:auto;max-width:100%;font-family:var(--font-sans, 'Helvetica Neue', Arial, sans-serif)">
<title id="ats-c1-t">Candidatos calificados que podés contactar el primer día de cada búsqueda</title>
<desc id="ats-c1-d">Modelo ilustrativo de diez búsquedas en cinco años. Con una base de talento, el pool del primer día crece de 0 a unas 64 personas calificadas. Empezando de cero cada vez, se queda en alrededor de 3.</desc>
<line x1="64" x2="610" y1="288.0" y2="288.0" style="stroke:var(--rule, #d8d5c6);stroke-width:1"/>
<text x="54" y="292.0" text-anchor="end" style="fill:var(--fg-mute, #6f6e66);font-size:12px">0</text>
<line x1="64" x2="610" y1="213.7" y2="213.7" style="stroke:var(--rule, #d8d5c6);stroke-width:1"/>
<text x="54" y="217.7" text-anchor="end" style="fill:var(--fg-mute, #6f6e66);font-size:12px">20</text>
<line x1="64" x2="610" y1="139.4" y2="139.4" style="stroke:var(--rule, #d8d5c6);stroke-width:1"/>
<text x="54" y="143.4" text-anchor="end" style="fill:var(--fg-mute, #6f6e66);font-size:12px">40</text>
<line x1="64" x2="610" y1="65.1" y2="65.1" style="stroke:var(--rule, #d8d5c6);stroke-width:1"/>
<text x="54" y="69.1" text-anchor="end" style="fill:var(--fg-mute, #6f6e66);font-size:12px">60</text>
<text x="64.0" y="308" text-anchor="middle" style="fill:var(--fg-mute, #6f6e66);font-size:12px">1</text>
<text x="124.7" y="308" text-anchor="middle" style="fill:var(--fg-mute, #6f6e66);font-size:12px">2</text>
<text x="185.3" y="308" text-anchor="middle" style="fill:var(--fg-mute, #6f6e66);font-size:12px">3</text>
<text x="246.0" y="308" text-anchor="middle" style="fill:var(--fg-mute, #6f6e66);font-size:12px">4</text>
<text x="306.7" y="308" text-anchor="middle" style="fill:var(--fg-mute, #6f6e66);font-size:12px">5</text>
<text x="367.3" y="308" text-anchor="middle" style="fill:var(--fg-mute, #6f6e66);font-size:12px">6</text>
<text x="428.0" y="308" text-anchor="middle" style="fill:var(--fg-mute, #6f6e66);font-size:12px">7</text>
<text x="488.7" y="308" text-anchor="middle" style="fill:var(--fg-mute, #6f6e66);font-size:12px">8</text>
<text x="549.3" y="308" text-anchor="middle" style="fill:var(--fg-mute, #6f6e66);font-size:12px">9</text>
<text x="610.0" y="308" text-anchor="middle" style="fill:var(--fg-mute, #6f6e66);font-size:12px">10</text>
<text x="94.3" y="328" text-anchor="middle" style="fill:var(--fg-mute, #6f6e66);font-size:11px;letter-spacing:.04em">AÑO 1</text>
<text x="215.7" y="328" text-anchor="middle" style="fill:var(--fg-mute, #6f6e66);font-size:11px;letter-spacing:.04em">AÑO 2</text>
<text x="337.0" y="328" text-anchor="middle" style="fill:var(--fg-mute, #6f6e66);font-size:11px;letter-spacing:.04em">AÑO 3</text>
<text x="458.3" y="328" text-anchor="middle" style="fill:var(--fg-mute, #6f6e66);font-size:11px;letter-spacing:.04em">AÑO 4</text>
<text x="579.7" y="328" text-anchor="middle" style="fill:var(--fg-mute, #6f6e66);font-size:11px;letter-spacing:.04em">AÑO 5</text>
<text x="64" y="14" style="fill:var(--fg-mute, #6f6e66);font-size:12px">Personas calificadas para llamar el día uno</text>
<polyline points="64.0,288.0 124.7,276.9 185.3,276.9 246.0,276.9 306.7,276.9 367.3,276.9 428.0,276.9 488.7,276.9 549.3,276.9 610.0,276.9" style="fill:none;stroke:var(--fg-mute, #6f6e66);stroke-width:2;stroke-linejoin:round;stroke-linecap:round"/>
<circle cx="64.0" cy="288.0" r="4" style="fill:var(--fg-mute, #6f6e66);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Búsqueda 1: 0 personas</title></circle>
<circle cx="124.7" cy="276.9" r="4" style="fill:var(--fg-mute, #6f6e66);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Búsqueda 2: 3 personas</title></circle>
<circle cx="185.3" cy="276.9" r="4" style="fill:var(--fg-mute, #6f6e66);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Búsqueda 3: 3 personas</title></circle>
<circle cx="246.0" cy="276.9" r="4" style="fill:var(--fg-mute, #6f6e66);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Búsqueda 4: 3 personas</title></circle>
<circle cx="306.7" cy="276.9" r="4" style="fill:var(--fg-mute, #6f6e66);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Búsqueda 5: 3 personas</title></circle>
<circle cx="367.3" cy="276.9" r="4" style="fill:var(--fg-mute, #6f6e66);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Búsqueda 6: 3 personas</title></circle>
<circle cx="428.0" cy="276.9" r="4" style="fill:var(--fg-mute, #6f6e66);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Búsqueda 7: 3 personas</title></circle>
<circle cx="488.7" cy="276.9" r="4" style="fill:var(--fg-mute, #6f6e66);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Búsqueda 8: 3 personas</title></circle>
<circle cx="549.3" cy="276.9" r="4" style="fill:var(--fg-mute, #6f6e66);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Búsqueda 9: 3 personas</title></circle>
<circle cx="610.0" cy="276.9" r="4" style="fill:var(--fg-mute, #6f6e66);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Búsqueda 10: 3 personas</title></circle>
<polyline points="64.0,288.0 124.7,248.1 185.3,212.5 246.0,180.6 306.7,152.1 367.3,126.5 428.0,103.7 488.7,83.3 549.3,65.1 610.0,48.7" style="fill:none;stroke:var(--accent, #ff6600);stroke-width:2.5;stroke-linejoin:round;stroke-linecap:round"/>
<circle cx="64.0" cy="288.0" r="4" style="fill:var(--accent, #ff6600);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Búsqueda 1: 0 personas</title></circle>
<circle cx="124.7" cy="248.1" r="4" style="fill:var(--accent, #ff6600);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Búsqueda 2: 11 personas</title></circle>
<circle cx="185.3" cy="212.5" r="4" style="fill:var(--accent, #ff6600);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Búsqueda 3: 20 personas</title></circle>
<circle cx="246.0" cy="180.6" r="4" style="fill:var(--accent, #ff6600);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Búsqueda 4: 29 personas</title></circle>
<circle cx="306.7" cy="152.1" r="4" style="fill:var(--accent, #ff6600);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Búsqueda 5: 37 personas</title></circle>
<circle cx="367.3" cy="126.5" r="4" style="fill:var(--accent, #ff6600);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Búsqueda 6: 43 personas</title></circle>
<circle cx="428.0" cy="103.7" r="4" style="fill:var(--accent, #ff6600);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Búsqueda 7: 50 personas</title></circle>
<circle cx="488.7" cy="83.3" r="4" style="fill:var(--accent, #ff6600);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Búsqueda 8: 55 personas</title></circle>
<circle cx="549.3" cy="65.1" r="4" style="fill:var(--accent, #ff6600);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Búsqueda 9: 60 personas</title></circle>
<circle cx="610.0" cy="48.7" r="4" style="fill:var(--accent, #ff6600);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Búsqueda 10: 64 personas</title></circle>
<text x="622.0" y="52.7" style="fill:var(--fg, #1d1d1b);font-size:13px;font-weight:600">Base de talento</text>
<text x="622.0" y="68.7" style="fill:var(--fg-mute, #6f6e66);font-size:12px">~64 personas</text>
<text x="622.0" y="272.9" style="fill:var(--fg, #1d1d1b);font-size:13px;font-weight:600">Desde cero</text>
<text x="622.0" y="288.9" style="fill:var(--fg-mute, #6f6e66);font-size:12px">~3 personas</text>
<text x="337.0" y="328" text-anchor="middle" style="fill:none"></text>
</svg>
<figcaption>Modelo ilustrativo, no son datos de clientes. Dos búsquedas por año, unos 12 candidatos fuertes por búsqueda que vale la pena volver a llamar, y un 20% de ellos por año que deja de estar disponible o de ser relevante. La línea naranja los conserva; la gris solo conserva lo que un recruiter se acuerda de la última búsqueda.</figcaption>
</figure>

<p>Hay dos cosas para notar en ese gráfico. La primera es la brecha: para la búsqueda diez, un equipo abre un rol con sesenta y pico de personas calificadas que ya conoce, que ya evaluó, con notas sobre cada una. El otro lo abre con tres nombres y un aviso. La segunda es la forma de la línea gris. Nunca sube. Diez búsquedas de trabajo y el punto de partida es el mismo que el primer día.</p>

<p>Eso es lo que pasa cuando cada posición nueva arranca publicando de nuevo, esperando postulantes y leyendo CVs desde cero. La ventaja que deberías haber construido simplemente desaparece.</p>

<h2>El problema del Excel no es el Excel</h2>

<p>Es fácil echarle la culpa a la planilla, y algo de culpa tiene. No hay un buscador digno de ese nombre, no hay historial por persona, no hay vínculo entre el CV, la entrevista y la decisión. Pero los equipos que se pasaron a un ATS de verdad y mantuvieron los mismos hábitos tienen el mismo problema con una interfaz más linda.</p>

<p>El problema de fondo es que tu proceso de contratación genera información todos los días, y estás dejando que la mayor parte de ese conocimiento se pierda. Se pierde en lugares concretos:</p>

<ol>
<li><strong>El rechazo sin motivo.</strong> "Archivado" no le dice nada al próximo recruiter. "Backend fuerte, quería 100% remoto, volver a ver si abrimos roles remotos" le dice todo.</li>
<li><strong>La entrevista que vive en la cabeza de alguien.</strong> La mejor señal de todo el proceso es la conversación, y suele ser la parte que nunca se escribe.</li>
<li><strong>El medalla de plata.</strong> La persona que quedó segunda para un rol es, por definición, un candidato precalificado para el próximo rol parecido. La mayoría de los sistemas la archivan al lado de gente que no pasó el primer filtro.</li>
<li><strong>El "ahora no".</strong> Alguien que recién había empezado en un trabajo, se estaba mudando o tenía otra oferta. El timing era el único problema, y nadie anotó cuándo volver a intentar.</li>
<li><strong>La búsqueda que arranca con un aviso.</strong> Si el primer paso de una posición nueva es publicarla en lugar de buscar en lo que ya tenés, la base no se está usando aunque exista.</li>
</ol>

<p>Ninguna de estas es una falla del software. Son momentos en los que el proceso produjo algo valioso y nada lo capturó.</p>

<h2>El loop</h2>

<p>Un buen ATS no debería ser solo el lugar donde gestionás candidatos. Debería ser una base de talento que vale más con cada búsqueda. Eso solo pasa si el proceso es un loop, no una línea:</p>

<figure>
<svg viewBox="0 0 720 300" width="100%" role="img" aria-labelledby="ats-c2-t" style="display:block;height:auto;max-width:100%;font-family:var(--font-sans, 'Helvetica Neue', Arial, sans-serif)">
<title id="ats-c2-t">El loop: cada búsqueda alimenta la base, y la base arranca la siguiente búsqueda</title>
<defs><marker id="ats-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" style="fill:var(--fg-mute, #6f6e66)"/></marker></defs>
<rect x="30" y="40" width="310" height="74" rx="4" style="fill:var(--bg-2, #ecebe0);stroke:var(--rule, #d8d5c6);stroke-width:1"/>
<text x="48" y="71" style="fill:var(--fg, #1d1d1b);font-size:16px;font-weight:600">Abrir un rol</text>
<text x="48" y="94" style="fill:var(--fg-mute, #6f6e66);font-size:13px">Buscar primero en la base</text>
<rect x="380" y="40" width="310" height="74" rx="4" style="fill:var(--bg-2, #ecebe0);stroke:var(--rule, #d8d5c6);stroke-width:1"/>
<text x="398" y="71" style="fill:var(--fg, #1d1d1b);font-size:16px;font-weight:600">Screening y entrevista</text>
<text x="398" y="94" style="fill:var(--fg-mute, #6f6e66);font-size:13px">Notas, puntajes, transcripciones</text>
<rect x="380" y="190" width="310" height="74" rx="4" style="fill:var(--bg-2, #ecebe0);stroke:var(--rule, #d8d5c6);stroke-width:1"/>
<text x="398" y="221" style="fill:var(--fg, #1d1d1b);font-size:16px;font-weight:600">Decidir</text>
<text x="398" y="244" style="fill:var(--fg-mute, #6f6e66);font-size:13px">Por qué sí, por qué no, por qué no ahora</text>
<rect x="30" y="190" width="310" height="74" rx="4" style="fill:var(--bg-2, #ecebe0);stroke:var(--accent, #ff6600);stroke-width:2"/>
<text x="48" y="221" style="fill:var(--fg, #1d1d1b);font-size:16px;font-weight:600">Base de talento</text>
<text x="48" y="244" style="fill:var(--fg-mute, #6f6e66);font-size:13px">Etiquetada, buscable, con fechas</text>
<line x1="346" y1="77" x2="374" y2="77" style="stroke:var(--fg-mute, #6f6e66);stroke-width:1.5" marker-end="url(#ats-arrow)"/>
<line x1="535" y1="120" x2="535" y2="184" style="stroke:var(--fg-mute, #6f6e66);stroke-width:1.5" marker-end="url(#ats-arrow)"/>
<line x1="374" y1="227" x2="346" y2="227" style="stroke:var(--fg-mute, #6f6e66);stroke-width:1.5" marker-end="url(#ats-arrow)"/>
<line x1="185" y1="184" x2="185" y2="120" style="stroke:var(--fg-mute, #6f6e66);stroke-width:1.5" marker-end="url(#ats-arrow)"/>
<text x="360" y="292" text-anchor="middle" style="fill:var(--fg-mute, #6f6e66);font-size:12px">Salteate la caja de abajo a la izquierda y el loop es una línea: publicar, esperar, leer CVs, repetir.</text>
</svg>
<figcaption>Cada búsqueda produce señal. Si esa señal queda en algún lugar donde se puede buscar, la siguiente búsqueda arranca desde ahí en lugar de desde un aviso.</figcaption>
</figure>

<h2>Qué están haciendo las herramientas al respecto</h2>

<p>La industria se dio cuenta. Redescubrir el talento que ya tenés es hoy una feature estrella en todo el mercado, y vale la pena ver cómo lo encara cada producto:</p>

<ul>
<li><strong><a href="https://www.ashbyhq.com/product-updates/ai-talent-rediscovery" target="_blank" rel="noopener">Ashby</a></strong> lanzó AI Talent Rediscovery, que evalúa a las personas que ya están en tu base contra los criterios de un rol nuevo, actualiza perfiles desactualizados y usa señales como el avance por etapas y los puntajes de los scorecards para rankearlas. Su CRM agrupa contactos en talent pools que pueden pasar directo a un pipeline activo.</li>
<li><strong><a href="https://support.greenhouse.io/hc/en-us/articles/30184390692379-Talent-Rediscovery" target="_blank" rel="noopener">Greenhouse</a></strong> tiene Talent Rediscovery y filtrado de talento sobre candidatos y prospects anteriores, filtrable por scorecard, hito alcanzado y motivo de rechazo, junto con un CRM para prospects de sourcing.</li>
<li><strong><a href="https://www.manatal.com/features/ai-recommendations" target="_blank" rel="noopener">Manatal</a></strong> lee la descripción del puesto y puntúa a los candidatos que ya están en tu pool contra ella con matching semántico, así los postulantes anteriores aparecen para roles nuevos sin una búsqueda manual.</li>
<li><strong><a href="https://spott.io/" target="_blank" rel="noopener">Spott</a></strong> es un ATS y CRM AI-native para agencias de recruiting, construido alrededor de una base de recruiting pensada para seguir siendo útil con el tiempo, alimentada por memoria de conversaciones y procesamiento de transcripciones de entrevistas.</li>
<li><strong><a href="https://selenios.com" target="_blank" rel="noopener">Selenios</a></strong>, donde soy CTO, así que leé esta con eso en mente. Nuestra apuesta está en el lado de entrada del loop: screenings, entrevistas y evaluaciones se convierten en notas estructuradas asociadas al candidato, así la base se vuelve más densa con cada conversación y no solo con cada CV.</li>
</ul>

<p>Fijate de qué depende cada una de estas features. El rediscovery rankea personas usando los scorecards, los motivos de rechazo, las notas y el historial de etapas que tu equipo registró. Un modelo de matching es tan bueno como lo que puede leer. Apuntá el mejor motor de rediscovery del mercado a una base llena de tarjetas archivadas sin motivos y sin notas, y va a redescubrir CVs. Es lo único que tiene.</p>

<h2>Hábitos que hacen que la base rinda interés compuesto</h2>

<p>La herramienta es necesaria, pero no alcanza. Estos son los hábitos que definen si tu ATS se convierte en una base de talento o en un archivero:</p>

<ol>
<li><strong>Buscá antes de publicar.</strong> El primer paso de cada rol nuevo es una consulta sobre la gente que ya conocés. Publicar viene después.</li>
<li><strong>Cada rechazo lleva un motivo.</strong> Uno corto y estructurado. "Skills", "seniority", "salario", "ubicación", "timing", más una oración. Lleva diez segundos y es el dato más reutilizable que vas a producir.</li>
<li><strong>Etiquetá a los medalla de plata.</strong> Cualquiera que llegó a las etapas finales es un lead para el próximo rol parecido. Hacé que se los pueda encontrar como tales.</li>
<li><strong>Ponele fecha al "ahora no".</strong> Si el problema fue el timing, anotá cuándo podría cambiar y dejá que el sistema te lo recuerde.</li>
<li><strong>Capturá la conversación.</strong> Las notas de entrevista y las evaluaciones van en el perfil del candidato, no en un doc, un hilo de chat o la memoria de alguien. Si valió una hora del tiempo de un entrevistador, vale la pena guardarlo.</li>
<li><strong>Medilo.</strong> Seguí cuántas contrataciones y finalistas salen de gente que ya estaba en tu base. Si ese número no crece año a año, la base no está rindiendo interés compuesto.</li>
</ol>

<h2>Una base, o un historial</h2>

<p>Un ATS usado como tablero te da un registro prolijo de lo que pasó. Un ATS usado como base de talento te da ventaja sobre lo que viene. La diferencia no es el software que compraste. Es si cada búsqueda deja algo que la siguiente pueda usar.</p>

<p>Si después de años contratando tu próxima búsqueda sigue arrancando de cero, no construiste una base de talento. Construiste un historial.</p>
]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Batch inference: el mismo modelo, a mitad de precio, y producción ni se entera]]></title>
      <link>https://me.jonymusky.com/es/blog/batch-inference-half-price</link>
      <guid isPermaLink="true">https://me.jonymusky.com/es/blog/batch-inference-half-price</guid>
      <pubDate>Thu, 24 Sep 2026 12:00:00 GMT</pubDate>
      <description><![CDATA[Todos los grandes proveedores de LLM venden un modo batch con 50% de descuento, y la mayoría de los equipos sigue corriendo su trabajo masivo on-demand. Cómo lo usamos para migrar decenas de miles de CVs a mitad de costo sin tocar producción, qué te cobra a cambio, y el checklist que aprendimos a los golpes.]]></description>
      <category>llms</category>
      <category>recruiting</category>
      <category>architecture</category>
      <content:encoded><![CDATA[<p>Migrar miles de candidatos a través de un modelo de lenguaje cuesta lo que cuesta. O la mitad. En Selenios recortamos hasta un 50% de la factura de tokens cada vez que procesamos datos en bulk, con el mismo modelo, los mismos prompts y los mismos resultados. La técnica se llama batch inference, está en la lista de precios de todos los grandes proveedores, y la mayoría de los equipos con los que hablo todavía no la usa para el único tipo de carga donde es plata regalada. Esto es qué es, por qué sale la mitad, qué te cobra a cambio, y el checklist que escribimos después de aprenderlo a los golpes.</p>

<h2>Qué es batch inference</h2>

<p>En tiempo real, cada request espera su respuesta. En batch no hay conversación. Escribís un archivo con un objeto JSON por línea, miles de prompts, y lo subís a un bucket. Le decís al proveedor "procesá esto cuando puedas". Un rato después aparece otro archivo con todas las respuestas. Sin servidores tuyos esperando en sockets, sin lógica de reintentos, sin una cola que tengas que operar.</p>

<p>En Amazon Bedrock, que es donde lo corremos, el input es un archivo JSONL en S3 donde cada línea lleva un identificador y el mismo body que le mandarías a <code>InvokeModel</code>:</p>

<pre><code>{"recordId":"a1f3c9e2","modelInput":{ …the InvokeModel body for cv 1… }}
{"recordId":"9c02b7d4","modelInput":{ …the InvokeModel body for cv 2… }}
…
{"recordId":"0d41f8a7","modelInput":{ …the InvokeModel body for cv N… }}</code></pre>

<p>Después, una sola llamada a la API crea el job:</p>

<pre><code>import { BedrockClient, CreateModelInvocationJobCommand } from '@aws-sdk/client-bedrock';

const { jobArn } = await bedrock.send(new CreateModelInvocationJobCommand({
  jobName: `cv-parse-${runId}-${shard}`,
  modelId,
  roleArn,
  inputDataConfig: { s3InputDataConfig: { s3Uri: `s3://${bucket}/input/${runId}/${shard}.jsonl` } },
  outputDataConfig: { s3OutputDataConfig: { s3Uri: `s3://${bucket}/output/${runId}/` } },
}));</code></pre>

<p>A partir de ahí hacés polling del estado del job, y cuando termina leés los archivos de output, matcheás cada línea con su <code>recordId</code>, y seguís con tu pipeline como si las llamadas se hubieran hecho una por una.</p>

<h2>Por qué cuesta la mitad</h2>

<p>Porque le estás vendiendo al proveedor lo único que le sobra: capacidad ociosa. Las flotas de inferencia están dimensionadas para el pico de tráfico interactivo, y fuera del pico las GPUs quedan medio vacías. Cuando resignás latencia, ellos reciben trabajo que pueden meter en esos huecos, y el precio del token baja un 50%.</p>

<p>No es una promoción ni un descuento negociado. Es el precio de lista del modo batch en Amazon Bedrock, Anthropic, OpenAI y Gemini, los cuatro a la mitad de su tarifa on-demand. Mismo modelo, mismos pesos, mismo output. Lo único por lo que pagás menos es la promesa de una respuesta inmediata.</p>

<h2>Un caso real: una migración con años de historia</h2>

<p>Hace poco migramos a un cliente que venía de otro sistema de recruiting. Eso significa decenas de miles de CVs distintos, y con ellos las búsquedas, las notas de los recruiters, los movimientos entre etapas del pipeline y las evaluaciones de años de historia. Cada CV pasó por un modelo de lenguaje en Bedrock para extraer su estructura en el formato que espera nuestro producto.</p>

<p>La factura on-demand habría sido un número. La factura en batch fue exactamente la mitad, con el mismo modelo y los mismos resultados. No voy a poner cifras absolutas acá, y no importa: lo que importa es la proporción, y se mantiene a cualquier escala. Llevalo a miles de candidatos con toda su historia y la cuenta da igual. La mitad.</p>

<p>Hay un segundo ahorro que no tiene nada que ver con batch y que deberías hacer igual: antes de mandar nada, hasheamos cada archivo. En un sistema de recruiting el mismo CV aparece adjunto a muchas postulaciones. Un candidato que se postuló a cinco búsquedas tiene cinco copias del mismo PDF. Con un MD5 del archivo como clave, el mismo CV se parsea una sola vez y el resultado se replica a cada postulación que lo referencia. El token más barato es el que nunca mandás.</p>

<h2>La ventaja de la que nadie habla: batch no compite con producción</h2>

<p>El precio es el titular, pero no es la mejor razón. El tráfico on-demand sale de un pool compartido, y cuando ese pool se llena el proveedor te responde con un 503 y un mensaje de "insufficient capacity". Lo vivimos. Ahora imaginate que, encima de tu tráfico normal, disparás miles de llamadas desde un script de migración por el mismo canal, con el mismo modelo y la misma cuenta. Lo pagan tus usuarios, en latencia y en errores, en las pantallas que realmente usan.</p>

<p>Batch es otra cola con otra cuota. La migración corre de noche y la plataforma ni se entera. La propia guía de Bedrock para errores de capacidad en on-demand dice lo mismo: mové las cargas offline a batch. Este es el argumento que me convenció más que el descuento. Pagar la mitad está bueno. Que el trabajo masivo nunca toque la experiencia de la gente que usa tu producto es lo que realmente gana.</p>

<h2>Qué te cobra a cambio</h2>

<p>Nada es gratis, así que acá va la cuenta, con los detalles de Bedrock porque es donde lo corremos:</p>

<ol>
<li><strong>Latencia.</strong> Minutos u horas, con el compromiso de terminar dentro de las 24 horas. No elegís cuándo.</li>
<li><strong>Menos features.</strong> En Bedrock batch no hay tool calling ni structured output. Pedís JSON en el prompt y lo parseás vos, con la misma validación que usarías para cualquier output de un modelo en el que no confiás.</li>
<li><strong>Sin camino de OCR en varios modelos.</strong> Si parte de tu input son imágenes escaneadas, esas necesitan su propia ruta on-demand.</li>
<li><strong>El orden del output no está garantizado.</strong> La línea 1 del input no es la línea 1 del output. El <code>recordId</code> es lo único que las une.</li>
<li><strong>Un mínimo por job.</strong> Por defecto, 100 registros. Con menos, el job se rechaza.</li>
</ol>

<p>Si tu caso de uso es interactivo, nada de esto sirve. Si nadie está esperando la respuesta, todo es aceptable.</p>

<h2>El checklist que aprendimos a los golpes</h2>

<p>Nada de esto está en la guía de getting started. Todo nos costó algo la primera vez.</p>

<ul>
<li><strong>Deduplicá antes de mandar.</strong> Hasheá el archivo, no la postulación. Un parseo por CV, no uno por postulación.</li>
<li><strong>Respetá las cuotas.</strong> Al menos 100 registros por job, 1 GB por archivo de input, 5 GB por job. Nosotros shardeamos en 50.000 registros.</li>
<li><strong>Uní el último shard si queda corto.</strong> 50.050 registros partidos ingenuamente en shards de 50.000 te dejan un shard final de 50, y ese job se rechaza. El fix son tres líneas, y es el tipo de bug que solo encontrás con datos reales:
<pre><code>function shard&lt;T&gt;(items: T[], size = 50_000, min = 100): T[][] {
  const shards: T[][] = [];
  for (let i = 0; i &lt; items.length; i += size) shards.push(items.slice(i, i + size));
  const last = shards.at(-1);
  if (shards.length &gt; 1 &amp;&amp; last &amp;&amp; last.length &lt; min) shards.at(-2)!.push(...shards.pop()!);
  return shards;
}</code></pre>
El shard combinado termina un poco por encima del tamaño objetivo, así que dejá el objetivo bien por debajo de los límites reales.</li>
<li><strong>Persistí el estado de cada job apenas lo aceptan.</strong> Un job se factura desde el segundo en que el proveedor lo acepta. Si tu script se cae después de crear el job pero antes de guardar su ARN, y lo relanzás, pagás dos veces el mismo trabajo. Escribí el ARN del job y el shard que cubre en almacenamiento durable antes de hacer cualquier otra cosa, y hacé que el launcher saltee los shards que ya tienen un job.</li>
<li><strong>Usá identificadores cortos y únicos.</strong> El output vuelve desordenado, así que cada línea necesita un <code>recordId</code> que mapee a tus propios datos sin ambigüedad. Un hash corto funciona mejor que una clave compuesta larga que después vas a tener que parsear.</li>
<li><strong>Validá con el mismo parser que usás on-demand.</strong> El import no debería poder distinguir por qué camino llegó un resultado. Si el output de batch necesita su propio parser, ahora tenés dos fuentes de verdad y una de las dos se va a desalinear.</li>
<li><strong>Borrá todos los archivos del bucket cuando termines.</strong> Inputs y outputs. Son CVs, o sea datos personales, y nada debería quedar en S3 después de verificar el import. Hacé que la limpieza sea parte del job, no una nota en un runbook.</li>
</ul>

<h2>Quién lo soporta</h2>

<p>Los cuatro grandes proveedores lo ofrecen con 50% de descuento, con límites distintos. A esta semana:</p>

<table>
<thead><tr><th>Proveedor</th><th>Cómo</th><th>Límites que conviene conocer</th></tr></thead>
<tbody>
<tr><td>Amazon Bedrock</td><td>JSONL en S3, <code>CreateModelInvocationJob</code></td><td>1 GB por archivo, 5 GB por job, mínimo 100 registros por job, sin tool calling ni structured output. Disponible para Claude, Nova, Llama, Mistral, DeepSeek, Qwen y otros.</td></tr>
<tr><td>Anthropic</td><td>Message Batches API</td><td>Hasta 100.000 requests o 256 MB por batch. La mayoría de los batches termina en menos de una hora. Resultados disponibles por 29 días.</td></tr>
<tr><td>OpenAI</td><td>Batch API, archivo JSONL</td><td>50.000 requests y 200 MB por archivo, ventana de 24 horas, rate limits separados de la API sincrónica.</td></tr>
<tr><td>Gemini</td><td>Batch Mode, inline o archivo</td><td>Archivos de hasta 2 GB, objetivo de 24 horas, el context caching funciona dentro del batch.</td></tr>
</tbody>
</table>

<p>Si ya usás el AI SDK, hay un camino más simple para batches chicos. El AI Gateway de Vercel soporta batch con <code>experimental_startBatch</code>, <code>experimental_getBatchStatus</code> y <code>experimental_getBatchResults</code>, sin S3 de por medio: los requests van inline, hasta 1.000 por batch y 4,5 MB, y el mismo código funciona contra modelos de OpenAI y Anthropic.</p>

<pre><code>import {
  experimental_startBatch as startBatch,
  experimental_getBatchStatus as getBatchStatus,
  experimental_getBatchResults as getBatchResults,
} from 'ai';

const batch = await startBatch({
  requests: cvs.map((cv) =&gt; ({
    id: cv.hash,
    type: 'text',
    model: modelId, // any OpenAI or Anthropic model on the gateway
    prompt: buildParsePrompt(cv.text),
  })),
});
await saveBatchReference(batch); // { version, id, provider }: persist it before anything else

// later, from a cron or a worker
const { status } = await getBatchStatus({ batch });
if (status === 'completed') {
  for await (const item of getBatchResults({ batch })) {
    if (item.status === 'succeeded') await importParsedCv(item.id, parseCv(item.text));
  }
}</code></pre>

<p>La misma forma que la versión de Bedrock, sin tener que hacerte cargo de un bucket, un rol y la limpieza. Es ideal para batches chicos y medianos. Para decenas de miles de CVs, el archivo en S3 sigue siendo la herramienta correcta.</p>

<h2>La regla</h2>

<p>Toda la decisión entra en una pregunta: <strong>¿alguien está esperando esta respuesta?</strong> Si sí, on-demand. Si no, batch. Migraciones, re-scoring masivo, enriquecimiento de datos, evals, clasificar registros históricos: ninguno tiene a un humano del otro lado mirando un spinner, y todos están pagando el doble si corren on-demand.</p>

<p>Mismo modelo, mitad de precio, y tus usuarios ni se enteran. Y la verdad, la mitad de precio es lo de menos. Lo que importa es que el trabajo masivo nunca toque la experiencia de la gente que usa tu producto.</p>]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Jev no genera. De eso se trata: un loop de QA en el browser sobre el modelo System One de TypeSafe]]></title>
      <link>https://me.jonymusky.com/es/blog/jev-system-one-browser-qa</link>
      <guid isPermaLink="true">https://me.jonymusky.com/es/blog/jev-system-one-browser-qa</guid>
      <pubDate>Sat, 19 Sep 2026 12:00:00 GMT</pubDate>
      <description><![CDATA[Esta semana llegó al AI Gateway de Vercel un modelo que devuelve decisiones calibradas en lugar de texto, y se convirtió en el lanzamiento con la adopción más rápida de su historia. Armé un loop de QA con Playwright donde Jev juzga cada paso, lo medí y lo comparé con los LLMs por los que ya estás pagando.]]></description>
      <category>ai-agents</category>
      <category>dev-tools</category>
      <content:encoded><![CDATA[<p>El 15 de septiembre apareció en el AI Gateway de Vercel un modelo que no puede escribir una oración. No puede llamar a una tool, no puede explicarse, y su output máximo es de cero tokens. Tres días después Vercel <a href="https://vercel.com/blog/ai-gateway-jev-model-launch">escribió</a> que había llegado a casi el 13% de los equipos pagos en sus primeras 24 horas, el doble que cualquier lanzamiento anterior y seis veces lo que logró Fable 5.1 en su primer día. El modelo es Jev, de una empresa llamada TypeSafe, y es el primero de lo que ellos llaman modelos System One. Pasé esta semana armando un loop de QA en el browser encima de él. Esto es qué es, por qué creo que el entusiasmo está justificado, qué construí y cómo se ven los números al lado de los LLMs por los que ya estás pagando.</p>

<p>Una nota práctica antes del argumento: al momento de escribir esto, Vercel tiene una promoción de lanzamiento y Jev es gratis en AI Gateway hasta el 25 de septiembre. Después cuesta USD 0.042 por millón de tokens de input, y el output es gratis porque no hay output. De cualquier manera cuesta casi nada, y eso es parte de la historia.</p>

<h2>Qué es Jev, en un párrafo</h2>

<p>Le pasás a Jev un <strong>estado</strong> (un string, un objeto JSON, hasta 32k tokens) y un conjunto de <strong>preguntas</strong>. Cada pregunta es una de tres primitivas: un <strong>boolean</strong> ("¿se cumple esta afirmación?"), un <strong>choice</strong> ("¿cuál de estas opciones?") o un <strong>score</strong> ("¿dónde cae en esta escala ordenada?"). Responde cada pregunta con un valor tipado y una distribución de probabilidad, y nada más. Sin prosa, sin traza de razonamiento, sin JSON para parsear. TypeSafe lo entrena con un método que llaman Reinforcement Learning for Calibrated Decisions, así que las probabilidades se optimizan contra resultados y no contra la preferencia de un humano por una respuesta linda. Un 0.97 debería acertar más o menos el 97% de las veces.</p>

<p>Con el AI SDK se ve así:</p>

<pre><code>import { experimental_evaluate as evaluate } from 'ai';

const result = await evaluate({
  model: 'typesafe-ai/jev',
  state: { url, title, visibleText },
  questions: {
    signedIn: {
      type: 'boolean',
      instructions: 'Is the user signed in, judging from the page?',
    },
    section: {
      type: 'choice',
      instructions: 'Which section is the page showing?',
      criteria: { dashboard: null, login: null, other: null },
    },
  },
});
// result.answers.signedIn.probability   -> 0.9
// result.answers.section.choice         -> 'dashboard'
// result.answers.section.probabilities  -> { dashboard: 1, login: 0, other: 0 }</code></pre>

<p>Esa es toda la API. El paquete <code>ai</code> sumó <code>experimental_evaluate</code> en la 7.0.105 exactamente para esta clase de modelo, y el gateway lo expone como un "evaluation model" al lado de los modelos de lenguaje, de embeddings y de imágenes. Es una columna nueva en el catálogo, no una fila nueva.</p>

<h2>Por qué esto es más importante que un clasificador barato</h2>

<p>Fijate dónde están realmente las llamadas a LLMs en un codebase en producción. Una minoría genera texto que va a leer un humano. La mayoría toma una decisión sobre la que va a actuar el código: rutear este ticket, ¿este CV cumple la pregunta excluyente?, ¿este mensaje es urgente?, ¿cuál de estos cinco candidatos es el que el usuario quiso decir?, ¿este paso salió bien? Durante tres años la forma de obtener esa decisión fue pedirle a un modelo de chat que por favor responda en JSON, parsear el JSON, reintentar cuando no parsea, y tratar la respuesta como binaria porque el modelo no te da un número honesto de qué tan seguro está.</p>

<p>Cada una de esas llamadas paga por generación. Pagás los tokens de output, esperás a que se hagan stream, y heredás un modo de falla (output mal formado) que no tiene nada que ver con la decisión. Los modos de structured output hicieron el parseo más confiable pero no lo hicieron más rápido ni más barato, y los logprobs, donde existen, no están calibrados. Jev elimina el paso de generación por completo. La decisión <em>es</em> el output. No hay nada que parsear, nada que reintentar por problemas de formato, y el número que vuelve significa lo que dice.</p>

<p>La consecuencia es que las decisiones se vuelven lo suficientemente baratas como para tomar muchas. Hacé cinco preguntas sobre el mismo estado y corren en paralelo en un solo request. Hacé una pregunta especulativa que capaz no necesitás. Puntuá a cada candidato en cuatro dimensiones y dejá que el ranking sea una suma ponderada que podés cambiar sin otra inferencia. Es una forma distinta de escribir software alrededor de un modelo, y es por eso que la documentación de TypeSafe se lee más como una guía de programación que como una guía de prompting. El modelo es una primitiva. El código es dueño del workflow.</p>

<p>Lo que Jev no puede hacer es igual de importante, porque los límites son el diseño. No genera, así que no puede escribir un selector, un resumen, un mail ni una línea de código. No puede planificar. Lee texto, no imágenes. Si tu tarea necesita algo de eso, seguís necesitando un modelo de lenguaje. El modelo mental correcto no es "un LLM más chico" sino "el juicio rápido y calibrado que ponés antes, al lado o después del LLM".</p>

<h2>Qué construí: un loop de QA donde Playwright maneja y Jev juzga</h2>

<p>El testing end-to-end en el browser es un buen lugar para probar esto, porque es exactamente el tipo de carga donde las herramientas actuales gastan un turno de un modelo frontier en cada click. Las herramientas de QA basadas en agentes (Expect, browser-use y compañía) le dan un browser a un agente de código y lo dejan planificar, clickear, tipear y verificar. Funciona, es lento, y es caro de la forma que se acumula: un flow de 20 pasos son 20 o más turnos del agente.</p>

<p>La división con la que terminé es simple y defendería cada línea:</p>

<table>
<thead><tr><th></th><th>Playwright (código)</th><th>Jev (modelo)</th></tr></thead>
<tbody>
<tr><td>Navegación, tipeo, esperas</td><td>sí</td><td></td></tr>
<tr><td>Video, trace, screenshots</td><td>sí</td><td></td></tr>
<tr><td>"¿Se cumple esta afirmación en la página?"</td><td></td><td>sí, una pregunta boolean</td></tr>
<tr><td>"¿Qué control visible coincide con esta intención?"</td><td></td><td>sí, un choice entre los controles que encontró el código</td></tr>
<tr><td>"¿Cuál es el próximo click hacia este objetivo?"</td><td></td><td>sí, un choice entre las acciones observadas o DONE / BLOCKED / WAIT</td></tr>
</tbody>
</table>

<p>El ingreso de texto nunca pasa cerca del modelo. El código saca un snapshot de la página (URL, título, texto visible deduplicado, el árbol aria, y cada control clickeable visible etiquetado con un índice), y Jev solo elige entre cosas que el código ya encontró. Nunca inventa un selector. Es la misma forma que el bridge <a href="https://github.com/wy-coliney/jev-browser-use">jev-browser-use</a> que alguien publicó para Codex esta semana, adaptado a Playwright pelado para que corra en cualquier lado.</p>

<p>Encima de las tres primitivas armé lo que un equipo realmente necesita:</p>

<ul>
<li><strong>Una CLI para agentes.</strong> Un flow es un array JSON de pasos. La CLI lo corre headless, lo filma e imprime un único resultado JSON con pass o fail por paso, la probabilidad detrás de cada juicio, el path al video y el path a un screenshot final. Exit code 0 o 1. Las credenciales se referencian como placeholders <code>${QA_EMAIL}</code> y se sustituyen desde el entorno de la propia CLI, así que un agente que escribe un flow nunca las ve.</li>
<li><strong>Un dashboard.</strong> Un único archivo HTML servido localmente, en inglés, español o portugués. Cada run, cada test, cada decisión de Jev con su probabilidad, latencia y cantidad de tokens, con el video embebido.</li>
<li><strong>Un skill.</strong> Un <code>SKILL.md</code> que le enseña a Claude Code, Codex, Cursor o Grok Build cómo escribir un flow y cómo leer el resultado. "Testeá este branch en el browser" pasa a ser algo que un agente puede hacer sin un humano mirando.</li>
</ul>

<figure>
<img src="/img/jev/dashboard-overview.png" alt="El dashboard de jev-browser-qa: cinco runs en la barra lateral, tiles de KPI para aprobados, fallidos, duración, llamadas a Jev, latencia media y tokens de input, y una card de test aprobado" width="1280" height="820" loading="lazy">
<figcaption>El dashboard después de una tarde de runs. 545 ms de latencia media de Jev en las cinco decisiones del último flow.</figcaption>
</figure>

<p>Acá va un run real contra nuestro producto. El flow hace login de forma determinística, después le pasa un objetivo a Jev y después le pide que verifique:</p>

<pre><code>{ "goal": "Open the account or workspace settings screen. Do not change any setting.",
  "maxSteps": 8,
  "denyNames": ["cerrar sesi", "logout", "eliminar", "delete", "guardar", "save"] },
{ "expect": "A settings or configuration screen is displayed." }</code></pre>

<pre><code>[8/9] goal: Open the account or workspace settings screen. Do not change any setting.
    step 1: Click button "Menú de usuario: Olivia Owner" p=0.76
    step 2: Click link "Mi cuenta" (href=/settings) p=0.99
    step 3: WAIT p=0.38
    step 4: DONE p=1.00
    passed (3245 ms)
[9/9] expect: A settings or configuration screen is displayed.
    passed p=0.99 (443 ms)</code></pre>

<p>Cuatro decisiones, 3.2 segundos incluyendo la carga de las páginas, y la tercera es mi favorita: la página estaba en plena transición después del click, y en lugar de clickear otra cosa Jev eligió WAIT con una probabilidad baja, que es exactamente lo que tiene que decir un modelo cuando el estado es ambiguo. En el flow de login, cuando le pedí "the button that submits the login form", eligió "Iniciar sesión" con 0.97 en una página en español que nunca había visto. La intención estaba en inglés. No importó.</p>

<figure>
<img src="/img/jev/dashboard-run.png" alt="Una card de run expandida en el dashboard: el video grabado a la izquierda y, a la derecha, el juicio de Jev al 99% más los cuatro pasos del objetivo con sus probabilidades y latencias" width="1280" height="600" loading="lazy">
<figcaption>El mismo run en el dashboard. Cada decisión se puede inspeccionar después, con el video al lado.</figcaption>
</figure>

<h2>Los números</h2>

<p>A Jev lo medí yo. Veinte llamadas secuenciales a través de AI Gateway desde Buenos Aires, cada una con una pregunta boolean y una choice sobre un estado de página realista de unos 400 tokens:</p>

<table>
<thead><tr><th>Jev vía AI Gateway</th><th>Valor</th></tr></thead>
<tbody>
<tr><td>Latencia p50 (ida y vuelta)</td><td>388 ms</td></tr>
<tr><td>Latencia p90</td><td>471 ms</td></tr>
<tr><td>mín / máx</td><td>330 ms / 883 ms</td></tr>
<tr><td>Tokens de input por llamada</td><td>395 (juicios), ~1,500 (pasos de objetivo con la lista de controles)</td></tr>
<tr><td>Costo por juicio</td><td>USD 0.0000166. Mil assertions por menos de dos centavos.</td></tr>
</tbody>
</table>

<p>La comparación de abajo es la parte para leer con cuidado. Los otros modelos <strong>no</strong> los corrí: tomé los precios de lista de la página de pricing de cada proveedor y las latencias de Artificial Analysis, y calculé cuánto costaría el mismo juicio si se lo pidieras a un modelo de lenguaje como un objeto JSON chico: 750 tokens de input (el estado más la pregunta) y 25 de output. El time to first token es la cifra publicada para la configuración sin reasoning cuando existe; el tiempo total suma esos 25 tokens a la velocidad de output publicada. Tomá la columna de latencia como una estimación y la de costo como aritmética.</p>

<table>
<thead><tr><th>Modelo</th><th>Precio in / out (USD por 1M)</th><th>Costo por juicio</th><th>vs Jev</th><th>Latencia estimada</th></tr></thead>
<tbody>
<tr><td><strong>Jev (medido)</strong></td><td>0.042 / 0</td><td>0.0000315</td><td>1x</td><td><strong>0.39 s medido</strong></td></tr>
<tr><td>GPT-5 nano</td><td>0.05 / 0.40</td><td>0.0000475</td><td>1.5x</td><td>~0.5 a 1 s sin reasoning (76 s con reasoning en high, según Artificial Analysis)</td></tr>
<tr><td>GPT-5 mini</td><td>0.25 / 2.00</td><td>0.00024</td><td>7.6x</td><td>~1 s sin reasoning</td></tr>
<tr><td>Gemini 3.5 Flash-Lite</td><td>0.30 / 2.50</td><td>0.00029</td><td>9.2x</td><td>~0.5 s sin thinking (9.4 s con thinking)</td></tr>
<tr><td>Claude Haiku 4.5</td><td>1.00 / 5.00</td><td>0.000875</td><td>28x</td><td>~1.0 s (0.69 s TTFT + 25 tokens a 83 tok/s)</td></tr>
<tr><td>Gemini 3.5 Flash</td><td>1.50 / 9.00</td><td>0.00135</td><td>43x</td><td>~1 s sin thinking (15 s con)</td></tr>
<tr><td>Claude Sonnet 5</td><td>2.00 / 10.00</td><td>0.00175</td><td>56x</td><td>~1.5 a 2 s</td></tr>
<tr><td>Claude Fable 5.1</td><td>10.00 / 50.00</td><td>0.00875</td><td>278x</td><td>varios segundos</td></tr>
</tbody>
</table>

<p>Se destacan tres cosas. Primero, los LLMs más baratos están a un múltiplo chico de Jev en precio, así que en el tier nano el costo solo no es el argumento; la latencia y la calibración sí. Segundo, los modelos que la gente realmente usa cuando quiere un juicio en el que pueda confiar (Haiku, Flash, Sonnet) cuestan entre 30 y 60 veces más y tienen entre dos y cinco veces la latencia, y aun así no te dan una probabilidad. Tercero, la cifra de titular de TypeSafe, "194x faster and 445x cheaper", es contra workflows de tipo reasoning, y una vez que mirás las latencias con reasoning en esa tabla (76 segundos para nano en high) deja de sonar a marketing. Para el loop de QA la comparación que importa es por flow: el run de settings de arriba tomó cinco decisiones por USD 0.0003. Corrélo después de cada commit en cada branch y la factura sigue siendo cero.</p>

<h2>Lo que aprendí en una semana</h2>

<ul>
<li><strong>Escribí las afirmaciones como un QA engineer escribe resultados esperados.</strong> "Se muestra una lista de posiciones abiertas, o un estado vacío que invita a crear la primera" funciona. "La página está bien" no. Cuando una afirmación es sutil, dale criterios explícitos de verdadero y falso; el modelo los usa.</li>
<li><strong>Una probabilidad cerca de tu umbral es feedback sobre la afirmación, no sobre el umbral.</strong> Afilá la pregunta. No bajes la vara.</li>
<li><strong>Las deny lists no son opcionales en un loop de acciones.</strong> Jev va a elegir "Cerrar sesión" sin problema si es el único control que parece avanzar. El loop solo ofrece controles únicos y permitidos, y los nombres destructivos nunca se ofrecen.</li>
<li><strong>Texto, no píxeles.</strong> Jev nunca ve el screenshot. Las regresiones visuales siguen necesitando un modelo de visión o un diff de píxeles. El screenshot del reporte es para el humano.</li>
<li><strong>La calibración es por población, no por respuesta.</strong> La documentación lo dice y se nota: un 0.76 en "clickear el menú de usuario" fue el click correcto, y un 0.38 en WAIT fue la duda correcta. Leé la distribución, no solo el argmax.</li>
<li><strong>Se combina con las herramientas de agentes que ya usás.</strong> El lugar natural para esto no es reemplazar a Expect o a un agente de código, sino ser la capa mecánica a la que llaman para dejar de gastar un turno por click. Eso es justamente lo que hace el bridge de Codex, y lo que hacen la CLI y el skill de acá para todo lo demás.</li>
</ul>

<h2>Probalo</h2>

<p>El proyecto es open source en <a href="https://github.com/jonymusky/jev-browser-qa">github.com/jonymusky/jev-browser-qa</a>. Es chico a propósito: tres primitivas, una CLI, un dashboard, un skill y un README que dice lo que Jev no va a hacer.</p>

<pre><code>git clone https://github.com/jonymusky/jev-browser-qa &amp;&amp; cd jev-browser-qa
pnpm install &amp;&amp; pnpm exec playwright install chromium
cp .env.example .env          # AI_GATEWAY_API_KEY, BASE_URL, QA_EMAIL, QA_PASSWORD
pnpm jev:smoke                # one call, no browser
pnpm -s qa run flows/login.json
pnpm dashboard                # http://localhost:4310</code></pre>

<p>Y para tu agente: <code>npx skills add https://github.com/jonymusky/jev-browser-qa --skill jev-qa</code>. Lo vengo corriendo desde Claude Code y desde <a href="https://docs.x.ai/build/overview">Grok Build</a>, el agente de terminal de xAI, que lee Agent Skills y <code>AGENTS.md</code> sin ninguna configuración: el mismo prompt, "test this branch with jev-qa and report the verdict with the video path", funciona en los dos, y ninguno de los agentes ve nunca una credencial porque los flows llevan placeholders <code>${QA_EMAIL}</code> que resuelve la CLI. En el repo hay un <a href="https://github.com/jonymusky/jev-browser-qa/blob/main/docs/agents/grok-build.md">walkthrough corto de Grok Build</a>, que incluye la forma headless <code>grok -p</code> para CI. Si querés probarlo sin instalar nada, publiqué un bot de Grok con el skill precargado: <a href="https://x.ai/bot/IoOXnqvhpn4i_C1TNq9P_">QA Engineer</a>. Pasale la URL de tu app y el flow que querés verificar, y te devuelve el veredicto, el video y el screenshot.</p>

<p>En este post tuve cuidado de separar lo que medí de lo que inferí, porque las promesas alrededor de Jev son grandes y el modelo tiene cuatro días. Pero el diseño no es un truco. Un modelo que devuelve una decisión calibrada en lugar de un párrafo, en 400 milisegundos, por una fracción de centavo, cambia en qué partes de tu sistema es razonable poner un modelo. Hasta ahora la respuesta era "en las partes que se lo pueden permitir". Desde esta semana la respuesta se parece más a "en cualquier lugar donde, si no, escribirías un if y te sentirías mal por eso".</p>]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Next.js 16.3 desde la silla del CTO: cinco apps, un mes, tres bugs]]></title>
      <link>https://me.jonymusky.com/es/blog/nextjs-16-3-from-the-cto-seat</link>
      <guid isPermaLink="true">https://me.jonymusky.com/es/blog/nextjs-16-3-from-the-cto-seat</guid>
      <pubDate>Thu, 03 Sep 2026 12:00:00 GMT</pubDate>
      <description><![CDATA[Adoptamos Next.js 16.3 en cinco apps en producción la misma semana que salió. Qué rindió, cómo Cache Components nos cambió en silencio los códigos de estado HTTP y por qué experimental sigue queriendo decir experimental.]]></description>
      <category>nextjs</category>
      <category>leadership</category>
      <content:encoded><![CDATA[<p>Next.js 16.3 salió a principios de agosto con el titular "la actualización más grande desde 16.0". La adoptamos esa misma semana en cinco apps en producción, después convivimos un mes con ella, y esta semana pasamos todo al último patch y revisamos de nuevo cada afirmación del release post. Esto es lo que se sostuvo, lo que no, y lo que le diría a otro CTO antes de que active los flags.</p>

<p>Contexto, para que los números signifiquen algo: corremos cinco apps de Next.js. Tres viven en un monorepo de pnpm: un producto de administración de 56 rutas detrás de auth, un sitio público para candidatos con formularios y entrevistas con IA, y un portal chico para empleados. Las otras dos están en repos separados: un dashboard interno de analytics y un sitio de marketing en tres idiomas con un blog en MDX. Las cinco hacen deploy en Vercel. Las cinco corren hoy con React Compiler y Cache Components.</p>

<h2>Lo que rindió desde el primer día</h2>

<p><strong>El cache de build es real.</strong> El cache en filesystem de Turbopack para <code>next build</code> viene activado por defecto en 16.3. Medido esta semana con el último patch, en una laptop: la app de 56 rutas compila en 30 segundos en frío y en 0,8 segundos cuando no cambió nada. En 16.3.0 el mismo rebuild con cache tardaba 6,6 segundos, así que la línea de patches lo mejoró ocho veces sin que tocáramos nada. Las apps más chicas andan entre 4 y 7 segundos en frío y medio segundo con cache. El tiempo total es mayor (la recolección de page data y la generación estática no se cachean), pero el paso de compilación era la parte que nos tenía esperando.</p>

<p><strong>La evicción de memoria cambió una decisión, no un número.</strong> Nunca medimos la memoria en dev, pero es la razón por la que nuestro sitio de marketing finalmente dejó <code>next dev --webpack</code> como default. Las sesiones largas de Turbopack crecían hasta que la laptop se quejaba; con la evicción activada por defecto, eso dejó de ser un motivo para mantener webpack. Webpack queda como script de fallback, y eso trae su propia lección más abajo.</p>

<p><strong><code>catchError</code> es el error boundary que realmente queríamos.</strong> Un <code>error.tsx</code> clásico se traga <code>notFound()</code> y <code>redirect()</code>, y su reset solo limpia el estado del cliente. El nuevo boundary de <code>next/error</code> no hace ninguna de las dos cosas: deja pasar esas dos, y su <code>retry()</code> vuelve a hacer fetch de los Server Components que tiene abajo. Lo usamos alrededor de páginas que hacen fetch en el server (con el fallback reportando a Sentry) y, en el blog, alrededor del cuerpo MDX, para que un post roto se degrade a un bloque de "volvé a intentar" en vez de una página en blanco.</p>

<p><strong>TypeScript 7 en <code>next build</code>, con una trampa de pnpm.</strong> Cuatro de las cinco apps hacen type-check con el <code>tsc</code> nativo. La que no es el sitio de marketing, porque lintea con <code>typescript-eslint</code>, cuyo parser se rompía con la API de TypeScript 7 cuando lo probamos. Las apps que lintean con Biome nunca tuvieron ese problema. Y con el linker aislado de pnpm, el compilador nativo no incluye automáticamente <code>node_modules/@types</code>, así que cada app necesitó un array <code>"types"</code> explícito en su tsconfig. Diez minutos, pero diez minutos confusos.</p>

<h2>Cache Components cambia la semántica HTTP, no solo la velocidad</h2>

<p>Esta es la parte que pondría en negrita en cada guía de migración. Con Cache Components una página hace streaming sobre un shell prerenderizado, y el shell sale con un 200. Si después tu página llama a <code>notFound()</code> mientras renderiza la parte dinámica, el status ya está en el cable. El usuario ve tu UI de not-found. Google ve un 200.</p>

<p>Lo medimos en el sitio de marketing antes de arreglarlo: un slug de blog inexistente, un slug de landing de anuncios inexistente y un slug de página de comparación inexistente respondían todos 200. Tres familias de rutas enteras devolviendo "encontrado" para cualquier basura, justo en la propiedad donde los crawlers son el cliente. El arreglo sigue la guía del propio framework: establecer que la página existe <em>antes</em> del primer byte. Nuestro proxy ahora valida los slugs del blog contra un manifest generado en build (así no puede quedar desactualizado en producción, y lleva las fechas de publicación para que los posts programados sigan funcionando), valida los otros dos contra sus tablas estáticas, y reescribe lo que no matchea a un path que ninguna ruta atiende. El router entonces sirve la página de not-found con nuestra marca y un 404 de verdad.</p>

<p>El segundo cambio semántico es que los segment configs desaparecieron. <code>export const revalidate = 3600</code>, <code>dynamic = "force-static"</code>, <code>dynamic = "force-dynamic"</code>: ninguno está permitido una vez que activás el flag. No es un renombre. <code>revalidate</code> pasa a ser un scope <code>'use cache'</code> con <code>cacheLife('hours')</code>, y todo lo que lea el reloj se tiene que mover <em>adentro</em> de ese scope. Nuestro blog filtra posts por fecha de publicación con <code>new Date()</code>; fuera de un scope de cache eso es data de request y un error de prerender directo, adentro el reloj se lee una vez por entrada de cache y los posts programados igual aparecen dentro de la hora. Una vez hecho eso, se prerenderizaron 204 páginas en build y un segundo hit a un post pasó de 129 ms a 29 ms.</p>

<h2>Root params es lo que destraba i18n, y todavía no lo encaramos</h2>

<p>El ejemplo de <code>import.meta.glob</code> del release post es un blog que lee archivos MDX con <code>gray-matter</code>. Es literalmente el nuestro, así que lo probamos en agosto. Compiló, y pasó las rutas del blog de dinámicas a completamente estáticas, lo que rompió el locale en tiempo de request de next-intl con 500s en runtime. Las lecturas sincrónicas de <code>fs</code> son estructurales hasta que el layout deje de derivar el locale de <code>params</code>.</p>

<p>Ese mismo layout es la razón por la que el sitio de marketing lleva <code>export const instant = false</code> en todo su árbol de locale: un layout que elige <code>&lt;html lang&gt;</code> y sus mensajes a partir de <code>params</code> no puede producir un shell estático instantáneo. Root params (<code>lang()</code> de <code>next/root-params</code>) más <code>setRequestLocale</code> es la solución, y es el próximo paso para esa app. Si tu app está internacionalizada con un segmento <code>[locale]</code>, esa migración es el precio de Instant Navigations, y conviene presupuestarla de entrada en vez de descubrirla a través de <code>instant = false</code>.</p>

<h2>Experimental quiere decir experimental</h2>

<p>El React Compiler basado en Rust es la feature que más me entusiasmaba y la que más nos costó. Pasaron dos cosas.</p>

<p>Primero, en nuestra app más grande mata el build por OOM en el contenedor estándar de Vercel, unos 25 segundos después de arrancar una compilación en frío. La misma app buildea bien por el camino de Babel (el doble de lento, pero entra), y las apps más chicas buildean bien con el port de Rust. Lo reportamos, un maintainer se involucró ese mismo día, y para fines de agosto habían reducido a la mitad el pico de memoria del compilador, con un camino de crecimiento cuadrático como el probable disparador en nuestro código. Ese trabajo está en canary. Esta semana volvimos a correr el experimento con el último patch de 16.3, en un preview deployment, y nos dio el mismo SIGKILL. Así que nuestra config mantiene un guard por target: port de Rust en todos lados excepto en los builds en Vercel de esa app, donde el compilador corre por Babel.</p>

<p>Segundo, el port de Rust pierde la clase de scoping <code>jsx-&lt;hash&gt;</code> en elementos renderizados condicionalmente dentro de componentes con estado, así que las reglas scoped de <code>&lt;style jsx&gt;</code> dejan de matchear sin avisar. Nos dejó los labels del sidebar invisibles en producción. Mantuvimos un repositorio con un repro mínimo (<a href="https://github.com/vercel/next.js/issues/96694">vercel/next.js#96694</a>) y esta semana lo volvimos a correr contra tres versiones: sigue roto en el último patch de 16.3, arreglado en el canary de 16.4. Eso convirtió un "capaz ya está arreglado" en un número de versión, y significa que nuestra regla de "nada de styled-jsx scoped" se queda exactamente hasta 16.4.</p>

<p>Dos cosas más chicas del mismo rubro. <code>output: 'standalone'</code> combinado con un adapter rompía los deploys en Vercel en 16.3.0 (faltaba un archivo de trace); el fix se mergeó a canary dos días después y, a esta semana, todavía no salió en ningún patch estable de 16.3. Fijate el tag, no el PR. Y el guard de config para nuestro fallback a webpack originalmente miraba <code>process.argv</code> buscando <code>--webpack</code>, lo que nunca matchea porque <code>next.config</code> se evalúa en el proceso interno del server de Next. Un reviewer lo detectó corriendo de verdad el fallback. Un guard que nunca ejercitás es un guard que no tenés.</p>

<h2>El bug que no tenía nada que ver con Next.js</h2>

<p>Toda la adopción de 16.3 del sitio de marketing se revisó, se aprobó y se mergeó el 4 de agosto. Se mergeó a la feature branch sobre la que estaba apilada, y esa branch nunca se volvió a mergear. Durante un mes el sitio corrió un binario de 16.3 con webpack en dev, sin compilador y con los segment configs viejos. No se rompió nada, que es exactamente por qué nadie se dio cuenta, y lo encontré recién esta semana haciendo grep en <code>main</code> buscando un flag que tendría que haber estado ahí. Volvimos a aplicar la intención a mano; un cherry-pick tenía diez archivos en conflicto contra un mes de trabajo de diseño. Los PRs apilados están bien. Los PRs apilados cuya base no es <code>main</code> necesitan un recordatorio en la base.</p>

<h2>Lo que le diría a otro CTO</h2>

<ul>
<li><strong>Tomá los patches.</strong> 16.3.3 arregló dos advisories críticos de ejecución remota de código, uno de ellos en la optimización de imágenes con AVIF, que es el primer formato que sirve nuestro sitio de marketing. Nada en la línea 16.3.x nos exigió cambios de código. No hay motivo para quedarse en 16.3.0.</li>
<li><strong>Medí tus 404 después de activar Cache Components.</strong> Un <code>curl</code> por familia de rutas. Si ves 200 donde esperás 404, mové los chequeos de existencia al proxy.</li>
<li><strong>Tratá <code>revalidate</code> como una migración.</strong> Encontrá cada segment config, encontrá lo que lee el reloj, movelo adentro de un scope de cache.</li>
<li><strong>Dale a cada flag experimental una salida de emergencia por target y una forma de volver a testearlo.</strong> La nuestra es un commit en el preview de un pull request y un revert. Seis minutos, y ahora sabemos la respuesta para este patch.</li>
<li><strong>Guardá un repo de repro por cada bug que reportes.</strong> Volver a correrlo contra una versión nueva cuesta dos minutos y responde la pregunta que los comentarios upstream no pueden responder.</li>
<li><strong>Un fix en canary no es un fix en tu build.</strong> Fijate el release tag.</li>
<li><strong>Si estás internacionalizado, planificá la migración a root params antes de planificar Instant Navigations.</strong> Es el mismo proyecto.</li>
</ul>

<p>16.3 es un buen release. Solo el cache de build y el error boundary ya justificaban el upgrade, y Cache Components es el modelo correcto para las apps que construimos. Pero el release post describe un destino, y el camino hasta ahí pasa por tu proxy, tu layout de i18n y el límite de memoria de tu contenedor de build. Saber eso antes de arrancar es la mayor parte del trabajo.</p>]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Ralph Loop: la metodología de desarrollo con IA que está arrasando en 2026]]></title>
      <link>https://me.jonymusky.com/es/blog/ralph-loop-autonomous-ai-development</link>
      <guid isPermaLink="true">https://me.jonymusky.com/es/blog/ralph-loop-autonomous-ai-development</guid>
      <pubDate>Thu, 16 Jan 2025 12:00:00 GMT</pubDate>
      <description><![CDATA[Descubrí la técnica Ralph Wiggum: un loop de agente de IA autónomo que deja a Claude Code trabajando sin parar hasta que tu proyecto esté terminado.]]></description>
      <category>ai-agents</category>
      <category>dev-tools</category>
      <content:encoded><![CDATA[El Ralph Loop (o técnica Ralph Wiggum) está revolucionando la forma en que los developers trabajan con asistentes de código con IA. Creada por Geoffrey Huntley, esta metodología, elegante de tan simple, mantiene a un agente de IA trabajando en una tarea hasta que esté realmente terminada.<br /><br /><strong>¿Qué es Ralph Loop?</strong><br /><br />En el fondo, Ralph es simplemente un loop en Bash. Le pasa una y otra vez el mismo prompt a un agente de código con IA como Claude Code. ¿Lo ingenioso? El progreso no queda en la ventana de contexto del LLM: vive en tus archivos y en el historial de git.<br /><br />Donde los workflows agénticos tradicionales se frenan cuando el LLM termina de llamar tools, Ralph sigue: verifica que esté completo, da feedback y corre otra iteración hasta que la tarea realmente sale bien.<br /><br /><strong>Cómo funciona</strong><br /><br />El plugin de Ralph usa un stop hook que se ejecuta cuando Claude intenta cerrar una sesión. En lugar de dejar que el proceso termine, el hook intercepta la salida y busca una 'palabra de seguridad' o promesa de finalización. Si el agente quiere irse sin esa palabra específica (por ejemplo, 'COMPLETE'), el hook bloquea la salida y sigue trabajando.<br /><br />Cada iteración arranca de cero con un contexto limpio, y usa los archivos y git como memoria en lugar del contexto del modelo. Este cambio de filosofía abraza los nuevos comienzos y deja que git sea la capa de memoria.<br /><br /><strong>Por qué importa</strong><br /><br />Boris Cherny, Head of Claude Code en Anthropic, lo formalizó en el plugin oficial ralph-wiggum. VentureBeat declaró a Ralph Wiggum 'el nombre más grande de la IA en este momento'.<br /><br />Para los developers, esto significa que podés armar ciclos de desarrollo autónomos donde Claude Code mejora tu proyecto de forma iterativa hasta terminarlo, con salvaguardas incorporadas para evitar loops infinitos y el uso excesivo de la API.<br /><br /><strong>Cómo empezar</strong><br /><br />Fijate estas implementaciones:<br />- <a href="https://github.com/snarktank/ralph" target="_blank" rel="noopener noreferrer">snarktank/ralph</a> - Loop de agente de IA autónomo para completar PRDs<br />- <a href="https://github.com/vercel-labs/ralph-loop-agent" target="_blank" rel="noopener noreferrer">vercel-labs/ralph-loop-agent</a> - Autonomía continua para el AI SDK<br />- <a href="https://github.com/frankbria/ralph-claude-code" target="_blank" rel="noopener noreferrer">frankbria/ralph-claude-code</a> - Loop de desarrollo autónomo con IA para Claude Code<br /><br />El Ralph Loop representa un cambio de paradigma en el desarrollo asistido por IA. En lugar de microgestionar cada interacción, definís el objetivo y dejás que el agente trabaje hasta terminar. Bienvenido al futuro de la programación.]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[MCP: el protocolo que conecta la IA con todo]]></title>
      <link>https://me.jonymusky.com/es/blog/mcp-model-context-protocol</link>
      <guid isPermaLink="true">https://me.jonymusky.com/es/blog/mcp-model-context-protocol</guid>
      <pubDate>Fri, 10 Jan 2025 12:00:00 GMT</pubDate>
      <description><![CDATA[Explorando el Model Context Protocol (MCP) de Anthropic y cómo está revolucionando la forma en que los modelos de IA interactúan con herramientas y fuentes de datos externas.]]></description>
      <category>ai-agents</category>
      <category>llms</category>
      <content:encoded><![CDATA[El Model Context Protocol (MCP) de Anthropic está cambiando cómo construimos aplicaciones de IA. En lugar de crear integraciones a medida para cada herramienta, MCP ofrece una forma estandarizada para que los modelos de IA se conecten con bases de datos, APIs, sistemas de archivos y mucho más.<br /><br />Lo que hace especial a MCP es su simplicidad. Definís 'tools' que tu IA puede usar, y el protocolo se encarga de la comunicación. O sea, construís una vez y te conectás en todos lados.<br /><br />Vengo experimentando con servidores MCP para distintos casos de uso: conectarme a GitHub para code reviews, integrarme con Slack para updates del equipo e incluso armar tools a medida para workflows específicos.<br /><br />El ecosistema está creciendo a toda velocidad. Mirá la documentación oficial: <a href="https://modelcontextprotocol.io" target="_blank" rel="noopener noreferrer">Documentación de MCP</a>.<br /><br />Si estás construyendo aplicaciones con IA, MCP tiene que estar sí o sí en tu radar. Es de esos protocolos que te hacen preguntarte por qué no lo tuvimos antes.]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Claude Code: desarrollo con IA desde tu terminal]]></title>
      <link>https://me.jonymusky.com/es/blog/claude-code-cli-experience</link>
      <guid isPermaLink="true">https://me.jonymusky.com/es/blog/claude-code-cli-experience</guid>
      <pubDate>Wed, 08 Jan 2025 12:00:00 GMT</pubDate>
      <description><![CDATA[Mi experiencia usando el CLI de Claude Code para las tareas de desarrollo del día a día y por qué se volvió una parte esencial de mi workflow.]]></description>
      <category>dev-tools</category>
      <category>ai-agents</category>
      <content:encoded><![CDATA[Después de semanas usando Claude Code como mi principal asistente de código, quería contarles cómo transformó mi workflow de desarrollo.<br /><br />La experiencia en el CLI es increíblemente fluida. Podés hacer preguntas sobre tu codebase, refactorizar código, escribir tests y hasta debuggear problemas, todo sin salir de la terminal. El manejo del contexto impresiona: entiende la estructura de tu proyecto y se mueve muy bien en codebases complejos.<br /><br />Algunas features que uso todos los días:<br />- Explorar código y entender código legacy<br />- Escribir unit tests para funciones existentes<br />- Refactorizar con confianza<br />- Prototipar rápido nuevas features<br /><br />La integración con Git es especialmente útil. Podés revisar cambios, escribir mensajes de commit y hasta crear pull requests directamente desde la conversación.<br /><br />Para los developers que viven en la terminal, Claude Code se siente como una extensión natural de tu workflow más que como una herramienta aparte.]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Por qué Hono es mi framework de cabecera para edge computing]]></title>
      <link>https://me.jonymusky.com/es/blog/hono-framework-2025</link>
      <guid isPermaLink="true">https://me.jonymusky.com/es/blog/hono-framework-2025</guid>
      <pubDate>Sun, 05 Jan 2025 12:00:00 GMT</pubDate>
      <description><![CDATA[Un análisis a fondo de Hono, el framework web ultrarrápido que funciona sin fricción en Cloudflare Workers, Deno, Bun y Node.js.]]></description>
      <category>edge</category>
      <category>architecture</category>
      <content:encoded><![CDATA[Después de armar varios proyectos con Hono, puedo decir con seguridad que se convirtió en mi framework favorito para edge computing y aplicaciones serverless.<br /><br />Lo que distingue a Hono:<br />- Cero dependencias y un bundle increíblemente chico<br />- Funciona en todos lados: Cloudflare Workers, Deno, Bun, Node.js, AWS Lambda<br />- Una API tipo Express que se siente familiar<br />- Middleware incorporado para las tareas comunes<br />- TypeScript-first con una inferencia de tipos excelente<br /><br />La performance es notable. En Cloudflare Workers, Hono prácticamente no suma overhead a tus cold starts. Sumado a su API intuitiva, podés pasar de la idea a una API deployada en minutos.<br /><br />Me encanta especialmente el ecosistema de middleware. Autenticación con JWT, CORS, compresión y caching están disponibles con un simple import.<br /><br />Si estás construyendo APIs para el edge, probá Hono: <a href="https://hono.dev" target="_blank" rel="noopener noreferrer">Documentación de Hono</a>.]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Durable execution: sistemas confiables con Temporal]]></title>
      <link>https://me.jonymusky.com/es/blog/temporal-durable-execution</link>
      <guid isPermaLink="true">https://me.jonymusky.com/es/blog/temporal-durable-execution</guid>
      <pubDate>Fri, 20 Dec 2024 12:00:00 GMT</pubDate>
      <description><![CDATA[Entendiendo los patrones de durable execution y cómo Temporal hace accesible la construcción de sistemas distribuidos tolerantes a fallas.]]></description>
      <category>architecture</category>
      <content:encoded><![CDATA[Siguiendo con mi post sobre la Replay Conference, quería meterme más a fondo en el concepto de durable execution y por qué importa para las aplicaciones modernas.<br /><br />El manejo de errores tradicional suele terminar en lógica de reintentos compleja, dolores de cabeza con el manejo de estado y fallas difíciles de debuggear. La durable execution da vuelta este modelo: tu código corre como si las fallas no existieran, y el framework se encarga del resto.<br /><br />Con Temporal, escribís workflows como funciones simples. Si un servidor se cae a mitad de la ejecución, el workflow se retoma automáticamente exactamente donde quedó. Sin checkpoints manuales, sin máquinas de estado complejas.<br /><br />Casos de uso reales que implementé:<br />- Pipelines de procesamiento de órdenes de larga duración<br />- Flujos de onboarding de usuarios en varios pasos<br />- Jobs batch programados con dependencias complejas<br />- Patrones Saga para transacciones distribuidas<br /><br />El cambio de modelo mental es grande, pero una vez que te hace click, te vas a preguntar cómo hacías para construir sistemas confiables sin esto.<br /><br />Empezá a explorar: <a href="https://temporal.io" target="_blank" rel="noopener noreferrer">Temporal.io</a>]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Lo mejor de la Replay Conference 2024]]></title>
      <link>https://me.jonymusky.com/es/blog/replay-conference-2024</link>
      <guid isPermaLink="true">https://me.jonymusky.com/es/blog/replay-conference-2024</guid>
      <pubDate>Tue, 26 Nov 2024 12:00:00 GMT</pubDate>
      <description><![CDATA[Lo más destacado y recursos de la Replay Conference 2024 de Temporal en Seattle.]]></description>
      <category>events</category>
      <category>architecture</category>
      <content:encoded><![CDATA[La semana pasada fui a la Replay Conference 2024 de Temporal en Seattle, ¡y fue una experiencia increíble! Acá van algunos highlights e ideas que me llevé del evento.<br /><br />Armé un resumen de lo que estuve explorando acá: <a href="https://gist.github.com/jonymusky/8d1f2bbf2ed3168f37b1383b77fa2cfb" target="_blank" rel="noopener noreferrer">Resumen de la Replay Conference</a>.<br /><br />Te recomiendo mucho que mires las charlas: estuvieron llenas de información útil para developers y arquitectos. Las encontrás en YouTube: <a href="https://www.youtube.com/watch?v=2floQta7GNs&list=PLl9kRkvFJrlR0xieUwBN_nNHW0oijCZa6" target="_blank" rel="noopener noreferrer">Charlas de la Replay Conference</a>.<br /><br />Temporal sigue redefiniendo cómo pensamos los workflows y los sistemas distribuidos, y esta conferencia no fue la excepción. Ya sea que recién arranques con Temporal o que lo uses hace rato, ¡estos recursos valen muchísimo!]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Explorando las joyas ocultas de Cloudflare]]></title>
      <link>https://me.jonymusky.com/es/blog/exploring-cloudflare</link>
      <guid isPermaLink="true">https://me.jonymusky.com/es/blog/exploring-cloudflare</guid>
      <pubDate>Tue, 26 Nov 2024 12:00:00 GMT</pubDate>
      <description><![CDATA[Descubrí cómo Cloudflare Tunnels, Workers y Email Redirects pueden darle un impulso enorme al desarrollo en startups y pruebas de concepto.]]></description>
      <category>edge</category>
      <category>architecture</category>
      <content:encoded><![CDATA[Este fin de semana me metí con algunas de las features menos conocidas pero potentes de Cloudflare: Tunnels, Workers (funciones serverless), Email Workers y Email Redirects.<br /><br />Si bien la mayoría de las empresas usa Cloudflare para optimizar requests, WAF, protección contra bots y la performance general del sitio, ¡hay muchísimo más para explorar! Muchas de estas funcionalidades suelen ser gratis y, para mí, pueden acelerar el desarrollo de manera significativa.<br /><br />Para startups o pruebas de concepto, Cloudflare puede cambiar las reglas del juego. Con Cloudflare Tunnels (50 seats gratis, como ngrok pero con más features), por ejemplo, podrías tener una Raspberry Pi o una Mac Mini corriendo una API simple desde tu casa y recortar los costos iniciales de infraestructura.<br /><br />Lo que también entusiasma es poder conectarte con Llama 3.1 (<a href="https://lnkd.in/deyeXCA9" target="_blank" rel="noopener noreferrer">Llama 3.1</a>) directamente desde Workers, lo que habilita integraciones con IA muy fáciles, y totalmente gratis.<br /><br />¡Te recomiendo mucho que lo pruebes! Mi única crítica es que la documentación podría ser mejor, pero el potencial que ofrecen estas herramientas vale mucho la pena.<br /><br />Para más detalles, mirá mi post en LinkedIn: <a href="https://www.linkedin.com/feed/update/urn:li:activity:7264245965698519040/" target="_blank" rel="noopener noreferrer">Cloudflare Insights</a>.]]></content:encoded>
    </item>
  </channel>
</rss>