Feat: Reorganizar tabs y permitir selección múltiple de categorías en notas
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m1s

- **Nuevas tabs reorganizadas:**
  - Organoléptica: Selectores de familia de fragancia-aroma y sabor
  - Descriptiva/Afectiva: Todos los sliders de intensidad (incluye impresión global)
  - Defectos: Tazas no uniformes, defectuosas y tipo de defecto
  - Impresión Global: Vista completa con todos los componentes

- **Selector de categorías mejorado:**
  - Permitir selección múltiple de categorías padre
  - Las subcategorías son la unión de las subcategorías de los padres seleccionados
  - Permitir selección múltiple de subcategorías
  - Actualizar resumen visual de selección

- **Tipos actualizados:**
  - NotaSeleccionada ahora usa arrays para categorias y subcategorias
  - TabCatacion actualizado con las nuevas tabs
  - Funciones de actualización modificadas para trabajar con arrays

- **Correcciones TypeScript:**
  - Usar JSON.parse(JSON.stringify()) para crear copias mutables de arrays readonly
  - Resolver incompatibilidades de tipos entre readonly y mutable arrays
This commit is contained in:
2025-10-18 02:57:14 -06:00
parent 1c4f09d9bd
commit 48e0d2f7dc
5 changed files with 481 additions and 181 deletions

View File

@@ -5,13 +5,13 @@
import type { SesionCatacion, Muestra, IntensidadValor } from '~/types/catacion'
import { crearSesionVacia, calcularPuntajeFinal } from '~/types/catacion'
export type TabCatacion = 'fragancia-aroma' | 'sabor' | 'impresion-global'
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<TabCatacion>('tab-activa', () => 'fragancia-aroma')
const tabActiva = useState<TabCatacion>('tab-activa', () => 'organoleptica')
const accordionAbierto = useState<string[]>('accordion-abierto', () => [])
/**
@@ -22,7 +22,7 @@ export const useCatacion = () => {
const nuevaSesion = crearSesionVacia(catador, cantidadMuestras)
await guardar(nuevaSesion)
// Resetear estado de UI
tabActiva.value = 'fragancia-aroma'
tabActiva.value = 'organoleptica'
accordionAbierto.value = []
return nuevaSesion
} catch (err) {
@@ -131,12 +131,12 @@ export const useCatacion = () => {
*/
const actualizarFraganciaAroma = async (
muestraId: number,
categoria: string | null,
subcategoria: string | null,
categorias: string[],
subcategorias: string[],
notaEspecifica: string | null
) => {
await actualizarMuestra(muestraId, {
fraganciaAromaNotas: { categoria: categoria as any, subcategoria, notaEspecifica },
fraganciaAromaNotas: { categorias: categorias as any, subcategorias, notaEspecifica },
})
}
@@ -145,12 +145,12 @@ export const useCatacion = () => {
*/
const actualizarSabor = async (
muestraId: number,
categoria: string | null,
subcategoria: string | null,
categorias: string[],
subcategorias: string[],
notaEspecifica: string | null
) => {
await actualizarMuestra(muestraId, {
saborNotas: { categoria: categoria as any, subcategoria, notaEspecifica },
saborNotas: { categorias: categorias as any, subcategorias, notaEspecifica },
})
}
@@ -207,7 +207,9 @@ export const useCatacion = () => {
*/
const obtenerMuestra = (muestraId: number): Muestra | null => {
if (!sesionActiva.value) return null
return sesionActiva.value.muestras.find(m => m.muestraId === muestraId) || null
const muestra = sesionActiva.value.muestras.find(m => m.muestraId === muestraId)
if (!muestra) return null
return JSON.parse(JSON.stringify(muestra)) as Muestra
}
/**
@@ -219,11 +221,11 @@ export const useCatacion = () => {
(intensidad) => intensidad.afectiva !== null
)
// Verificar notas de fragancia/aroma
const fraganciaAromaCompleta = muestra.fraganciaAromaNotas.categoria !== null
// Verificar notas de fragancia/aroma (al menos una categoría)
const fraganciaAromaCompleta = muestra.fraganciaAromaNotas.categorias.length > 0
// Verificar notas de sabor
const saborCompleto = muestra.saborNotas.categoria !== null
// 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
@@ -250,13 +252,13 @@ export const useCatacion = () => {
(intensidad) => intensidad.descriptiva !== null
).length
// Notas fragancia/aroma
// Notas fragancia/aroma (al menos una categoría)
total += 1
if (muestra.fraganciaAromaNotas.categoria) completados += 1
if (muestra.fraganciaAromaNotas.categorias.length > 0) completados += 1
// Notas sabor
// Notas sabor (al menos una categoría)
total += 1
if (muestra.saborNotas.categoria) completados += 1
if (muestra.saborNotas.categorias.length > 0) completados += 1
// Gustos predominantes
total += 1
@@ -271,7 +273,7 @@ export const useCatacion = () => {
const eliminarSesionActual = async () => {
try {
await eliminar()
tabActiva.value = 'fragancia-aroma'
tabActiva.value = 'organoleptica'
accordionAbierto.value = []
} catch (err) {
console.error('Error al eliminar sesión:', err)
@@ -293,7 +295,7 @@ export const useCatacion = () => {
const estadisticasSesion = computed(() => {
if (!sesionActiva.value) return null
const muestras = sesionActiva.value.muestras
const muestras = JSON.parse(JSON.stringify(sesionActiva.value.muestras)) as Muestra[]
const totalMuestras = muestras.length
const muestrasCompletas = muestras.filter(esMuestraCompleta).length
const promedioCompletitud = muestras.reduce((acc, m) => acc + porcentajeCompletitud(m), 0) / totalMuestras