# 📚 Sistema de Stores - Analítica Núcleo Este directorio contiene el sistema de gestión de estado de la aplicación usando **Pinia** y **localStorage** para caché persistente. ## 📋 Índice - [Arquitectura General](#-arquitectura-general) - [Stores Disponibles](#-stores-disponibles) - [Metadata Store](#-metadata-store) - [Table Data Factory](#-table-data-factory) - [Flujo de Datos](#-flujo-de-datos) - [Uso en Componentes](#-uso-en-componentes) - [API Reference](#-api-reference) - [Cache y Persistencia](#-cache-y-persistencia) - [Ejemplos Avanzados](#-ejemplos-avanzados) --- ## 🏗️ Arquitectura General El sistema de stores se compone de dos partes principales: ``` app/stores/ ├── metadata.ts # Store de metadatos de tablas ├── tableDataFactory.ts # Factory para crear stores de datos └── README.md # Este archivo ``` ### Flujo de Datos ```mermaid graph TB A[App Startup] --> B[Metadata Store] B --> C{¿Tiene cache?} C -->|Sí| D[Carga desde localStorage] C -->|No| E[Fetch desde API] E --> F[/api/metadata] F --> G[Guarda en localStorage] D --> H[Metadata disponible] G --> H H --> I[Plugin Auto-registro] I --> J[Crea stores por tabla] K[Usuario selecciona tabla] --> L{¿Store existe?} L -->|Sí| M{¿Tiene cache?} L -->|No| N[Crea store] N --> M M -->|Sí| O[Carga desde cache] M -->|No| P[Fetch desde API] P --> Q[/api/data/:table] Q --> R[Guarda en cache] O --> S[Datos disponibles] R --> S ``` --- ## 🗂️ Stores Disponibles ### 1. **Metadata Store** (`metadata.ts`) Store global que gestiona la información de todas las tablas disponibles. **Responsabilidades:** - Mantener lista de tablas disponibles - Información estructural (columnas, claves primarias, etc.) - Estadísticas de cada tabla - Cache persistente de metadatos ### 2. **Table Data Stores** (Factory pattern) Stores dinámicos, uno por cada tabla de la base de datos. **Responsabilidades:** - Almacenar registros de una tabla específica - Gestionar estado de carga - Cache persistente por tabla - Operaciones de filtrado y búsqueda --- ## 🔍 Metadata Store ### Estructura de Datos ```typescript interface TableMetadata { table: string // Nombre de la tabla rowCount: number // Total de registros primaryKey: string // Clave primaria approxSizeBytes: number // Tamaño aproximado en bytes columns: string[] // Lista de columnas createdAtRange?: { from: string // Fecha de creación más antigua to: string // Fecha de creación más reciente } lastRefreshed?: string // Última actualización de metadatos sampleRow?: Record // Registro de ejemplo } ``` ### Estado del Store ```typescript { metadata: TableMetadata[] // Array de metadatos por tabla loading: boolean // Estado de carga error: string | null // Mensaje de error lastUpdated: string | null // Timestamp última actualización initialized: boolean // Si el store está inicializado } ``` ### Getters Disponibles | Getter | Tipo | Descripción | |--------|------|-------------| | `allTables` | `TableMetadata[]` | Todas las tablas disponibles | | `getTableMetadata(name)` | `TableMetadata \| undefined` | Metadatos de una tabla específica | | `totalTables` | `number` | Cantidad total de tablas | | `totalRecords` | `number` | Suma de registros de todas las tablas | | `tableNames` | `string[]` | Lista de nombres de tablas | | `hasMetadata` | `boolean` | Si hay metadatos cargados | | `isLoading` | `boolean` | Si está cargando actualmente | | `hasError` | `boolean` | Si hay un error | | `formattedLastUpdated` | `string` | Fecha formateada de última actualización | | `isStale` | `boolean` | Si los datos tienen > 5 minutos | ### Actions Disponibles | Action | Parámetros | Descripción | |--------|------------|-------------| | `loadMetadata(force?)` | `force: boolean` | Carga lazy, solo si necesario | | `refreshMetadata()` | - | Actualización forzada | | `clearMetadata()` | - | Limpia cache y datos | | `initialize()` | - | Inicializa el store (cache + fetch) | ### Fuente de Datos **Endpoint:** `GET /api/metadata` **Respuesta:** ```json [ { "table": "usuarios", "rowCount": 1523, "primaryKey": "id", "approxSizeBytes": 245760, "columns": ["id", "nombre", "email", "created_at"], "createdAtRange": { "from": "2024-01-01T00:00:00Z", "to": "2025-01-20T15:30:00Z" } }, // ... más tablas ] ``` ### Uso Básico ```typescript import { useMetadataStore } from '~/stores/metadata' // En un componente const metadataStore = useMetadataStore() // Inicializar (carga desde cache o API) await metadataStore.initialize() // Acceder a los datos console.log(metadataStore.allTables) console.log(metadataStore.totalTables) // Obtener metadatos de una tabla específica const usuariosMetadata = metadataStore.getTableMetadata('usuarios') // Refrescar metadatos await metadataStore.refreshMetadata() // Verificar frescura de datos if (metadataStore.isStale) { console.log('Los metadatos tienen más de 5 minutos') } ``` --- ## 🏭 Table Data Factory Sistema factory que crea stores dinámicos para cada tabla. ### Estructura de Datos ```typescript interface TableDataState> { data: T[] // Registros cacheados loading: boolean // Estado de carga error: string | null // Mensaje de error lastUpdated: string | null // Timestamp última actualización initialized: boolean // Si el store está inicializado limit: number // Límite de registros } ``` ### Getters Disponibles | Getter | Tipo | Descripción | |--------|------|-------------| | `allRecords` | `T[]` | Todos los registros cacheados | | `hasData` | `boolean` | Si hay datos disponibles | | `isLoading` | `boolean` | Si está cargando actualmente | | `hasError` | `boolean` | Si hay un error | | `recordCount` | `number` | Cantidad de registros | | `formattedLastUpdated` | `string` | Fecha formateada | | `isStale` | `boolean` | Si los datos tienen > 5 minutos | ### Actions Disponibles | Action | Parámetros | Descripción | |--------|------------|-------------| | `loadData(force?)` | `force: boolean` | Carga lazy | | `refreshData()` | - | Actualización forzada | | `clearData()` | - | Limpia cache y datos | | `initialize()` | - | Inicializa (cache + fetch) | | `getRecord(id)` | `id: string \| number` | Obtiene un registro por ID | | `filterRecords(predicate)` | `predicate: (record: T) => boolean` | Filtra registros | ### Fuente de Datos **Endpoint:** `GET /api/data/:tableName?limit=100` **Respuesta:** ```json { "table": "usuarios", "count": 100, "limit": 100, "records": [ { "id": 1, "nombre": "Juan Pérez", "email": "juan@example.com", "created_at": "2024-01-15T10:30:00Z" }, // ... más registros ] } ``` ### Creación de Stores #### Método 1: Usando la Factory Directamente ```typescript import { createTableDataStore } from '~/stores/tableDataFactory' // Crear un store para la tabla "usuarios" const useUsuariosStore = createTableDataStore('usuarios', 100) // Usar el store const usuariosStore = useUsuariosStore() await usuariosStore.initialize() console.log(usuariosStore.allRecords) ``` #### Método 2: Usando el Helper ```typescript import { useTableDataStore } from '~/stores/tableDataFactory' // Crear y usar el store en un solo paso const usuariosStore = useTableDataStore('usuarios', 100) await usuariosStore.initialize() ``` #### Método 3: Usando el Plugin (Recomendado) ```typescript // El plugin auto-registra todos los stores const { $getTableStore } = useNuxtApp() // Obtener un store ya registrado const usuariosStore = $getTableStore('usuarios') if (usuariosStore) { await usuariosStore.initialize() console.log(usuariosStore.allRecords) } ``` --- ## 🔄 Flujo de Datos ### Inicialización de la App ```typescript // 1. App.vue o plugin se ejecuta onMounted(async () => { // 2. Metadata store se inicializa const metadataStore = useMetadataStore() await metadataStore.initialize() // 3. Plugin auto-registra stores (tableStores.client.ts) // Se crean stores para cada tabla encontrada en metadataStore.allTables // 4. Los stores están listos pero NO han cargado datos aún // Esto es lazy loading - solo cargan cuando se necesitan }) ``` ### Carga de Datos de una Tabla ```typescript // Usuario selecciona tabla "usuarios" const selectTable = async (tableName: string) => { // 1. Obtener o crear store const store = $getTableStore(tableName) || useTableDataStore(tableName) // 2. Inicializar (intenta cache primero) await store.initialize() // Flujo interno de initialize(): // ├─ loadFromCache() // │ └─ Lee localStorage['table-data-usuarios'] // │ ├─ Si existe → usa datos cacheados // │ └─ Si no existe → continúa // └─ loadData() // └─ fetchData() // └─ $fetch('/api/data/usuarios?limit=100') // └─ Guarda en localStorage // └─ Actualiza store // 3. Datos disponibles console.log(store.allRecords) } ``` ### Actualización Manual ```typescript // Usuario hace click en "Actualizar datos" const refreshTable = async () => { // 1. Ejecutar refreshData() await store.refreshData() // Flujo interno de refreshData(): // └─ fetchData() // ├─ loading = true // ├─ $fetch('/api/data/usuarios?limit=100') // ├─ Actualiza store.data // ├─ Actualiza lastUpdated // ├─ Guarda en localStorage // └─ loading = false } ``` --- ## 💻 Uso en Componentes ### Ejemplo Completo: Explorador de Tablas ```vue ``` ### Ejemplo: Búsqueda y Filtrado ```typescript import { useTableDataStore } from '~/stores/tableDataFactory' const usuariosStore = useTableDataStore('usuarios') await usuariosStore.initialize() // Filtrar usuarios activos const usuariosActivos = usuariosStore.filterRecords( user => user.activo === true ) // Buscar usuario por ID const usuario = usuariosStore.getRecord(123) // Búsqueda personalizada const usuariosPorEmail = usuariosStore.filterRecords( user => user.email.includes('@gmail.com') ) console.log(`Encontrados ${usuariosPorEmail.length} usuarios con Gmail`) ``` --- ## 📖 API Reference ### Metadata Store API ```typescript interface MetadataStore { // State metadata: TableMetadata[] loading: boolean error: string | null lastUpdated: string | null initialized: boolean // Getters allTables: TableMetadata[] getTableMetadata(name: string): TableMetadata | undefined totalTables: number totalRecords: number tableNames: string[] hasMetadata: boolean isLoading: boolean hasError: boolean formattedLastUpdated: string isStale: boolean // Actions loadMetadata(force?: boolean): Promise refreshMetadata(): Promise clearMetadata(): void initialize(): Promise } ``` ### Table Data Store API ```typescript interface TableDataStore { // State data: T[] loading: boolean error: string | null lastUpdated: string | null initialized: boolean limit: number // Getters allRecords: T[] hasData: boolean isLoading: boolean hasError: boolean recordCount: number formattedLastUpdated: string isStale: boolean // Actions loadData(force?: boolean): Promise refreshData(): Promise clearData(): void initialize(): Promise getRecord(id: string | number): T | undefined filterRecords(predicate: (record: T) => boolean): T[] } ``` --- ## 💾 Cache y Persistencia ### Estrategia de Cache El sistema usa **localStorage** para persistir datos: ```typescript // Estructura de cache en localStorage { // Metadatos "metadata-cache": { metadata: TableMetadata[], lastUpdated: "2025-01-20T10:30:00Z" }, // Datos de tabla usuarios "table-data-usuarios": { data: [...registros], lastUpdated: "2025-01-20T10:35:00Z", limit: 100 }, // Datos de tabla productos "table-data-productos": { data: [...registros], lastUpdated: "2025-01-20T10:40:00Z", limit: 100 } } ``` ### Políticas de Cache 1. **Freshness Check**: Los datos se consideran "stale" después de 5 minutos 2. **Lazy Loading**: Los stores solo cargan cuando se accede a ellos 3. **Cache-First**: Siempre intenta cargar desde cache primero 4. **Auto-Refresh**: El usuario controla manualmente cuándo refrescar 5. **Offline Support**: Datos disponibles incluso sin conexión ### Limpieza de Cache ```typescript // Limpiar cache de metadatos const metadataStore = useMetadataStore() metadataStore.clearMetadata() // Limpiar cache de una tabla const usuariosStore = useTableDataStore('usuarios') usuariosStore.clearData() // Limpiar TODO el cache (localStorage) if (process.client) { localStorage.clear() } ``` --- ## 🎯 Ejemplos Avanzados ### Sincronización Automática ```typescript // Auto-refrescar cada 5 minutos si los datos están stale const autoRefresh = () => { const store = useTableDataStore('usuarios') setInterval(async () => { if (store.isStale && !store.isLoading) { console.log('Refrescando datos automáticamente...') await store.refreshData() } }, 5 * 60 * 1000) // 5 minutos } ``` ### Validación de Datos ```typescript // Verificar integridad de datos cacheados const validateCache = async (tableName: string) => { const store = useTableDataStore(tableName) const metadata = metadataStore.getTableMetadata(tableName) if (!metadata) return false // Verificar que el número de registros sea razonable if (store.recordCount > metadata.rowCount) { console.warn('Cache corrupto, limpiando...') store.clearData() await store.refreshData() return false } return true } ``` ### Composable Personalizado ```typescript // composables/useTableData.ts export function useTableData(tableName: string) { const store = useTableDataStore(tableName) const initialized = ref(false) onMounted(async () => { await store.initialize() initialized.value = true }) // Auto-refrescar si stale watch(() => store.isStale, async (isStale) => { if (isStale && initialized.value) { await store.refreshData() } }) return { data: computed(() => store.allRecords), loading: computed(() => store.isLoading), error: computed(() => store.error), refresh: () => store.refreshData() } } ``` ### TypeScript Tipado ```typescript // Definir tipos para tus tablas interface Usuario { id: number nombre: string email: string activo: boolean created_at: string } interface Producto { id: number nombre: string precio: number stock: number } // Usar stores tipados const usuariosStore = useTableDataStore('usuarios') const productosStore = useTableDataStore('productos') // TypeScript ahora conoce la estructura usuariosStore.allRecords.forEach(user => { console.log(user.email) // ✅ TypeScript sabe que existe // console.log(user.foo) // ❌ Error: Property 'foo' does not exist }) ``` --- ## 🐛 Debugging ### Inspeccionar Estado ```typescript // En DevTools Console import { useMetadataStore } from '~/stores/metadata' import { useTableDataStore } from '~/stores/tableDataFactory' const metadataStore = useMetadataStore() const usuariosStore = useTableDataStore('usuarios') // Ver estado completo console.log('Metadata:', metadataStore.$state) console.log('Usuarios:', usuariosStore.$state) // Ver cache console.log('Cache:', { metadata: localStorage.getItem('metadata-cache'), usuarios: localStorage.getItem('table-data-usuarios') }) ``` ### Logs Detallados Descomenta los `console.log` en los stores para ver el flujo completo: ```typescript // En tableDataFactory.ts línea 144 console.log(`[${tableName}] Fetching data...`) // En tableDataFactory.ts línea 158 console.log(`[${tableName}] Data fetched: ${this.recordCount} records`) // En tableDataFactory.ts línea 164 console.log(`[${tableName}] Saved to cache`) ``` --- ## 📝 Notas Importantes ### ⚠️ Limitaciones 1. **Límite de localStorage**: ~5-10MB por dominio - Solución: Implementar estrategia LRU si se excede 2. **Límite de registros**: Por defecto 100 registros por tabla - Modificable en la factory: `useTableDataStore('tabla', 500)` 3. **Sin paginación**: Todos los registros se cargan de una vez - Para tablas grandes, considerar implementar paginación 4. **Sin sincronización real-time**: Los datos se actualizan manualmente - Para real-time, considerar WebSockets ### 🚀 Performance Tips 1. **Lazy Loading**: Los stores no cargan datos hasta que se necesitan 2. **Cache-First**: Usa cache para respuesta instantánea 3. **Selectores Computados**: Usa `computed()` para filtros complejos 4. **Debounce**: Para búsquedas, usa debounce para evitar renders excesivos ### 🔒 Seguridad - Los datos en localStorage NO están encriptados - No almacenes información sensible (contraseñas, tokens) - El API backend debe validar permisos y autenticación --- ## 📚 Referencias - [Pinia Documentation](https://pinia.vuejs.org/) - [Nuxt 4 Documentation](https://nuxt.com/) - [LocalStorage API](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) --- **Última actualización:** 2025-01-20 **Versión:** 1.0.0 **Autor:** Equipo Núcleo