Resumen de todo lo entregado en Etapa 7:
Fix A1 (src/auth/AuthContext.tsx): isProfileLoaded: boolean agregado como estado. Arranca en false, se pone true en el finally del sync effect (después de syncProfileFromDB), y cuando onAuthStateChange devuelve null (usuario no autenticado). Se resetea a false en cada SIGN_IN para gatear el re-sync.
Fix A2 (src/features/library/LibraryPage.tsx): Guard temprano después de todos los hooks — si !user || !isProfileLoaded devuelve un skeleton de tres columnas animado, idéntico al que usa HomeView. El contenido rol-dependiente solo se renderiza cuando el perfil está sincronizado.
Fix A3 (GlobalSettingsPanel.tsx + WorkspacePage.tsx): El campo "Cliente" es texto estático (<p>) para client_editor y client_viewer. En el topbar de WorkspacePage se oculta el input inline y el ícono de lápiz; si hay un clientName se muestra como texto inerte.
Fix B (supabase/migrations/016_add_simulations_update_policy.sql): Migración creada + aplicada vía Management API a inq-roi-desa. pg_policies confirma las 4 políticas: simulations_select, simulations_insert, simulations_update, simulations_delete.
Tests: tests/features/library/library-ui.test.tsx actualizado — mock incluye isProfileLoaded, 2 tests nuevos para isProfileLoaded: false. Total: 586 tests, todos verdes. Build limpio.
src/features/auth/ConfirmInvitePage.tsx — nuevo componente con tres estados:
idle: pantalla de bienvenida + botón "Confirmar acceso" (estado inicial con params válidos)
loading: botón con spinner mientras se llama a verifyOtp
error: mensaje descriptivo + link a /login (token vacío/incorrecto en params, o fallo en verifyOtp)
El verifyOtp solo se llama al click del usuario — nunca al montar, por eso el pre-scan del email no consume el token.
src/router.tsx — ruta /auth/confirm agregada como pública (PUBLIC_PATHS) + confirmInviteRoute en el árbol de rutas.
tests/features/auth/confirm-invite.test.tsx — 6 tests cubriendo: no-call on mount, error con params vacíos, error con type distinto, llamada correcta a verifyOtp, navegación en éxito, y error con mensaje de detalle.
ResetPasswordPage no requiere cambios — updateUser({ password }) solo necesita sesión activa, que verifyOtp crea en el paso previo.