Pregunta y Casos de Uso
Una aplicación web multiinquilino de gestión de proyectos admite tanto el renderizado del lado del servidor (SSR) como la edición sin conexión. Tiene cuatro tipos de estado: el servidor debe reconocer la sesión de inicio de sesión en todas las pestañas; las preferencias de tema e idioma deben persistir en una visita posterior; un borrador de formulario de varios pasos pertenece únicamente a la pestaña actual y puede desaparecer cuando esa pestaña se cierra; y hasta 10.000 registros de tareas estructurados con archivos adjuntos de imagen deben admitir consultas, ediciones y sincronización sin conexión después de reconectarse.
Asigne estos datos a cookies, localStorage, sessionStorage e IndexedDB. Explique cómo el diseño gestiona XSS y CSRF, el aislamiento de origen, un primer renderizado SSR consistente, pestañas concurrentes, cuotas y desalojo (eviction), actualizaciones de versiones de la base de datos, cierre de sesión y limpieza local, y sincronización sin conexión.
La cifra de 10.000 registros es una restricción del escenario que obliga a debatir sobre consultas estructuradas, acceso asíncrono y un protocolo de sincronización. No es una garantía de capacidad del navegador. La habilidad principal es tomar decisiones de persistencia en el frontend a partir de la semántica de la plataforma Web, por lo que la categoría es frontend.
Qué Está Evaluando el Entrevistador
En primer lugar, ¿el candidato pregunta quién lee los datos, cuánto tiempo viven, qué forma tienen, cuánto costaría una divulgación y qué sistema es la autoridad antes de seleccionar una API? Una tabla de capacidad memorizada no puede explicar SSR, la autenticación o la consistencia sin conexión.
En segundo lugar, ¿puede el candidato separar la persistencia de la seguridad? La persistencia no hace que localStorage sea adecuado para un identificador de sesión. Los scripts del mismo origen pueden leerlo y modificarlo, por lo que una carga útil de XSS puede robar o manipular sus datos. Una cookie HttpOnly evita que JavaScript lea el identificador de sesión, pero no impide que el código que ya se está ejecutando en la página emita solicitudes autenticadas.
En tercer lugar, ¿comprende el candidato ambos lados del comportamiento de las cookies? El navegador envía las cookies coincidentes con las solicitudes, lo que facilita el renderizado en el servidor y la autenticación. Este mismo envío automático requiere defensas contra CSRF. SameSite es una defensa, no un reemplazo para las comprobaciones de origen, los tokens CSRF o la reautenticación para acciones sensibles.
En cuarto lugar, ¿puede el candidato tratar a IndexedDB como una réplica local falible? Admite objetos estructurados asíncronos, índices y blobs, pero no se sincroniza con un servidor. La aplicación es responsable de los fallos de cuota, el desalojo, la limpieza, las actualizaciones bloqueadas y los conflictos de concurrencia.
En quinto lugar, ¿puede el candidato proporcionar una matriz de fallos ejecutable en lugar de tratar «sobrevive a la recarga» como la única prueba?
Preguntas Aclaratorias Antes de Responder
- ¿Qué valores necesita el servidor para el primer renderizado? Si el SSR debe emitir HTML para el estado de inicio de sesión, idioma o tema correctos, esos valores necesitan una fuente legible por el servidor, o el producto debe aceptar un cambio tras el montaje en el cliente.
- ¿Qué modelo de autenticación se está utilizando? Esta respuesta asume sesiones del lado del servidor. Un diseño de token de API puro también necesita emisión explícita, rotación, revocación y reglas para solicitudes entre sitios.
- ¿Debe el borrador tener realmente un alcance exclusivo de pestaña? Si los usuarios esperan una recuperación tras cerrar la pestaña o continuar en otro dispositivo,
sessionStorageya no cumple con el requisito; use IndexedDB o el servidor. - ¿Los datos sin conexión contienen información confidencial? Los dispositivos compartidos, el XSS, los perfiles de navegador y las copias de seguridad locales cambian lo que se puede escribir en el disco.
- ¿Cómo se consultan y actualizan los 10.000 registros? Las consultas por proyecto, hora de actualización o estado de sincronización hacen que los índices y las transacciones sean más importantes que el simple acceso clave-valor.
- ¿Es el servidor la fuente final de verdad? Este escenario asume un servidor con autoridad y una copia local reconstruible. Los datos creados sin conexión irremplazables necesitan una persistencia más sólida, exportación y controles de conflicto.
- ¿Qué se considera un conflicto? Las ediciones simultáneas en dispositivos pueden usar rechazo por versión, fusión de campos o una prioridad de negocio. La API de almacenamiento no puede elegir esa política por el producto.
- ¿Debe el cierre de sesión eliminar los datos de todos los inquilinos? Cuando varias cuentas comparten un navegador, la limpieza necesita espacios de nombres de usuario e inquilino para que el siguiente usuario no pueda ver la réplica del usuario anterior.
Estructura de Respuesta de 30 Segundos
«Mapeo los datos según el lector, la vida útil, la forma, la sensibilidad y la fuente de verdad. La autenticación utiliza una sesión del servidor y una cookie __Host- con Secure, HttpOnly y un valor apropiado de SameSite, además de defensas contra CSRF. Las preferencias pequeñas no confidenciales usan localStorage, con un valor inicial legible por el servidor cuando el SSR lo necesita. Un borrador desechable de una sola pestaña utiliza sessionStorage. Los registros estructurados y las imágenes utilizan IndexedDB con índices, transacciones, un esquema versionado y una bandeja de salida (outbox). El servidor sigue siendo la autoridad; la sincronización utiliza versiones y claves de idempotencia y resuelve conflictos explícitamente. Trate el almacenamiento local como borrable y susceptible a fallos de cuota, y luego pruebe XSS, CSRF, múltiples pestañas, desalojo, actualizaciones bloqueadas, reconexiones y limpieza al cerrar sesión».
Análisis Detallado Paso a Paso
Paso 1: Construir la matriz de decisión con cinco preguntas
Para cada valor, haga cinco preguntas: ¿el lector es el servidor, la pestaña actual o todas las páginas del mismo origen?; ¿la vida útil es un renderizado, una pestaña, una sesión del navegador o múltiples sesiones?; ¿los datos son una cadena pequeña, objetos estructurados o blobs?; ¿qué tan perjudiciales son la divulgación y la manipulación?; y ¿es la autoridad el servidor o el dispositivo local?
Eso produce la asignación inicial para este escenario:
Login session -> Server session + random identifier in an HttpOnly cookie
Theme and language -> localStorage; add a server-readable initial value when SSR requires it
Single-tab draft -> sessionStorage
Offline data/images -> IndexedDB + an application-level synchronization protocolEsto no es una clasificación por capacidad. La capacidad definitoria de una cookie aquí es llegar al servidor con las solicitudes. El límite definitorio de sessionStorage es el contexto de navegación de nivel superior. IndexedDB proporciona transacciones asíncronas, objetos estructurados e índices.
Paso 2: Hacer que el servidor sea la autoridad para la autenticación
El navegador almacena solo un identificador de sesión de alta entropía y corta duración. Los permisos reales, la expiración y la revocación residen en el servidor. Configure Secure, HttpOnly, un valor apropiado de SameSite, y preferiblemente use una cookie __Host- sin Domain y con una ruta raíz. El cierre de sesión, los cambios de contraseña y los eventos de riesgo invalidan la sesión del servidor; eliminar solo un valor del navegador es insuficiente.
HttpOnly reduce el acceso directo de los scripts al identificador de sesión, pero el XSS de mismo origen aún puede emitir acciones autenticadas desde la página activa. La codificación de salida, CSP y otras defensas contra XSS siguen siendo necesarias, y las operaciones de alto riesgo pueden requerir reautenticación. Debido a que las cookies coincidentes se envían automáticamente, las solicitudes que cambian el estado también deben validar el origen y usar un token CSRF. SameSite no debe ser el único control.
Poner el identificador de sesión en localStorage lo hace legible para cualquier script del mismo origen que se ejecute con éxito. El cifrado en el lado del cliente no es una solución automática: si el JavaScript de la página puede obtener la clave de descifrado o llamar a la ruta de descifrado, el XSS del mismo origen generalmente también puede hacerlo.
Paso 3: Usar localStorage para preferencias pequeñas y no confidenciales
localStorage tiene alcance de origen, almacena claves y valores de cadena, persiste entre sesiones del navegador y expone operaciones síncronas. Las preferencias pequeñas como el tema, el idioma o la densidad de la tabla encajan aquí. No son permanentes: borrar los datos del sitio, finalizar una sesión de navegación privada o las políticas del navegador pueden eliminarlas.
El servidor no puede leer directamente localStorage. Si el primer marco de SSR debe usar el idioma o tema correctos, sincronice una preferencia validada con una cookie legible por el servidor o el perfil de usuario y defina qué copia prevalece. Si el valor es solo de cliente, emita un valor predeterminado estable y cámbielo después del montaje para que el HTML del servidor y el primer renderizado del cliente no difieran.
Evite almacenar una gran matriz de registros en localStorage y analizar repetidamente todo el valor JSON. La serialización síncrona y el acceso en el hilo principal crecen con el conjunto de datos, mientras que el modelo carece de las transacciones e índices de IndexedDB.
Paso 4: Colocar solo el estado desechable de la pestaña en sessionStorage
sessionStorage está particionado por origen y contexto de navegación de nivel superior. Sobrevive a las recargas en la misma pestaña y se borra cuando se cierra la pestaña o la ventana. Eso lo hace apropiado para el número de paso de la pestaña actual y el borrador desechable, pero no para un carrito compartido entre pestañas, la recuperación a largo plazo o el estado entre dispositivos.
El límite del abridor (opener) es fácil de pasar por alto. Una página recién abierta puede recibir inicialmente una copia del sessionStorage de su abridor; luego, las dos copias cambian de forma independiente. Si el borrador nunca debe copiarse, elimine la relación del abridor o incluya un ID de instancia por pestaña en el borrador y valídelo durante la recuperación.
Los scripts del mismo origen también pueden leer y escribir en sessionStorage. Que se «borre al cerrar» no es una razón para almacenar una credencial de larga duración. Antes de persistir un borrador, excluya campos como contraseñas, datos de pago o información médica que no deberían escribirse en el disco.
Paso 5: Construir una réplica reconstruible sin conexión en IndexedDB
IndexedDB ofrece solicitudes asíncronas, transacciones, almacenes de objetos, claves, índices y almacenamiento de blobs. Se adapta a los registros estructurados y las imágenes de este escenario. Particione los datos por tenantId + userId, cree almacenes de objetos para tareas, archivos adjuntos y una bandeja de salida (outbox), y agregue solo los índices requeridos por consultas reales como proyecto, hora de actualización y estado de sincronización.
Utilice una versión explícita de la base de datos y migraciones incrementales. La apertura de una nueva versión puede bloquearse por conexiones de pestañas antiguas. Esas pestañas deben escuchar versionchange, cerrar sus conexiones antiguas y pedir al usuario que recargue. La nueva página debe manejar blocked en lugar de bloquearse indefinidamente durante el inicio.
IndexedDB es una base de datos local, no un servicio de sincronización. Una escritura exitosa significa que la transacción local se confirmó. La aplicación aún registra las versiones local y del servidor, un ID de operación y el estado de sincronización, y hace que los reintentos sean idempotentes.
Paso 6: Diseñar la sincronización sin conexión y los conflictos entre pestañas explícitamente
Confirme cada cambio de negocio sin conexión y su entrada en la bandeja de salida en una única transacción de IndexedDB. Después de reconectarse, un worker realiza la carga por ID de operación. El servidor deduplica con una clave de idempotencia y compara la versión del registro. Solo después del acuse de recibo, otra transacción debe actualizar la versión del servidor y eliminar el elemento de la bandeja de salida. Esto evita modificar el registro mientras se pierde la operación pendiente.
La política de conflictos proviene del negocio. Una preferencia de bajo riesgo podría usar la última escritura gana (last writer wins); el estado de la tarea puede rechazar una discrepancia de versión y pedir al usuario que fusione; los cambios financieros o de permisos pueden prohibir las confirmaciones sin conexión. Un evento storage o BroadcastChannel puede indicar a otras pestañas que recarguen los datos, pero una notificación no es un bloqueo (lock) y no puede reemplazar las transacciones de IndexedDB ni las comprobaciones de versión del servidor. El evento storage no se dispara en el documento que realizó la escritura.
Paso 7: Tratar la cuota, el desalojo y la limpieza como fallos normales
Las cuotas del navegador varían según el navegador, el dispositivo y el modo. IndexedDB suele utilizar un almacenamiento del tipo mejor esfuerzo (best-effort), que un usuario puede borrar y un navegador puede desalojar ante la falta de espacio. Las escrituras también pueden fallar por falta de cuota. Utilice estimaciones de almacenamiento para observar el uso, solicite persistencia con cautela para datos irremplazables y maneje siempre los fallos de transacción y cuota.
La política debe incluir límites de archivos adjuntos, limpieza de los menos utilizados recientemente (LRU), compresión o eliminación de versiones antiguas confirmadas por el servidor y un estado recuperable de «espacio local lleno». Al cerrar sesión, revoque primero la sesión del servidor, luego cierre las conexiones a la base de datos y elimine los datos de IndexedDB, preferencias y borradores de ese usuario e inquilino. Notifique también a otras pestañas. La eliminación local no puede sustituir a la revocación en el servidor.
Paso 8: Verificar los límites con una matriz de fallos
Pruebe el primer renderizado SSR y la hidratación; recargar, duplicar, abrir nuevamente y cerrar pestañas; cerrar sesión desde otra pestaña; alcance legible por scripts bajo XSS; solicitudes de cambio de estado entre sitios; navegación privada; datos del sitio borrados; agotamiento de cuota; una pestaña antigua que bloquea una actualización de la base de datos; dos pestañas editando concurrentemente; reintentos sin conexión, respuestas duplicadas y fuera de orden, y conflictos; y cambio de inquilino sin exponer datos antiguos.
Aprobar significa más que «los datos permanecen». La autenticación es revocable, los valores confidenciales no se exponen a JavaScript, los borradores respetan el límite de la pestaña, una escritura sin conexión no puede perder su entrada en la bandeja de salida, la sincronización duplicada no duplica los efectos en el negocio, las actualizaciones se recuperan, los fallos de cuota tienen una alternativa y el servidor puede reconstruir la réplica local.
Respuesta de Muestra de Alta Calidad
«Comienzo con el lector, la vida útil, el modelo de datos, el límite de confianza y la fuente de verdad en lugar de la capacidad. El servidor y cada pestaña necesitan la sesión de inicio de sesión, por lo que la sesión real reside en el servidor y el navegador mantiene una cookie __Host- con Secure, HttpOnly y un valor adecuado de SameSite. Eso reduce el acceso al token mediante JavaScript, pero XSS aún puede actuar como el usuario, y el envío automático de cookies todavía requiere un token CSRF, comprobaciones de origen y reautenticación para acciones sensibles.
El tema y el idioma son preferencias pequeñas no confidenciales, por lo que utilizan localStorage. Si el SSR las necesita en el primer marco, sincronizo una preferencia validada legible por el servidor y defino la copia con autoridad. El borrador de formulario desechable de la pestaña actual utiliza sessionStorage: la recarga lo restaura y el cierre lo elimina. También tengo en cuenta que una nueva página copie inicialmente el valor del abridor.
Las 10.000 tareas e imágenes utilizan IndexedDB. La base de datos está particionada por usuario e inquilino y utiliza un esquema versionado, índices y transacciones. Cada edición de negocio y entrada en la bandeja de salida se confirman juntas. Al reconectarse, el cliente realiza la carga con un ID de operación idempotente, y el servidor compara las versiones de los registros antes de confirmar o devolver un conflicto. Las notificaciones entre pestañas solo provocan una recarga; las transacciones locales y las versiones del servidor garantizan la corrección.
Trato todo el almacenamiento local como borrable, susceptible a fallos de cuota y disponible para scripts del mismo origen. Manejo errores de cuota, limpio archivos adjuntos reconstruibles, cierro conexiones antiguas durante las actualizaciones y revoque la sesión del servidor antes de limpiar el espacio de nombres local del usuario al cerrar sesión. Mi matriz de pruebas cubre SSR e hidratación, copias de pestañas, XSS, CSRF, cuota, desalojo, actualizaciones bloqueadas, edición concurrente, reintentos sin conexión y cambio de inquilino».
Errores Comunes
- Elegir mecánicamente a partir de una tabla de capacidad → pasa por alto a los lectores del servidor, los límites de las pestañas y la autoridad → use primero la matriz de cinco dimensiones.
- Poner el identificador de sesión en
localStorage→ el XSS del mismo origen puede leerlo y filtrarlo → use una cookie protegida respaldada por una sesión del servidor y continúe previniendo XSS. - Asumir que
HttpOnlyelimina XSS → los scripts maliciosos aún pueden realizar acciones autenticadas → separe la defensa contra el robo de tokens de la defensa de acciones. - Usar únicamente
SameSitepara CSRF → las políticas del navegador, los tipos de solicitud y los flujos de negocio aún tienen límites → combine tokens, comprobaciones de origen y reautenticación. - Hacer que el servidor dependa de
localStorage→ SSR no puede leer el almacenamiento del navegador → proporcione un valor inicial legible por el servidor o acepte un cambio posterior al montaje. - Asumir que una nueva pestaña siempre tiene
sessionStoragevacío → un abridor (opener) puede proporcionar la copia inicial → elimine el abridor o agregue un ID de instancia de pestaña. - Poner un arreglo grande en
localStorage→ requiere análisis síncrono, reescrituras de valores completos y carece de transacciones indexadas → use IndexedDB. - Asumir que IndexedDB se sincroniza automáticamente → la confirmación local y el acuse de recibo del servidor son eventos diferentes → implemente una bandeja de salida, idempotencia, versiones y política de conflictos.
- Tratar la notificación entre pestañas como un bloqueo distribuido → los mensajes pueden retrasarse y no pueden decidir un conflicto en el servidor → recargue después de la notificación y use transacciones más versiones para garantizar la corrección.
- Asumir que los datos locales son permanentes → la limpieza, el desalojo, el modo privado y las cuotas pueden eliminarlos → haga que sean reconstruibles y maneje los fallos de escritura.
- Borrar solo la cookie al cerrar sesión → IndexedDB y las preferencias pueden filtrarse a la siguiente cuenta → revoque en el servidor, luego limpie cada réplica local de usuario e inquilino.
Preguntas de Seguimiento y Respuestas
Pregunta de seguimiento 1: ¿Puedo cifrar un token y guardarlo en localStorage?
Si el JavaScript de la página puede obtener la clave o llamar a la ruta de descifrado, un script malicioso del mismo origen que se ejecute con éxito generalmente también puede hacerlo. Eso no resuelve el robo de sesiones por XSS en este escenario. Un límite más sólido es una sesión en el servidor con una cookie HttpOnly más prevención de XSS, protección CSRF, rotación y revocación. El cifrado del lado del cliente aborda una amenaza solo cuando su clave está fuera de la misma superficie de ataque.
Pregunta de seguimiento 2: ¿Qué pasa si un borrador debe sobrevivir al cierre de la pestaña?
El requisito de vida útil ha cambiado, por lo que sessionStorage ya no encaja. Un borrador no confidencial que solo necesita recuperación local puede usar IndexedDB. Un borrador que deba compartirse entre dispositivos o que no deba perderse debe sincronizarse con el servidor. Cualquiera de las opciones necesita un período de retención, aislamiento de usuario e inquilino y reglas que excluyan los campos confidenciales de la persistencia.
Pregunta de seguimiento 3: ¿Qué pasa si dos pestañas editan la misma tarea sin conexión?
Almacene una versión del servidor y una revisión local en cada registro y utilice una transacción de IndexedDB para cada escritura. BroadcastChannel o una notificación de cambio de datos indica a otras pestañas que recarguen, pero el servidor sigue realizando una actualización condicional con respecto a la versión. Tras un conflicto, el negocio elige la fusión de campos, una solicitud al usuario o el rechazo. La última pestaña en recibir un mensaje no puede convertirse en la fuente de verdad.
Pregunta de seguimiento 4: ¿Qué pasa si una pestaña antigua bloquea la actualización de IndexedDB?
Las conexiones antiguas escuchan versionchange, se cierran y solicitan una recarga. La nueva página gestiona blocked con un estado recuperable en lugar de esperar indefinidamente. Las migraciones son incrementales, idempotentes y toleran datos históricos parciales. Antes del lanzamiento, mantenga una pestaña de versión antigua conectada y abra la nueva versión para verificar el cierre, los mensajes y la migración.
Pregunta de seguimiento 5: ¿Cómo debería degradarse la aplicación cuando el almacenamiento del navegador está lleno?
Gestione los errores de cuota y de transacción, deje de almacenar en caché los archivos adjuntos nuevos primero y elimine los archivos adjuntos antiguos confirmados por el servidor y las versiones que se puedan reconstruir. Una bandeja de salida no sincronizada tiene mayor prioridad que la caché descargable. Si el espacio aún es insuficiente, indique al usuario que se reconecte y sincronice o libere espacio; nunca informe un guardado silencioso como exitoso.
Pregunta de seguimiento 6: ¿Cuál es el orden correcto de cierre de sesión?
Primero pida al servidor que revoque la sesión actual para que las cookies copiadas y las pestañas aún abiertas también pierdan el acceso. Luego notifique a otras pestañas, detenga el trabajo de sincronización, cierre las conexiones de IndexedDB, elimine las bases de datos, borradores y preferencias del usuario e inquilino, y finalmente ingrese a la interfaz de usuario de sesión cerrada. Si la solicitud de red falla, no presente un falso cierre de sesión solo local como confirmado; restrinja el trabajo posterior y muestre que la revocación está pendiente.