<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Cloud on Carles Abarca</title><link>https://carlesabarca.com/es/tags/cloud/</link><description>Recent content in Cloud on Carles Abarca</description><generator>Hugo -- gohugo.io</generator><language>es</language><copyright>© 2026 Carles Abarca</copyright><lastBuildDate>Sat, 26 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://carlesabarca.com/es/tags/cloud/index.xml" rel="self" type="application/rss+xml"/><item><title>Minix: un agente híbrido que no necesita enviar cada pregunta a la nube</title><link>https://carlesabarca.com/es/posts/minix-hybrid-agent/</link><pubDate>Sat, 26 Sep 2026 00:00:00 +0000</pubDate><guid>https://carlesabarca.com/es/posts/minix-hybrid-agent/</guid><description>Una conversación, IA local y cloud. Una prueba de seis turnos utilizó cerca de un 48% menos de tokens cloud, con el diálogo completo, la arquitectura y sus límites.</description><content:encoded>&lt;style&gt;
.minix-article pre { white-space: pre-wrap; overflow-wrap: anywhere; }
&lt;/style&gt;
&lt;div class="minix-article"&gt;
&lt;p&gt;En los últimos tiempos estamos viendo un ritmo frenético de innovación en modelos de IA &lt;em&gt;open-weight&lt;/em&gt; (o modelos de pesos abiertos) que prometen &lt;strong&gt;“IA gratuita e ilimitada”&lt;/strong&gt; corriendo en equipos de uso doméstico. Y las cifras empiezan a ser sorprendentes: un MacBook Pro con M4 Max puede incorporar hasta 128 GB de memoria unificada y 546 GB/s de ancho de banda, mientras que un Mac Studio con M3 Ultra llega hasta los 512 GB y supera los 800 GB/s. Apple afirma que este último puede ejecutar localmente modelos de lenguaje de más de &lt;strong&gt;600.000 millones de parámetros&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;La evolución de los propios modelos es igual de importante. Arquitecturas &lt;em&gt;Mixture of Experts&lt;/em&gt; (MoE) permiten construir modelos enormes activando únicamente una fracción de sus parámetros en cada inferencia. Qwen3-30B-A3B, por ejemplo, tiene 30.000 millones de parámetros pero activa aproximadamente &lt;strong&gt;3.000 millones por token&lt;/strong&gt;. La combinación de modelos más eficientes, cuantización y hardware con grandes cantidades de memoria unificada está reduciendo rápidamente la distancia entre la IA disponible en grandes &lt;em&gt;datacenters&lt;/em&gt; y la que podemos ejecutar de forma privada y local en nuestros propios equipos.&lt;/p&gt;
&lt;p&gt;Ahora bien, que la distancia se haya reducido no significa que los modelos &lt;em&gt;frontier&lt;/em&gt; en la nube hayan dejado de ser necesarios. Cuando una tarea exige razonamiento complejo, capacidades &lt;em&gt;agentic&lt;/em&gt; avanzadas o cuando la latencia y el &lt;em&gt;throughput&lt;/em&gt; —TTFT (&lt;em&gt;time to first token&lt;/em&gt;) y velocidad de inferencia en &lt;em&gt;tokens per second&lt;/em&gt;— son factores determinantes, la infraestructura cloud y los modelos &lt;em&gt;frontier&lt;/em&gt; siguen teniendo ventajas importantes.&lt;/p&gt;
&lt;p&gt;La cuestión es que los canales de YouTube dedicados a IA local se multiplican, y los debates entre los &lt;em&gt;local AI fanboys&lt;/em&gt; y los &lt;em&gt;frontier AI fanboys&lt;/em&gt; se suceden sin que exista realmente un ganador: &lt;strong&gt;están discutiendo cosas distintas&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Desde mi punto de vista, el modelo de uso óptimo es la &lt;strong&gt;IA híbrida&lt;/strong&gt;: combinar modelos locales y modelos en la nube para optimizar costes, disponer de mecanismos de contingencia que reduzcan la dependencia de un único proveedor y contar con una alternativa cuando manejamos información que preferimos no enviar fuera de nuestros propios sistemas.&lt;/p&gt;
&lt;p&gt;De esa visión nació &lt;strong&gt;Minix&lt;/strong&gt;. Es un experimento funcional que ya utilizo como mi asistente personal: mantengo una única conversación y el sistema decide, para cada petición, &lt;strong&gt;dónde tiene más sentido ejecutar la inteligencia&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;No quiero elegir un modelo cada vez que escribo. Quiero resolver una traducción breve localmente y recurrir al &lt;em&gt;cloud&lt;/em&gt; cuando necesito un razonamiento más exigente. Y quiero saber cuándo ocurre cada cosa.&lt;/p&gt;

&lt;h2 class="relative group"&gt;El servidor de Minix: un Mac mini M4 Pro con 24 GB
 &lt;div id="el-servidor-de-minix-un-mac-mini-m4-pro-con-24-gb" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#el-servidor-de-minix-un-mac-mini-m4-pro-con-24-gb" aria-label="Ancla"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;El servidor de LiteLLM es un &lt;strong&gt;Mac mini con Apple M4 Pro y 24 GB de memoria unificada&lt;/strong&gt;. Es el punto de entrada a los modelos: recibe las peticiones de Minix, ejecuta la selección de ruta y conecta el agente con la inferencia local o con el proveedor cloud. La inferencia local se sirve mediante LM Studio. No necesito una granja de servidores para experimentar con esta arquitectura; sí necesito respetar la memoria disponible y no cargar simultáneamente dos modelos grandes.&lt;/p&gt;
&lt;p&gt;Este Mac es una dependencia compartida: si falla el cloud, la ruta local puede seguir disponible; si se detiene el propio servidor de LiteLLM, tener dos destinos de inferencia no garantiza continuidad. El fallback entre modelos y la alta disponibilidad del gateway son problemas distintos.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Una conversación, tres LLMs y un router
 &lt;div id="una-conversación-tres-llms-y-un-router" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#una-conversaci%c3%b3n-tres-llms-y-un-router" aria-label="Ancla"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Hablo con Minix por Matrix. Hermes mantiene el historial y el bucle del agente: recibe mi mensaje, llama al modelo, ejecuta herramientas si corresponde y devuelve la respuesta. LiteLLM ofrece un punto de entrada común, &lt;code&gt;minix-smart&lt;/code&gt;, detrás del cual trabaja el router de complejidad.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;minix-smart&lt;/code&gt; no es un LLM.&lt;/strong&gt; Es un alias de routing. Para entender el sistema hay que separar los modelos que piensan de los componentes que transportan, clasifican y encaminan sus llamadas.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Función&lt;/th&gt;
 &lt;th&gt;Alias o componente&lt;/th&gt;
 &lt;th&gt;Modelo configurado&lt;/th&gt;
 &lt;th&gt;Papel en la prueba&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Clasificar la petición localmente&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;minix-classifier&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;qwen35-08b-classifier-bench&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Seis decisiones, una por turno&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Generar respuestas locales&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;minix-local&lt;/code&gt;, servido por LM Studio&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;google/gemma-4-12b&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;T1, T3 y T5&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Generar respuestas cloud&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;minix-cloud&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;chatgpt/gpt-5.5&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;T2, T4 y T6&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Atender la categoría REASONING&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;minix-think&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;chatgpt/gpt-5.5&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Ruta configurada, no utilizada en estos seis turnos&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Son &lt;strong&gt;tres modelos distintos&lt;/strong&gt;, no cuatro: &lt;code&gt;minix-cloud&lt;/code&gt; y &lt;code&gt;minix-think&lt;/code&gt; apuntan al mismo nombre de modelo cloud. SIMPLE y MEDIUM se encaminan a &lt;code&gt;minix-local&lt;/code&gt;; COMPLEX a &lt;code&gt;minix-cloud&lt;/code&gt;; REASONING a &lt;code&gt;minix-think&lt;/code&gt;. El default si falla la clasificación es &lt;code&gt;minix-cloud&lt;/code&gt;: no debo confundir esa política con una recuperación local ante una caída del cloud. La afinidad de sesión está desactivada, de modo que una respuesta cloud no fija el destino de toda la conversación.&lt;/p&gt;
