<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Bitácora Dev</title><description>Artículos sobre inteligencia artificial aplicada, infraestructura self-hosted y desarrollo de software. Experiencias reales, medidas reales.</description><link>https://raicesbinarias.es/</link><language>es</language><lastBuildDate>Sun, 18 Oct 2026 00:00:00 GMT</lastBuildDate><generator>Astro · Bitácora Dev</generator><ttl>60</ttl><image><url>https://raicesbinarias.es/img/logo-feed.png</url><title>Bitácora Dev</title><link>https://raicesbinarias.es/</link></image><atom:link href="https://raicesbinarias.es/rss.xml" rel="self" type="application/rss+xml"/><item><title>Montar un agente de IA en casa: lo que cuesta de verdad</title><link>https://raicesbinarias.es/articulos/agente-ia-en-casa-coste-real/</link><guid isPermaLink="true">https://raicesbinarias.es/articulos/agente-ia-en-casa-coste-real/</guid><description>Ejecutar un agente propio en el homelab no es gratis ni es magia. Costes de tokens, latencias, qué modelo usar para qué y las trampas que me encontré por el camino.</description><pubDate>Sun, 18 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Llevo meses con agentes de IA corriendo dentro de mi propia red: uno que gestiona tareas del
día a día, otro que vigila servicios y otro que escribe documentación. Cuando cuento que
“tengo agentes en casa”, la pregunta es siempre la misma: &lt;em&gt;¿y eso cuánto cuesta?&lt;/em&gt; La respuesta
honesta es que depende de una cosa que casi nadie mide bien: &lt;strong&gt;qué modelo usas para cada
tarea&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;lo-primero-local-no-significa-gratis&quot;&gt;Lo primero: local no significa gratis&lt;/h2&gt;
&lt;p&gt;Hay una idea muy extendida de que si el modelo corre en tu máquina, el coste es cero. Es
falso, solo se paga en otra moneda:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hardware&lt;/strong&gt;: un modelo de 7-8B en cuantización Q4 necesita unos 6 GB de VRAM libres para
ir cómodo. En una GPU integrada, o tiras de CPU, o no cabe.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Energía y calor&lt;/strong&gt;: una GPU al 100% durante horas se nota en la factura y en el ruido.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tiempo&lt;/strong&gt;: la primera respuesta de un modelo cargado en frío puede tardar 20 o 30 segundos.
Eso mata cualquier flujo interactivo.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;En mi caso, la conclusión fue clara: &lt;strong&gt;local para lo que se repite y no necesita cabeza, API
para lo que sí&lt;/strong&gt;. Un modelo pequeño local para clasificar, resumir o extraer campos; un modelo
grande remoto para razonar, escribir o decidir.&lt;/p&gt;
&lt;h2 id=&quot;la-aritmética-que-de-verdad-importa&quot;&gt;La aritmética que de verdad importa&lt;/h2&gt;
&lt;p&gt;El error típico es mirar solo el precio por millón de tokens de entrada y salida. Lo que
cambia la factura es el &lt;strong&gt;tamaño del contexto que arrastras en cada llamada&lt;/strong&gt;. Si tu agente
manda 12.000 tokens de historial en cada iteración de un bucle de 8 pasos, has pagado
96.000 tokens de entrada para una tarea que quizá necesitaba 2.000.&lt;/p&gt;
&lt;p&gt;Tres reglas que aplico siempre:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Contexto mínimo viable.&lt;/strong&gt; Cada herramienta externa que expones al modelo añade tokens a
&lt;em&gt;cada&lt;/em&gt; llamada. Si tienes 30 herramientas descritas, estás pagando por las 30 en todas las
peticiones.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Recorta el historial, no lo acumules.&lt;/strong&gt; Resumir los turnos viejos cuesta una llamada
barata y ahorra muchas caras.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mide.&lt;/strong&gt; Sin métricas por tarea no sabes si el gasto viene de un bucle que se repite o de
un contexto inflado.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;astro-code bitacora-mate-dark&quot; style=&quot;background-color:#0c0f13;color:#d7dbe1; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#5F6B7A;font-style:italic&quot;&gt;# Mal: el agente arrastra todo el historial en cada paso&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D7DBE1&quot;&gt;messages &lt;/span&gt;&lt;span style=&quot;color:#7C8798&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#D7DBE1&quot;&gt; history &lt;/span&gt;&lt;span style=&quot;color:#7C8798&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#7C8798&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#D7DBE1&quot;&gt;nuevo_mensaje&lt;/span&gt;&lt;span style=&quot;color:#7C8798&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#5F6B7A;font-style:italic&quot;&gt;# Bien: ventana recortada + resumen de lo anterior&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D7DBE1&quot;&gt;messages &lt;/span&gt;&lt;span style=&quot;color:#7C8798&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#7C8798&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#D7DBE1&quot;&gt;resumen_previo&lt;/span&gt;&lt;span style=&quot;color:#7C8798&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color:#7C8798&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#D7DBE1&quot;&gt; history&lt;/span&gt;&lt;span style=&quot;color:#7C8798&quot;&gt;[-&lt;/span&gt;&lt;span style=&quot;color:#D9A441&quot;&gt;6&lt;/span&gt;&lt;span style=&quot;color:#7C8798&quot;&gt;:]&lt;/span&gt;&lt;span style=&quot;color:#7C8798&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#7C8798&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#D7DBE1&quot;&gt;nuevo_mensaje&lt;/span&gt;&lt;span style=&quot;color:#7C8798&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;latencias-el-enemigo-silencioso&quot;&gt;Latencias: el enemigo silencioso&lt;/h2&gt;
&lt;p&gt;Un agente que tarda 4 minutos en hacer algo que tú harías en 30 segundos no se usa. Y el
tiempo se va casi siempre en tres sitios:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cadenas de herramientas&lt;/strong&gt; que se llaman en secuencia cuando podrían ir en paralelo.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reintentos silenciosos&lt;/strong&gt;: un proveedor que devuelve error y el agente reintenta tres veces
con backoff exponencial. El usuario solo ve que “va lento”.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Arranque en frío&lt;/strong&gt; del modelo local.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Medir el tiempo por paso, y no solo el total, fue lo que me dejó ver que la mitad del tiempo
se iba en un reintento que no hacía falta.&lt;/p&gt;
&lt;h2 id=&quot;cosas-que-rompen-y-nadie-te-cuenta&quot;&gt;Cosas que rompen y nadie te cuenta&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Credenciales que caducan.&lt;/strong&gt; Un token expirado convierte un agente en un generador de
errores. Si tienes varias tareas programadas, van a fallar todas a la vez y a la misma hora.
Merece la pena tener un chequeo periódico que valide la credencial antes de que lo haga el
trabajo real.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Procesos que mueren en silencio.&lt;/strong&gt; Un &lt;em&gt;listener&lt;/em&gt; que nadie supervisa deja de funcionar y
la tarea que dependía de él se queda esperando para siempre. Si un componente es crítico,
necesita su propio servicio con reinicio automático, no un proceso lanzado a mano.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;El estado compartido.&lt;/strong&gt; Si dos agentes se coordinan por un canal (Redis, cola, fichero),
ese canal es infraestructura: hay que tratarlo como tal, con su autenticación y su
monitorización.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Un agente no es un script con esteroides: es un servicio más. Si no lo despliegas,
monitorizas y respaldas como tal, te va a despertar de noche.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;cómo-lo-tengo-montado-hoy&quot;&gt;Cómo lo tengo montado hoy&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Modelo pequeño local&lt;/strong&gt; para tareas de clasificación y extracción: coste cero por llamada y
datos que no salen de casa.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Modelo grande remoto&lt;/strong&gt; para razonamiento y redacción, con contexto recortado y medición
por tarea.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Credenciales centralizadas&lt;/strong&gt; y un chequeo diario que avisa si alguna ha dejado de valer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Todo bajo supervisión&lt;/strong&gt;: los componentes críticos corren con reinicio automático y logs
rotados, porque un log de 1,5 GB también es un fallo de diseño.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;qué-haría-distinto-si-empezara-hoy&quot;&gt;Qué haría distinto si empezara hoy&lt;/h2&gt;
&lt;p&gt;Empezaría por &lt;strong&gt;medir antes de optimizar&lt;/strong&gt;: registrar tokens, latencia y coste por tarea desde
el primer día. Y separaría desde el principio lo que es &lt;em&gt;infraestructura&lt;/em&gt; de lo que es
&lt;em&gt;experimento&lt;/em&gt;. La mayor parte del tiempo perdido no vino de elegir mal el modelo, sino de
tratar como juguete algo que estaba sosteniendo tareas reales.&lt;/p&gt;
</content:encoded><dc:creator>Rubén</dc:creator><media:content url="https://raicesbinarias.es/img/portada-agentes.png" medium="image" type="image/png" width="1200" height="630"/><category>IA</category><category>Homelab</category><category>Agentes</category><category>Costes</category><enclosure url="https://raicesbinarias.es/img/portada-agentes.png" length="362600" type="image/png"/></item><item><title>RAG en local: cuándo merece la pena y cuándo es un juguete</title><link>https://raicesbinarias.es/articulos/rag-local-cuando-merece-la-pena/</link><guid isPermaLink="true">https://raicesbinarias.es/articulos/rag-local-cuando-merece-la-pena/</guid><description>Búsqueda semántica sobre tus propios documentos. Dónde gana al grep de toda la vida, dónde pierde y cómo saberlo antes de montar media infraestructura.</description><pubDate>Sun, 11 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Cada pocas semanas alguien pregunta cómo montar un RAG sobre su documentación. La respuesta
corta es que &lt;strong&gt;depende de la pregunta que vayas a hacer&lt;/strong&gt;, y que hay un porcentaje alto de
casos donde un &lt;code&gt;grep&lt;/code&gt; bien afinado gana. Este artículo es el criterio que aplico antes de
levantar nada.&lt;/p&gt;
&lt;h2 id=&quot;qué-resuelve-rag-de-verdad&quot;&gt;Qué resuelve RAG de verdad&lt;/h2&gt;
&lt;p&gt;RAG (generación aumentada por recuperación) sirve para una cosa concreta: &lt;strong&gt;encontrar trozos
de texto relevantes cuando la coincidencia exacta de palabras no funciona&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Gana cuando:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Las preguntas son conceptuales: &lt;em&gt;“¿cómo decidimos los topes de facturación?”&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;El vocabulario del que pregunta y el del documento no coinciden.&lt;/li&gt;
&lt;li&gt;La información está repartida y hay que juntar varios fragmentos.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pierde cuando:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Buscas un identificador, un error exacto o un nombre de función → &lt;code&gt;grep&lt;/code&gt; es instantáneo,
determinista y no alucina.&lt;/li&gt;
&lt;li&gt;El corpus es pequeño (menos de, digamos, 50 documentos): leerlos enteros es más barato y más
fiable que indexarlos.&lt;/li&gt;
&lt;li&gt;Necesitas que la respuesta sea &lt;strong&gt;auditable al 100%&lt;/strong&gt;: con RAG siempre hay un fragmento
recuperado y un modelo interpretando.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;la-decisión-en-una-tabla&quot;&gt;La decisión, en una tabla&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situación&lt;/th&gt;
&lt;th&gt;Herramienta&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Busco un error o un ID exacto&lt;/td&gt;
&lt;td&gt;&lt;code&gt;grep&lt;/code&gt;/ripgrep&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Corpus pequeño (&amp;lt; 50 docs)&lt;/td&gt;
&lt;td&gt;leer completo, sin índice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Preguntas conceptuales, vocabulario variable&lt;/td&gt;
&lt;td&gt;RAG&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Necesito citas verificables&lt;/td&gt;
&lt;td&gt;RAG con enlaces al fragmento&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Datos muy sensibles&lt;/td&gt;
&lt;td&gt;local, y solo si el índice se queda en casa&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;montar-el-índice-las-decisiones-que-importan&quot;&gt;Montar el índice: las decisiones que importan&lt;/h2&gt;
&lt;h3 id=&quot;1-el-troceado-chunking&quot;&gt;1. El troceado (chunking)&lt;/h3&gt;
&lt;p&gt;Es la decisión que más afecta a la calidad y la que menos gente revisa. Trozos demasiado
pequeños pierden contexto; demasiado grandes diluyen la señal y encarecen cada consulta.&lt;/p&gt;
&lt;pre class=&quot;astro-code bitacora-mate-dark&quot; style=&quot;background-color:#0c0f13;color:#d7dbe1; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#5F6B7A;font-style:italic&quot;&gt;# Un troceado razonable para documentación técnica:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#5F6B7A;font-style:italic&quot;&gt;# respeta los encabezados y no parte código por la mitad.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C08B4A&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#8B9FD8&quot;&gt; chunk&lt;/span&gt;&lt;span style=&quot;color:#7C8798&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D7DBE1&quot;&gt;texto&lt;/span&gt;&lt;span style=&quot;color:#7C8798&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#D7DBE1&quot;&gt; max_tokens&lt;/span&gt;&lt;span style=&quot;color:#7C8798&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#D9A441&quot;&gt;600&lt;/span&gt;&lt;span style=&quot;color:#7C8798&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#D7DBE1&quot;&gt; solape&lt;/span&gt;&lt;span style=&quot;color:#7C8798&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#D9A441&quot;&gt;80&lt;/span&gt;&lt;span style=&quot;color:#7C8798&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D7DBE1&quot;&gt;    bloques &lt;/span&gt;&lt;span style=&quot;color:#7C8798&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#8B9FD8&quot;&gt; partir_por_encabezados&lt;/span&gt;&lt;span style=&quot;color:#7C8798&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#8B9FD8&quot;&gt;texto&lt;/span&gt;&lt;span style=&quot;color:#7C8798&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C08B4A&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#7C8798&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#8B9FD8&quot;&gt;ventana&lt;/span&gt;&lt;span style=&quot;color:#7C8798&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#8B9FD8&quot;&gt;b&lt;/span&gt;&lt;span style=&quot;color:#7C8798&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#8B9FD8&quot;&gt; max_tokens&lt;/span&gt;&lt;span style=&quot;color:#7C8798&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#8B9FD8&quot;&gt; solape&lt;/span&gt;&lt;span style=&quot;color:#7C8798&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#C08B4A&quot;&gt; for&lt;/span&gt;&lt;span style=&quot;color:#D7DBE1&quot;&gt; b &lt;/span&gt;&lt;span style=&quot;color:#C08B4A&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#D7DBE1&quot;&gt; bloques&lt;/span&gt;&lt;span style=&quot;color:#7C8798&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Regla práctica: &lt;strong&gt;el trozo debe responder por sí solo a una pregunta&lt;/strong&gt;. Si al leerlo hace falta
el párrafo anterior para entenderlo, es demasiado pequeño.&lt;/p&gt;
&lt;h3 id=&quot;2-el-modelo-de-embeddings&quot;&gt;2. El modelo de embeddings&lt;/h3&gt;
&lt;p&gt;Aquí sí conviene gastar un poco de cabeza: un modelo malo de embeddings hace que el problema
parezca de “generación” cuando en realidad es de recuperación. Y ojo con mezclar idiomas: si el
corpus está en español, el modelo tiene que ir bien en español, no solo en inglés.&lt;/p&gt;
&lt;h3 id=&quot;3-el-re-ranking&quot;&gt;3. El re-ranking&lt;/h3&gt;
&lt;p&gt;Recuperar 20 candidatos y quedarse con los 5 mejores cuesta poquísimo y suele mejorar el
resultado más que cambiar de modelo generador.&lt;/p&gt;
&lt;h2 id=&quot;cómo-evaluar-sin-engañarte&quot;&gt;Cómo evaluar sin engañarte&lt;/h2&gt;
&lt;p&gt;La trampa clásica es probar con 5 preguntas que tú mismo has escrito sabiendo ya la respuesta:
todo funciona. El método que uso:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Conjunto de preguntas reales&lt;/strong&gt; (20-30), incluyendo las que sabes que fallan.&lt;/li&gt;
&lt;li&gt;Para cada una, marcar &lt;strong&gt;qué documento debería salir&lt;/strong&gt;. Sin esto no hay evaluación posible.&lt;/li&gt;
&lt;li&gt;Medir dos cosas separadas: &lt;strong&gt;¿recuperó el documento correcto?&lt;/strong&gt; y &lt;strong&gt;¿la respuesta final fue
correcta?&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Repetir con &lt;code&gt;grep&lt;/code&gt; como línea base. Si tu RAG no supera al &lt;code&gt;grep&lt;/code&gt; en recuperación, no
merece la complejidad.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;Si no puedes medir si el sistema acierta, no tienes un sistema: tienes una demo.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;lo-que-se-rompe-en-producción&quot;&gt;Lo que se rompe en producción&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Índice desactualizado.&lt;/strong&gt; Si los documentos cambian y el índice no, el RAG responde con
datos viejos con total seguridad. La reindexación tiene que ser parte del flujo, no un script
manual que se olvida.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Coste por consulta descontrolado.&lt;/strong&gt; Recuperar 50 fragmentos y pasarlos todos al modelo
multiplica el coste sin mejorar la respuesta.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Confianza excesiva en la respuesta.&lt;/strong&gt; RAG no elimina las alucinaciones: las reduce cuando
la recuperación es buena, y las esconde cuando es mala. Hay que citar siempre el fragmento de
origen y comprobarlo.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;mi-conclusión&quot;&gt;Mi conclusión&lt;/h2&gt;
&lt;p&gt;Para buscarme a mí mismo entre mis notas: &lt;code&gt;grep&lt;/code&gt; y nombres de fichero decentes. Para preguntas
conceptuales sobre documentación larga y cambiante: RAG, con índice local, troceado que respeta
la estructura y evaluación medida. Y en cualquier caso, &lt;strong&gt;la decisión se toma con el conjunto de
preguntas reales delante&lt;/strong&gt;, no con la sensación de que las respuestas suenan bien.&lt;/p&gt;
</content:encoded><dc:creator>Rubén</dc:creator><media:content url="https://raicesbinarias.es/img/portada-rag.png" medium="image" type="image/png" width="1200" height="630"/><category>IA</category><category>RAG</category><category>Búsqueda</category><category>Datos</category><enclosure url="https://raicesbinarias.es/img/portada-rag.png" length="280358" type="image/png"/></item><item><title>Refactorizar una app en producción sin romperla: el método</title><link>https://raicesbinarias.es/articulos/refactorizar-en-produccion-sin-romperla/</link><guid isPermaLink="true">https://raicesbinarias.es/articulos/refactorizar-en-produccion-sin-romperla/</guid><description>Cómo mover un proyecto grande a una arquitectura por capas, fusionar meses de trabajo divergente y desplegarlo en producción sin incidencias. Con el orden exacto de los pasos.</description><pubDate>Sun, 04 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Toda aplicación que dura acaba teniendo el mismo problema: la lógica de negocio vive en las
vistas, las vistas llaman al ORM directamente y nadie se atreve a tocar nada. La semana
pasada cerré una refactorización de meses sobre una aplicación en producción: &lt;strong&gt;reorganización
por capas, fusión de dos líneas de desarrollo divergentes y despliegue final sin incidencias&lt;/strong&gt;.
Este es el orden que seguí, porque el orden es lo que hace que no se rompa nada.&lt;/p&gt;
&lt;h2 id=&quot;1-nunca-refactorices-sin-tests-que-te-cubran&quot;&gt;1. Nunca refactorices sin tests que te cubran&lt;/h2&gt;
&lt;p&gt;Antes de mover una sola línea, necesitas &lt;strong&gt;tests de caracterización&lt;/strong&gt;: pruebas que fijen el
comportamiento actual, aunque el código sea feo. No buscan corrección, buscan &lt;em&gt;“que siga
haciendo lo mismo”&lt;/em&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code bitacora-mate-dark&quot; style=&quot;background-color:#0c0f13;color:#d7dbe1; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#5F6B7A;font-style:italic&quot;&gt;# Congela el comportamiento de las zonas que vas a tocar&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B9FD8&quot;&gt;pytest&lt;/span&gt;&lt;span style=&quot;color:#5FB8A6&quot;&gt; apps/facturacion&lt;/span&gt;&lt;span style=&quot;color:#5FB8A6&quot;&gt; apps/informes&lt;/span&gt;&lt;span style=&quot;color:#D9A441&quot;&gt; -q&lt;/span&gt;&lt;span style=&quot;color:#D9A441&quot;&gt; --create-db&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dos detalles que ahorran disgustos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;--create-db&lt;/code&gt;&lt;/strong&gt; (esquema fresco). Una base de tests reutilizada de una ejecución anterior
hace fallar tests correctos con errores que parecen del código, y te manda a cazar fantasmas.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ejecuta la suite completa contra el commit anterior&lt;/strong&gt; cuando algo falla. Si ya fallaba
antes, no es una regresión: es deuda. Y hay que decirlo con la prueba delante, no de memoria.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;2-separar-en-capas-pero-por-dentro&quot;&gt;2. Separar en capas, pero por dentro&lt;/h2&gt;
&lt;p&gt;El patrón que mejor me ha funcionado es el aburrido:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;selectors/&lt;/code&gt; → consultas a base de datos.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;services/&lt;/code&gt; → lógica de negocio y cálculos.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;views/&lt;/code&gt; → orquestación fina: validar entrada, llamar al servicio, devolver respuesta.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Lo importante no es el nombre de las carpetas, sino que &lt;strong&gt;el cálculo salga de la vista&lt;/strong&gt;. En
cuanto un cálculo vive en una función propia, se puede probar sin HTTP, sin sesión y sin
plantillas. Ahí es donde la refactorización empieza a pagarse sola.&lt;/p&gt;
&lt;h2 id=&quot;3-ramas-divergentes-comparar-remotos-no-locales&quot;&gt;3. Ramas divergentes: comparar remotos, no locales&lt;/h2&gt;
&lt;p&gt;Si llevas meses con dos líneas de trabajo (la refactorizada y la de funcionalidades), las
ramas locales mienten: probablemente están atrasadas. Compara siempre contra el remoto, tras
un &lt;code&gt;fetch --prune&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code bitacora-mate-dark&quot; style=&quot;background-color:#0c0f13;color:#d7dbe1; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B9FD8&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#5FB8A6&quot;&gt; fetch&lt;/span&gt;&lt;span style=&quot;color:#D9A441&quot;&gt; --all&lt;/span&gt;&lt;span style=&quot;color:#D9A441&quot;&gt; --prune&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B9FD8&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#5FB8A6&quot;&gt; rev-list&lt;/span&gt;&lt;span style=&quot;color:#D9A441&quot;&gt; --count&lt;/span&gt;&lt;span style=&quot;color:#5FB8A6&quot;&gt; origin/main..origin/refactor&lt;/span&gt;&lt;span style=&quot;color:#5F6B7A;font-style:italic&quot;&gt;   # commits que le faltan a main&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B9FD8&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#5FB8A6&quot;&gt; cherry&lt;/span&gt;&lt;span style=&quot;color:#5FB8A6&quot;&gt; origin/main&lt;/span&gt;&lt;span style=&quot;color:#5FB8A6&quot;&gt; origin/refactor&lt;/span&gt;&lt;span style=&quot;color:#7C8798&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#8B9FD8&quot;&gt; head&lt;/span&gt;&lt;span style=&quot;color:#5F6B7A;font-style:italic&quot;&gt;       # parches ya aplicados (-) vs nuevos (+)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Y para saber qué ficheros faltan de verdad, a nivel de árbol:&lt;/p&gt;
&lt;pre class=&quot;astro-code bitacora-mate-dark&quot; style=&quot;background-color:#0c0f13;color:#d7dbe1; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B9FD8&quot;&gt;comm&lt;/span&gt;&lt;span style=&quot;color:#D9A441&quot;&gt; -23&lt;/span&gt;&lt;span style=&quot;color:#5FB8A6&quot;&gt; &amp;lt;(&lt;/span&gt;&lt;span style=&quot;color:#8B9FD8&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#5FB8A6&quot;&gt; ls-tree &lt;/span&gt;&lt;span style=&quot;color:#D9A441&quot;&gt;-r&lt;/span&gt;&lt;span style=&quot;color:#D9A441&quot;&gt; --name-only&lt;/span&gt;&lt;span style=&quot;color:#5FB8A6&quot;&gt; origin/refactor &lt;/span&gt;&lt;span style=&quot;color:#7C8798&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color:#8B9FD8&quot;&gt; sort&lt;/span&gt;&lt;span style=&quot;color:#5FB8A6&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#5FB8A6&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#5FB8A6&quot;&gt;         &amp;lt;(&lt;/span&gt;&lt;span style=&quot;color:#8B9FD8&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#5FB8A6&quot;&gt; ls-tree &lt;/span&gt;&lt;span style=&quot;color:#D9A441&quot;&gt;-r&lt;/span&gt;&lt;span style=&quot;color:#D9A441&quot;&gt; --name-only&lt;/span&gt;&lt;span style=&quot;color:#5FB8A6&quot;&gt; origin/main &lt;/span&gt;&lt;span style=&quot;color:#7C8798&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color:#8B9FD8&quot;&gt; sort&lt;/span&gt;&lt;span style=&quot;color:#5FB8A6&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;4-el-merge-limpio-que-pierde-código-el-pitfall-que-más-me-mordió&quot;&gt;4. El merge limpio que pierde código (el pitfall que más me mordió)&lt;/h2&gt;
&lt;p&gt;Un merge puede resolver &lt;strong&gt;sin marcar conflicto&lt;/strong&gt; y aun así &lt;strong&gt;tirar un cambio&lt;/strong&gt;. Ocurre cuando
una rama extrajo un bloque a un método y la otra modificó ese mismo bloque: el hunk entrante
desaparece dentro de la llamada al método nuevo. Git dice “todo bien” y tú has perdido un
arreglo.&lt;/p&gt;
&lt;p&gt;Verificación obligatoria tras cada merge, por cada cambio semántico:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;grep&lt;/code&gt; el nombre del parámetro/método nuevo en el fichero fusionado.&lt;/li&gt;
&lt;li&gt;Ojo al caso traicionero: &lt;strong&gt;el parámetro existe pero la lógica que lo usa no&lt;/strong&gt;. Parece
portado y no lo está.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git diff origin/&amp;lt;rama-features&amp;gt; HEAD -- &amp;lt;fichero&amp;gt;&lt;/code&gt;: lo que salga debe ser estructura, nunca
comportamiento que falta.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;Un merge que termina “limpio” no demuestra nada. La prueba es buscar el comportamiento, no
confiar en el mensaje de git.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;5-fusionar-en-el-orden-correcto&quot;&gt;5. Fusionar en el orden correcto&lt;/h2&gt;
&lt;p&gt;El orden que funcionó:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Refactor → línea de funcionalidades (ahí están los conflictos gordos).&lt;/li&gt;
&lt;li&gt;Línea unificada → &lt;code&gt;main&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Comprobar si es fast-forward: &lt;code&gt;git merge-base --is-ancestor A B&lt;/code&gt; antes de mergear.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Y si hay conflicto en ficheros de cálculo, se resuelve &lt;strong&gt;a mano&lt;/strong&gt;, nunca con &lt;code&gt;--ours&lt;/code&gt;/&lt;code&gt;--theirs&lt;/code&gt;
en bloque. En ficheros de interfaz que se reescribieron junto con sus plantillas y sus rutas,
se toma la versión entrante entera: mezclar media interfaz nueva con media vieja es peor que
elegir una.&lt;/p&gt;
&lt;h2 id=&quot;6-desplegar-con-red-no-con-fé&quot;&gt;6. Desplegar con red, no con fé&lt;/h2&gt;
&lt;p&gt;Antes de tocar producción:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Copia de la base de datos&lt;/strong&gt; con verificación de integridad (&lt;code&gt;pg_restore -l&lt;/code&gt; sobre el dump,
y contar filas de las tablas clave antes y después).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Guardar el trabajo local del servidor&lt;/strong&gt;: si el checkout de producción tiene cambios a mano,
haz un parche antes de resetear. Y comprueba si esos cambios ya están en la release.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Anotar los identificadores de las imágenes Docker actuales&lt;/strong&gt; para poder volver atrás.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Un modo de mantenimiento real&lt;/strong&gt;, no un &lt;code&gt;rm&lt;/code&gt; a mano de un fichero.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code bitacora-mate-dark&quot; style=&quot;background-color:#0c0f13;color:#d7dbe1; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#5F6B7A;font-style:italic&quot;&gt;# Antes de desplegar: copia + parche de cambios locales + inventario de imágenes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B9FD8&quot;&gt;bash&lt;/span&gt;&lt;span style=&quot;color:#5FB8A6&quot;&gt; scripts/pre_deploy_backup.sh&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#8B9FD8&quot;&gt;docker&lt;/span&gt;&lt;span style=&quot;color:#5FB8A6&quot;&gt; inspect&lt;/span&gt;&lt;span style=&quot;color:#D9A441&quot;&gt; --format&lt;/span&gt;&lt;span style=&quot;color:#5FB8A6&quot;&gt; &apos;{{.Image}}&apos;&lt;/span&gt;&lt;span style=&quot;color:#5FB8A6&quot;&gt; web&lt;/span&gt;&lt;span style=&quot;color:#7C8798&quot;&gt; &amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#5FB8A6&quot;&gt; imagenes_previas.txt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Y tras el despliegue, un script de verificación que compruebe lo que importa: commit
desplegado, migraciones aplicadas sin pendientes (&lt;code&gt;migrate --check&lt;/code&gt;), traducciones compiladas,
traducciones presentes, petición HTTP real y &lt;strong&gt;conteo de filas de las tablas críticas&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;7-limpiar&quot;&gt;7. Limpiar&lt;/h2&gt;
&lt;p&gt;Cuando la rama ya está integrada, se borra. Pero con criterio:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Demuestra que está contenida antes de borrarla:
&lt;code&gt;git rev-list --count origin/main..origin/&amp;lt;rama&amp;gt;&lt;/code&gt; debe dar &lt;strong&gt;0&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Si está checkouteada en un &lt;em&gt;worktree&lt;/em&gt;, no la puedes borrar: primero
&lt;code&gt;git -C &amp;lt;worktree&amp;gt; checkout --detach &amp;lt;sha&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Deja una rama de seguridad &lt;code&gt;backup/&amp;lt;rama&amp;gt;-pre-merge-&amp;lt;fecha&amp;gt;&lt;/code&gt; y decide después si sobra.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;lo-que-me-llevo&quot;&gt;Lo que me llevo&lt;/h2&gt;
&lt;p&gt;La refactorización no fue difícil por el código: fue difícil por &lt;strong&gt;la incertidumbre&lt;/strong&gt;. Cada
paso buscaba reducirla: tests que congelan el comportamiento, comparaciones contra el remoto,
verificación de cada cambio tras el merge, copias con integridad comprobada y un despliegue con
marcha atrás preparada. Si tuviera que resumirlo en una frase: &lt;strong&gt;no se trata de escribir código
mejor, se trata de no perder nada por el camino&lt;/strong&gt;.&lt;/p&gt;
</content:encoded><dc:creator>Rubén</dc:creator><media:content url="https://raicesbinarias.es/img/portada-refactor.png" medium="image" type="image/png" width="1200" height="630"/><category>Desarrollo</category><category>Django</category><category>Refactorización</category><category>Despliegue</category><enclosure url="https://raicesbinarias.es/img/portada-refactor.png" length="186484" type="image/png"/></item></channel></rss>