Files
cataRio/nuxt4/app/composables/useIndexedDB.ts
josedario87 e69780c321
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m3s
Fix: Agregar migración de datos para sesiones antiguas
- Agregar función migrateSesion() que convierte el formato antiguo al nuevo
- Formato antiguo: { categoria: string, subcategoria: string }
- Formato nuevo: { categorias: string[], subcategorias: string[] }
- Migración automática al cargar sesiones desde IndexedDB
- Maneja casos donde categorias/subcategorias son null o undefined
2025-10-18 03:00:00 -06:00

354 lines
9.5 KiB
TypeScript

/**
* Composable para manejo de sesiones de catación en IndexedDB
* Maneja solo una sesión activa a la vez
*/
import type { SesionCatacion } from '~/types/catacion'
const DB_NAME = 'RioCataDB'
const DB_VERSION = 1
const STORE_NAME = 'sesiones'
const ACTIVE_SESSION_KEY = 'sesion-activa'
/**
* Inicializa y retorna la base de datos IndexedDB
*/
function openDatabase(): Promise<IDBDatabase> {
return new Promise((resolve, reject) => {
// Verificar si estamos en el cliente
if (typeof window === 'undefined' || !window.indexedDB) {
reject(new Error('IndexedDB no está disponible en este entorno'))
return
}
const request = window.indexedDB.open(DB_NAME, DB_VERSION)
request.onerror = () => {
reject(new Error('Error al abrir la base de datos'))
}
request.onsuccess = () => {
resolve(request.result)
}
request.onupgradeneeded = (event) => {
const db = (event.target as IDBOpenDBRequest).result
// Crear object store si no existe
if (!db.objectStoreNames.contains(STORE_NAME)) {
const objectStore = db.createObjectStore(STORE_NAME, { keyPath: 'sessionId' })
// Crear índice por fecha para consultas futuras
objectStore.createIndex('fecha', 'fecha', { unique: false })
}
}
})
}
/**
* Guarda o actualiza la sesión activa en IndexedDB
*/
async function saveSession(sesion: SesionCatacion): Promise<void> {
try {
const db = await openDatabase()
const transaction = db.transaction([STORE_NAME], 'readwrite')
const objectStore = transaction.objectStore(STORE_NAME)
// Eliminar todas las sesiones anteriores
await new Promise<void>((resolve, reject) => {
const clearRequest = objectStore.clear()
clearRequest.onsuccess = () => resolve()
clearRequest.onerror = () => reject(new Error('Error al limpiar sesiones anteriores'))
})
// Guardar la nueva sesión
await new Promise<void>((resolve, reject) => {
const addRequest = objectStore.add(sesion)
addRequest.onsuccess = () => resolve()
addRequest.onerror = () => reject(new Error('Error al guardar la sesión'))
})
db.close()
} catch (error) {
console.error('Error en saveSession:', error)
throw error
}
}
/**
* Migra una sesión del formato antiguo al nuevo
*/
function migrateSesion(sesion: any): SesionCatacion {
// Migrar cada muestra
const muestrasMigradas = sesion.muestras.map((muestra: any) => {
const muestraMigrada = { ...muestra }
// Migrar fraganciaAromaNotas
if (muestra.fraganciaAromaNotas) {
const notas = muestra.fraganciaAromaNotas
// Si tiene el formato antiguo (categoria como string)
if ('categoria' in notas && typeof notas.categoria === 'string') {
muestraMigrada.fraganciaAromaNotas = {
categorias: notas.categoria ? [notas.categoria] : [],
subcategorias: notas.subcategoria ? [notas.subcategoria] : [],
notaEspecifica: notas.notaEspecifica,
}
}
// Si ya tiene el formato nuevo pero es null
else if (notas.categorias === null || notas.categorias === undefined) {
muestraMigrada.fraganciaAromaNotas = {
categorias: [],
subcategorias: [],
notaEspecifica: notas.notaEspecifica || null,
}
}
}
// Migrar saborNotas
if (muestra.saborNotas) {
const notas = muestra.saborNotas
// Si tiene el formato antiguo (categoria como string)
if ('categoria' in notas && typeof notas.categoria === 'string') {
muestraMigrada.saborNotas = {
categorias: notas.categoria ? [notas.categoria] : [],
subcategorias: notas.subcategoria ? [notas.subcategoria] : [],
notaEspecifica: notas.notaEspecifica,
}
}
// Si ya tiene el formato nuevo pero es null
else if (notas.categorias === null || notas.categorias === undefined) {
muestraMigrada.saborNotas = {
categorias: [],
subcategorias: [],
notaEspecifica: notas.notaEspecifica || null,
}
}
}
return muestraMigrada
})
return {
...sesion,
muestras: muestrasMigradas,
}
}
/**
* Carga la sesión activa desde IndexedDB
*/
async function loadSession(): Promise<SesionCatacion | null> {
try {
const db = await openDatabase()
const transaction = db.transaction([STORE_NAME], 'readonly')
const objectStore = transaction.objectStore(STORE_NAME)
const sesion = await new Promise<SesionCatacion | null>((resolve, reject) => {
// Obtener todas las claves
const getAllRequest = objectStore.getAll()
getAllRequest.onsuccess = () => {
const sesiones = getAllRequest.result as SesionCatacion[]
// Retornar la primera sesión (debería haber solo una)
let sesion = sesiones.length > 0 ? sesiones[0] : null
// Migrar sesión si es necesario
if (sesion) {
sesion = migrateSesion(sesion)
}
resolve(sesion || null)
}
getAllRequest.onerror = () => {
reject(new Error('Error al cargar la sesión'))
}
})
db.close()
return sesion
} catch (error) {
console.error('Error en loadSession:', error)
return null
}
}
/**
* Verifica si existe una sesión activa
*/
async function hasActiveSession(): Promise<boolean> {
try {
const sesion = await loadSession()
return sesion !== null
} catch (error) {
console.error('Error en hasActiveSession:', error)
return false
}
}
/**
* Elimina la sesión activa
*/
async function deleteSession(): Promise<void> {
try {
const db = await openDatabase()
const transaction = db.transaction([STORE_NAME], 'readwrite')
const objectStore = transaction.objectStore(STORE_NAME)
await new Promise<void>((resolve, reject) => {
const clearRequest = objectStore.clear()
clearRequest.onsuccess = () => resolve()
clearRequest.onerror = () => reject(new Error('Error al eliminar la sesión'))
})
db.close()
} catch (error) {
console.error('Error en deleteSession:', error)
throw error
}
}
/**
* Actualiza una sesión existente (timestamp de modificación)
*/
async function updateSession(sesion: SesionCatacion): Promise<void> {
try {
// Actualizar timestamp de modificación
sesion.modificadoEn = Date.now()
const db = await openDatabase()
const transaction = db.transaction([STORE_NAME], 'readwrite')
const objectStore = transaction.objectStore(STORE_NAME)
await new Promise<void>((resolve, reject) => {
const putRequest = objectStore.put(sesion)
putRequest.onsuccess = () => resolve()
putRequest.onerror = () => reject(new Error('Error al actualizar la sesión'))
})
db.close()
} catch (error) {
console.error('Error en updateSession:', error)
throw error
}
}
/**
* Composable principal para manejo de IndexedDB
*/
export const useIndexedDB = () => {
// Estado reactivo de la sesión
const sesionActiva = useState<SesionCatacion | null>('sesion-activa', () => null)
const cargando = useState<boolean>('sesion-cargando', () => false)
const error = useState<Error | null>('sesion-error', () => null)
/**
* Inicializa el composable cargando la sesión activa
*/
const inicializar = async () => {
if (import.meta.server) {
// No hacer nada en el servidor
return
}
try {
cargando.value = true
error.value = null
const sesion = await loadSession()
sesionActiva.value = sesion
} catch (err) {
error.value = err as Error
console.error('Error al inicializar IndexedDB:', err)
} finally {
cargando.value = false
}
}
/**
* Guarda la sesión activa
*/
const guardar = async (sesion: SesionCatacion) => {
if (import.meta.server) {
console.warn('No se puede guardar en IndexedDB desde el servidor')
return
}
try {
cargando.value = true
error.value = null
await saveSession(sesion)
sesionActiva.value = sesion
} catch (err) {
error.value = err as Error
console.error('Error al guardar sesión:', err)
throw err
} finally {
cargando.value = false
}
}
/**
* Actualiza la sesión activa
*/
const actualizar = async (sesion: SesionCatacion) => {
if (import.meta.server) {
console.warn('No se puede actualizar en IndexedDB desde el servidor')
return
}
try {
cargando.value = true
error.value = null
await updateSession(sesion)
sesionActiva.value = sesion
} catch (err) {
error.value = err as Error
console.error('Error al actualizar sesión:', err)
throw err
} finally {
cargando.value = false
}
}
/**
* Elimina la sesión activa
*/
const eliminar = async () => {
if (import.meta.server) {
console.warn('No se puede eliminar de IndexedDB desde el servidor')
return
}
try {
cargando.value = true
error.value = null
await deleteSession()
sesionActiva.value = null
} catch (err) {
error.value = err as Error
console.error('Error al eliminar sesión:', err)
throw err
} finally {
cargando.value = false
}
}
/**
* Verifica si hay una sesión activa
*/
const tieneSecion = computed(() => sesionActiva.value !== null)
return {
// Estado
sesionActiva: readonly(sesionActiva),
cargando: readonly(cargando),
error: readonly(error),
tieneSecion,
// Métodos
inicializar,
guardar,
actualizar,
eliminar,
}
}