&lt;p&gt;Uso aquí los alias actuales. La prueba histórica y sus apéndices conservan los nombres anteriores: &lt;code&gt;kathy-smart&lt;/code&gt; → &lt;code&gt;minix-smart&lt;/code&gt;, &lt;code&gt;kathy-local&lt;/code&gt; → &lt;code&gt;minix-local&lt;/code&gt;, &lt;code&gt;kathy-classifier&lt;/code&gt; → &lt;code&gt;minix-classifier&lt;/code&gt;, &lt;code&gt;kathy-premium&lt;/code&gt; → &lt;code&gt;minix-cloud&lt;/code&gt; y &lt;code&gt;kathy-codex&lt;/code&gt; → &lt;code&gt;minix-think&lt;/code&gt;. El sexto alias, &lt;code&gt;minix-smart-mlx&lt;/code&gt;, es auxiliar y ya existía; no lo convierto en una ruta adicional de este experimento. &lt;code&gt;minix-think&lt;/code&gt; conserva &lt;code&gt;reasoning_effort: high&lt;/code&gt;, que ya estaba configurado antes de la migración. La tabla relaciona las funciones actuales con los turnos históricos, no con una nueva ejecución.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="low"
 alt="Arquitectura de Minix: clasificador Qwen local, Gemma local y GPT-5.5 cloud; extensiones previstas en rosa"
 src="../../../images/minix/architecture-es.svg"
 &gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;El dibujo muestra el flujo configurado, incluida la ruta REASONING que no apareció en la prueba. La privacidad prevista aparece separada y en rosa discontinua; la contingencia desplegada, en verde. Los turnos y llamadas indicados son los del benchmark histórico. Tengo herramientas web y de archivos, además de un terminal Docker sin red; eso no convierte las herramientas web en locales ni demuestra aislamiento de todo el agente. En estos seis turnos no se invocó ninguna herramienta ni un modelo auxiliar de visión; no añado un cuarto LLM sin evidencia.&lt;/p&gt;
