Volver al blog
ReactUXFrontend

Interfaz que miente en React: la lección de Theranos

20 de octubre de 2026·6 min de lectura·Diego Horvatti

Theranos prometía cientos de análisis de sangre con una gota sacada del dedo. Durante años, lo que más convencía era la pantalla. El aparato Edison tenía una interfaz bonita, una barra de progreso y un resultado al final. Detrás de eso casi nada funcionaba. Ahora salió un documental sobre Elizabeth Holmes, "You Can See Everything", y la newsletter Read The Profile publicó una crítica dura: según ella, la película engaña igual que engañaba la empresa. Me quedé con eso en la cabeza por un motivo muy de dev. Mucha interfaz que miente en React nace de la misma tentación: mostrar que todo está bien antes de saber si lo está.

Qué pasó con Theranos, en 30 segundos

Para quien no lo siguió: Theranos llegó a valer cerca de 9 mil millones de dólares. Cerró una alianza con Walgreens y puso análisis en tiendas reales, con pacientes reales. En 2015, el periodista John Carreyrou, del Wall Street Journal, mostró que la mayoría de los análisis corrían en máquinas comerciales de otros fabricantes, adaptadas. El Edison no hacía lo que la empresa decía.

Holmes fue condenada por fraude a inversores en 2022 y recibió más de 11 años de prisión. El nuevo documental vuelve a la historia, y la crítica de Read The Profile dice que empaqueta el relato de una forma que deja al espectador con la impresión equivocada. No voy a juzgar la película sin haberla visto. Lo que me interesa es otra cosa.

El libro "Bad Blood", del propio Carreyrou, describe demos en las que el aparato mostraba en pantalla que estaba trabajando mientras el resultado, en la práctica, venía de otro lugar. La interfaz era el producto. Y la interfaz mentía.

Por qué es tan fácil escribir una interfaz que miente en React

Nadie se levanta con ganas de engañar al usuario. La mentira en la UI aparece de a poco, por prisa o por estética. El PM pide "hazlo más fluido". El diseñador no quiere una pantalla de error fea. Tú quieres cerrar la task antes del viernes.

Entonces surgen algunos patrones que ya vi en code review más de una vez (y escribí algunos, lo confieso):

  • catch que se traga el error y muestra un toast de éxito igual.
  • Barra de progreso animada con setInterval, sin ninguna relación con el progreso real.
  • Update optimista que nunca se deshace cuando la API falla.
  • Datos viejos del caché mostrados como si fueran actuales, sin ningún aviso.
  • Skeleton que gira para siempre cuando el fetch se rompe.

Cada uno parece inofensivo. Juntos, crean una app en la que el usuario ya no puede creer lo que ve.

Una pantalla que siempre dice "salió bien" no informa nada.

El error tragado: el caso más común

Este es un clásico. El formulario guarda, algo sale mal en el servidor, y el usuario recibe confeti.

async function handleSave() {
  try {
    await api.updateProfile(form)
  } catch (e) {
    console.error(e) // nadie va a leer esto en producción
  }
  toast.success('¡Perfil actualizado!')
}

El toast está fuera del try. Va a aparecer sí o sí. El usuario cierra la pestaña feliz, vuelve mañana y el perfil sigue igual. Ahora no confía en ningún otro toast de tu app.

La versión honesta es aburrida y corta:

async function handleSave() {
  try {
    await api.updateProfile(form)
    toast.success('¡Perfil actualizado!')
  } catch {
    toast.error('No se pudo guardar. Tus datos siguen en el formulario.')
  }
}

Fíjate en la segunda frase del error. Dice qué pasó y qué no se perdió. Eso vale más que cualquier ilustración tierna de "ups".

Update optimista sin rollback es Theranos en miniatura

El update optimista es genial. El like aparece al instante, la lista se reordena al instante, se siente como una app nativa. El problema es cuando el optimismo se vuelve una promesa que nadie cumple.

Ya vi bastante este patrón:

function like() {
  setLikes((n) => n + 1)
  api.like(postId) // sin await, sin catch, sin vuelta atrás
}

Si la request falla, el número queda mal en pantalla hasta el próximo reload. El usuario vio un estado que nunca existió en la base de datos. Es literalmente la barra de progreso del Edison: la pantalla afirma algo que el sistema no hizo.

En React 19, useOptimistic resuelve esto de forma elegante, porque el valor optimista es temporal por definición. Cuando la action termina, vuelve al estado real:

