Los patrones que lo esconden
Spinners, esqueletos y actualizaciones optimistas gestionan la percepción de la espera, pero solo uno de los tres saca de verdad el viaje al servidor del camino crítico.
La industria ha desarrollado un repertorio notable de técnicas para que una espera se sienta menos larga. Son buenas técnicas y funcionan de verdad, pero casi todas operan sobre la percepción del tiempo, no sobre el tiempo. Distinguir cuáles esconden la latencia y cuál la elimina es la diferencia entre maquillar un problema y resolverlo, y ambas cosas son legítimas siempre que sepas cuál estás haciendo.
- Clasificar los patrones de espera según operen sobre la percepción o sobre la arquitectura.
- Entender qué comunica realmente un indicador de progreso y cuándo se vuelve contraproducente.
- Analizar la actualización optimista como una réplica local en miniatura, con su deuda de reconciliación.
- Formular el criterio que decide cuándo el optimismo es legítimo y cuándo es una mentira.
El repertorio de la espera
Cuando un sistema no puede responder de inmediato, tiene tres respuestas posibles ante el usuario: no decir nada, decir que está trabajando, o fingir que ya ha terminado.
No decir nada es la peor: una interfaz congelada es indistinguible de una interfaz rota, y el usuario reacciona repitiendo el gesto, lo cual empeora la carga. Decir que está trabajando es el territorio del indicador de progreso, y su valor es real: un cuerpo abundante de trabajo en interacción persona-ordenador muestra que la retroalimentación cambia la duración percibida de una espera, incluso cuando la duración medida es idéntica. Fingir que ya ha terminado es el territorio del optimismo, y es el único de los tres que altera la estructura del problema.
flowchart TB
A[El usuario actua] --> B{Hay respuesta inmediata}
B -->|No, y no se avisa| C[Interfaz congelada]
B -->|No, pero se avisa| D[Spinner o esqueleto]
B -->|Si, aplicada en local| E[Actualizacion optimista]
C --> F[Espera sufrida]
D --> G[Espera gestionada]
E --> H[Espera eliminada del camino critico]
style F fill:#f38ba8,color:#11111b
style G fill:#f9e2af,color:#11111b
style H fill:#a6e3a1,color:#11111bFíjate en la etiqueta del último nodo. No dice que la espera desaparezca: dice que sale del camino crítico. El viaje al servidor sigue ocurriendo; lo que cambia es que el usuario ya no está dentro de él.
El indicador y el esqueleto
Un indicador de progreso hace dos afirmaciones. La primera es “he recibido tu gesto”, y es la más valiosa, porque elimina la ambigüedad entre lentitud y avería. La segunda es “esto va a tardar”, y es una confesión: el sistema admite que el usuario debe detenerse.
Los indicadores indeterminados —una rueda que gira sin fin— cumplen la primera afirmación pero no la segunda, y por eso su utilidad decae con el tiempo: pasado cierto punto dejan de informar y se vuelven un símbolo de impotencia. Los determinados, que muestran cuánto falta, son mejores cuando existe una fracción honesta que mostrar, y peores cuando esa fracción es inventada.
El esqueleto va un paso más allá: en lugar de anunciar que falta contenido, dibuja su forma anticipada. Su ventaja no es solo estética. Al reservar el espacio, evita el desplazamiento del contenido cuando los datos llegan, y ese desplazamiento es una de las agresiones más caras de una interfaz, porque invalida la posición que el usuario ya había memorizado.
El esqueleto solo funciona si la forma que dibuja coincide con la que llega. Si prometes tres filas y llegan diez, o prometes una tarjeta ancha y llega una estrecha, el salto final es peor que no haber prometido nada: has invertido en una expectativa concreta y luego la has roto. Un esqueleto es un contrato de disposición, y romper contratos de disposición cuesta más caro que no firmarlos.
Hay además una paradoja de umbral que casi todo el mundo descubre tarde. Si la respuesta llega muy rápido, mostrar el indicador es peor que no mostrarlo: aparece y desaparece en un parpadeo, y ese destello se percibe como un defecto, no como información. Pero si la respuesta es lenta, no mostrarlo es intolerable. La solución habitual —retrasar el indicador un instante y mostrarlo solo si la operación sigue viva— es correcta, y también instructiva, porque revela que el indicador no informa sobre la operación sino sobre una hipótesis acerca de su duración.
// El indicador no informa de la operacion: informa de una sospecha
let visible = false;
const t = setTimeout(() => { visible = true; pintarIndicador(); }, umbral);
operacion().finally(() => {
clearTimeout(t); // si llego antes del umbral, nunca se vio
if (visible) ocultarIndicador();
});
Existe la tentación opuesta: hacer la espera agradable —animaciones ingeniosas, mensajes rotativos, barras que avanzan a un ritmo diseñado— hasta el punto de que deja de percibirse como un defecto. Es eficaz a corto plazo y peligroso a medio: cuando la espera está bien decorada, deja de generar presión para eliminarla. Los equipos acaban puliendo el estado de carga en lugar de preguntarse por qué existe. Una espera cómoda es una espera que nadie va a arreglar.
La actualización optimista
La actualización optimista invierte el orden convencional. En lugar de pedir, esperar y luego reflejar, la interfaz aplica el efecto localmente de inmediato, envía la petición en segundo plano y reconcilia cuando llega la respuesta.
// Aplicar primero, confirmar despues
function marcarHecha(tarea) {
const anterior = clonar(estado); // guarda para poder revertir
aplicarEnLocal(estado, { id: tarea.id, hecha: true });
pintar(estado); // el usuario ya lo ve
enviar({ tipo: 'marcarHecha', id: tarea.id })
.then((confirmado) => reconciliar(estado, confirmado))
.catch(() => {
estado = anterior; // deuda: hay que revertir
pintar(estado);
avisarDelFallo(tarea);
});
}
Este patrón es, estructuralmente, una arquitectura local-first reducida a su mínima expresión. Hay una réplica local del estado, hay una escritura que se aplica sin coordinación, hay un envío asíncrono y hay una política de reconciliación. La política, eso sí, es primitiva: gana el servidor y, si rechaza, se revierte.
Y ahí aparece la deuda. Revertir es aceptable cuando el rechazo es raro y el usuario sigue mirando; es terrible cuando el rechazo es frecuente o llega tarde, porque para entonces la persona ya ha construido sobre una realidad falsa. Además, el estado revertido puede haber sido usado como base de otras acciones, y deshacer una cadena es mucho más difícil que deshacer un paso.
El problema se agrava en cuanto hay más de una operación en vuelo, que es lo normal en cuanto el patrón funciona bien. Dos peticiones enviadas en orden pueden completarse en orden inverso, así que aplicar las respuestas conforme llegan puede revertir un cambio posterior con el resultado de uno anterior. La solución exige llevar la cuenta de qué operaciones locales están pendientes y reconstruir el estado visible como la última verdad confirmada más las operaciones aún no resueltas, en orden. Quien haya escrito ese bucle habrá notado la sensación familiar: acaba de implementar, a mano, un registro de operaciones con reordenación causal.
La actualización optimista es honesta cuando el cliente puede calcular por sí mismo el resultado que el servidor va a producir. Marcar una tarea como hecha, renombrar algo tuyo, reordenar una lista privada: el cliente conoce la regla y no necesita permiso. Deja de ser honesta cuando el resultado depende de información que el cliente no tiene: si un nombre está libre, si queda inventario, qué precio final aplica, si tienes autorización. Fingir un resultado que no puedes deducir no es optimismo, es adivinación con interfaz bonita.
Esconder frente a eliminar
Con esto podemos ordenar el repertorio en dos familias con propiedades muy distintas.
Intervención perceptual
Spinners, esqueletos, animaciones. Cambian la duración vivida, no la real. Coste bajo, aplicables siempre, sin efectos sobre la corrección.
Intervención arquitectónica
Optimismo, precarga, réplica local. Cambian dónde ocurre el trabajo. Coste alto, aplicables a veces, con efectos sobre la corrección.
La deuda
Todo lo que se adelanta se debe reconciliar. La reversión, el conflicto y el error diferido son la factura del optimismo.
La prueba del corte
Desconecta la red. Lo perceptual se convierte en una espera infinita bien decorada. Lo arquitectónico sigue funcionando.
La última tarjeta contiene la prueba decisiva. Un patrón que esconde la latencia depende de que la respuesta acabe llegando; si no llega, el disfraz cae y aparece la espera desnuda. Un patrón que elimina la latencia del camino crítico no depende de eso: la interacción ya se completó antes de que la red importara.
Nada de esto convierte a la familia perceptual en un error. Ocultar es la respuesta correcta siempre que la espera sea genuinamente inevitable: una operación que solo el servidor puede resolver, un cómputo pesado, una transferencia grande. Lo que hay que evitar es el uso de la familia perceptual como sustituto de la arquitectónica, es decir, decorar una espera que existía únicamente porque alguien decidió, sin pensarlo, que ese dato viviría lejos.
Ante cada espera, el orden correcto de preguntas es: primero, ¿tiene que existir? Si la respuesta es no, elimínala moviendo el dato o adelantando el efecto. Solo si la respuesta es sí —y hay muchos casos legítimos donde lo es— pasa a la segunda pregunta: ¿cómo la comunico honestamente? Invertir el orden de estas dos preguntas es el defecto de proceso que produce interfaces con estados de carga primorosos sobre arquitecturas que nunca se cuestionaron.
Vale la pena reparar en lo que ocurre cuando un equipo adopta actualizaciones optimistas de forma generalizada, porque es una de las trayectorias más instructivas del oficio. Empieza como un truco de interfaz: un botón que responde al instante mientras la petición vuela. Pero para que ese truco funcione en más de un sitio hace falta un lugar donde guardar el estado adelantado, y aparece una copia local del modelo. Para que la copia sobreviva a la navegación hace falta que persista, y aparece un almacén. Para que el usuario no vea saltos hace falta decidir qué pasa cuando la respuesta contradice lo adelantado, y aparece una política de resolución. Para que dos pestañas no se peleen hace falta coordinarlas, y aparece un protocolo. Sin haberlo decidido nunca explícitamente, el equipo ha construido una réplica, un motor de sincronización y un esquema de conflictos: exactamente los tres componentes que definen una arquitectura local-first, solo que improvisados, sin garantías formales y sin nombre. Esta es la razón profunda por la que merece la pena estudiar local-first aunque no pienses adoptarlo entero. La alternativa no es no tener estos problemas: la alternativa es tenerlos igual, dispersos por la base de código, resueltos caso a caso por quien tuvo el turno de guardia y sin ninguna propiedad demostrable. El campo entero puede leerse como el intento de convertir ese conjunto de parches locales en una teoría con garantías, y todo lo que viene después de este nivel es precisamente eso.
- Recorre una aplicación que uses y cataloga cada estado de espera que encuentres como intervención perceptual o arquitectónica.
- Aplica la prueba del corte: describe qué pasaría con cada uno de esos estados si la red desapareciera en mitad de la operación.
- Toma tres acciones concretas y decide si el optimismo sería legítimo, aplicando el criterio de si el cliente puede deducir el resultado.
- Diseña la reversión de una acción optimista que el servidor rechaza tarde, cuando el usuario ya ha realizado dos acciones encima. Explica por qué es difícil.
- Argumenta en qué caso un esqueleto es peor que un indicador simple, apoyándote en la idea de contrato de disposición.