&lt;p&gt;&lt;a href="../../../images/minix/architecture-es.mmd" &gt;Fuente Mermaid&lt;/a&gt; · &lt;a href="../../../images/minix/architecture-es.html" &gt;Versión HTML&lt;/a&gt;&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;Código del diagrama editable&lt;/summary&gt;
&lt;div class="highlight-wrapper"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-mermaid" data-lang="mermaid"&gt;flowchart TB
 M[&amp;#34;Matrix&amp;lt;br/&amp;gt;Mi conversación&amp;#34;]
 H[&amp;#34;Hermes · Minix&amp;lt;br/&amp;gt;Historial + bucle de herramientas&amp;#34;]
 T[&amp;#34;Web · archivos · terminal&amp;lt;br/&amp;gt;Docker: terminal sin red&amp;lt;br/&amp;gt;0 llamadas en la prueba&amp;#34;]
 R[&amp;#34;LiteLLM · minix-smart&amp;lt;br/&amp;gt;Router / alias, NO es un LLM&amp;lt;br/&amp;gt;Sin afinidad de sesión&amp;#34;]
 Q[&amp;#34;minix-classifier · LLM local&amp;lt;br/&amp;gt;qwen35-08b-classifier-bench&amp;lt;br/&amp;gt;6 llamadas históricas&amp;#34;]
 L[&amp;#34;minix-local · LM Studio&amp;lt;br/&amp;gt;google/gemma-4-12b&amp;lt;br/&amp;gt;LLM local · T1, T3, T5&amp;#34;]
 P[&amp;#34;minix-cloud&amp;lt;br/&amp;gt;COMPLEX · T2, T4, T6&amp;lt;br/&amp;gt;También default del clasificador&amp;#34;]
 C[&amp;#34;minix-think&amp;lt;br/&amp;gt;REASONING · effort high&amp;lt;br/&amp;gt;No usado en los seis turnos&amp;#34;]
 G[&amp;#34;LLM cloud · chatgpt/gpt-5.5&amp;lt;br/&amp;gt;Mismo modelo en los dos alias&amp;#34;]
 O[&amp;#34;Respuesta + metadatos de ruta&amp;lt;br/&amp;gt;Hermes → Matrix · local / cloud&amp;lt;br/&amp;gt;No audita llamadas auxiliares&amp;#34;]
 S[&amp;#34;Sustitución local PREVISTA&amp;lt;br/&amp;gt;Historial + texto + herramientas&amp;lt;br/&amp;gt;Mapa de identidades local&amp;#34;]
 K[&amp;#34;Cloud con marcadores&amp;lt;br/&amp;gt;Solo texto con marcadores&amp;lt;br/&amp;gt;Flujo PREVISTO&amp;#34;]
 F[&amp;#34;Contingencia DESPLEGADA&amp;lt;br/&amp;gt;smart / cloud / think → local · máximo 1 salto&amp;lt;br/&amp;gt;500 / 429: 1 reintento · 400 / 401 / timeout: 0&amp;lt;br/&amp;gt;Cooldown: 60 s tras 2 fallos&amp;#34;]
 M --&amp;gt; H
 H --&amp;gt; T
 T --&amp;gt; H
 H --&amp;gt; R
 R --&amp;gt; Q
 Q --&amp;gt; |&amp;#34;tier&amp;#34;| R
 R --&amp;gt; |&amp;#34;SIMPLE / MEDIUM&amp;#34;| L
 R --&amp;gt; |&amp;#34;COMPLEX / default&amp;#34;| P
 R --&amp;gt; |&amp;#34;REASONING&amp;#34;| C
 P --&amp;gt; G
 C --&amp;gt; G
 L --&amp;gt; O
 G --&amp;gt; O
 O --&amp;gt; |&amp;#34;tool loop&amp;#34;| H
 S -.-&amp;gt; K
 V[&amp;#34;Restauración LOCAL prevista&amp;lt;br/&amp;gt;Validar marcadores + reponer&amp;lt;br/&amp;gt;Mapa privado permanece local&amp;#34;]
 D[&amp;#34;Salud local cada 300 s · cloud sin sondeos: recuperación pasiva&amp;lt;br/&amp;gt;Local terminal: error si falla · sin vuelta al cloud&amp;lt;br/&amp;gt;Sin deadline global · filtro de salud puede fallar abierto si todos están no saludables&amp;#34;]
 K -.-&amp;gt; V
 S -.-&amp;gt; V
 R --&amp;gt; |&amp;#34;fallback&amp;#34;| F
 P --&amp;gt; |&amp;#34;fallback&amp;#34;| F
 C --&amp;gt; |&amp;#34;fallback&amp;#34;| F
 F --&amp;gt; L
 classDef planned fill:#2b1725,stroke:#fb7185,stroke-dasharray:5 5,color:#ffffff
 class S,K,V planned
 classDef deployed fill:#064e3b,stroke:#34d399,color:#ffffff
 class F,D deployed&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/details&gt;
&lt;p&gt;La respuesta lleva una marca: 🖥️ para generación local y ☁️ para generación cloud. La añade el sistema a partir de metadatos de la ruta servida, no el modelo describiéndose a sí mismo. Identifica la generación final, no audita todas las llamadas auxiliares de una tarea con herramientas.&lt;/p&gt;
&lt;p&gt;He contrastado los nombres y mapeos con la configuración y los artefactos del experimento. Los logs corroboran directamente Gemma y el clasificador. En el híbrido no se archivó el header bruto de cada respuesta cloud: la categoría cloud está respaldada por el marcador y su código de renderizado, y GPT-5.5 por el mapeo configurado. En el baseline sí se conservaron los seis headers del deployment &lt;code&gt;kathy-premium&lt;/code&gt;. Eso identifica el deployment, no un fingerprint independiente de los pesos del proveedor.&lt;/p&gt;

&lt;h2 class="relative group"&gt;La prueba: volver al local después de usar el cloud
 &lt;div id="la-prueba-volver-al-local-después-de-usar-el-cloud" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#la-prueba-volver-al-local-despu%c3%a9s-de-usar-el-cloud" aria-label="Ancla"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Preparé seis peticiones sobre un mismo proyecto, &lt;strong&gt;Puente&lt;/strong&gt;, un sincronizador de archivos entre dos servidores que pueden trabajar desconectados. Fijé los prompts antes de empezar, sin pedir una ruta concreta ni ajustar el clasificador durante la prueba. Conservé la conversación existente, incluido su historial anterior.&lt;/p&gt;
&lt;p&gt;La secuencia fue &lt;strong&gt;local → cloud → local → cloud → local → cloud&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;T1, local:&lt;/strong&gt; una bienvenida y tres nombres alternativos. Gemma respondió con Nexo, Vínculo y Nodo.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;T2, cloud:&lt;/strong&gt; metadatos, reglas de fusión, pseudocódigo, tombstones y análisis de convergencia. La respuesta propuso causalidad y variantes concurrentes, pero no una prueba completa del algoritmo.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;T3, local:&lt;/strong&gt; extracción literal de tres valores. Devolvió &lt;code&gt;nombre=Puente&lt;/code&gt;, &lt;code&gt;nodos=A,B&lt;/code&gt; y &lt;code&gt;política=conservar conflictos&lt;/code&gt;, exactamente como pedía el prompt.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;T4, cloud:&lt;/strong&gt; un conflicto concreto entre edición y borrado. La respuesta conservó las variantes &lt;code&gt;(2,1)&lt;/code&gt; y &lt;code&gt;(1,2)&lt;/code&gt;, resumió el contexto como &lt;code&gt;(2,2)&lt;/code&gt; y explicó una resolución posterior &lt;code&gt;(3,2)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;T5, local:&lt;/strong&gt; una traducción breve al inglés: «Puente preserves concurrent edits and requires explicit resolution of conflicts.»&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;T6, cloud:&lt;/strong&gt; revisión crítica de tres atajos peligrosos: escoger un contenido por hash, recolectar tombstones demasiado pronto y reutilizar una identidad con el contador a cero.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Lo interesante no es solo que una pregunta difícil llegue al cloud. Es que &lt;strong&gt;la siguiente pregunta sencilla vuelve al local conservando la conversación&lt;/strong&gt;. No necesito abrir otro chat ni trasladar yo el contexto.&lt;/p&gt;
&lt;p&gt;Para poder revisar lo ocurrido sin depender de mi resumen, incluyo &lt;a href="../../../es/posts/minix-hybrid-agent/#conversaciones-completas-los-seis-turnos" &gt;los seis prompts y las doce respuestas completas, híbridas y cloud, al final de este artículo&lt;/a&gt;. Conservo el texto original, incluidos sus errores; no lo he reconstruido ni corregido para que la prueba parezca mejor.&lt;/p&gt;
&lt;p&gt;Hubo seis respuestas finales en cada ejecución, sin herramientas ni reintentos observados. Las tareas sencillas fueron satisfactorias en ambas. Las complejas aportaron ideas útiles, pero mantuvieron lagunas sobre fusión general de conflictos y recolección segura de versiones. Mi revisión no fue ciega ni demuestra equivalencia estadística de calidad: escoger un modelo cloud no convierte su respuesta en una prueba formal.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Qué medí y qué significa el 48%
 &lt;div id="qué-medí-y-qué-significa-el-48" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#qu%c3%a9-med%c3%ad-y-qu%c3%a9-significa-el-48" aria-label="Ancla"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Benchmark histórico, anterior a la migración de alias y a la nueva política de contingencia.&lt;/strong&gt; Conservo las medidas originales sin cambios; no describen una repetición con la configuración actual.&lt;/p&gt;
&lt;p&gt;Registré los tokens de entrada y salida, incluido el contexto de cada llamada. Después repetí los mismos seis prompts en una ejecución íntegramente cloud, acumulando sus propias respuestas, no las del híbrido.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Turno&lt;/th&gt;
 &lt;th&gt;Ruta híbrida&lt;/th&gt;
 &lt;th style="text-align: right"&gt;Tokens locales de respuesta&lt;/th&gt;
 &lt;th style="text-align: right"&gt;Tokens cloud híbridos&lt;/th&gt;
 &lt;th style="text-align: right"&gt;Tokens cloud del baseline&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;T1&lt;/td&gt;
 &lt;td&gt;local&lt;/td&gt;
 &lt;td style="text-align: right"&gt;7.351&lt;/td&gt;
 &lt;td style="text-align: right"&gt;0&lt;/td&gt;
 &lt;td style="text-align: right"&gt;8.257&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;T2&lt;/td&gt;
 &lt;td&gt;cloud&lt;/td&gt;
 &lt;td style="text-align: right"&gt;0&lt;/td&gt;
 &lt;td style="text-align: right"&gt;9.356&lt;/td&gt;
 &lt;td style="text-align: right"&gt;9.368&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;T3&lt;/td&gt;
 &lt;td&gt;local&lt;/td&gt;
 &lt;td style="text-align: right"&gt;8.650&lt;/td&gt;
 &lt;td style="text-align: right"&gt;0&lt;/td&gt;
 &lt;td style="text-align: right"&gt;9.441&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;T4&lt;/td&gt;
 &lt;td&gt;cloud&lt;/td&gt;
 &lt;td style="text-align: right"&gt;0&lt;/td&gt;
 &lt;td style="text-align: right"&gt;10.284&lt;/td&gt;
 &lt;td style="text-align: right"&gt;10.301&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;T5&lt;/td&gt;
 &lt;td&gt;local&lt;/td&gt;
 &lt;td style="text-align: right"&gt;9.631&lt;/td&gt;
 &lt;td style="text-align: right"&gt;0&lt;/td&gt;
 &lt;td style="text-align: right"&gt;10.379&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;T6&lt;/td&gt;
 &lt;td&gt;cloud&lt;/td&gt;
 &lt;td style="text-align: right"&gt;0&lt;/td&gt;
 &lt;td style="text-align: right"&gt;11.316&lt;/td&gt;
 &lt;td style="text-align: right"&gt;11.454&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td style="text-align: right"&gt;&lt;strong&gt;25.632&lt;/strong&gt;&lt;/td&gt;
 &lt;td style="text-align: right"&gt;&lt;strong&gt;30.956&lt;/strong&gt;&lt;/td&gt;
 &lt;td style="text-align: right"&gt;&lt;strong&gt;59.200&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Son totales de entrada más salida, no solo el texto visible. El baseline suma &lt;strong&gt;56.550 de entrada + 2.650 de salida&lt;/strong&gt;; la parte cloud del híbrido, &lt;strong&gt;28.510 + 2.446&lt;/strong&gt;. El clasificador consumió otros &lt;strong&gt;2.014 tokens locales&lt;/strong&gt; y unos &lt;strong&gt;1,71 segundos&lt;/strong&gt; acumulados, aparte de las llamadas de respuesta.&lt;/p&gt;
&lt;p&gt;La diferencia observada es:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;(59.200 − 30.956) / 59.200 × 100 = 47,71%: 28.244 tokens cloud menos.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;No he convertido los 25.632 tokens locales en supuestos tokens cloud ahorrados: comparo el consumo cloud de dos ejecuciones reales. Tampoco he eliminado ese trabajo; he trasladado parte a infraestructura local. Los 14.336 tokens de caché cloud del híbrido y los 15.360 del baseline ya están incluidos en la entrada, no sumados dos veces. Los distintos tokenizadores impiden interpretar la suma local/cloud como cómputo equivalente.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Es una comparación indicativa, no un ahorro causal controlado ni universal.&lt;/strong&gt; Recuperé el historial inicial disponible y las herramientas, pero no todas las instrucciones dinámicas originales con exactitud. El híbrido pasó por Matrix y el baseline por API; sus respuestas y cachés evolucionaron de forma distinta. Una conversación de seis turnos tampoco mide la precisión general del clasificador.&lt;/p&gt;
&lt;p&gt;La afirmación defendible es: &lt;strong&gt;en esta prueba, Minix utilizó aproximadamente un 48% menos de tokens cloud que una ejecución comparable íntegramente en la nube&lt;/strong&gt;. No significa un 48% menos de factura ni más velocidad: no he calculado costes monetarios, electricidad, hardware o mantenimiento, ni he realizado un benchmark controlado de latencia.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Continuidad: contingencia reactiva ya desplegada
 &lt;div id="continuidad-contingencia-reactiva-ya-desplegada" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#continuidad-contingencia-reactiva-ya-desplegada" aria-label="Ancla"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;He desplegado una política acotada en LiteLLM: &lt;code&gt;minix-cloud&lt;/code&gt;, &lt;code&gt;minix-think&lt;/code&gt; y &lt;code&gt;minix-smart&lt;/code&gt; pueden recurrir a &lt;code&gt;minix-local&lt;/code&gt;, con &lt;strong&gt;un máximo de un salto de fallback&lt;/strong&gt;. El local es terminal: si también falla, la petición termina con error, sin volver al cloud ni abrir un ciclo. No es una promesa de que Gemma resuelva cualquier tarea con la misma calidad.&lt;/p&gt;
&lt;p&gt;Para errores cloud HTTP 500 y 429 permito &lt;strong&gt;un reintento&lt;/strong&gt;; para 400, 401 y timeouts, &lt;strong&gt;cero reintentos&lt;/strong&gt;. No reintentar no equivale a desactivar el fallback. Tras el segundo fallo, el deployment entra en un cooldown de &lt;strong&gt;60 segundos&lt;/strong&gt;. Conservo los timeouts de inferencia existentes; no hay un plazo máximo global estricto para toda la secuencia.&lt;/p&gt;
&lt;p&gt;Compruebo periódicamente la salud local cada &lt;strong&gt;300 segundos&lt;/strong&gt;. No hago sondeos cloud en segundo plano: su recuperación es &lt;strong&gt;pasiva&lt;/strong&gt;, cuando vence el cooldown y una nueva petición vuelve a probar la ruta. Si todos los destinos aparecen como no saludables, el filtro de salud puede permitir intentarlos de nuevo (&lt;em&gt;fail-open&lt;/em&gt;); eso no cambia que un fallo final del local termine la petición. Los dos alias cloud comparten proveedor y modelo: no son redundancia independiente.&lt;/p&gt;
&lt;p&gt;La verificación distingue pruebas de fallos y uso real. Dispongo de &lt;strong&gt;28 pruebas unitarias aprobadas&lt;/strong&gt; y evidencia previa de &lt;strong&gt;12 casos loopback aislados&lt;/strong&gt;, no repetidos durante esta migración. No provoqué una caída en producción. En producción comprobé respuestas &lt;strong&gt;streaming HTTP 200&lt;/strong&gt; de local, cloud y think sin fallback, con contenido, finalización y sus identificadores de ruta exactos; también probé smart y confirmé una respuesta final local real en Matrix. Esto valida esas rutas, no todos los modos de la API.&lt;/p&gt;
&lt;p&gt;Queda una limitación concreta: la llamada cloud &lt;strong&gt;no streaming&lt;/strong&gt; devuelve &lt;code&gt;Unknown items in responses API response: []&lt;/code&gt;. Reproduje el mismo fallo con la configuración anterior; no lo atribuyo a la migración ni lo doy por resuelto. La ruta streaming sí superó la comprobación.&lt;/p&gt;
&lt;p&gt;El fallback de inferencia no implica reevaluación semántica de la tarea, aviso automático de modo degradado ni protección demostrada frente a repetir herramientas con efectos externos. No afirmo haber validado esos comportamientos. Consultar un servicio externo caído tampoco se vuelve posible por cambiar de modelo. Deben seguir disponibles el runtime, el almacenamiento y el canal: &lt;strong&gt;la inferencia local no garantiza que un chat remoto sobreviva a una pérdida de conexión&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Privacidad: enviar el problema, no necesariamente las identidades
 &lt;div id="privacidad-enviar-el-problema-no-necesariamente-las-identidades" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#privacidad-enviar-el-problema-no-necesariamente-las-identidades" aria-label="Ancla"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;La otra decisión que quiero incorporar no es de dificultad, sino de datos: &lt;strong&gt;¿qué información necesita salir de mi entorno para resolver la tarea?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;El mecanismo previsto sustituiría localmente datos sensibles por identificadores consistentes, manteniendo el mapa de correspondencias bajo control local, con acceso restringido y una vida limitada a la tarea. Por ejemplo, mi petición ficticia:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;«Prepara un correo para Elena, de Taller Norte, sobre el proyecto Brisa».&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;se convertiría, antes de salir, en:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;«Prepara un correo para [PERSONA_1], de [EMPRESA_1], sobre el proyecto [PROYECTO_1]».&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;El cloud redactaría usando esos marcadores. A la vuelta, la capa local validaría cada identificador y repondría los valores originales para mí. La misma persona conservaría el mismo marcador dentro de la tarea; uno desconocido o alterado no daría permiso para inventar nombres ni recuperar otra correspondencia.&lt;/p&gt;
&lt;p&gt;No bastaría con limpiar el último mensaje. Quiero aplicar el control a todo lo que sale: historial, documentos, resultados de herramientas y metadatos pertinentes. Un adjunto que no pueda tratarse con seguridad debería quedarse local o excluirse. Si resolver la tarea exige revelar una identidad, la alternativa sería mantenerla local, pedirme autorización específica o explicar la limitación, no enviar el original silenciosamente.&lt;/p&gt;
&lt;p&gt;Esto es &lt;strong&gt;seudonimización reversible&lt;/strong&gt;, no anonimato garantizado. El contexto puede revelar identidades aunque desaparezcan los nombres; la detección y los marcadores también pueden fallar. Por eso incluyo validación de salida, registros sin secretos y un tratamiento conservador de los casos dudosos en el comportamiento que busco.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Mi asistente, no una guerra entre modelos
 &lt;div id="mi-asistente-no-una-guerra-entre-modelos" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#mi-asistente-no-una-guerra-entre-modelos" aria-label="Ancla"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;No uso Minix para demostrar que el local sustituye siempre al cloud, ni al revés. Lo uso para escoger dónde ejecutar cada tarea sin tener que gestionar manualmente esa elección.&lt;/p&gt;
&lt;p&gt;El experimento ya me permite observar algo concreto: una conversación que vuelve al local después de pasar por el cloud, con un consumo cloud menor en la comparación realizada. La contingencia reactiva ya está desplegada; el control de datos sigue siendo el siguiente paso, sin borrar los límites de lo que he medido.&lt;/p&gt;
&lt;p&gt;La pregunta deja de ser «¿cuál es el modelo más potente?» y pasa a ser otra más útil:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;¿Qué parte de esta tarea necesita realmente salir de mi entorno?&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;

&lt;h2 class="relative group"&gt;Conversaciones completas: los seis turnos
 &lt;div id="conversaciones-completas-los-seis-turnos" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#conversaciones-completas-los-seis-turnos" aria-label="Ancla"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Conservo aquí los seis prompts originales y las doce respuestas completas: una respuesta híbrida y una íntegramente cloud por turno. Cada ejecución acumuló sus propias respuestas; las presento juntas para facilitar la revisión, no como si fueran una sola conversación.&lt;/p&gt;
&lt;p&gt;Los bloques son transcripciones literales en el idioma original (español, salvo las traducciones solicitadas en T5). Mantengo incluso los plurales y las afirmaciones discutibles del material citado; no los convierto en mi voz editorial. Los identificadores de experimento forman parte de los prompts. Los iconos de la ejecución híbrida son metadatos añadidos por el sistema, no palabras generadas por el modelo. No incluyo instrucciones internas, historial anterior, identificadores de Matrix ni credenciales.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lectura crítica:&lt;/strong&gt; las tareas sencillas son satisfactorias en ambas ejecuciones. Los diseños de T2 no demuestran una fusión general de conjuntos de conflictos; T4 resuelve el ejemplo concreto, no esa prueba general. T6 aporta riesgos y propiedades útiles, pero no demuestra condiciones mínimas de recolección. En el baseline, rechazar operaciones antiguas dominadas no debe confundirse con descartar ediciones concurrentes legítimas. No ejecuté aquí ningún sincronizador distribuido ni una evaluación ciega de calidad.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Índice
 &lt;div id="índice" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%c3%adndice" aria-label="Ancla"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;&lt;a href="../../../es/posts/minix-hybrid-agent/#t1" &gt;T1&lt;/a&gt; · &lt;a href="../../../es/posts/minix-hybrid-agent/#t2" &gt;T2&lt;/a&gt; · &lt;a href="../../../es/posts/minix-hybrid-agent/#t3" &gt;T3&lt;/a&gt; · &lt;a href="../../../es/posts/minix-hybrid-agent/#t4" &gt;T4&lt;/a&gt; · &lt;a href="../../../es/posts/minix-hybrid-agent/#t5" &gt;T5&lt;/a&gt; · &lt;a href="../../../es/posts/minix-hybrid-agent/#t6" &gt;T6&lt;/a&gt;&lt;/p&gt;

&lt;h3 class="relative group"&gt;T1
 &lt;div id="t1" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#t1" aria-label="Ancla"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;

&lt;h4 class="relative group"&gt;Mi prompt original
 &lt;div id="mi-prompt-original" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#mi-prompt-original" aria-label="Ancla"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h4&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[MXHYB-20260926-A T1] Vamos a diseñar juntos un pequeño sincronizador de archivos entre dos servidores que pueden trabajar desconectados, A y B. Lo llamaremos Puente. De momento, responde en español con una bienvenida de una frase y tres nombres alternativos cortos. No crees archivos ni ejecutes nada.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 class="relative group"&gt;Respuesta híbrida — local
 &lt;div id="respuesta-híbrida--local" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#respuesta-h%c3%adbrida--local" aria-label="Ancla"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h4&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;🖥️ Bienvenido al diseño del proyecto Puente para la sincronización entre servidores.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Nombres alternativos: Nexo, Vínculo, Nodo.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 class="relative group"&gt;Respuesta íntegramente cloud
 &lt;div id="respuesta-íntegramente-cloud" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#respuesta-%c3%adntegramente-cloud" aria-label="Ancla"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h4&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Bienvenido a Puente: podemos diseñar un sincronizador simple, robusto y preparado para desconexiones entre A y B.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; Nexo
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; Brisa
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- Trama&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 class="relative group"&gt;T2
 &lt;div id="t2" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#t2" aria-label="Ancla"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;

&lt;h4 class="relative group"&gt;Mi prompt original
 &lt;div id="mi-prompt-original-1" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#mi-prompt-original-1" aria-label="Ancla"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h4&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[MXHYB-20260926-A T2] Sigamos con Puente. A y B pueden editar o borrar el mismo archivo mientras están desconectados; los relojes físicos no son fiables y la red duplica y reordena mensajes. Diseña un algoritmo de sincronización que converja sin perder silenciosamente ediciones concurrentes. Especifica metadatos, reglas de fusión, tombstones y condiciones para recolectarlos. Da pseudocódigo y justifica convergencia e idempotencia; incluye un contraejemplo a elegir siempre la fecha más reciente. Unas 450 palabras, sin ejecutar ni crear archivos.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 class="relative group"&gt;Respuesta híbrida — nube
 &lt;div id="respuesta-híbrida--nube" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#respuesta-h%c3%adbrida--nube" aria-label="Ancla"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h4&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;☁️ Un diseño seguro para Puente debe tratar cada archivo como un objeto replicado con historial causal, no como “el último gana”.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gs"&gt;**Metadatos por archivo**&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`id`&lt;/span&gt;: ruta lógica o identificador estable.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`replica_id`&lt;/span&gt;: &lt;span class="sb"&gt;`A`&lt;/span&gt; o &lt;span class="sb"&gt;`B`&lt;/span&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`version_vector`&lt;/span&gt;: mapa &lt;span class="sb"&gt;`{replica_id: contador}`&lt;/span&gt; que resume las actualizaciones conocidas.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`dot`&lt;/span&gt;: par único &lt;span class="sb"&gt;`(replica_id, contador)`&lt;/span&gt; de la operación local.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`content_hash`&lt;/span&gt;: hash del contenido, si existe.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`deleted`&lt;/span&gt;: booleano.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`tombstone`&lt;/span&gt;: registro de borrado con &lt;span class="sb"&gt;`dot`&lt;/span&gt;, &lt;span class="sb"&gt;`version_vector`&lt;/span&gt; y fecha local solo informativa.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`conflicts`&lt;/span&gt;: conjunto de variantes concurrentes, cada una con contenido y metadatos causales.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Los relojes físicos no deciden nada. Solo sirven para mostrar información al usuario.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gs"&gt;**Reglas**&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Cada edición local incrementa el contador propio y crea una nueva versión causal. Cada borrado crea un tombstone, no elimina inmediatamente el registro. Al sincronizar, se comparan vectores:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; Si &lt;span class="sb"&gt;`remote.version_vector &amp;lt;= local.version_vector`&lt;/span&gt;, el remoto ya está incluido: ignorar.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; Si &lt;span class="sb"&gt;`local.version_vector &amp;lt;= remote.version_vector`&lt;/span&gt;, aceptar remoto.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; Si ninguno domina al otro, hay concurrencia:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;-&lt;/span&gt; edición vs edición: conservar ambas variantes en &lt;span class="sb"&gt;`conflicts`&lt;/span&gt;, o intentar fusión automática si el tipo lo permite; si la fusión no es limpia, exponer conflicto.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;-&lt;/span&gt; edición vs borrado: no borrar silenciosamente; conservar la edición como conflicto contra tombstone.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;-&lt;/span&gt; borrado vs borrado: fusionar tombstones tomando la unión causal.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gs"&gt;**Pseudocódigo**&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;```text
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;edit(file, content):
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; clock[self] += 1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; dot = (self, clock[self])
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; file.content = content
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; file.deleted = false
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; file.version_vector[self] = clock[self]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; file.dot = dot
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; file.conflicts = {}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;delete(file):
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; clock[self] += 1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; dot = (self, clock[self])
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; file.content = null
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; file.deleted = true
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; file.tombstone = {dot, version_vector + self counter}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; file.version_vector[self] = clock[self]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;merge(local, remote):
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if dominates(local.vv, remote.vv):
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return local
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if dominates(remote.vv, local.vv):
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return remote
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; merged.vv = pointwise_max(local.vv, remote.vv)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if local.deleted and remote.deleted:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; merged.deleted = true
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; merged.tombstone = merge_tombstones(local, remote)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return merged
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if not local.deleted and not remote.deleted:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; result = try_semantic_merge(local.content, remote.content)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if result.clean:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; merged.content = result.content
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; merged.conflicts = {}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; else:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; merged.content = null
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; merged.conflicts = {variant(local), variant(remote)}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return merged
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; merged.deleted = false
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; merged.content = null
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; merged.conflicts = {variant(local), variant(remote)}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return merged
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;```&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gs"&gt;**Tombstones y recolección**&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Un tombstone solo puede eliminarse cuando todas las réplicas conocidas han observado causalmente el borrado. Con dos servidores, A puede recolectar un tombstone de B cuando sabe que B ha recibido un vector que domina el vector del tombstone, y viceversa. En sistemas ampliables, hace falta un mecanismo de membresía estable o acknowledgements por réplica. Si hay réplicas desconocidas o restauraciones desde copias antiguas, no se debe recolectar aún.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gs"&gt;**Convergencia e idempotencia**&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;La convergencia viene de usar una unión determinista: máximo punto a punto de vectores, preservación de variantes concurrentes y reglas simétricas. Si A y B reciben los mismos estados, acaban calculando el mismo resultado. La idempotencia se obtiene porque reentregar el mismo mensaje no cambia nada: un vector ya dominado se ignora, y los conflictos se identifican por &lt;span class="sb"&gt;`dot`&lt;/span&gt; o hash causal, no por número de recepción.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gs"&gt;**Contraejemplo al “más reciente gana”**&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;A edita &lt;span class="sb"&gt;`informe.txt`&lt;/span&gt; a “versión legal” con reloj adelantado a las 12:10. B, desconectado, edita a “versión financiera” con reloj atrasado a las 12:05. Ambas ediciones son concurrentes y válidas. Elegir la fecha más reciente descarta silenciosamente la versión financiera, aunque no fue sobrescrita causalmente por A.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 class="relative group"&gt;Respuesta íntegramente cloud
 &lt;div id="respuesta-íntegramente-cloud-1" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#respuesta-%c3%adntegramente-cloud-1" aria-label="Ancla"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h4&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Propongo que Puente use versiones causales, no fechas físicas. Cada servidor replica un conjunto de objetos CRDT por ruta lógica, con detección explícita de conflictos.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gs"&gt;**Metadatos por archivo**&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`node_id`&lt;/span&gt;: &lt;span class="sb"&gt;`A`&lt;/span&gt; o &lt;span class="sb"&gt;`B`&lt;/span&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`version_vector`&lt;/span&gt;: mapa &lt;span class="sb"&gt;`{nodo: contador}`&lt;/span&gt; que identifica causalidad.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`content_hash`&lt;/span&gt;: hash del contenido.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`op_id`&lt;/span&gt;: par único &lt;span class="sb"&gt;`(node_id, contador)`&lt;/span&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`deleted`&lt;/span&gt;: booleano.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`tombstone`&lt;/span&gt;: si está borrado, guarda &lt;span class="sb"&gt;`delete_vector`&lt;/span&gt;, &lt;span class="sb"&gt;`delete_op_id`&lt;/span&gt; y fecha local solo informativa.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`conflicts`&lt;/span&gt;: conjunto de variantes concurrentes, cada una con contenido/hash/vector.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Cada operación local incrementa el contador del nodo. Una edición genera una versión con &lt;span class="sb"&gt;`deleted=false`&lt;/span&gt;; un borrado genera una tombstone con &lt;span class="sb"&gt;`deleted=true`&lt;/span&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gs"&gt;**Reglas de sincronización**&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Al recibir un estado remoto para una ruta:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;```pseudo
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;function dominates(v1, v2):
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return forall n: v1[n] &amp;gt;= v2[n] and exists n: v1[n] &amp;gt; v2[n]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;function concurrent(v1, v2):
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return not dominates(v1, v2) and not dominates(v2, v1)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;function merge(local, remote):
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if remote.op_id already_seen:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return local
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mark_seen(remote.op_id)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if dominates(remote.vector, local.vector):
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return remote
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if dominates(local.vector, remote.vector):
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return local
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if concurrent(local.vector, remote.vector):
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return resolve_concurrent(local, remote)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;```&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;La resolución concurrente no debe descartar silenciosamente. Para archivos binarios o cambios incompatibles:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;```pseudo
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;function resolve_concurrent(x, y):
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if x.deleted and y.deleted:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return tombstone(union_vector(x.vector, y.vector))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if x.deleted and not y.deleted:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return conflict_record(
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; visible = y,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; conflicts = [tombstone_variant(x)]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if not x.deleted and y.deleted:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return conflict_record(
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; visible = x,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; conflicts = [tombstone_variant(y)]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if same content_hash:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return version(content=x.content, vector=union_vector(x.vector, y.vector))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if textual_auto_merge_possible(x.content, y.content, base):
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return merged_file(
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; content=three_way_merge(base, x, y),
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; vector=union_vector(x.vector, y.vector),
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; conflicts=[]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return conflict_record(
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; visible = deterministic_choice(x, y), // solo para presentación
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; conflicts = [x, y],
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; vector = union_vector(x.vector, y.vector)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;```&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;En disco, un conflicto puede exponerse como &lt;span class="sb"&gt;`archivo`&lt;/span&gt;, &lt;span class="sb"&gt;`archivo.conflict-A`&lt;/span&gt;, &lt;span class="sb"&gt;`archivo.conflict-B`&lt;/span&gt; o como un manifiesto. Lo importante: ambas ediciones quedan accesibles.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gs"&gt;**Tombstones**&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Una tombstone representa “esta ruta fue borrada en esta causalidad”. Debe propagarse igual que una edición para evitar que una réplica antigua resucite el archivo. Puede recolectarse solo cuando todos los nodos conocidos han observado el borrado:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;```pseudo
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;function can_gc_tombstone(t):
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return forall node in membership:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ack_vector[node] &amp;gt;= t.delete_vector[node]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;```&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Si la membresía cambia, hace falta un protocolo de épocas: no se recolectan tombstones de una época hasta que todos los miembros de esa época hayan acusado recibo, o hasta que exista una instantánea completa posterior aceptada por todos los miembros activos.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gs"&gt;**Convergencia e idempotencia**&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Converge porque la fusión es determinista, conmutativa y asociativa sobre vectores causales y conjuntos de variantes. Los mensajes duplicados no cambian el resultado porque &lt;span class="sb"&gt;`op_id`&lt;/span&gt; y los hashes hacen la aplicación idempotente. El reordenamiento no importa: si llega primero una versión antigua, luego será dominada; si llegan versiones concurrentes, se conservan ambas.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gs"&gt;**Contraejemplo a “fecha más reciente”**&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;A y B editan &lt;span class="sb"&gt;`nota.txt`&lt;/span&gt; desconectados. A escribe “precio 10” a las 10:05, pero su reloj va adelantado; B escribe “precio 12” a las 10:01 reales, con reloj atrasado marcando 09:50. Al reconectar, elegir la fecha más reciente conserva A y borra B, aunque son cambios concurrentes y ambos deben revisarse. Peor aún: con relojes desviados, el resultado puede depender del reloj defectuoso, no de la causalidad.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 class="relative group"&gt;T3
 &lt;div id="t3" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#t3" aria-label="Ancla"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;

&lt;h4 class="relative group"&gt;Mi prompt original
 &lt;div id="mi-prompt-original-2" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#mi-prompt-original-2" aria-label="Ancla"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h4&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[MXHYB-20260926-A T3] Para la ficha de Puente, extrae literalmente estos tres valores del texto, sin explicaciones ni consultar nada: «nombre=Puente; nodos=A,B; política=conservar conflictos». Devuelve solo tres líneas con formato clave=valor.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 class="relative group"&gt;Respuesta híbrida — local
 &lt;div id="respuesta-híbrida--local-1" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#respuesta-h%c3%adbrida--local-1" aria-label="Ancla"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h4&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;🖥️ nombre=Puente
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nodos=A,B
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;política=conservar conflictos&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 class="relative group"&gt;Respuesta íntegramente cloud
 &lt;div id="respuesta-íntegramente-cloud-2" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#respuesta-%c3%adntegramente-cloud-2" aria-label="Ancla"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h4&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nombre=Puente
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nodos=A,B
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;política=conservar conflictos&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 class="relative group"&gt;T4
 &lt;div id="t4" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#t4" aria-label="Ancla"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;

&lt;h4 class="relative group"&gt;Mi prompt original
 &lt;div id="mi-prompt-original-3" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#mi-prompt-original-3" aria-label="Ancla"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h4&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[MXHYB-20260926-A T4] Probemos el diseño de Puente con este historial. Parten del mismo archivo con vector (1,1). Desconectados, A lo edita y genera (2,1), B lo borra y genera (1,2). A recibe primero el borrado, luego un duplicado del archivo inicial; B recibe primero la edición y luego un duplicado del borrado. Calcula el estado final de ambos y demuestra qué propiedades debe cumplir la fusión para coincidir. Explica cómo resolver explícitamente el conflicto con una nueva versión y por qué borrar el tombstone demasiado pronto puede resucitar datos. Señala cualquier ambigüedad de tu propuesta anterior y corrígela. Unas 350 palabras; razonamiento en el chat, sin herramientas ni archivos.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 class="relative group"&gt;Respuesta híbrida — nube
 &lt;div id="respuesta-híbrida--nube-1" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#respuesta-h%c3%adbrida--nube-1" aria-label="Ancla"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h4&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;☁️ Estado inicial: archivo existente con vector &lt;span class="sb"&gt;`(1,1)`&lt;/span&gt;. Luego hay dos operaciones concurrentes: edición de A con vector &lt;span class="sb"&gt;`(2,1)`&lt;/span&gt; y borrado de B con vector &lt;span class="sb"&gt;`(1,2)`&lt;/span&gt;. Ninguna domina a la otra, porque A tiene más información de A y B más información de B.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gs"&gt;**Estado final esperado**&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; En A, al recibir el borrado &lt;span class="sb"&gt;`(1,2)`&lt;/span&gt;, la fusión detecta concurrencia edición-vs-borrado y debe producir un conflicto explícito: variante editada de A &lt;span class="sb"&gt;`(2,1)`&lt;/span&gt; frente a tombstone de B &lt;span class="sb"&gt;`(1,2)`&lt;/span&gt;, con vector fusionado &lt;span class="sb"&gt;`(2,2)`&lt;/span&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; El duplicado del archivo inicial &lt;span class="sb"&gt;`(1,1)`&lt;/span&gt; queda dominado por &lt;span class="sb"&gt;`(2,2)`&lt;/span&gt;, así que se ignora.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; En B, al recibir la edición &lt;span class="sb"&gt;`(2,1)`&lt;/span&gt;, obtiene el mismo conflicto edición-vs-borrado con vector &lt;span class="sb"&gt;`(2,2)`&lt;/span&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; El duplicado del borrado &lt;span class="sb"&gt;`(1,2)`&lt;/span&gt; queda dominado por &lt;span class="sb"&gt;`(2,2)`&lt;/span&gt; o ya está incluido como variante/tombstone, así que no cambia nada.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Por tanto, ambos terminan con un estado conflictivo convergente: “archivo no resuelto”, con la edición de A preservada y el borrado de B preservado como intención concurrente. No debe aparecer como archivo normal ni como borrado definitivo hasta que alguien resuelva.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gs"&gt;**Propiedades necesarias de la fusión**&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;La fusión debe ser:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; conmutativa: &lt;span class="sb"&gt;`merge(edición, borrado) = merge(borrado, edición)`&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; asociativa: el orden de llegada no cambia el resultado final;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; idempotente: duplicados como &lt;span class="sb"&gt;`(1,1)`&lt;/span&gt; o &lt;span class="sb"&gt;`(1,2)`&lt;/span&gt; no alteran el estado;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; inflacionaria causalmente: no pierde dots ni conocimiento causal;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; determinista: las mismas entradas producen el mismo estado.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gs"&gt;**Resolución explícita**&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Un usuario o regla superior debe crear una nueva operación, por ejemplo en A: “conservar edición y descartar borrado”. Esa resolución incrementa A y genera &lt;span class="sb"&gt;`(3,2)`&lt;/span&gt;, dominando tanto &lt;span class="sb"&gt;`(2,1)`&lt;/span&gt; como &lt;span class="sb"&gt;`(1,2)`&lt;/span&gt;. El nuevo estado contiene el archivo elegido, &lt;span class="sb"&gt;`conflicts={}`&lt;/span&gt;, y un registro causal de que el tombstone conflictivo fue observado y resuelto. Si se elige borrar, se crea un nuevo tombstone &lt;span class="sb"&gt;`(3,2)`&lt;/span&gt; que domina la edición.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gs"&gt;**Riesgo de recolectar tombstones**&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Si B borra el tombstone demasiado pronto y luego recibe desde A una copia antigua del archivo &lt;span class="sb"&gt;`(1,1)`&lt;/span&gt;, puede no tener memoria causal del borrado &lt;span class="sb"&gt;`(1,2)`&lt;/span&gt;. Entonces podría tratar el archivo viejo como válido y resucitar datos borrados. El tombstone debe conservarse hasta que todos los nodos conocidos hayan reconocido un vector que domina el borrado y no puedan reintroducir estados anteriores.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gs"&gt;**Ambigüedad corregida**&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Antes dije &lt;span class="sb"&gt;`merged.deleted=false`&lt;/span&gt; en edición-vs-borrado. Eso es ambiguo: podría parecer que el archivo existe. Corrección: el estado debe ser &lt;span class="sb"&gt;`status=conflict`&lt;/span&gt;, sin contenido canónico, con variantes explícitas &lt;span class="sb"&gt;`{edit_variant, delete_variant}`&lt;/span&gt; y vector &lt;span class="sb"&gt;`(2,2)`&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 class="relative group"&gt;Respuesta íntegramente cloud
 &lt;div id="respuesta-íntegramente-cloud-3" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#respuesta-%c3%adntegramente-cloud-3" aria-label="Ancla"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h4&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Estado inicial: archivo vivo con vector &lt;span class="sb"&gt;`(1,1)`&lt;/span&gt;. Durante la desconexión:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; A edita: versión viva &lt;span class="sb"&gt;`EA = (2,1)`&lt;/span&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; B borra: tombstone &lt;span class="sb"&gt;`DB = (1,2)`&lt;/span&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Estos vectores son concurrentes: &lt;span class="sb"&gt;`(2,1)`&lt;/span&gt; no domina a &lt;span class="sb"&gt;`(1,2)`&lt;/span&gt; y &lt;span class="sb"&gt;`(1,2)`&lt;/span&gt; no domina a &lt;span class="sb"&gt;`(2,1)`&lt;/span&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gs"&gt;**Estado final esperado**&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Cuando A recibe el borrado &lt;span class="sb"&gt;`DB`&lt;/span&gt;, fusiona &lt;span class="sb"&gt;`EA`&lt;/span&gt; con &lt;span class="sb"&gt;`DB`&lt;/span&gt; y obtiene un conflicto explícito: “edición de A” frente a “borrado de B”. El archivo no debe quedar simplemente vivo ni simplemente borrado; debe quedar en estado conflictivo con ambas variantes preservadas.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Luego A recibe un duplicado del archivo inicial &lt;span class="sb"&gt;`(1,1)`&lt;/span&gt;. Como &lt;span class="sb"&gt;`(2,1)`&lt;/span&gt; y &lt;span class="sb"&gt;`(1,2)`&lt;/span&gt; dominan causalmente a &lt;span class="sb"&gt;`(1,1)`&lt;/span&gt;, ese duplicado se ignora.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Cuando B recibe la edición &lt;span class="sb"&gt;`EA`&lt;/span&gt;, fusiona &lt;span class="sb"&gt;`DB`&lt;/span&gt; con &lt;span class="sb"&gt;`EA`&lt;/span&gt; y debe obtener exactamente el mismo conflicto. Luego recibe un duplicado del borrado &lt;span class="sb"&gt;`DB`&lt;/span&gt;; como ya está incluido en el estado conflictivo, no cambia nada.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Resultado final en ambos: conflicto &lt;span class="sb"&gt;`{EA viva, DB tombstone}`&lt;/span&gt;, vector resumen &lt;span class="sb"&gt;`(2,2)`&lt;/span&gt; o conjunto causal equivalente, con contenido de A preservado y borrado de B preservado.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gs"&gt;**Propiedades necesarias de la fusión**&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;La fusión debe ser:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; Conmutativa: &lt;span class="sb"&gt;`merge(EA, DB) = merge(DB, EA)`&lt;/span&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; Asociativa: el agrupamiento de mensajes no cambia el resultado.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; Idempotente: duplicados como &lt;span class="sb"&gt;`(1,1)`&lt;/span&gt; o &lt;span class="sb"&gt;`DB`&lt;/span&gt; no alteran el estado.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; Causal: una versión dominada por el estado actual no puede resucitar ni borrar nada.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; Conservadora ante concurrencia: si edición y borrado son concurrentes, no se descarta ninguno silenciosamente.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gs"&gt;**Resolución explícita**&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;El usuario o una política superior puede resolver el conflicto creando una nueva operación causalmente posterior, por ejemplo en A:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sb"&gt;`R = (3,2)`&lt;/span&gt; si A ya conoce &lt;span class="sb"&gt;`DB`&lt;/span&gt;, con decisión “mantener contenido editado” o “confirmar borrado”.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Si mantiene el archivo, &lt;span class="sb"&gt;`R`&lt;/span&gt; contiene el contenido elegido y domina tanto &lt;span class="sb"&gt;`EA`&lt;/span&gt; como &lt;span class="sb"&gt;`DB`&lt;/span&gt;. Si confirma borrado, &lt;span class="sb"&gt;`R`&lt;/span&gt; es una nueva tombstone que también domina ambas variantes. Al propagarse, &lt;span class="sb"&gt;`R`&lt;/span&gt; reemplaza el conflicto porque ya no hay concurrencia: hay una decisión posterior.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gs"&gt;**Riesgo de recolectar tombstones pronto**&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Si B borra la tombstone &lt;span class="sb"&gt;`DB`&lt;/span&gt; antes de que A la haya visto, A puede reenviar una versión viva antigua o concurrente y B no tendrá memoria causal suficiente para saber que hubo un borrado. El archivo puede “resucitar” como si el borrado nunca hubiera existido.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gs"&gt;**Ambigüedad corregida**&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Antes dije que un conflicto podía tener una variante “visible” elegida determinísticamente. Eso puede inducir a pérdida semántica. Mejor: el estado canónico debe ser un conjunto de variantes concurrentes, sin variante ganadora. La presentación puede destacar una, pero la sincronización debe conservar todas hasta una resolución causal posterior.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 class="relative group"&gt;T5
 &lt;div id="t5" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#t5" aria-label="Ancla"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;

&lt;h4 class="relative group"&gt;Mi prompt original
 &lt;div id="mi-prompt-original-4" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#mi-prompt-original-4" aria-label="Ancla"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h4&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[MXHYB-20260926-A T5] Una tarea pequeña para la documentación de Puente: traduce al inglés solo esta frase: «Puente conserva las ediciones concurrentes y pide una resolución explícita de los conflictos». Responde únicamente con la traducción.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 class="relative group"&gt;Respuesta híbrida — local
 &lt;div id="respuesta-híbrida--local-2" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#respuesta-h%c3%adbrida--local-2" aria-label="Ancla"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h4&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;🖥️ Puente preserves concurrent edits and requires explicit resolution of conflicts.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 class="relative group"&gt;Respuesta íntegramente cloud
 &lt;div id="respuesta-íntegramente-cloud-4" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#respuesta-%c3%adntegramente-cloud-4" aria-label="Ancla"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h4&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Puente preserves concurrent edits and requires explicit conflict resolution.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 class="relative group"&gt;T6
 &lt;div id="t6" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#t6" aria-label="Ancla"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;

&lt;h4 class="relative group"&gt;Mi prompt original
 &lt;div id="mi-prompt-original-5" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#mi-prompt-original-5" aria-label="Ancla"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h4&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[MXHYB-20260926-A T6] Revisión crítica final de Puente: un compañero propone guardar solo el vector máximo por componente y un único contenido elegido por hash; eliminar tombstones cuando ambos servidores hayan visto su vector; y, tras perder el disco, reutilizar el identificador del servidor con contador cero. Evalúa rigurosamente las tres decisiones, distingue convergencia de conservación de intención y explica un contraejemplo concreto para cada decisión insegura. Formula los supuestos mínimos bajo los que recolectar tombstones sí sería seguro, incluyendo mensajes antiguos en tránsito y réplicas restauradas. Cierra con tres pruebas de propiedades que detectarían estos fallos. Unas 450 palabras, sin ejecutar ni crear archivos.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 class="relative group"&gt;Respuesta híbrida — nube
 &lt;div id="respuesta-híbrida--nube-2" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#respuesta-h%c3%adbrida--nube-2" aria-label="Ancla"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h4&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;☁️ &lt;span class="gs"&gt;**Hallazgos críticos**&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; Guardar solo el vector máximo y un único contenido elegido por hash puede converger, pero no conserva intención. Si A edita “precio=10” con &lt;span class="sb"&gt;`(2,1)`&lt;/span&gt; y B edita “precio=12” con &lt;span class="sb"&gt;`(1,2)`&lt;/span&gt;, el máximo &lt;span class="sb"&gt;`(2,2)`&lt;/span&gt; no indica que hubo dos valores concurrentes; elegir por hash descarta una edición válida sin resolución explícita. Es convergente porque ambos acabarán con el mismo hash, pero semánticamente incorrecto: convierte un conflicto en una pérdida silenciosa.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; Eliminar tombstones cuando ambos servidores “han visto su vector” solo es seguro si esa frase significa reconocimiento causal estable y no simple recepción momentánea. Contraejemplo: B borra &lt;span class="sb"&gt;`f`&lt;/span&gt; con &lt;span class="sb"&gt;`(1,2)`&lt;/span&gt;; A lo ve y ambos eliminan el tombstone. Después llega un mensaje antiguo en tránsito con el archivo inicial &lt;span class="sb"&gt;`(1,1)`&lt;/span&gt;. Si ya no queda memoria del borrado, el archivo puede reaparecer. La convergencia local no basta: hace falta impedir la resurrección de estados causalmente obsoletos.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; Reutilizar el identificador del servidor con contador cero tras perder el disco rompe la unicidad de dots y la causalidad. Contraejemplo: antes del fallo, A generó &lt;span class="sb"&gt;`(A,5)`&lt;/span&gt; y B lo conserva. Tras reinstalarse como &lt;span class="sb"&gt;`A`&lt;/span&gt; desde cero, A genera otra operación &lt;span class="sb"&gt;`(A,1)`&lt;/span&gt;, &lt;span class="sb"&gt;`(A,2)`&lt;/span&gt;... B puede considerarlas dominadas por su vector &lt;span class="sb"&gt;`{A:5}`&lt;/span&gt; e ignorarlas, o confundirlas con historia antigua. Resultado: actualizaciones nuevas desaparecen o se mezclan con generaciones anteriores.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gs"&gt;**Convergencia vs intención**&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Convergencia significa que réplicas que reciben la misma información terminan en el mismo estado. Conservación de intención significa que operaciones concurrentes no se sobrescriben sin una relación causal o una resolución explícita. Un algoritmo “elige hash menor” puede ser determinista, conmutativo e idempotente, pero sigue siendo una política de pérdida silenciosa.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gs"&gt;**Cuándo recolectar tombstones**&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;La recolección es segura solo bajo supuestos mínimos fuertes:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; membresía cerrada: se conocen todas las réplicas que pueden emitir o reenviar estados;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; identificadores de réplica persistentes y no reutilizados;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; cada réplica ha confirmado de forma duradera un vector que domina el tombstone;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; no existen mensajes antiguos en tránsito que puedan reaparecer sin ser filtrados por memoria causal;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; las restauraciones desde backup conservan, como mínimo, el vector causal reconocido y la información de borrados aún no estabilizados;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; si una réplica pierde esa memoria, debe reincorporarse con nuevo identificador/generación y sincronización completa, no reutilizando contadores antiguos.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Con solo A y B, puede recolectarse un tombstone de B cuando A sabe duraderamente que B sabe que A también ha observado un vector que domina el borrado, y ambos conservan una frontera causal estable que rechaza estados anteriores. Si no hay control de colas, snapshots, backups y mensajes duplicados, la recolección debe retrasarse o mantener un resumen causal compacto.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gs"&gt;**Pruebas de propiedades**&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; Concurrencia edición-edición: generar dos ediciones desconectadas y verificar que el estado final contiene ambas variantes o una fusión explícitamente marcada, nunca un único contenido arbitrario.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; Borrado contra mensaje antiguo: borrar, sincronizar, intentar reinyectar un estado anterior y comprobar que no resucita el archivo tras compactación.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- Pérdida de disco: reiniciar una réplica con el mismo ID y contador cero debe ser rechazado o detectado; con nuevo ID/generación, sus nuevas operaciones no deben ser confundidas con historia vieja.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 class="relative group"&gt;Respuesta íntegramente cloud
 &lt;div id="respuesta-íntegramente-cloud-5" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#respuesta-%c3%adntegramente-cloud-5" aria-label="Ancla"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h4&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;La propuesta mezcla convergencia superficial con preservación semántica. Que todos acaben con el mismo estado no basta: Puente debe conservar intenciones concurrentes hasta una resolución causal explícita.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gs"&gt;**1. Vector máximo + único contenido por hash**&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Es inseguro. Guardar solo el máximo componente a componente, por ejemplo unir &lt;span class="sb"&gt;`(2,1)`&lt;/span&gt; y &lt;span class="sb"&gt;`(1,2)`&lt;/span&gt; en &lt;span class="sb"&gt;`(2,2)`&lt;/span&gt;, destruye la información de que eran versiones concurrentes. Si además se elige un único contenido por hash, se obtiene convergencia, pero con pérdida silenciosa.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Contraejemplo: A y B parten de &lt;span class="sb"&gt;`x=0`&lt;/span&gt; con &lt;span class="sb"&gt;`(1,1)`&lt;/span&gt;. Desconectados, A escribe &lt;span class="sb"&gt;`x=10`&lt;/span&gt; → &lt;span class="sb"&gt;`(2,1)`&lt;/span&gt; y B escribe &lt;span class="sb"&gt;`x=20`&lt;/span&gt; → &lt;span class="sb"&gt;`(1,2)`&lt;/span&gt;. Al sincronizar, ambos guardan vector &lt;span class="sb"&gt;`(2,2)`&lt;/span&gt; y el contenido con hash menor. Convergen, sí, pero la edición perdedora desaparece y el vector resultante finge que existe una versión que causó ambas decisiones, aunque nadie resolvió el conflicto.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Corrección: el estado debe contener un conjunto de variantes concurrentes o un registro de conflicto; el vector máximo puede ser resumen, no sustituto del historial causal relevante.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gs"&gt;**2. Eliminar tombstones cuando ambos servidores hayan visto su vector**&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Puede ser seguro solo bajo supuestos estrictos, pero la frase es incompleta. Si hay exactamente A y B, almacenamiento estable, identificidades persistentes, canales que no entregan mensajes anteriores tras el acuse, y ambos han confirmado haber integrado una versión posterior que domina la tombstone, entonces se puede recolectar.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Contraejemplo inseguro: B borra &lt;span class="sb"&gt;`f`&lt;/span&gt; con &lt;span class="sb"&gt;`(1,2)`&lt;/span&gt;. A aún tiene en una cola antigua una edición viva &lt;span class="sb"&gt;`(2,1)`&lt;/span&gt; enviada antes o durante la partición. Ambos intercambian acuses de la tombstone y B la recolecta. Después llega el mensaje viejo &lt;span class="sb"&gt;`(2,1)`&lt;/span&gt; a B o B restaura una copia anterior que no recordaba la tombstone. Si no queda memoria causal del borrado, el archivo puede resucitar o volver a conflicto falso.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Supuestos mínimos para GC: membresía cerrada o épocas explícitas; todos los miembros de la época han persistido un estado que domina la tombstone; no existen remitentes autorizados capaces de enviar estados no dominados; los canales o logs garantizan que mensajes antiguos anteriores a la barrera han sido drenados, descartados por época, o serán rechazados por un filtro causal persistido; las restauraciones desde backup deben traer un vector de réplica/época no ambiguo o requerir resincronización completa antes de emitir cambios. En la práctica, conservar un “floor” causal persistente por ruta o por réplica evita aceptar operaciones por debajo de lo ya recolectado.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gs"&gt;**3. Reutilizar identificador con contador cero tras perder disco**&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Es muy inseguro. Los identificadores de réplica y contadores forman la unicidad de operaciones. Reutilizar &lt;span class="sb"&gt;`A`&lt;/span&gt; con contador cero hace indistinguibles operaciones nuevas de antiguas o dominadas.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Contraejemplo: antes del fallo, A emitió &lt;span class="sb"&gt;`(5,3)`&lt;/span&gt; y B lo conserva. A pierde disco, vuelve como &lt;span class="sb"&gt;`A`&lt;/span&gt; contador &lt;span class="sb"&gt;`0`&lt;/span&gt;, edita y emite &lt;span class="sb"&gt;`(1,3)`&lt;/span&gt; o &lt;span class="sb"&gt;`(1,0)`&lt;/span&gt;. B puede tratarlo como antiguo y descartarlo, o mezclarlo causalmente con operaciones previas de A. Peor: si reaparecen mensajes antiguos de A, pueden colisionar con nuevos &lt;span class="sb"&gt;`op_id`&lt;/span&gt;. Resultado: pérdida, resurrección o divergencia.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Corrección: tras pérdida de disco, generar nueva identidad de réplica o recuperar contador/epoch desde almacenamiento duradero. Alternativamente, reincorporarse mediante protocolo de rejoin que asigna nueva época y prohíbe emitir operaciones hasta descargar un snapshot válido.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gs"&gt;**Pruebas de propiedades**&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; Concurrencia preservada: para dos ediciones concurrentes distintas, tras cualquier orden y duplicación de mensajes, ambas variantes siguen presentes hasta una resolución explícita.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; No resurrección tras borrado: una versión viva no dominante enviada antes de una tombstone recolectada no puede recrear el archivo.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- Identidad única: después de crash/rejoin, ninguna operación nueva puede compartir &lt;span class="sb"&gt;`op_id`&lt;/span&gt; ni ser ordenada falsamente bajo una época anterior.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 class="relative group"&gt;Procedencia
 &lt;div id="procedencia" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#procedencia" aria-label="Ancla"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;Extraído de los campos &lt;code&gt;prompt&lt;/code&gt; y &lt;code&gt;response&lt;/code&gt; de &lt;code&gt;turn-1.json&lt;/code&gt; a &lt;code&gt;turn-6.json&lt;/code&gt; de cada ejecución. Los seis prompts coinciden con el fixture &lt;code&gt;prompts.json&lt;/code&gt;. No he reescrito ni corregido las respuestas. La tabla de consumo y los límites de la comparación figuran en las secciones anteriores de este artículo.&lt;/p&gt;
&lt;/div&gt;</content:encoded><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://carlesabarca.com/posts/minix-hybrid-agent/featured.png"/></item></channel></rss>