Fuente única de verdad
La culminación del nivel: si derivar vence a duplicar, su conclusión lógica es un principio —cada dato debe tener exactamente un dueño, un único lugar autoritativo donde vive, y todo lo demás debe derivar de él en vez de copiarlo—. Es la fuente única de verdad, el antídoto contra la clase más profunda de bugs de estado: dos copias del mismo hecho que discrepan. Aquí se define, se aprende a asignar dueño a cada dato, y se ve cuándo copiar a conciencia.
Llegamos a la síntesis del nivel. Si derivar vence a duplicar, su conclusión lógica es un principio con nombre propio: cada dato debe tener exactamente un dueño —un único lugar autoritativo donde vive— y todo lo demás debe derivar de él, nunca copiarlo. Es la fuente única de verdad, y es el antídoto contra la clase más honda de bugs de estado: dos copias del mismo hecho que, con el tiempo, dejan de coincidir.
- Entender la fuente única de verdad: un solo dueño por cada dato.
- Ver cómo la duplicación crea invariantes de sincronización que inevitablemente se rompen.
- Distinguir poseer un dato de derivarlo o referenciarlo.
- Asignar dueño a cada dato: URL, servidor, store global o componente local.
El bug más profundo: dos verdades
Cuando el mismo hecho vive en dos sitios, has creado sin querer un invariante: “estos dos deben coincidir siempre”. Nada en el programa lo garantiza salvo tu vigilancia, y la vigilancia humana falla. El síntoma es universal y exasperante: la barra lateral dice “3 mensajes sin leer”, el título de la pestaña dice “5”, y ambos tienen razón según su copia. No hay un bug de cálculo; hay dos fuentes que debían ser una.
Este patrón tiene mil disfraces: el contador de la insignia que no cuadra con la lista, el precio del resumen que difiere del precio del detalle, el usuario que aparece conectado en una pestaña y desconectado en otra. Todos comparten la misma anatomía: un hecho, dos lugares donde se guardó, y ningún mecanismo que garantice que digan lo mismo. Reconocer la anatomía es entender que no son bugs distintos, sino el mismo bug con distinta ropa.
Es el bug más profundo porque no se arregla arreglando código: puedes corregir cada síntoma —forzar que la barra relea, añadir un efecto que copie el valor— y el problema reaparece en el siguiente punto de copia. La única cura estructural es de diseño, no de parche: eliminar la segunda fuente. Por eso pertenece a esta lección y no a un capítulo de depuración; es una decisión de arquitectura disfrazada de bug de interfaz.
La fuente única de verdad disuelve el problema en vez de resolverlo. Existe un solo contador de no leídos; la barra lateral, el título y la insignia no guardan cada uno el suyo, sino que derivan del único que hay. Si solo existe una copia, es imposible que dos discrepen: la consistencia deja de ser algo que hay que mantener y pasa a ser algo que no se puede violar. Ese salto —de invariante vigilado a imposibilidad estructural— es el corazón del principio.
El nombre viene de las bases de datos, donde la normalización persigue exactamente esto: que cada hecho se guarde una sola vez, de modo que actualizarlo sea cambiar una fila y no cazar copias por todas las tablas. Es el principio DRY —no te repitas— aplicado a los datos en vez de al código. Lo que la normalización hace por el esquema de una base, la fuente única de verdad lo hace por el estado de una aplicación.
Un dueño por dato
La regla operativa es simple de enunciar y difícil de honrar: cada dato tiene exactamente un hogar autoritativo, y todo lo demás sostiene una referencia o una derivación, jamás una copia. Esto convierte “mantener sincronizado” —imposible a escala— en “leer de la fuente” —trivialmente consistente—. El trabajo no desaparece, cambia de naturaleza: en lugar de propagar cambios a muchas copias, calculas vistas desde un único origen.
// Dos verdades: cada vista guarda su propio contador. Se desincronizaran.
const barra = { sinLeer: 3 };
const titulo = { sinLeer: 5 }; // ya discrepan y nadie sabe cual es el bueno
// Una verdad: la lista es la fuente; todo lo demas deriva de ella.
const mensajes = [{ leido: false }, { leido: true }, { leido: false }];
const sinLeer = () => mensajes.filter((m) => !m.leido).length;
// la barra y el titulo llaman a sinLeer(): imposible que discrepen
flowchart TD F[fuente unica: lista de mensajes] --> D[derivado: cuantos sin leer] D --> V1[barra lateral] D --> V2[titulo de la pestana] D --> V3[insignia del icono] style F fill:#89b4fa,color:#11111b style D fill:#cba6f7,color:#11111b
El patrón visual es siempre el mismo: un dato fuente en el centro y muchas vistas colgando de él como radios de una rueda. Las vistas nunca hablan entre sí ni guardan su propia versión; todas preguntan al centro. Cuando el dato cambia, cambia en un solo lugar y todas las vistas se recalculan solas. Ese dibujo —un centro, muchos derivados— es la forma canónica de un estado bien diseñado, y lo reconocerás en cada arquitectura del track.
En la versión de una sola verdad, “sin leer” ya no es un dato que alguien actualiza: es una pregunta que se le hace a la fuente. Nadie puede olvidarse de sincronizar un contador que no existe. La consistencia no se logra con cuidado, se logra con ausencia: quitando la segunda copia. Conviene precisar que dueño único no significa lugar físico único —una caché es una copia legítima en otro sitio—, sino autoridad única: cuando dos difieren, sabemos sin discutir cuál manda y cuál cede.
El principio reordena decisiones que parecían de detalle. En un formulario controlado, la fuente de verdad del valor es el estado, no el DOM: el input muestra lo que el estado dice, no al revés. En los datos persistidos, la fuente es la base de datos del servidor, y la caché del cliente —TanStack Query, que verás en el nivel 32— es explícitamente una copia, tratada como tal, con su obsolescencia y su revalidación. La honestidad de llamarla “cache” en vez de fingir que es la verdad es justo lo que evita que cliente y servidor se contradigan.
Referenciar no es copiar, y la distinción es la mitad del oficio. Guardar el identificador de un usuario y leer sus datos de la fuente es sostener una referencia; volcar el objeto usuario entero en tres sitios es crear tres copias. Cuando dudes, guarda la referencia mínima —un id, una clave— y deriva el resto: una referencia obsoleta se nota y se corrige, pero una copia obsoleta se disfraza de dato bueno y engaña.
De aquí se sigue un corolario útil: cada dato debería tener no solo un dueño, sino un único escritor. Muchos lugares pueden leerlo; uno solo debería mutarlo. Cuando dos rutas de código escriben el mismo estado, vuelves a la dependencia del orden de la lección anterior; cuando solo una escribe y las demás preguntan, el flujo se vuelve seguible y la causa de cada cambio queda localizada en un único sitio.
Quién es el dueño: un mapa de propiedad
Decidir de quién es cada dato no es burocracia: es la mayor parte del diseño de estado. Pregúntate, dato por dato, quién manda. ¿Manda la URL, porque el dato debe ser compartible y navegable? ¿Manda el servidor, porque es persistente y común a varios usuarios? ¿Manda un store global de cliente, porque cruza toda la sesión pero no sale del navegador? ¿O manda el componente local, porque es efímero y de UI?
La URL
Para lo compartible y navegable: la búsqueda, el filtro, la pestaña activa, la página. Si quieres que un enlace reproduzca el estado, su dueño es la URL.
El servidor
Para lo persistente y común a varios usuarios: el perfil, los pedidos, el catálogo. El cliente solo tiene copias; la verdad vive aquí.
El store global
Para lo que cruza toda la sesión sin salir del navegador: el tema, el usuario autenticado, un carrito aún no enviado al servidor.
El componente local
Para lo efímero y de UI: un desplegable abierto, el foco, un texto a medio escribir. Que nunca suba más arriba de lo necesario.
| Dato | Dueño natural | Herramienta típica en 2026 |
|---|---|---|
| Filtro de búsqueda | La URL | Router y search params |
| Perfil de usuario | El servidor | TanStack Query (cache) |
| Tema claro u oscuro | Store global | Zustand, signals |
| Menú abierto | Componente local | Estado local del componente |
El mismo dato puede cambiar de dueño según el producto, y eso está bien: el idioma de la interfaz puede vivir en la URL si quieres enlaces localizados, o en el servidor si es una preferencia de cuenta. Lo que no cambia es la exigencia de que tenga un dueño y solo uno. Elegirlo es un acto de diseño consciente, no un descuido que se resuelve guardándolo en todas partes por comodidad.
Acertar la propiedad resuelve de antemano media docena de preguntas que, mal planteadas, se vuelven bugs. Poner en un store global algo que la URL debía poseer rompe el botón de “atrás” y los enlaces compartidos. Guardar en estado de cliente algo que el servidor posee produce datos obsoletos. Este mapa de propiedad es el puente hacia la taxonomía del estado del próximo nivel; por ahora, la disciplina es no escribir un solo dato sin poder nombrar a su dueño.
Una señal de que un dueño está mal elegido es la aparición de código de sincronización: efectos que copian de un lado a otro, listeners que mantienen dos estados a la par. Ese pegamento suele ser el síntoma de que un dato tiene dos dueños y estás supliendo a mano lo que un buen reparto de propiedad haría gratis. Cuando te descubras escribiendo sincronización, no la perfecciones: pregúntate quién debería ser el dueño único y borra la otra copia.
Cuándo duplicar a conciencia
El principio no es “nunca copies”, y aquí está el matiz que separa el dogma de la maestría. A veces hay que copiar: una caché acelera, una actualización optimista mejora la sensación de rapidez, una réplica local-first (los CRDTs del nivel 38) permite trabajar sin red. Copiar no es el pecado. El pecado es copiar por accidente y fingir que no lo hiciste, dejando el invariante de sincronización sin dueño y sin plan.
Copiar a conciencia significa declararlo y definir la reconciliación. Una caché es una copia con una política de invalidación explícita. Una actualización optimista es una copia con un rollback explícito para cuando el servidor la rechace. La desnormalización es una copia con un procedimiento de reconstrucción. En los tres casos sigue habiendo una única fuente de verdad; lo que añades es una copia derivada, con nombre y con reglas claras de cómo vuelve a alinearse con la fuente. Copiar sin ese contrato es acumular deuda que se paga en bugs.
La parte difícil de una copia consciente nunca es hacerla, sino definir cómo vuelve a alinearse: cuándo caduca, quién gana si fuente y copia difieren, qué se muestra mientras se reconcilian. Si no puedes responder esas tres preguntas, no tienes una cache: tienes una segunda verdad esperando contradecir a la primera. La reconciliación es el contrato, y sin él copiar es solo posponer el bug.
Los CRDTs del nivel 38 llevan esta idea al extremo: son estructuras diseñadas para que muchas réplicas diverjan y luego converjan sin conflicto, porque las reglas de reconciliación están horneadas en el propio tipo de dato. No son la excepción a la fuente única de verdad, sino su forma más sofisticada: cuando la red no permite una sola copia física, se define matemáticamente cómo las copias vuelven a ser una.
Nada de esto contradice leer el dato derivado en mil sitios: leer no es copiar. Cien componentes pueden preguntar cuántos mensajes hay sin leer y todos obtienen la misma respuesta, porque todos derivan de la misma fuente. El pecado nunca fue mostrar un dato en muchos lugares, sino almacenarlo en muchos lugares. Derivar por todas partes es sano; duplicar aunque sea una vez es el principio del desacuerdo.
La copia peligrosa no es la que declaras, sino la que haces sin darte cuenta: un useState inicializado con una prop que luego cambia, un dato del servidor volcado a un store y nunca revalidado, una desnormalización sin plan de reconstrucción. Todas prometen comodidad hoy y cobran incoherencia mañana. La regla es simple: si copias, ponle nombre de cache y escribe cuándo se invalida. Una copia con contrato es una herramienta; una copia sin contrato es un bug esperando su fecha.
Este principio no es uno más: es el punto donde se juntan los cuatro hilos que has ido tejiendo. Minimiza, porque hay una copia y no muchas. Aísla, porque hay un dueño y un solo lugar donde cambiar el dato. Explicita, porque puedes señalar con el dedo quién posee cada cosa. Y deriva, porque todo lo demás se calcula desde la fuente en vez de duplicarla. La ontología del nivel 0 decía que todo estado es reactividad más una disciplina de mutación; este nivel muestra que el cimiento de esa disciplina es la propiedad: quién manda sobre cada dato. Antes de alcanzar un signal o un store, dibuja el mapa de propiedad —nombra cada dato y su único dueño—, y nueve de cada diez bugs difíciles de estado se desvanecen, no porque hayas elegido una herramienta mejor, sino porque has dejado de mantener sincronizadas dos cosas que siempre debieron ser una. Todo lo que viene después en el track —signals, máquinas, Flux, Elm, TCA, CRDTs— es maquinaria para honrar una fuente única de verdad: distintos motores para propagar cambios desde el origen a sus derivados sin que nada se desincronice. Quien interioriza esto deja de preguntar “dónde guardo este dato” y empieza a preguntar “de quién es este dato”, que es la pregunta de la que cuelga todo lo demás.
Has recorrido el arco entero: qué es el estado (memoria que persiste), cuánto sobra (esencial frente a accidental), por qué duele (compartido y mutable), qué hacer (minimizar, aislar, explicitar, derivar) y dónde ancla todo (una única fuente de verdad). Con este mapa mental, el resto del track deja de ser un catálogo de librerías y se vuelve un estudio de cómo cada una honra —o traiciona— estos principios.
- Encuentra en tu app dos lugares que muestren el mismo hecho (un contador, un nombre, un estado de sesión) y comprueba si guardan copias separadas. Únelos en una sola fuente de la que ambos deriven.
- Toma cinco datos de un módulo y asígnale a cada uno su dueño: URL, servidor, store global o componente local. ¿Alguno está en el sitio equivocado?
- Busca un estado de cliente que sea en realidad una copia de datos del servidor. Renómbralo mentalmente como “cache” y define su política: ¿cuándo se considera obsoleto, cuándo se revalida?
- Localiza una copia que hagas a conciencia (una optimista, una desnormalización) y escribe su contrato de reconciliación: ¿cómo y cuándo vuelve a alinearse con la fuente?