# 📚 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<string, unknown> // 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<T = Record<string, unknown>> {
  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
<template>
  <div>
    <!-- Selector de tabla -->
    <select v-model="selectedTable" @change="onTableChange">
      <option v-for="table in metadataStore.tableNames" :key="table">
        {{ table }}
      </option>
    </select>

    <!-- Información de cache -->
    <div v-if="currentStore">
      <p>Última actualización: {{ currentStore.formattedLastUpdated }}</p>
      <p v-if="currentStore.isStale" class="warning">
        ⚠️ Datos desactualizados
      </p>
      <button @click="refreshData" :disabled="currentStore.isLoading">
        {{ currentStore.isLoading ? 'Actualizando...' : 'Actualizar' }}
      </button>
    </div>

    <!-- Tabla de datos -->
    <div v-if="currentStore?.hasData">
      <p>{{ currentStore.recordCount }} registros</p>
      <table>
        <thead>
          <tr>
            <th v-for="col in columns" :key="col">{{ col }}</th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="record in currentStore.allRecords" :key="record.id">
            <td v-for="col in columns" :key="col">
              {{ record[col] }}
            </td>
          </tr>
        </tbody>
      </table>
    </div>

    <!-- Estados -->
    <div v-else-if="currentStore?.isLoading">Cargando...</div>
    <div v-else-if="currentStore?.hasError">
      Error: {{ currentStore.error }}
    </div>
    <div v-else>Selecciona una tabla</div>
  </div>
</template>

<script setup lang="ts">
import { useMetadataStore } from '~/stores/metadata'
import { useTableDataStore } from '~/stores/tableDataFactory'

const metadataStore = useMetadataStore()
const selectedTable = ref('')
const currentStore = ref<ReturnType<typeof useTableDataStore> | null>(null)

// Columnas de la tabla actual
const columns = computed(() => {
  if (!currentStore.value?.hasData) return []
  const firstRecord = currentStore.value.allRecords[0]
  return Object.keys(firstRecord)
})

// Cambio de tabla
async function onTableChange() {
  if (!selectedTable.value) return

  // Obtener store de la tabla
  currentStore.value = useTableDataStore(selectedTable.value)

  // Inicializar (usa cache si existe)
  await currentStore.value.initialize()
}

// Refrescar datos
async function refreshData() {
  if (!currentStore.value) return
  await currentStore.value.refreshData()
}

// Inicializar metadatos al montar
onMounted(async () => {
  await metadataStore.initialize()

  // Auto-seleccionar primera tabla
  if (metadataStore.tableNames.length > 0) {
    selectedTable.value = metadataStore.tableNames[0]
    await onTableChange()
  }
})
</script>
```

### 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<void>
  refreshMetadata(): Promise<void>
  clearMetadata(): void
  initialize(): Promise<void>
}
```

### Table Data Store API

```typescript
interface TableDataStore<T> {
  // 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<void>
  refreshData(): Promise<void>
  clearData(): void
  initialize(): Promise<void>
  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<Usuario>('usuarios')
const productosStore = useTableDataStore<Producto>('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