const [optimisticLikes, addLike] = useOptimistic(
  likes,
  (current, delta: number) => current + delta
)

async function likeAction() {
  addLike(1)
  try {
    const updated = await api.like(postId)
    setLikes(updated.likes)
  } catch {
    toast.error('No se pudo dar like ahora.')
  }
}

Si la API falla, likes no cambia, y optimisticLikes vuelve solo al valor verdadero cuando termina la transición. El optimismo existe, pero tiene fecha de vencimiento. Con TanStack Query el razonamiento es el mismo: onMutate guarda el snapshot, onError lo restaura, onSettled invalida y vuelve a buscar.

Loading falso y datos viejos: las mentiras silenciosas

La barra de progreso fake es tentadora porque hace la espera más "agradable". Hay hasta estudios que dicen que el usuario tolera mejor una barra que se mueve. Está bien usar una animación indeterminada. El problema es la barra que llega al 99% y se traba ahí dos minutos, porque el número era inventado desde el principio. El usuario aprende rápido que tu 99% no significa nada.

La regla que sigo: si no tengo progreso real, uso un indicador indeterminado. Si lo tengo (upload con onprogress, job con etapas), muestro el número de verdad.

Los datos viejos son más sutiles. Con un staleTime alto o SWR, muestras el caché mientras revalidas. Eso es bueno para el rendimiento. Pero en una pantalla de saldo, stock o estado de pedido, mostrar el valor de hace 10 minutos sin ninguna señal es decir algo falso con cara de verdad. Un pequeño "actualizado hace 10 min", o un spinner discreto en la esquina mientras isFetching es true, ya lo resuelve.

const { data, isFetching, dataUpdatedAt } = useQuery({
  queryKey: ['saldo'],
  queryFn: getSaldo,
})

return (
  <p>
    $ {data?.valor}
    {isFetching && <small> actualizando…</small>}
  </p>
)

Qué cambia en tu flujo de trabajo

No hace falta un framework nuevo ni una lib. Hacen falta algunos hábitos:

  1. Todo estado de UI tiene que tener un estado de error diseñado. Si el Figma no tiene la pantalla de error, la task no está lista para empezar.
  2. El code review pregunta "¿y si falla?" en cada await. Si la respuesta es "el usuario no se entera", es un bug.
  3. Prueba el camino triste a propósito. En DevTools, pestaña Network, puedes bloquear una URL específica o simular offline. Cinco minutos haciendo esto encuentran más problemas que una semana de pruebas felices.
  4. Optimista solo con rollback. Sin rollback no es update optimista, es adivinar.

Nada de esto es difícil. Solo es incómodo, porque te obliga a admitir en pantalla que el sistema a veces falla. Y falla de verdad, el tuyo y el de todo el mundo.

Mi opinión

El caso Theranos es extremo, claro. Nadie va preso por un toast mentiroso. Pero el mecanismo es el mismo: la interfaz se vuelve vitrina y deja de ser ventana. Y el costo aparece igual, solo que más chico y más lento. Primero el usuario duda de un botón, después de la app entera, después de tu soporte.

Prefiero una UI un poco más fea que dice la verdad a una UI preciosa que tengo que defender en una reunión de incidente. El usuario perdona un error. Lo que no perdona es descubrir que la pantalla le mintió. Si quieres ver cómo manejo esto en producción, las apps están en mis proyectos.

Resumen para LinkedIn

Theranos no engañó al mundo con sangre. Lo engañó con una pantalla bonita y una barra de progreso.

Me quedé pensando en esto con el nuevo documental sobre Elizabeth Holmes, y la lección sirve directo para quien escribe frontend.

Toast de éxito fuera del try. Update optimista que nunca se deshace. Barra de progreso que llega al 99% y se queda trabada ahí. Ya vi todo eso en code review, y confieso que escribí algunos.

Cada uno parece inofensivo. Juntos, le enseñan al usuario a no creer en nada de lo que aparece en la pantalla.

Prefiero una UI un poco más fea que dice la verdad a una UI preciosa que tengo que defender en una reunión de incidente. El usuario perdona un error. No perdona descubrir que la pantalla le mintió.

Escribí el artículo completo con los ejemplos en React y useOptimistic. Link en los comentarios.

#React #Frontend #UX #DesarrolloWeb #CodeReview