/** * 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 = 'organoleptica' | 'descriptiva-afectiva' | 'defectos' | 'impresion-global' export const useCatacion = () => { const { sesionActiva, cargando, error, guardar, actualizar, eliminar } = useIndexedDB() // Estado de la UI const tabActiva = useState('tab-activa', () => 'organoleptica') const accordionAbierto = useState('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 = 'organoleptica' 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) => { if (!sesionActiva.value) { throw new Error('No hay sesión activa') } try { const muestra = sesionActiva.value.muestras.find(m => m.muestraId === muestraId) if (!muestra) { throw new Error(`Muestra con ID ${muestraId} no encontrada`) } // Actualizar muestra directamente (mutación) Object.assign(muestra, muestraActualizada) // Recalcular puntaje final si hay cambios en intensidades if (muestraActualizada.intensidades) { muestra.puntajeFinal = calcularPuntajeFinal(muestra) } // Guardar en IndexedDB await actualizar(sesionActiva.value) } 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 muestra = sesionActiva.value.muestras.find(m => m.muestraId === muestraId) if (!muestra) { throw new Error(`Muestra con ID ${muestraId} no encontrada`) } // Actualizar valor de intensidad directamente (mutación) muestra.intensidades[parametro][tipo] = valor // Recalcular puntaje final muestra.puntajeFinal = calcularPuntajeFinal(muestra) // Guardar en IndexedDB (pasa la sesión actual, no un clon) await actualizar(sesionActiva.value) } 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, categorias: string[], subcategorias: string[], notaEspecifica: string | null ) => { await actualizarMuestra(muestraId, { fraganciaAromaNotas: { categorias: categorias as any, subcategorias, notaEspecifica }, }) } /** * Actualiza las notas de sabor */ const actualizarSabor = async ( muestraId: number, categorias: string[], subcategorias: string[], notaEspecifica: string | null ) => { await actualizarMuestra(muestraId, { saborNotas: { categorias: categorias as any, subcategorias, 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 const muestra = sesionActiva.value.muestras.find(m => m.muestraId === muestraId) return muestra || 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 (al menos una categoría) const fraganciaAromaCompleta = muestra.fraganciaAromaNotas.categorias.length > 0 // Verificar notas de sabor (al menos una categoría) const saborCompleto = muestra.saborNotas.categorias.length > 0 // 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 (al menos una categoría) total += 1 if (muestra.fraganciaAromaNotas.categorias.length > 0) completados += 1 // Notas sabor (al menos una categoría) total += 1 if (muestra.saborNotas.categorias.length > 0) 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 = 'organoleptica' 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, } }