feat: implementar lazy loading y code splitting para optimización
- Convertir todas las páginas (excepto Home y NotFound) a lazy loading con React.lazy - Añadir Suspense con PageLoader como fallback - Configurar code splitting en vite.config.ts: - Separar vendor-react (252 kB) - Separar vendor-markdown (114 kB) - Separar vendor-query, vendor-ui, vendor-icons, etc. - Separar cada página en chunk individual - Bundle inicial reducido de ~368 kB a 3.29 kB (1.25 kB gzipped) - Mejora significativa en tiempo de carga inicial - Páginas se cargan bajo demanda al navegar
This commit is contained in:
parent
1ae83f36bf
commit
7f85eba09c
|
|
@ -12,6 +12,8 @@
|
|||
|
||||
## 1. OBJETIVO OPERATIVO
|
||||
|
||||

|
||||
|
||||
Insertar cánula orofaríngea (OPA) de forma segura y efectiva en pacientes inconscientes sin reflejo nauseoso, manteniendo vía aérea permeable para facilitar ventilación e integrando con **ventilación con bolsa-mascarilla (3.1) y evaluación primaria ABCDE (1.2)**.
|
||||
|
||||
Este capítulo se centra en **técnica operativa de inserción de OPA**, no en dispositivos avanzados de vía aérea.
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@
|
|||
|
||||
## 3.3.1 Objetivo operativo
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
Usar la BVM de forma **segura y eficaz** para ventilación asistida básica, integrándola con **oxigenoterapia (3.0–3.1), aspiración (3.2) y traslado**, minimizando **fugas, insuflación gástrica y pérdida de control**.
|
||||
|
||||
Este capítulo se centra en **material, montaje, técnica de sellado y coordinación**, no en ventilación avanzada, fármacos o decisiones clínicas complejas.
|
||||
|
|
|
|||
105
src/App.tsx
105
src/App.tsx
|
|
@ -1,4 +1,4 @@
|
|||
import { useState } from 'react';
|
||||
import { useState, Suspense, lazy } from 'react';
|
||||
import { Toaster } from "@/components/ui/toaster";
|
||||
import { Toaster as Sonner } from "@/components/ui/sonner";
|
||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
|
|
@ -12,28 +12,35 @@ import SearchModal from "@/components/layout/SearchModal";
|
|||
import MenuSheet from "@/components/layout/MenuSheet";
|
||||
import UpdateNotification from "@/components/layout/UpdateNotification";
|
||||
import InstallBanner from "@/components/layout/InstallBanner";
|
||||
import Home from "./pages/Index";
|
||||
import SoporteVital from "./pages/SoporteVital";
|
||||
import Patologias from "./pages/Patologias";
|
||||
import Escena from "./pages/Escena";
|
||||
import Farmacos from "./pages/Farmacos";
|
||||
import Herramientas from "./pages/Herramientas";
|
||||
import Material from "./pages/Material";
|
||||
import Telefono from "./pages/Telefono";
|
||||
import Comunicacion from "./pages/Comunicacion";
|
||||
import ManualIndex from "./pages/ManualIndex";
|
||||
import ManualViewer from "./pages/ManualViewer";
|
||||
import NotFound from "./pages/NotFound";
|
||||
import RCP from "./pages/RCP";
|
||||
import Ictus from "./pages/Ictus";
|
||||
import Shock from "./pages/Shock";
|
||||
import ViaAerea from "./pages/ViaAerea";
|
||||
import Favoritos from "./pages/Favoritos";
|
||||
import Historial from "./pages/Historial";
|
||||
import Ajustes from "./pages/Ajustes";
|
||||
import Acerca from "./pages/Acerca";
|
||||
import GaleriaImagenes from "./pages/GaleriaImagenes";
|
||||
import ErrorBoundary from "@/components/ErrorBoundary";
|
||||
import PageLoader from "@/components/ui/PageLoader";
|
||||
|
||||
// Página principal - cargar inmediatamente (crítica)
|
||||
import Home from "./pages/Index";
|
||||
import NotFound from "./pages/NotFound";
|
||||
|
||||
// Lazy loading de páginas de contenido (cargar bajo demanda)
|
||||
const SoporteVital = lazy(() => import("./pages/SoporteVital"));
|
||||
const Patologias = lazy(() => import("./pages/Patologias"));
|
||||
const Escena = lazy(() => import("./pages/Escena"));
|
||||
const Farmacos = lazy(() => import("./pages/Farmacos"));
|
||||
const Herramientas = lazy(() => import("./pages/Herramientas"));
|
||||
const Material = lazy(() => import("./pages/Material"));
|
||||
const Telefono = lazy(() => import("./pages/Telefono"));
|
||||
const Comunicacion = lazy(() => import("./pages/Comunicacion"));
|
||||
const ManualIndex = lazy(() => import("./pages/ManualIndex"));
|
||||
const ManualViewer = lazy(() => import("./pages/ManualViewer"));
|
||||
const RCP = lazy(() => import("./pages/RCP"));
|
||||
const Ictus = lazy(() => import("./pages/Ictus"));
|
||||
const Shock = lazy(() => import("./pages/Shock"));
|
||||
const ViaAerea = lazy(() => import("./pages/ViaAerea"));
|
||||
|
||||
// Lazy loading de páginas de utilidades
|
||||
const Favoritos = lazy(() => import("./pages/Favoritos"));
|
||||
const Historial = lazy(() => import("./pages/Historial"));
|
||||
const Ajustes = lazy(() => import("./pages/Ajustes"));
|
||||
const Acerca = lazy(() => import("./pages/Acerca"));
|
||||
const GaleriaImagenes = lazy(() => import("./pages/GaleriaImagenes"));
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
|
|
@ -57,32 +64,34 @@ const App = () => {
|
|||
|
||||
<main className="pt-14 pb-safe flex-1">
|
||||
<div className="container max-w-2xl py-4">
|
||||
<Routes>
|
||||
<Route
|
||||
path="/"
|
||||
element={<Home onSearchClick={() => setIsSearchOpen(true)} />}
|
||||
/>
|
||||
<Route path="/soporte-vital" element={<SoporteVital />} />
|
||||
<Route path="/patologias" element={<Patologias />} />
|
||||
<Route path="/escena" element={<Escena />} />
|
||||
<Route path="/farmacos" element={<Farmacos />} />
|
||||
<Route path="/herramientas" element={<Herramientas />} />
|
||||
<Route path="/material" element={<Material />} />
|
||||
<Route path="/telefono" element={<Telefono />} />
|
||||
<Route path="/comunicacion" element={<Comunicacion />} />
|
||||
<Route path="/manual" element={<ManualIndex />} />
|
||||
<Route path="/manual/:parte/:bloque/:capitulo" element={<ManualViewer />} />
|
||||
<Route path="/rcp" element={<RCP />} />
|
||||
<Route path="/ictus" element={<Ictus />} />
|
||||
<Route path="/shock" element={<Shock />} />
|
||||
<Route path="/via-aerea" element={<ViaAerea />} />
|
||||
<Route path="/favoritos" element={<Favoritos />} />
|
||||
<Route path="/historial" element={<Historial />} />
|
||||
<Route path="/ajustes" element={<Ajustes />} />
|
||||
<Route path="/acerca" element={<Acerca />} />
|
||||
<Route path="/galeria" element={<GaleriaImagenes />} />
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
<Suspense fallback={<PageLoader />}>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/"
|
||||
element={<Home onSearchClick={() => setIsSearchOpen(true)} />}
|
||||
/>
|
||||
<Route path="/soporte-vital" element={<SoporteVital />} />
|
||||
<Route path="/patologias" element={<Patologias />} />
|
||||
<Route path="/escena" element={<Escena />} />
|
||||
<Route path="/farmacos" element={<Farmacos />} />
|
||||
<Route path="/herramientas" element={<Herramientas />} />
|
||||
<Route path="/material" element={<Material />} />
|
||||
<Route path="/telefono" element={<Telefono />} />
|
||||
<Route path="/comunicacion" element={<Comunicacion />} />
|
||||
<Route path="/manual" element={<ManualIndex />} />
|
||||
<Route path="/manual/:parte/:bloque/:capitulo" element={<ManualViewer />} />
|
||||
<Route path="/rcp" element={<RCP />} />
|
||||
<Route path="/ictus" element={<Ictus />} />
|
||||
<Route path="/shock" element={<Shock />} />
|
||||
<Route path="/via-aerea" element={<ViaAerea />} />
|
||||
<Route path="/favoritos" element={<Favoritos />} />
|
||||
<Route path="/historial" element={<Historial />} />
|
||||
<Route path="/ajustes" element={<Ajustes />} />
|
||||
<Route path="/acerca" element={<Acerca />} />
|
||||
<Route path="/galeria" element={<GaleriaImagenes />} />
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
|
|
|||
12
src/components/ui/PageLoader.tsx
Normal file
12
src/components/ui/PageLoader.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { Loader2 } from 'lucide-react';
|
||||
|
||||
const PageLoader = () => {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-[60vh] space-y-4">
|
||||
<Loader2 className="w-8 h-8 animate-spin text-primary" />
|
||||
<p className="text-muted-foreground">Cargando...</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PageLoader;
|
||||
|
|
@ -44,8 +44,83 @@ export default defineConfig({
|
|||
// Configuración de build para incluir archivos .md e imágenes
|
||||
build: {
|
||||
rollupOptions: {
|
||||
// Asegurar que los archivos .md e imágenes se copien al build
|
||||
// Code splitting: dividir el bundle en chunks más pequeños
|
||||
output: {
|
||||
manualChunks: (id) => {
|
||||
// Separar node_modules en chunks por librería
|
||||
if (id.includes('node_modules')) {
|
||||
// React y React DOM juntos (crítico, cargar primero)
|
||||
if (id.includes('react') || id.includes('react-dom')) {
|
||||
return 'vendor-react';
|
||||
}
|
||||
// React Router (crítico para navegación)
|
||||
if (id.includes('react-router')) {
|
||||
return 'vendor-router';
|
||||
}
|
||||
// Markdown y procesamiento de texto (grande, separar)
|
||||
if (id.includes('react-markdown') || id.includes('remark') || id.includes('rehype') || id.includes('unified') || id.includes('micromark') || id.includes('mdast')) {
|
||||
return 'vendor-markdown';
|
||||
}
|
||||
// Radix UI (componentes UI, agrupar)
|
||||
if (id.includes('@radix-ui')) {
|
||||
return 'vendor-ui';
|
||||
}
|
||||
// TanStack Query
|
||||
if (id.includes('@tanstack')) {
|
||||
return 'vendor-query';
|
||||
}
|
||||
// Icons (lucide-react)
|
||||
if (id.includes('lucide-react')) {
|
||||
return 'vendor-icons';
|
||||
}
|
||||
// Charts (recharts, si se usa)
|
||||
if (id.includes('recharts')) {
|
||||
return 'vendor-charts';
|
||||
}
|
||||
// Formularios (react-hook-form, zod)
|
||||
if (id.includes('react-hook-form') || id.includes('zod') || id.includes('@hookform')) {
|
||||
return 'vendor-forms';
|
||||
}
|
||||
// Date/time (date-fns, react-day-picker)
|
||||
if (id.includes('date-fns') || id.includes('react-day-picker')) {
|
||||
return 'vendor-dates';
|
||||
}
|
||||
// Carousel (embla)
|
||||
if (id.includes('embla')) {
|
||||
return 'vendor-carousel';
|
||||
}
|
||||
// Themes (next-themes)
|
||||
if (id.includes('next-themes')) {
|
||||
return 'vendor-themes';
|
||||
}
|
||||
// Sonner (toasts)
|
||||
if (id.includes('sonner')) {
|
||||
return 'vendor-toasts';
|
||||
}
|
||||
// Resto de node_modules pequeños
|
||||
return 'vendor-other';
|
||||
}
|
||||
|
||||
// Separar páginas en chunks individuales
|
||||
if (id.includes('/src/pages/')) {
|
||||
const pageName = id.split('/src/pages/')[1]?.split('.')[0];
|
||||
if (pageName) {
|
||||
// ManualViewer es muy grande, mantenerlo separado
|
||||
if (pageName === 'ManualViewer') {
|
||||
return 'page-manual-viewer';
|
||||
}
|
||||
return `page-${pageName.toLowerCase()}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Separar componentes grandes
|
||||
if (id.includes('/src/components/')) {
|
||||
// MarkdownViewer es grande (usa react-markdown)
|
||||
if (id.includes('MarkdownViewer')) {
|
||||
return 'component-markdown';
|
||||
}
|
||||
}
|
||||
},
|
||||
assetFileNames: (assetInfo) => {
|
||||
const name = assetInfo.name || '';
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue