Reconciliar con el servidor
Una actualización optimista completa se despliega en tres fases: aplicar el cambio local como predicción, disparar la petición, y sustituir la predicción por la respuesta real cuando el servidor responde. Esta lección desmenuza la fase de reconciliación —el momento en que la verdad provisional del cliente cede ante la verdad canónica del servidor— y explica por qué la respuesta del servidor manda casi siempre: conoce identificadores reales, marcas de tiempo, campos calculados y efectos secundarios que el cliente jamás pudo predecir. Contrasta las dos estrategias de reconciliación, escribir la respuesta en la cache frente a invalidar y volver a pedir, y muestra el caso canónico del identificador temporal que se canjea por el definitivo. La tesis: el cliente propone, el servidor dispone.
La lección anterior dejó al cliente adelantándose al servidor: pinta la predicción y lanza la petición. Pero un optimismo que solo sabe adelantarse es un optimismo a medias, porque tarde o temprano el servidor responde y esa respuesta trae información que la predicción no tenía. Reconciliar es el acto de aceptar esa respuesta como la verdad definitiva y sustituir por ella la predicción provisional. No es un detalle de limpieza posterior: es la fase que convierte una apuesta arriesgada en un sistema correcto. El cliente propuso una versión de la realidad; ahora el servidor la confirma, la corrige o la enriquece, y la interfaz debe rendirse ante ese veredicto. Esta lección trata la fase menos vistosa y más importante del ciclo optimista: la humildad de deshacer tu propia predicción para abrazar la verdad que llega.
- Descomponer una mutación optimista en sus tres fases: aplicar, disparar y reconciliar.
- Entender por qué la respuesta del servidor es la autoridad y debe sobrescribir la predicción del cliente.
- Distinguir las dos estrategias de reconciliación: escribir la respuesta en la cache frente a invalidar y refetch.
- Dominar el caso canónico del identificador temporal canjeado por el definitivo tras la confirmación.
Las tres fases de una mutación optimista
Toda actualización optimista bien hecha atraviesa tres fases ordenadas. Primero aplicas el cambio local: escribes la predicción en el estado y la interfaz se actualiza al instante. Segundo, disparas la petición contra el servidor. Tercero —y aquí vive esta lección— cuando la respuesta real llega, sustituyes la predicción por el dato canónico que el servidor devuelve. Ese tercer paso es la reconciliación: el punto donde la verdad provisional se retira para dejar entrar a la verdad definitiva.
async function agregarTarea(texto: string) {
const prediccion = { id: "temporal", texto, hecha: false };
setTareas((prev) => [...prev, prediccion]); // fase 1: aplicar
const real = await api.crearTarea(texto); // fase 2: disparar
setTareas((prev) => // fase 3: reconciliar
prev.map((t) => (t.id === "temporal" ? real : t)),
);
}
Fíjate en que la fase tres no añade una tarea nueva: reemplaza la predicción por el objeto que devolvió el servidor. La tarea que el usuario vio aparecer al instante y la tarea que ahora vive en el estado ocupan el mismo lugar visual, pero la segunda es la real, con todos sus campos correctos.
Esta división en tres tiempos no es teoría abstracta: es exactamente la interfaz que exponen las herramientas maduras. En TanStack Query, aplicar la predicción vive en onMutate, reconciliar en onSuccess o en la invalidación de onSettled, y revertir en onError. Lo verás con detalle en la lección cuatro; por ahora basta con reconocer que aplicar, disparar y reconciliar no son un invento pedagógico, sino la estructura que el ecosistema ya cristalizó en tres retrollamadas.
Por qué la respuesta del servidor manda
La predicción del cliente es, por definición, incompleta. El servidor sabe cosas que el navegador no podía saber en el momento de mutar: el identificador real que asigna la base de datos, la marca de tiempo exacta de creación, campos derivados que dependen de otros registros, normalizaciones, efectos secundarios que dispararon otras reglas de negocio. La predicción es un marcador de posición plausible; la respuesta es el registro autoritativo. Por eso reconciliar es sobrescribir, no fusionar a ciegas: si mezclaras la predicción con la respuesta campo a campo, arriesgarías conservar valores inventados por el cliente que el servidor nunca aceptó.
// MAL: fusionar conserva campos que el servidor no reconocio
const reconciliado = { ...prediccion, ...respuesta };
// Si la prediccion inflo un campo que la respuesta omite, ese campo
// fantasma sobrevive a la reconciliacion y contamina el estado.
// BIEN: sobrescribir con el registro canonico del servidor
const reconciliado = respuesta;
La regla, dura pero salvadora, es que la respuesta del servidor no complementa a la predicción: la sustituye. Fusionar solo es aceptable cuando sabes con certeza que el servidor devuelve un objeto parcial por diseño y que los campos ausentes deben conservar su valor previo, y ese caso es la excepción, no la norma.
El contraste entre lo que el cliente pudo predecir y lo que el servidor sabe se ve mejor con los dos objetos lado a lado:
// La prediccion no tenia estos campos; solo el servidor los conoce
const prediccion = { id: "temporal", texto };
const real = {
id: "a1b2c3", // id definitivo de la base de datos
texto,
creadoEn: "2026-07-26T10:00:00Z", // marca de tiempo del servidor
autor: "yo", // resuelto desde la sesion
};
// Reconciliar es quedarse con `real`, jamas con una mezcla de ambos.
El patrón más peligroso del optimismo es el identificador falso. Pintas una tarea con id: "temporal" porque el real aún no existe, y todo funciona hasta que el usuario, en ese medio segundo, pulsa para borrarla o editarla: enviarías al servidor una petición sobre un identificador que no reconoce. La reconciliación resuelve esto al canjear el temporal por el definitivo en cuanto llega la respuesta, pero mientras tanto debes tratar las entidades con identificador temporal como no confirmadas: deshabilitar o encolar las acciones que dependan de un identificador real hasta que la reconciliación las bautice.
flowchart LR A[aplicar prediccion local] --> B[disparar peticion] B --> C[servidor procesa] C --> D[respuesta canonica llega] D --> E[sustituir prediccion por respuesta] style A fill:#f9e2af,color:#11111b style E fill:#a6e3a1,color:#11111b
Dos estrategias: escribir o invalidar
Cuando la respuesta llega, tienes dos maneras de reconciliar, y la elección tiene consecuencias. La primera es confiar en la respuesta y escribirla directamente en tu estado o cache: rápida, sin viajes extra, pero asume que la respuesta trae el objeto completo y coherente. La segunda es invalidar el dato y volver a pedirlo —descartar tanto la predicción como la respuesta parcial y preguntar al servidor por la verdad fresca—: cuesta un viaje de ida y vuelta adicional, pero garantiza que lo que muestras es exactamente lo que el servidor tiene, incluidos los efectos que tu mutación pudo desencadenar en otros registros.
Escribir la respuesta
Metes el objeto devuelto en la cache y das por reconciliado. Cero viajes extra, latencia mínima. Correcto solo si la respuesta es completa y no hubo efectos colaterales en otros datos.
Invalidar y refetch
Marcas el dato como obsoleto y vuelves a pedirlo. Un viaje más, pero la garantía de coherencia total con el servidor. La opción segura cuando una mutación toca más de lo que devuelve.
En código, la diferencia entre ambas cabe en la retrollamada de éxito. Escribir la respuesta es un reemplazo local; invalidar es una orden de olvido que fuerza una lectura fresca desde el servidor.
// Estrategia A: escribir la respuesta. Rapida, asume respuesta completa.
onSuccess: (real) =>
setTareas((prev) => prev.map((t) => (t.id === "temporal" ? real : t))),
// Estrategia B: invalidar y refetch. Un viaje mas, coherencia garantizada.
onSuccess: () => invalidar(["tareas"]),
Si el usuario dispara dos mutaciones seguidas sobre el mismo dato, sus respuestas pueden regresar en orden inverso al que salieron. Escribir la segunda respuesta y luego, ya tarde, la primera dejaría el dato anclado en un estado viejo. Por eso invalidar y refetch tras estabilizar es más robusto que escribir cada respuesta al vuelo: la lectura final refleja siempre el último estado del servidor, sin importar en qué orden volvieron las escrituras. Y cuando escribas respuestas, procura que la reconciliación sea idempotente, de modo que una respuesta rezagada no pueda revivir un dato ya superado.
En la práctica muchos equipos combinan ambas: escriben la respuesta para el efecto inmediato y, además, invalidan en segundo plano para asegurar la coherencia a la primera oportunidad. Esa doble red es la que ofrecen de fábrica librerías como TanStack Query, cuyo mecanismo estudiaremos en la lección cuatro. Lo esencial es reconocer que la reconciliación no es opcional: sin ella, tu interfaz se queda anclada en la predicción y jamás aprende lo que el servidor decidió de verdad.
Cuando una mutación toca más de lo que devuelve
Existe un caso donde escribir la respuesta nunca basta y la invalidación se vuelve obligatoria: cuando la mutación tiene efectos que van más allá del registro que devuelve. Crear un comentario no solo produce un comentario; incrementa un contador, puede reordenar una lista por fecha, alimenta un feed de actividad, invalida una vista de estadísticas. La respuesta trae el comentario nuevo, pero no trae la onda expansiva que ese comentario provocó en los demás datos. Escribir la respuesta reconcilia el registro directo y deja obsoletos, en silencio, todos los que rozó de refilón.
onSuccess: () => {
invalidar(["comentarios", postId]); // la lista directa
invalidar(["post", postId]); // el contador de comentarios
invalidar(["feed"]); // el feed de actividad global
},
Por eso la pregunta que decide la estrategia no es cuánto pesa un viaje extra, sino cuánto se extiende el efecto de la mutación. Si la operación es local y autocontenida, escribe la respuesta. Si desencadena efectos en datos que otras vistas muestran, invalida todo lo que pudo tocar: es la única forma de que la reconciliación alcance a la onda entera y no solo a su epicentro.
La reconciliación es lo que impide que el optimismo degenere en delirio. Sin ella, una interfaz optimista sería un cliente que grita su predicción al mundo y jamás escucha la respuesta: un narrador poco fiable convencido de que su versión de los hechos es la única. Reconciliar es la disciplina opuesta —la humildad estructural de un sistema que se atreve a adelantarse precisamente porque ha decidido de antemano rendirse ante la verdad cuando esta llegue—. Aquí conviene nombrar con precisión una asimetría que gobierna toda la arquitectura de datos moderna: la verdad del cliente y la verdad del servidor no tienen el mismo rango. La del cliente es una hipótesis local, útil y efímera; la del servidor es el registro canónico del que todos los demás clientes también beberán. Cuando ambas coinciden, la reconciliación es invisible y la predicción acertó. Cuando difieren —porque el servidor calculó un total distinto, asignó otro identificador o rechazó parte de la operación— la reconciliación arbitra sin dudar a favor del servidor, porque una aplicación con dos autoridades sobre el mismo dato no tiene ninguna. Esta jerarquía es la misma que impone la replicación en los sistemas distribuidos, donde una réplica puede diverger a sabiendas pero siempre existe un mecanismo que decide qué versión gana cuando convergen. Trasladada al frontend, la lección es exacta: puedes dejar que el cliente vaya por delante todo lo que quieras, siempre que hayas construido el camino de vuelta por el que la verdad del servidor regresa a corregirlo. El optimismo sin reconciliación es una promesa que nunca se comprueba; con ella, es una hipótesis que el sistema se compromete a verificar en cuanto físicamente puede. Esa segunda cosa es la única que merece llamarse ingeniería.
- Toma una mutación optimista que ya tengas y localiza las tres fases: dónde aplicas, dónde disparas y dónde —o si— reconcilias.
- Si te falta la fase de reconciliación, añádela: sustituye la predicción por la respuesta real del servidor en lugar de dejar la predicción anclada.
- Provoca a propósito una divergencia haciendo que el servidor devuelva un campo calculado distinto del que predijiste, y comprueba que tras reconciliar gana el valor del servidor.
- Implementa el canje de identificador temporal por definitivo y verifica qué ocurre si intentas borrar la entidad antes de que llegue la reconciliación.
- Decide para esa mutación si reconcilias escribiendo la respuesta o invalidando y refetch, y justifica la elección según si la operación tiene efectos en otros datos.
- Traza la onda expansiva de una mutación —qué contadores, listas o vistas derivadas toca— y enumera todas las claves que tendrías que invalidar para que la reconciliación alcance la onda entera.