Feat: Implementar UI completa de RioCata - Sistema de catación de café
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m3s
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m3s
Agregar sistema completo de catación de café con las siguientes características: - Tipos TypeScript completos para sesiones, muestras, intensidades y notas - Composable useIndexedDB para gestión de sesión activa en cliente - Composable useCatacion con lógica de negocio para actualización de muestras - Componentes reutilizables: * SliderIntensidad: Slider dual para valores descriptivos (1-10) y afectivos (1-15) * SelectorFamilia: Selector jerárquico de familias de notas (3 niveles) * SelectorTazas: Selector de tazas (1-5) para uniformidad y defectos * ResumenMuestra: Header de accordion con progreso y estadísticas * FormularioMuestra: Formulario completo con 3 tabs (Fragancia/Aroma, Sabor, Impresión Global) - Páginas: * /cata: Gestión de sesiones (crear nueva o continuar existente) * /cata/sesion: Interfaz principal de catación con accordions y tabs - Tema dual: * Modo claro: Fondo blanco, texto negro, outlines azules * Modo oscuro: Fondo negro, texto verde terminal, estilo monospace - Diseño mobile-first responsive con CSS vanilla (sin @apply de Tailwind) - Configuración PWA con almacenamiento en IndexedDB
This commit is contained in:
346
nuxt4/app/composables/useCatacion.ts
Normal file
346
nuxt4/app/composables/useCatacion.ts
Normal file
@@ -0,0 +1,346 @@
|
||||
/**
|
||||
* Composable para manejar la lógica de negocio de catación
|
||||
*/
|
||||
|
||||
import type { SesionCatacion, Muestra, IntensidadValor } from '~/types/catacion'
|
||||
import { crearSesionVacia, calcularPuntajeFinal } from '~/types/catacion'
|
||||
|
||||
export type TabCatacion = 'fragancia-aroma' | 'sabor' | 'impresion-global'
|
||||
|
||||
export const useCatacion = () => {
|
||||
const { sesionActiva, cargando, error, guardar, actualizar, eliminar } = useIndexedDB()
|
||||
|
||||
// Estado de la UI
|
||||
const tabActiva = useState<TabCatacion>('tab-activa', () => 'fragancia-aroma')
|
||||
const accordionAbierto = useState<string[]>('accordion-abierto', () => [])
|
||||
|
||||
/**
|
||||
* Crea una nueva sesión de catación
|
||||
*/
|
||||
const crearNuevaSesion = async (catador: string, cantidadMuestras: number) => {
|
||||
try {
|
||||
const nuevaSesion = crearSesionVacia(catador, cantidadMuestras)
|
||||
await guardar(nuevaSesion)
|
||||
// Resetear estado de UI
|
||||
tabActiva.value = 'fragancia-aroma'
|
||||
accordionAbierto.value = []
|
||||
return nuevaSesion
|
||||
} catch (err) {
|
||||
console.error('Error al crear nueva sesión:', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Actualiza una muestra específica y guarda en IndexedDB
|
||||
*/
|
||||
const actualizarMuestra = async (muestraId: number, muestraActualizada: Partial<Muestra>) => {
|
||||
if (!sesionActiva.value) {
|
||||
throw new Error('No hay sesión activa')
|
||||
}
|
||||
|
||||
try {
|
||||
const sesionClonada = JSON.parse(JSON.stringify(sesionActiva.value)) as SesionCatacion
|
||||
const indexMuestra = sesionClonada.muestras.findIndex(m => m.muestraId === muestraId)
|
||||
|
||||
if (indexMuestra === -1) {
|
||||
throw new Error(`Muestra con ID ${muestraId} no encontrada`)
|
||||
}
|
||||
|
||||
// Actualizar muestra
|
||||
const muestraActual = sesionClonada.muestras[indexMuestra]
|
||||
if (!muestraActual) {
|
||||
throw new Error(`Muestra con ID ${muestraId} no encontrada`)
|
||||
}
|
||||
|
||||
sesionClonada.muestras[indexMuestra] = {
|
||||
...muestraActual,
|
||||
...muestraActualizada,
|
||||
} as Muestra
|
||||
|
||||
// Recalcular puntaje final si hay cambios en intensidades
|
||||
if (muestraActualizada.intensidades) {
|
||||
const muestraFinal = sesionClonada.muestras[indexMuestra]
|
||||
if (muestraFinal) {
|
||||
sesionClonada.muestras[indexMuestra]!.puntajeFinal = calcularPuntajeFinal(muestraFinal)
|
||||
}
|
||||
}
|
||||
|
||||
await actualizar(sesionClonada)
|
||||
} catch (err) {
|
||||
console.error('Error al actualizar muestra:', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Actualiza un valor de intensidad específico
|
||||
*/
|
||||
const actualizarIntensidad = async (
|
||||
muestraId: number,
|
||||
parametro: keyof Muestra['intensidades'],
|
||||
tipo: 'descriptiva' | 'afectiva',
|
||||
valor: number | null
|
||||
) => {
|
||||
if (!sesionActiva.value) {
|
||||
throw new Error('No hay sesión activa')
|
||||
}
|
||||
|
||||
try {
|
||||
const sesionClonada = JSON.parse(JSON.stringify(sesionActiva.value)) as SesionCatacion
|
||||
const indexMuestra = sesionClonada.muestras.findIndex(m => m.muestraId === muestraId)
|
||||
|
||||
if (indexMuestra === -1) {
|
||||
throw new Error(`Muestra con ID ${muestraId} no encontrada`)
|
||||
}
|
||||
|
||||
// Actualizar valor de intensidad
|
||||
const muestraActual = sesionClonada.muestras[indexMuestra]
|
||||
if (!muestraActual) {
|
||||
throw new Error(`Muestra con ID ${muestraId} no encontrada`)
|
||||
}
|
||||
|
||||
const intensidadActual = muestraActual.intensidades[parametro]
|
||||
sesionClonada.muestras[indexMuestra]!.intensidades[parametro] = {
|
||||
...intensidadActual,
|
||||
[tipo]: valor,
|
||||
}
|
||||
|
||||
// Recalcular puntaje final
|
||||
const muestraFinal = sesionClonada.muestras[indexMuestra]
|
||||
if (muestraFinal) {
|
||||
sesionClonada.muestras[indexMuestra]!.puntajeFinal = calcularPuntajeFinal(muestraFinal)
|
||||
}
|
||||
|
||||
await actualizar(sesionClonada)
|
||||
} catch (err) {
|
||||
console.error('Error al actualizar intensidad:', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Actualiza el nombre de una muestra
|
||||
*/
|
||||
const actualizarNombreMuestra = async (muestraId: number, nombre: string) => {
|
||||
await actualizarMuestra(muestraId, { nombre })
|
||||
}
|
||||
|
||||
/**
|
||||
* Actualiza las notas de fragancia/aroma
|
||||
*/
|
||||
const actualizarFraganciaAroma = async (
|
||||
muestraId: number,
|
||||
categoria: string | null,
|
||||
subcategoria: string | null,
|
||||
notaEspecifica: string | null
|
||||
) => {
|
||||
await actualizarMuestra(muestraId, {
|
||||
fraganciaAromaNotas: { categoria: categoria as any, subcategoria, notaEspecifica },
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Actualiza las notas de sabor
|
||||
*/
|
||||
const actualizarSabor = async (
|
||||
muestraId: number,
|
||||
categoria: string | null,
|
||||
subcategoria: string | null,
|
||||
notaEspecifica: string | null
|
||||
) => {
|
||||
await actualizarMuestra(muestraId, {
|
||||
saborNotas: { categoria: categoria as any, subcategoria, notaEspecifica },
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Actualiza tazas no uniformes
|
||||
*/
|
||||
const actualizarTazasNoUniformes = async (muestraId: number, tazas: number[]) => {
|
||||
await actualizarMuestra(muestraId, { tazasNoUniformes: tazas })
|
||||
}
|
||||
|
||||
/**
|
||||
* Actualiza tazas defectuosas
|
||||
*/
|
||||
const actualizarTazasDefectuosas = async (muestraId: number, tazas: number[]) => {
|
||||
await actualizarMuestra(muestraId, { tazasDefectuosas: tazas })
|
||||
}
|
||||
|
||||
/**
|
||||
* Actualiza el tipo de defecto
|
||||
*/
|
||||
const actualizarDefecto = async (muestraId: number, defecto: string | null) => {
|
||||
await actualizarMuestra(muestraId, { defecto: defecto as any })
|
||||
}
|
||||
|
||||
/**
|
||||
* Actualiza sensaciones en boca
|
||||
*/
|
||||
const actualizarSensacionBoca = async (muestraId: number, sensaciones: string[]) => {
|
||||
await actualizarMuestra(muestraId, { sensacionEnBoca: sensaciones as any })
|
||||
}
|
||||
|
||||
/**
|
||||
* Actualiza gustos predominantes (máximo 2)
|
||||
*/
|
||||
const actualizarGustosPredominantes = async (muestraId: number, gustos: string[]) => {
|
||||
if (gustos.length > 2) {
|
||||
throw new Error('Máximo 2 gustos predominantes permitidos')
|
||||
}
|
||||
if (gustos.length < 1) {
|
||||
throw new Error('Mínimo 1 gusto predominante requerido')
|
||||
}
|
||||
await actualizarMuestra(muestraId, { gustosPredominantes: gustos as any })
|
||||
}
|
||||
|
||||
/**
|
||||
* Actualiza notas adicionales
|
||||
*/
|
||||
const actualizarOtrasNotas = async (muestraId: number, notas: string) => {
|
||||
await actualizarMuestra(muestraId, { otrasNotas: notas })
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene una muestra específica
|
||||
*/
|
||||
const obtenerMuestra = (muestraId: number): Muestra | null => {
|
||||
if (!sesionActiva.value) return null
|
||||
return sesionActiva.value.muestras.find(m => m.muestraId === muestraId) || null
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica si una muestra está completa (tiene todos los campos requeridos)
|
||||
*/
|
||||
const esMuestraCompleta = (muestra: Muestra): boolean => {
|
||||
// Verificar intensidades afectivas (son las que cuentan para el puntaje)
|
||||
const intensidadesCompletas = Object.values(muestra.intensidades).every(
|
||||
(intensidad) => intensidad.afectiva !== null
|
||||
)
|
||||
|
||||
// Verificar notas de fragancia/aroma
|
||||
const fraganciaAromaCompleta = muestra.fraganciaAromaNotas.categoria !== null
|
||||
|
||||
// Verificar notas de sabor
|
||||
const saborCompleto = muestra.saborNotas.categoria !== null
|
||||
|
||||
// Verificar gustos predominantes (mínimo 1, máximo 2)
|
||||
const gustosCompletos = muestra.gustosPredominantes.length >= 1 && muestra.gustosPredominantes.length <= 2
|
||||
|
||||
return intensidadesCompletas && fraganciaAromaCompleta && saborCompleto && gustosCompletos
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene el porcentaje de completitud de una muestra
|
||||
*/
|
||||
const porcentajeCompletitud = (muestra: Muestra): number => {
|
||||
let total = 0
|
||||
let completados = 0
|
||||
|
||||
// Intensidades afectivas (8 campos)
|
||||
total += 8
|
||||
completados += Object.values(muestra.intensidades).filter(
|
||||
(intensidad) => intensidad.afectiva !== null
|
||||
).length
|
||||
|
||||
// Intensidades descriptivas (8 campos)
|
||||
total += 8
|
||||
completados += Object.values(muestra.intensidades).filter(
|
||||
(intensidad) => intensidad.descriptiva !== null
|
||||
).length
|
||||
|
||||
// Notas fragancia/aroma
|
||||
total += 1
|
||||
if (muestra.fraganciaAromaNotas.categoria) completados += 1
|
||||
|
||||
// Notas sabor
|
||||
total += 1
|
||||
if (muestra.saborNotas.categoria) completados += 1
|
||||
|
||||
// Gustos predominantes
|
||||
total += 1
|
||||
if (muestra.gustosPredominantes.length >= 1) completados += 1
|
||||
|
||||
return Math.round((completados / total) * 100)
|
||||
}
|
||||
|
||||
/**
|
||||
* Elimina la sesión actual
|
||||
*/
|
||||
const eliminarSesionActual = async () => {
|
||||
try {
|
||||
await eliminar()
|
||||
tabActiva.value = 'fragancia-aroma'
|
||||
accordionAbierto.value = []
|
||||
} catch (err) {
|
||||
console.error('Error al eliminar sesión:', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exporta la sesión actual como JSON
|
||||
*/
|
||||
const exportarSesion = (): string | null => {
|
||||
if (!sesionActiva.value) return null
|
||||
return JSON.stringify(sesionActiva.value, null, 2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcula estadísticas generales de la sesión
|
||||
*/
|
||||
const estadisticasSesion = computed(() => {
|
||||
if (!sesionActiva.value) return null
|
||||
|
||||
const muestras = sesionActiva.value.muestras
|
||||
const totalMuestras = muestras.length
|
||||
const muestrasCompletas = muestras.filter(esMuestraCompleta).length
|
||||
const promedioCompletitud = muestras.reduce((acc, m) => acc + porcentajeCompletitud(m), 0) / totalMuestras
|
||||
const puntajePromedio = muestras.reduce((acc, m) => acc + m.puntajeFinal, 0) / totalMuestras
|
||||
|
||||
return {
|
||||
totalMuestras,
|
||||
muestrasCompletas,
|
||||
porcentajeCompletitud: Math.round(promedioCompletitud),
|
||||
puntajePromedio: Math.round(puntajePromedio),
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
// Estado de la sesión
|
||||
sesionActiva,
|
||||
cargando,
|
||||
error,
|
||||
|
||||
// Estado de la UI
|
||||
tabActiva,
|
||||
accordionAbierto,
|
||||
|
||||
// Estadísticas
|
||||
estadisticasSesion,
|
||||
|
||||
// Métodos CRUD de sesión
|
||||
crearNuevaSesion,
|
||||
eliminarSesionActual,
|
||||
exportarSesion,
|
||||
|
||||
// Métodos de actualización de muestras
|
||||
actualizarMuestra,
|
||||
actualizarIntensidad,
|
||||
actualizarNombreMuestra,
|
||||
actualizarFraganciaAroma,
|
||||
actualizarSabor,
|
||||
actualizarTazasNoUniformes,
|
||||
actualizarTazasDefectuosas,
|
||||
actualizarDefecto,
|
||||
actualizarSensacionBoca,
|
||||
actualizarGustosPredominantes,
|
||||
actualizarOtrasNotas,
|
||||
|
||||
// Utilidades
|
||||
obtenerMuestra,
|
||||
esMuestraCompleta,
|
||||
porcentajeCompletitud,
|
||||
}
|
||||
}
|
||||
288
nuxt4/app/composables/useIndexedDB.ts
Normal file
288
nuxt4/app/composables/useIndexedDB.ts
Normal file
@@ -0,0 +1,288 @@
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
const sesion = sesiones.length > 0 ? sesiones[0] : null
|
||||
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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user