Pedir persistencia: la API y sus heurísticas
La API para solicitar el modo persistente devuelve un simple booleano que decide el navegador con criterios heurísticos sobre el compromiso del usuario, así que tu arquitectura no puede dar por hecho que la respuesta sea afirmativa.
Sabiendo que el modo por defecto autoriza al navegador a borrarlo todo, la reacción natural es buscar el interruptor que lo desactiva. Existe, y cabe en una línea. Lo que no existe es la certeza de que funcione: la llamada no configura nada, solicita algo, y quien decide es el navegador aplicando criterios que no publica del todo, que evalúa con información sobre el usuario que tú no tienes y que puede resolver de forma distinta en dos máquinas idénticas. Aprender esta API lleva un minuto. Aprender a construir una arquitectura que siga siendo correcta cuando la respuesta es negativa —que es el verdadero contenido de esta lección— lleva bastante más.
- Manejar la API de solicitud y de consulta del modo persistente, y su relación con la API de permisos.
- Entender la naturaleza heurística de la decisión y por qué el navegador no la toma según lo que tú necesitas.
- Elegir el momento correcto de la solicitud, que condiciona la probabilidad de obtener una respuesta afirmativa.
- Diseñar el camino de la negativa como la rama principal y no como un caso de error.
Una petición que devuelve un booleano
La superficie de la API es minúscula. El objeto navigator.storage expone persist, que solicita el cambio de modo y resuelve con un booleano, y persisted, que consulta el estado actual sin solicitar nada. Ambas devuelven promesas. La forma correcta de usarlas es siempre en ese orden: preguntar primero, pedir solo si hace falta, y no volver a pedir en bucle si la respuesta fue que no.
async function asegurarPersistencia() {
if (!navigator.storage || !navigator.storage.persist) {
return false; // el navegador no implementa el StorageManager
}
if (await navigator.storage.persisted()) {
return true; // ya somos persistentes: no vuelvas a pedirlo
}
return navigator.storage.persist(); // true o false, lo decide el navegador
}
Un matiz sobre la idempotencia de la llamada: repetirla no daña nada, pero tampoco ayuda. Si el estado ya es persistente resuelve afirmativamente sin más trámite, y si el navegador ya decidió que no, insistir en cada carga no cambia su criterio y sí puede traducirse en interrupciones repetidas allí donde la decisión implica al usuario. La comprobación previa con persisted no es una optimización: es la diferencia entre preguntar una vez y molestar siempre.
Hay una tercera puerta, menos conocida y muy útil: el permiso persistent-storage se puede interrogar a través de la API de permisos sin provocar ninguna solicitud. El estado resultante distingue tres situaciones que el booleano de persist colapsa en una: concedido, denegado y pendiente de decidir. Saber en cuál estás cambia lo que tiene sentido hacer a continuación, porque insistir cuando el estado es de denegación explícita no solo es inútil sino potencialmente molesto.
const permiso = await navigator.permissions.query({ name: "persistent-storage" });
// permiso.state vale "granted", "denied" o "prompt"
if (permiso.state === "prompt") {
// Aqui si tiene sentido pedirla, y conviene hacerlo en buen momento
}
Dos detalles de implementación evitan errores frecuentes. El primero es que la detección de característica no sobra: hay contextos donde el StorageManager no está disponible, y una llamada directa sin comprobación rompe el arranque entero por una funcionalidad que era opcional. El segundo es que la API exige un contexto seguro, de modo que probarla en un servidor de desarrollo sin cifrado producirá resultados que no se parecen a los de producción y llevará a conclusiones falsas sobre por qué la respuesta llega negativa.
Cómo decide el navegador
Aquí es donde la mayoría de las explicaciones se vuelven vagas, y con razón: la especificación deja deliberadamente la decisión en manos de la implementación, y las implementaciones no se comprometen con una fórmula fija porque la ajustan con el tiempo. Lo que sí puede describirse es la clase de criterio que se aplica. No es una comprobación técnica sobre tu aplicación —no importa qué API uses, cuántos datos guardes ni cuán bien esté escrito tu código—, sino una estimación del compromiso del usuario con tu sitio. La pregunta que el navegador intenta responder no es si tus datos son importantes, sino si a esta persona le importaría perderlos.
flowchart LR A[el usuario hace algo significativo] --> B[llamada a persist] B --> C[el navegador evalua sus propios indicios] C --> D[respuesta afirmativa cubo persistente] C --> E[respuesta negativa sigues en best effort] E --> F[se activa tu plan alternativo] style D fill:#a6e3a1,color:#11111b style E fill:#f9e2af,color:#11111b style F fill:#89b4fa,color:#11111b
Los indicios que suelen citarse en la documentación de la plataforma son de ese mismo orden: que el sitio esté instalado como aplicación, que figure en marcadores, que el usuario le haya concedido permiso de notificaciones o que acumule un historial de visitas y de tiempo de uso. Algunos navegadores optan por preguntar directamente al usuario mediante un diálogo; otros deciden en silencio a partir de esos indicios. En ambos casos el resultado es el mismo desde tu perspectiva: un booleano que llega sin explicación y que puedes no controlar en absoluto.
Y aquí hay que ser explícito con una tentación que asoma en cuanto se conoce la lista de indicios: intentar fabricarlos. Provocar visitas artificiales, empujar al usuario a marcar el sitio con una interrupción, pedir permiso de notificaciones que no vas a usar solo para mejorar tu perfil. Es mala ingeniería por tres motivos que se refuerzan entre sí. Los criterios cambian sin previo aviso, de modo que cualquier optimización contra ellos caduca; el usuario percibe la manipulación y paga con desconfianza justo en el terreno donde más la necesitas; y sobre todo, el esfuerzo se dirige al síntoma. Si tu arquitectura solo es correcta cuando el navegador dice que sí, el problema no es la tasa de respuestas afirmativas.
Dos supuestos erróneos se cuelan con facilidad. El primero es tratar el resultado como una propiedad estable: los indicios en que se basa pueden cambiar —el usuario desinstala la aplicación, elimina el marcador, revoca las notificaciones— y con ellos puede cambiar el estado del permiso. Conviene comprobar el modo en cada arranque en lugar de guardarlo como una verdad adquirida. El segundo es extrapolar de una máquina a todas: que tu navegador de desarrollo conceda la persistencia al instante, porque llevas meses abriendo tu propio sitio a diario, no dice absolutamente nada de lo que ocurrirá en el ordenador de alguien que acaba de descubrirte.
Este diseño tiene una implicación que descoloca a quien viene del desarrollo de servidor y conviene enunciar sin rodeos: el navegador no está de tu parte en esta negociación, está de parte del usuario. No evalúa la calidad de tu aplicación, la criticidad de tus datos ni la seriedad de tu equipo, porque nada de eso puede comprobarlo y todo el mundo lo afirmaría. Evalúa la única evidencia que tiene a mano y que ningún sitio puede falsear del todo: el comportamiento observado de la persona que está delante.
El momento de pedirla
Como la decisión depende del compromiso percibido, el instante de la solicitud importa más que su formulación. Llamar a persist en la primera línea del arranque, antes de que el usuario haya hecho nada, es el peor momento posible: ningún indicio se ha acumulado todavía, y en los navegadores que preguntan se traduce en un diálogo que interrumpe a alguien que aún no sabe qué es tu aplicación ni por qué debería confiarle su disco. La respuesta previsible es la negativa, y a partir de ahí insistir cuesta más.
El momento adecuado es aquel en que el usuario acaba de hacer algo que crea valor local: guardar su primer documento, completar una configuración laboriosa, descargar un conjunto de datos para trabajar sin conexión, aceptar la invitación a instalar la aplicación. En ese instante los indicios están en su punto más alto y, si el navegador pregunta, la pregunta tiene un contexto evidente para quien la recibe.
Nunca al arrancar
En el primer render no hay compromiso que evaluar ni contexto que ofrecer, y la negativa temprana es la más difícil de revertir después.
Tras crear valor
Justo después de la primera escritura significativa del usuario, cuando existe algo concreto que perder y él lo sabe.
Al instalar o al descargar
Instalar la aplicación o preparar datos para trabajar sin conexión son los dos momentos con mayor probabilidad de respuesta afirmativa.
Una vez, no en bucle
Si la respuesta fue negativa, reintentar en cada carga no mejora las probabilidades y sí degrada la experiencia. Espera a un nuevo hito.
Cuando el navegador puede mostrar un diálogo, tu aplicación tiene la oportunidad de precederlo con una explicación propia, breve y en lenguaje humano. No hables de cuotas, cubos ni desalojo: habla de lo que el usuario perdería. Una frase del tipo para que tus documentos sigan aquí aunque tu disco se llene, el navegador va a pedirte permiso convierte una interrupción incomprensible en una decisión informada, y de paso te permite no lanzar la solicitud si el usuario declina tu propia explicación, reservando el intento real para más adelante. Es el mismo patrón que se usa con las notificaciones, y por las mismas razones.
Existe además un momento intermedio que suele pasarse por alto: el del regreso. Un usuario que vuelve a tu aplicación después de varias sesiones ha acumulado, por definición, más indicios de los que tenía la primera vez, y probablemente también más datos que perder. Reintentar la solicitud en la enésima visita significativa —no en la enésima carga— es barato, tiene mejores probabilidades que el primer intento y no molesta a nadie si la última respuesta negativa fue hace semanas y no hace un minuto.
Un último apunte sobre el instante: si tu aplicación ofrece instalarse, ese flujo y la solicitud de persistencia deberían ir juntos. Quien acaba de instalar algo ha manifestado el compromiso más claro que existe, y encadenar la petición inmediatamente después aprovecha ese momento en lugar de desperdiciarlo. Es también el punto donde la explicación resulta más natural, porque instalar y conservar los datos localmente son, para el usuario, la misma idea.
Diseñar para el no
Todo lo anterior sería un ejercicio de optimización de tasas si no fuera por la restricción que gobierna la lección: la respuesta negativa es un resultado completamente normal, y tu aplicación tiene que ser correcta en ese caso. La prueba de fuego de un diseño maduro es sencilla de enunciar: si al forzar la respuesta negativa cambia la corrección de tu aplicación —y no solo su perfil de riesgo—, el diseño está mal.
const persistente = await asegurarPersistencia();
// Correcto: el booleano ajusta la politica, no la arquitectura
politicaDeSincronizacion.intervalo = persistente ? "relajado" : "agresivo";
avisos.mostrarRiesgoDePerdida = !persistente;
// Incorrecto: la arquitectura entera colgando de una decision ajena
// if (!persistente) throw new Error("no se puede usar la aplicacion");
Conviene además exponer el estado en algún lugar visible de los ajustes, y no solo por transparencia. Una aplicación que muestra si sus datos están protegidos frente al desalojo, cuánto ocupa y cuándo se sincronizó por última vez le da al usuario avanzado la información para actuar —instalarla, marcarla, exportar— y le da a tu equipo de soporte la primera pregunta que hacer cuando alguien reporte que ha perdido su trabajo. Ocultar esa información no hace el problema más pequeño: lo hace más difícil de diagnosticar.
// En los ajustes: estado legible, no jerga de plataforma
const protegido = await navigator.storage.persisted();
render({
titulo: protegido ? "Tus datos estan protegidos" : "Tus datos podrian borrarse",
detalle: protegido
? "El navegador no los eliminara para liberar espacio."
: "Si tu dispositivo se queda sin espacio, el navegador puede borrarlos.",
accion: protegido ? null : "Instalar la aplicacion para protegerlos",
});
Este criterio tiene una virtud secundaria que se agradece en el mantenimiento: una aplicación cuya corrección no depende del modo de almacenamiento solo necesita probarse una vez, mientras que la que sí depende obliga a duplicar cada escenario de prueba en sus dos variantes y a razonar sobre la interacción entre ambas. Reducir la respuesta a un ajuste de política no es únicamente más robusto frente al usuario; también es la mitad de superficie que verificar.
Lo que sí puede y debe cambiar con la respuesta es la política: con qué frecuencia empujas los cambios al servidor, con cuánta insistencia recuerdas al usuario que exporte, cuánto material precargas para trabajar sin conexión, qué mensaje aparece en los ajustes. Un origen en best-effort merece una estrategia más conservadora y una comunicación más honesta; un origen persistente puede permitirse acumular más y sincronizar con menos urgencia. En ambos casos, el usuario no pierde nada que no pueda recuperar, y esa invariante es la que la lección cinco convierte en el eje de la arquitectura.
Conviene entender por qué la plataforma diseñó esta API como una petición y no como una configuración, porque la razón revela algo profundo sobre el modelo de confianza de la web y sobre los límites reales del software local-first. Un navegador que concediera almacenamiento inamovible a quien lo pidiera dejaría de ser un agente del usuario y pasaría a ser un intermediario neutral entre el disco de una persona y la voluntad de cualquier sitio que visite una vez; al hacer que la concesión dependa de indicios de compromiso —instalación, marcadores, uso repetido—, la plataforma está codificando una tesis discutible pero coherente: el derecho a ocupar espacio permanente en el dispositivo de alguien no se reclama, se gana con uso. Y la consecuencia para ti es más incómoda de lo que parece, porque significa que la propiedad más deseable de una aplicación local-first —que los datos del usuario no desaparezcan— no es una propiedad que puedas implementar: es una que puedes solicitar. Ningún patrón de código, ninguna librería y ningún motor de sincronización cambia ese hecho. Ahora bien, precisamente por eso la responsabilidad no se transfiere junto con la decisión: que el navegador tenga la última palabra sobre el desalojo no te exime de tener la primera sobre la reconstruibilidad. El ingeniero que pide persistencia, obtiene un no y concluye que ya no puede hacer nada ha confundido delegar una decisión con delegar un deber. La pregunta que sigue siendo enteramente tuya, la respondan como la respondan, es qué le queda al usuario cuando el cubo se vacíe.
- Escribe la función que consulta con
persisted, comprueba el estado con la API de permisos y solo entonces llama apersist, registrando el resultado. - Localiza en tu aplicación el momento exacto en que el usuario crea su primer valor local y traslada allí la solicitud, lejos del arranque.
- Redacta la explicación previa en lenguaje de usuario, sin mencionar cuotas ni desalojo, y compruébala en voz alta con alguien ajeno al proyecto.
- Fuerza la respuesta negativa en un perfil limpio y recorre la aplicación entera: anota cada punto donde el comportamiento cambia y decide si es política o corrección.
- Elimina toda dependencia de corrección respecto del booleano, dejando únicamente ajustes de política: frecuencia de sincronización, avisos y volumen precargado.