Files
cataRio/nuxt4/app/components/cata/FormularioMuestra.vue
josedario87 8cf62f0057
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m5s
Feat: Implementar selección múltiple y filtrado de subcategorías
- Cambiar subcategoriaActiva a subcategoriasActivas (array) para permitir selección múltiple
- Agregar flex-wrap a barra de subcategorías en lugar de scroll horizontal
- Implementar toggleSubcategoria para agregar/quitar subcategorías
- Pasar subcategoriasActivas a FormularioMuestra como prop
- Agregar helpers deberMostrarSeccion para filtrar inputs
- Aplicar v-if a todas las secciones según subcategorías activas
- Botón Limpiar Todo para resetear filtros
2025-10-18 14:47:38 -06:00

919 lines
37 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="formulario-muestra p-4 space-y-6">
<!-- Tab 1: Organoléptica (solo selectores de familia) -->
<div v-if="tabActiva === 'organoleptica'" class="tab-content cata-fade-in">
<h4 class="tab-section-title cata-text mb-4">
Características Organolépticas
</h4>
<!-- Selector de Familia de Fragancia/Aroma -->
<div v-if="mostrarFraganciaAroma" class="form-section">
<CataSelectorFamilia
tipo="fragancia-aroma"
label="Familia de Fragancia y Aroma"
:model-value="muestra.fraganciaAromaNotas"
@update:model-value="actualizarFraganciaAroma"
/>
</div>
<!-- Selector de Familia de Sabor -->
<div v-if="mostrarSaborOrganoleptica" class="form-section">
<CataSelectorFamilia
tipo="sabor"
label="Familia de Sabor"
:model-value="muestra.saborNotas"
@update:model-value="actualizarSabor"
/>
</div>
</div>
<!-- Tab 2: Descriptiva/Afectiva (todos los sliders incluyendo impresión global) -->
<div v-if="tabActiva === 'descriptiva-afectiva'" class="tab-content cata-fade-in">
<h4 class="tab-section-title cata-text mb-4">
Intensidades Descriptivas y Afectivas
</h4>
<!-- Sliders de Fragancia -->
<div v-if="mostrarFraganciaSlider" class="form-section">
<div class="flex items-center justify-between mb-3">
<h5 class="form-section-title cata-text" :style="{ color: getCategoryColor('fragancia') }">Fragancia</h5>
<div class="flex gap-2">
<UBadge :style="{ backgroundColor: getCategoryColor('fragancia'), opacity: 0.4 }" class="text-xs">
📊 {{ muestra.intensidades.fragancia.descriptiva ?? '-' }}
</UBadge>
<UBadge :style="{ backgroundColor: getCategoryColor('fragancia'), opacity: 0.7 }" class="text-xs">
{{ muestra.intensidades.fragancia.afectiva ?? '-' }}
</UBadge>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<CataSliderIntensidad
tipo="descriptiva"
:model-value="muestra.intensidades.fragancia.descriptiva"
:color="getCategoryColor('fragancia')"
@update:model-value="(v) => actualizarIntensidad('fragancia', 'descriptiva', v)"
/>
<CataSliderIntensidad
tipo="afectiva"
:model-value="muestra.intensidades.fragancia.afectiva"
:color="getCategoryColor('fragancia')"
@update:model-value="(v) => actualizarIntensidad('fragancia', 'afectiva', v)"
/>
</div>
</div>
<!-- Sliders de Aroma -->
<div v-if="mostrarAromaSlider" class="form-section">
<div class="flex items-center justify-between mb-3">
<h5 class="form-section-title cata-text" :style="{ color: getCategoryColor('aroma') }">Aroma</h5>
<div class="flex gap-2">
<UBadge :style="{ backgroundColor: getCategoryColor('aroma'), opacity: 0.4 }" class="text-xs">
📊 {{ muestra.intensidades.aroma.descriptiva ?? '-' }}
</UBadge>
<UBadge :style="{ backgroundColor: getCategoryColor('aroma'), opacity: 0.7 }" class="text-xs">
❤️ {{ muestra.intensidades.aroma.afectiva ?? '-' }}
</UBadge>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<CataSliderIntensidad
tipo="descriptiva"
:model-value="muestra.intensidades.aroma.descriptiva"
:color="getCategoryColor('aroma')"
@update:model-value="(v) => actualizarIntensidad('aroma', 'descriptiva', v)"
/>
<CataSliderIntensidad
tipo="afectiva"
:model-value="muestra.intensidades.aroma.afectiva"
:color="getCategoryColor('aroma')"
@update:model-value="(v) => actualizarIntensidad('aroma', 'afectiva', v)"
/>
</div>
</div>
<!-- Sliders de Sabor -->
<div v-if="mostrarSaborSlider" class="form-section">
<div class="flex items-center justify-between mb-3">
<h5 class="form-section-title cata-text" :style="{ color: getCategoryColor('sabor') }">Sabor</h5>
<div class="flex gap-2">
<UBadge :style="{ backgroundColor: getCategoryColor('sabor'), opacity: 0.4 }" class="text-xs">
📊 {{ muestra.intensidades.sabor.descriptiva ?? '-' }}
</UBadge>
<UBadge :style="{ backgroundColor: getCategoryColor('sabor'), opacity: 0.7 }" class="text-xs">
❤️ {{ muestra.intensidades.sabor.afectiva ?? '-' }}
</UBadge>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<CataSliderIntensidad
tipo="descriptiva"
:model-value="muestra.intensidades.sabor.descriptiva"
:color="getCategoryColor('sabor')"
@update:model-value="(v) => actualizarIntensidad('sabor', 'descriptiva', v)"
/>
<CataSliderIntensidad
tipo="afectiva"
:model-value="muestra.intensidades.sabor.afectiva"
:color="getCategoryColor('sabor')"
@update:model-value="(v) => actualizarIntensidad('sabor', 'afectiva', v)"
/>
</div>
</div>
<!-- Sliders de Sabor Residual -->
<div v-if="mostrarSaborResidualSlider" class="form-section">
<div class="flex items-center justify-between mb-3">
<h5 class="form-section-title cata-text" :style="{ color: getCategoryColor('saborResidual') }">Sabor Residual</h5>
<div class="flex gap-2">
<UBadge :style="{ backgroundColor: getCategoryColor('saborResidual'), opacity: 0.4 }" class="text-xs">
📊 {{ muestra.intensidades.saborResidual.descriptiva ?? '-' }}
</UBadge>
<UBadge :style="{ backgroundColor: getCategoryColor('saborResidual'), opacity: 0.7 }" class="text-xs">
❤️ {{ muestra.intensidades.saborResidual.afectiva ?? '-' }}
</UBadge>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<CataSliderIntensidad
tipo="descriptiva"
:model-value="muestra.intensidades.saborResidual.descriptiva"
:color="getCategoryColor('saborResidual')"
@update:model-value="(v) => actualizarIntensidad('saborResidual', 'descriptiva', v)"
/>
<CataSliderIntensidad
tipo="afectiva"
:model-value="muestra.intensidades.saborResidual.afectiva"
:color="getCategoryColor('saborResidual')"
@update:model-value="(v) => actualizarIntensidad('saborResidual', 'afectiva', v)"
/>
</div>
</div>
<!-- Sliders de Acidez -->
<div v-if="mostrarAcidezSlider" class="form-section">
<div class="flex items-center justify-between mb-3">
<h5 class="form-section-title cata-text" :style="{ color: getCategoryColor('acidez') }">Acidez</h5>
<div class="flex gap-2">
<UBadge :style="{ backgroundColor: getCategoryColor('acidez'), opacity: 0.4 }" class="text-xs">
📊 {{ muestra.intensidades.acidez.descriptiva ?? '-' }}
</UBadge>
<UBadge :style="{ backgroundColor: getCategoryColor('acidez'), opacity: 0.7 }" class="text-xs">
❤️ {{ muestra.intensidades.acidez.afectiva ?? '-' }}
</UBadge>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<CataSliderIntensidad
tipo="descriptiva"
:model-value="muestra.intensidades.acidez.descriptiva"
:color="getCategoryColor('acidez')"
@update:model-value="(v) => actualizarIntensidad('acidez', 'descriptiva', v)"
/>
<CataSliderIntensidad
tipo="afectiva"
:model-value="muestra.intensidades.acidez.afectiva"
:color="getCategoryColor('acidez')"
@update:model-value="(v) => actualizarIntensidad('acidez', 'afectiva', v)"
/>
</div>
</div>
<!-- Sliders de Dulzor -->
<div v-if="mostrarDulzorSlider" class="form-section">
<div class="flex items-center justify-between mb-3">
<h5 class="form-section-title cata-text" :style="{ color: getCategoryColor('dulzor') }">Dulzor</h5>
<div class="flex gap-2">
<UBadge :style="{ backgroundColor: getCategoryColor('dulzor'), opacity: 0.4 }" class="text-xs">
📊 {{ muestra.intensidades.dulzor.descriptiva ?? '-' }}
</UBadge>
<UBadge :style="{ backgroundColor: getCategoryColor('dulzor'), opacity: 0.7 }" class="text-xs">
❤️ {{ muestra.intensidades.dulzor.afectiva ?? '-' }}
</UBadge>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<CataSliderIntensidad
tipo="descriptiva"
:model-value="muestra.intensidades.dulzor.descriptiva"
:color="getCategoryColor('dulzor')"
@update:model-value="(v) => actualizarIntensidad('dulzor', 'descriptiva', v)"
/>
<CataSliderIntensidad
tipo="afectiva"
:model-value="muestra.intensidades.dulzor.afectiva"
:color="getCategoryColor('dulzor')"
@update:model-value="(v) => actualizarIntensidad('dulzor', 'afectiva', v)"
/>
</div>
</div>
<!-- Sliders de Sensación en Boca -->
<div v-if="mostrarSensacionBocaSlider" class="form-section">
<div class="flex items-center justify-between mb-3">
<h5 class="form-section-title cata-text" :style="{ color: getCategoryColor('sensacionBoca') }">Sensación en la Boca</h5>
<div class="flex gap-2">
<UBadge :style="{ backgroundColor: getCategoryColor('sensacionBoca'), opacity: 0.4 }" class="text-xs">
📊 {{ muestra.intensidades.sensacionBoca.descriptiva ?? '-' }}
</UBadge>
<UBadge :style="{ backgroundColor: getCategoryColor('sensacionBoca'), opacity: 0.7 }" class="text-xs">
❤️ {{ muestra.intensidades.sensacionBoca.afectiva ?? '-' }}
</UBadge>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<CataSliderIntensidad
tipo="descriptiva"
:model-value="muestra.intensidades.sensacionBoca.descriptiva"
:color="getCategoryColor('sensacionBoca')"
@update:model-value="(v) => actualizarIntensidad('sensacionBoca', 'descriptiva', v)"
/>
<CataSliderIntensidad
tipo="afectiva"
:model-value="muestra.intensidades.sensacionBoca.afectiva"
:color="getCategoryColor('sensacionBoca')"
@update:model-value="(v) => actualizarIntensidad('sensacionBoca', 'afectiva', v)"
/>
</div>
</div>
<!-- Sliders de Impresión Global -->
<div v-if="mostrarImpresionGlobalSlider" class="form-section">
<div class="flex items-center justify-between mb-3">
<h5 class="form-section-title cata-text" :style="{ color: getCategoryColor('impresionGlobal') }">Impresión Global</h5>
<div class="flex gap-2">
<UBadge :style="{ backgroundColor: getCategoryColor('impresionGlobal'), opacity: 0.4 }" class="text-xs">
📊 {{ muestra.intensidades.impresionGlobal.descriptiva ?? '-' }}
</UBadge>
<UBadge :style="{ backgroundColor: getCategoryColor('impresionGlobal'), opacity: 0.7 }" class="text-xs">
❤️ {{ muestra.intensidades.impresionGlobal.afectiva ?? '-' }}
</UBadge>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<CataSliderIntensidad
tipo="descriptiva"
:model-value="muestra.intensidades.impresionGlobal.descriptiva"
:color="getCategoryColor('impresionGlobal')"
@update:model-value="(v) => actualizarIntensidad('impresionGlobal', 'descriptiva', v)"
/>
<CataSliderIntensidad
tipo="afectiva"
:model-value="muestra.intensidades.impresionGlobal.afectiva"
:color="getCategoryColor('impresionGlobal')"
@update:model-value="(v) => actualizarIntensidad('impresionGlobal', 'afectiva', v)"
/>
</div>
</div>
</div>
<!-- Tab 3: Defectos (tazas y defectos) -->
<div v-if="tabActiva === 'defectos'" class="tab-content cata-fade-in">
<h4 class="tab-section-title cata-text mb-4">
Defectos y Uniformidad
</h4>
<!-- Tazas No Uniformes -->
<div class="form-section">
<CataSelectorTazas
tipo="uniformes"
label="Tazas NO Uniformes"
:model-value="muestra.tazasNoUniformes"
@update:model-value="actualizarTazasNoUniformes"
/>
</div>
<!-- Tazas Defectuosas -->
<div class="form-section">
<CataSelectorTazas
tipo="defectuosas"
label="Tazas Defectuosas"
:model-value="muestra.tazasDefectuosas"
@update:model-value="actualizarTazasDefectuosas"
/>
</div>
<!-- Tipo de Defecto -->
<div v-if="muestra.tazasDefectuosas.length > 0" class="form-section">
<label class="block text-sm font-medium mb-2 cata-text">
Tipo de Defecto
</label>
<div class="grid grid-cols-2 sm:grid-cols-4 gap-2">
<button
v-for="tipo in tiposDefectos"
:key="tipo || 'ninguno'"
type="button"
:class="[
'cata-checkbox',
{ 'cata-checkbox-checked': muestra.defecto === tipo },
]"
@click="actualizarDefecto(tipo)"
>
<span class="cata-text">{{ tipo || 'Ninguno' }}</span>
</button>
</div>
</div>
</div>
<!-- Tab 4: Impresión Global (muestra TODOS los componentes) -->
<div v-if="tabActiva === 'impresion-global'" class="tab-content cata-fade-in">
<h4 class="tab-section-title cata-text mb-4">
Visión Global Completa
</h4>
<!-- Sección: Organoléptica -->
<div class="global-section mb-6 p-4 cata-outline-box rounded-lg">
<h5 class="global-section-title cata-text mb-4">Características Organolépticas</h5>
<!-- Selector de Familia de Fragancia/Aroma -->
<div class="form-section mb-4">
<CataSelectorFamilia
tipo="fragancia-aroma"
label="Familia de Fragancia y Aroma"
:model-value="muestra.fraganciaAromaNotas"
@update:model-value="actualizarFraganciaAroma"
/>
</div>
<!-- Selector de Familia de Sabor -->
<div class="form-section">
<CataSelectorFamilia
tipo="sabor"
label="Familia de Sabor"
:model-value="muestra.saborNotas"
@update:model-value="actualizarSabor"
/>
</div>
</div>
<!-- Sección: Intensidades -->
<div class="global-section mb-6 p-4 cata-outline-box rounded-lg">
<h5 class="global-section-title cata-text mb-4">Intensidades Descriptivas y Afectivas</h5>
<!-- Fragancia -->
<div class="form-section mb-4">
<div class="flex items-center justify-between mb-3">
<h6 class="form-subsection-title cata-text" :style="{ color: getCategoryColor('fragancia') }">Fragancia</h6>
<div class="flex gap-2">
<UBadge :style="{ backgroundColor: getCategoryColor('fragancia'), opacity: 0.4 }" class="text-xs">
📊 {{ muestra.intensidades.fragancia.descriptiva ?? '-' }}
</UBadge>
<UBadge :style="{ backgroundColor: getCategoryColor('fragancia'), opacity: 0.7 }" class="text-xs">
❤️ {{ muestra.intensidades.fragancia.afectiva ?? '-' }}
</UBadge>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<CataSliderIntensidad
tipo="descriptiva"
:model-value="muestra.intensidades.fragancia.descriptiva"
:color="getCategoryColor('fragancia')"
@update:model-value="(v) => actualizarIntensidad('fragancia', 'descriptiva', v)"
/>
<CataSliderIntensidad
tipo="afectiva"
:model-value="muestra.intensidades.fragancia.afectiva"
:color="getCategoryColor('fragancia')"
@update:model-value="(v) => actualizarIntensidad('fragancia', 'afectiva', v)"
/>
</div>
</div>
<!-- Aroma -->
<div class="form-section mb-4">
<div class="flex items-center justify-between mb-3">
<h6 class="form-subsection-title cata-text" :style="{ color: getCategoryColor('aroma') }">Aroma</h6>
<div class="flex gap-2">
<UBadge :style="{ backgroundColor: getCategoryColor('aroma'), opacity: 0.4 }" class="text-xs">
📊 {{ muestra.intensidades.aroma.descriptiva ?? '-' }}
</UBadge>
<UBadge :style="{ backgroundColor: getCategoryColor('aroma'), opacity: 0.7 }" class="text-xs">
❤️ {{ muestra.intensidades.aroma.afectiva ?? '-' }}
</UBadge>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<CataSliderIntensidad
tipo="descriptiva"
:model-value="muestra.intensidades.aroma.descriptiva"
:color="getCategoryColor('aroma')"
@update:model-value="(v) => actualizarIntensidad('aroma', 'descriptiva', v)"
/>
<CataSliderIntensidad
tipo="afectiva"
:model-value="muestra.intensidades.aroma.afectiva"
:color="getCategoryColor('aroma')"
@update:model-value="(v) => actualizarIntensidad('aroma', 'afectiva', v)"
/>
</div>
</div>
<!-- Sabor -->
<div class="form-section mb-4">
<div class="flex items-center justify-between mb-3">
<h6 class="form-subsection-title cata-text" :style="{ color: getCategoryColor('sabor') }">Sabor</h6>
<div class="flex gap-2">
<UBadge :style="{ backgroundColor: getCategoryColor('sabor'), opacity: 0.4 }" class="text-xs">
📊 {{ muestra.intensidades.sabor.descriptiva ?? '-' }}
</UBadge>
<UBadge :style="{ backgroundColor: getCategoryColor('sabor'), opacity: 0.7 }" class="text-xs">
❤️ {{ muestra.intensidades.sabor.afectiva ?? '-' }}
</UBadge>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<CataSliderIntensidad
tipo="descriptiva"
:model-value="muestra.intensidades.sabor.descriptiva"
:color="getCategoryColor('sabor')"
@update:model-value="(v) => actualizarIntensidad('sabor', 'descriptiva', v)"
/>
<CataSliderIntensidad
tipo="afectiva"
:model-value="muestra.intensidades.sabor.afectiva"
:color="getCategoryColor('sabor')"
@update:model-value="(v) => actualizarIntensidad('sabor', 'afectiva', v)"
/>
</div>
</div>
<!-- Sabor Residual -->
<div class="form-section mb-4">
<div class="flex items-center justify-between mb-3">
<h6 class="form-subsection-title cata-text" :style="{ color: getCategoryColor('saborResidual') }">Sabor Residual</h6>
<div class="flex gap-2">
<UBadge :style="{ backgroundColor: getCategoryColor('saborResidual'), opacity: 0.4 }" class="text-xs">
📊 {{ muestra.intensidades.saborResidual.descriptiva ?? '-' }}
</UBadge>
<UBadge :style="{ backgroundColor: getCategoryColor('saborResidual'), opacity: 0.7 }" class="text-xs">
❤️ {{ muestra.intensidades.saborResidual.afectiva ?? '-' }}
</UBadge>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<CataSliderIntensidad
tipo="descriptiva"
:model-value="muestra.intensidades.saborResidual.descriptiva"
:color="getCategoryColor('saborResidual')"
@update:model-value="(v) => actualizarIntensidad('saborResidual', 'descriptiva', v)"
/>
<CataSliderIntensidad
tipo="afectiva"
:model-value="muestra.intensidades.saborResidual.afectiva"
:color="getCategoryColor('saborResidual')"
@update:model-value="(v) => actualizarIntensidad('saborResidual', 'afectiva', v)"
/>
</div>
</div>
<!-- Acidez -->
<div class="form-section mb-4">
<div class="flex items-center justify-between mb-3">
<h6 class="form-subsection-title cata-text" :style="{ color: getCategoryColor('acidez') }">Acidez</h6>
<div class="flex gap-2">
<UBadge :style="{ backgroundColor: getCategoryColor('acidez'), opacity: 0.4 }" class="text-xs">
📊 {{ muestra.intensidades.acidez.descriptiva ?? '-' }}
</UBadge>
<UBadge :style="{ backgroundColor: getCategoryColor('acidez'), opacity: 0.7 }" class="text-xs">
❤️ {{ muestra.intensidades.acidez.afectiva ?? '-' }}
</UBadge>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<CataSliderIntensidad
tipo="descriptiva"
:model-value="muestra.intensidades.acidez.descriptiva"
:color="getCategoryColor('acidez')"
@update:model-value="(v) => actualizarIntensidad('acidez', 'descriptiva', v)"
/>
<CataSliderIntensidad
tipo="afectiva"
:model-value="muestra.intensidades.acidez.afectiva"
:color="getCategoryColor('acidez')"
@update:model-value="(v) => actualizarIntensidad('acidez', 'afectiva', v)"
/>
</div>
</div>
<!-- Dulzor -->
<div class="form-section mb-4">
<div class="flex items-center justify-between mb-3">
<h6 class="form-subsection-title cata-text" :style="{ color: getCategoryColor('dulzor') }">Dulzor</h6>
<div class="flex gap-2">
<UBadge :style="{ backgroundColor: getCategoryColor('dulzor'), opacity: 0.4 }" class="text-xs">
📊 {{ muestra.intensidades.dulzor.descriptiva ?? '-' }}
</UBadge>
<UBadge :style="{ backgroundColor: getCategoryColor('dulzor'), opacity: 0.7 }" class="text-xs">
❤️ {{ muestra.intensidades.dulzor.afectiva ?? '-' }}
</UBadge>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<CataSliderIntensidad
tipo="descriptiva"
:model-value="muestra.intensidades.dulzor.descriptiva"
:color="getCategoryColor('dulzor')"
@update:model-value="(v) => actualizarIntensidad('dulzor', 'descriptiva', v)"
/>
<CataSliderIntensidad
tipo="afectiva"
:model-value="muestra.intensidades.dulzor.afectiva"
:color="getCategoryColor('dulzor')"
@update:model-value="(v) => actualizarIntensidad('dulzor', 'afectiva', v)"
/>
</div>
</div>
<!-- Sensación en Boca -->
<div class="form-section mb-4">
<div class="flex items-center justify-between mb-3">
<h6 class="form-subsection-title cata-text" :style="{ color: getCategoryColor('sensacionBoca') }">Sensación en la Boca</h6>
<div class="flex gap-2">
<UBadge :style="{ backgroundColor: getCategoryColor('sensacionBoca'), opacity: 0.4 }" class="text-xs">
📊 {{ muestra.intensidades.sensacionBoca.descriptiva ?? '-' }}
</UBadge>
<UBadge :style="{ backgroundColor: getCategoryColor('sensacionBoca'), opacity: 0.7 }" class="text-xs">
❤️ {{ muestra.intensidades.sensacionBoca.afectiva ?? '-' }}
</UBadge>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<CataSliderIntensidad
tipo="descriptiva"
:model-value="muestra.intensidades.sensacionBoca.descriptiva"
:color="getCategoryColor('sensacionBoca')"
@update:model-value="(v) => actualizarIntensidad('sensacionBoca', 'descriptiva', v)"
/>
<CataSliderIntensidad
tipo="afectiva"
:model-value="muestra.intensidades.sensacionBoca.afectiva"
:color="getCategoryColor('sensacionBoca')"
@update:model-value="(v) => actualizarIntensidad('sensacionBoca', 'afectiva', v)"
/>
</div>
</div>
<!-- Impresión Global -->
<div class="form-section">
<div class="flex items-center justify-between mb-3">
<h6 class="form-subsection-title cata-text" :style="{ color: getCategoryColor('impresionGlobal') }">Impresión Global</h6>
<div class="flex gap-2">
<UBadge :style="{ backgroundColor: getCategoryColor('impresionGlobal'), opacity: 0.4 }" class="text-xs">
📊 {{ muestra.intensidades.impresionGlobal.descriptiva ?? '-' }}
</UBadge>
<UBadge :style="{ backgroundColor: getCategoryColor('impresionGlobal'), opacity: 0.7 }" class="text-xs">
❤️ {{ muestra.intensidades.impresionGlobal.afectiva ?? '-' }}
</UBadge>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<CataSliderIntensidad
tipo="descriptiva"
:model-value="muestra.intensidades.impresionGlobal.descriptiva"
:color="getCategoryColor('impresionGlobal')"
@update:model-value="(v) => actualizarIntensidad('impresionGlobal', 'descriptiva', v)"
/>
<CataSliderIntensidad
tipo="afectiva"
:model-value="muestra.intensidades.impresionGlobal.afectiva"
:color="getCategoryColor('impresionGlobal')"
@update:model-value="(v) => actualizarIntensidad('impresionGlobal', 'afectiva', v)"
/>
</div>
</div>
</div>
<!-- Sección: Defectos -->
<div class="global-section mb-6 p-4 cata-outline-box rounded-lg">
<h5 class="global-section-title cata-text mb-4">Defectos y Uniformidad</h5>
<!-- Tazas No Uniformes -->
<div class="form-section mb-4">
<CataSelectorTazas
tipo="uniformes"
label="Tazas NO Uniformes"
:model-value="muestra.tazasNoUniformes"
@update:model-value="actualizarTazasNoUniformes"
/>
</div>
<!-- Tazas Defectuosas -->
<div class="form-section mb-4">
<CataSelectorTazas
tipo="defectuosas"
label="Tazas Defectuosas"
:model-value="muestra.tazasDefectuosas"
@update:model-value="actualizarTazasDefectuosas"
/>
</div>
<!-- Tipo de Defecto -->
<div v-if="muestra.tazasDefectuosas.length > 0" class="form-section">
<label class="block text-sm font-medium mb-2 cata-text">
Tipo de Defecto
</label>
<div class="grid grid-cols-2 sm:grid-cols-4 gap-2">
<button
v-for="tipo in tiposDefectos"
:key="tipo || 'ninguno'"
type="button"
:class="[
'cata-checkbox',
{ 'cata-checkbox-checked': muestra.defecto === tipo },
]"
@click="actualizarDefecto(tipo)"
>
<span class="cata-text">{{ tipo || 'Ninguno' }}</span>
</button>
</div>
</div>
</div>
<!-- Sección: Detalles Adicionales -->
<div class="global-section mb-6 p-4 cata-outline-box rounded-lg">
<h5 class="global-section-title cata-text mb-4">Detalles Adicionales</h5>
<!-- Sensaciones en Boca (selección múltiple) -->
<div class="form-section mb-4">
<label class="block text-sm font-medium mb-2 cata-text">
Sensaciones en la Boca (múltiples)
</label>
<div class="grid grid-cols-2 sm:grid-cols-3 gap-2">
<button
v-for="sensacion in sensacionesBoca"
:key="sensacion"
type="button"
:class="[
'cata-checkbox',
{ 'cata-checkbox-checked': muestra.sensacionEnBoca.includes(sensacion) },
]"
@click="toggleSensacionBoca(sensacion)"
>
<span class="cata-text text-sm">{{ sensacion }}</span>
</button>
</div>
</div>
<!-- Gustos Predominantes (máx 2) -->
<div class="form-section mb-4">
<label class="block text-sm font-medium mb-2 cata-text">
Gustos Predominantes (mín 1, máx 2)
</label>
<div class="grid grid-cols-2 sm:grid-cols-5 gap-2">
<button
v-for="gusto in gustosPredominantes"
:key="gusto"
type="button"
:class="[
'cata-checkbox',
{ 'cata-checkbox-checked': muestra.gustosPredominantes.includes(gusto) },
]"
:disabled="!muestra.gustosPredominantes.includes(gusto) && muestra.gustosPredominantes.length >= 2"
@click="toggleGustoPredominante(gusto)"
>
<span class="cata-text">{{ gusto }}</span>
</button>
</div>
</div>
<!-- Otras Notas -->
<div class="form-section">
<label class="block text-sm font-medium mb-2 cata-text">
Otras Notas
</label>
<textarea
v-model="otrasNotasLocal"
class="cata-input w-full min-h-[100px] resize-y"
placeholder="Notas adicionales sobre el café, cuerpo, balance, etc..."
@blur="actualizarOtrasNotas"
/>
</div>
</div>
<!-- Puntaje Final (solo lectura) -->
<div class="global-section p-4 cata-outline-box rounded-lg">
<div class="puntaje-final">
<div class="flex items-baseline justify-between">
<span class="text-sm cata-text opacity-75">Puntaje Final:</span>
<span class="text-3xl font-bold cata-text">{{ muestra.puntajeFinal }}</span>
</div>
<p class="text-xs cata-text opacity-60 mt-1">
Suma de valores afectivos
</p>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import type { Muestra, NotaSeleccionada, TipoDefecto, SensacionBoca, GustoPredominante } from '~/types/catacion'
import type { TabCatacion, Subcategoria } from '~/composables/useCatacion'
import { SENSACIONES_BOCA, GUSTOS_PREDOMINANTES, TIPOS_DEFECTOS } from '~/types/catacion'
interface FormularioMuestraProps {
/** Muestra a editar */
muestra: Muestra
/** Tab activa */
tabActiva: TabCatacion
/** Subcategorías activas (filtros) */
subcategoriasActivas?: Subcategoria[]
}
const props = withDefaults(defineProps<FormularioMuestraProps>(), {
subcategoriasActivas: () => [],
})
const { actualizarIntensidad: actualizarIntensidadCatacion } = useCatacion()
const { getCategoryColor } = useCategoryColors()
// Listas para los selectores
const sensacionesBoca = SENSACIONES_BOCA
const gustosPredominantes = GUSTOS_PREDOMINANTES
const tiposDefectos = TIPOS_DEFECTOS
// Helpers para filtrado por subcategorías
const deberMostrarSeccion = (subcategorias: Subcategoria[]): boolean => {
// Si no hay filtros activos, mostrar todo
if (!props.subcategoriasActivas || props.subcategoriasActivas.length === 0) return true
// Si hay filtros, verificar si alguno coincide
return subcategorias.some(sub => props.subcategoriasActivas?.includes(sub))
}
// Para Organoléptica
const mostrarFraganciaAroma = computed(() => deberMostrarSeccion(['fragancia-aroma']))
const mostrarSaborOrganoleptica = computed(() => deberMostrarSeccion(['sabor']))
const mostrarSensacionBocaOrganoleptica = computed(() => deberMostrarSeccion(['sensacion-boca']))
const mostrarGustosPredominantes = computed(() => deberMostrarSeccion(['gustos-predominantes']))
// Para Descriptiva/Afectiva
const mostrarFraganciaSlider = computed(() => deberMostrarSeccion(['descriptiva', 'afectiva', 'fragancia']))
const mostrarAromaSlider = computed(() => deberMostrarSeccion(['descriptiva', 'afectiva', 'aroma']))
const mostrarSaborSlider = computed(() => deberMostrarSeccion(['descriptiva', 'afectiva', 'sabor']))
const mostrarSaborResidualSlider = computed(() => deberMostrarSeccion(['descriptiva', 'afectiva', 'sabor-residual']))
const mostrarAcidezSlider = computed(() => deberMostrarSeccion(['descriptiva', 'afectiva', 'acidez']))
const mostrarDulzorSlider = computed(() => deberMostrarSeccion(['descriptiva', 'afectiva', 'dulzor']))
const mostrarSensacionBocaSlider = computed(() => deberMostrarSeccion(['descriptiva', 'afectiva', 'sensacion-boca']))
const mostrarImpresionGlobalSlider = computed(() => deberMostrarSeccion(['descriptiva', 'afectiva', 'impresion-global']))
// Estado local para otras notas
const otrasNotasLocal = ref(props.muestra.otrasNotas)
// Actualizar intensidad
const actualizarIntensidad = async (
parametro: keyof Muestra['intensidades'],
tipo: 'descriptiva' | 'afectiva',
valor: number | null
) => {
await actualizarIntensidadCatacion(props.muestra.muestraId, parametro, tipo, valor)
}
// Actualizar fragancia/aroma
const { actualizarFraganciaAroma: actualizarFraganciaAromaCatacion } = useCatacion()
const actualizarFraganciaAroma = async (nota: NotaSeleccionada) => {
await actualizarFraganciaAromaCatacion(
props.muestra.muestraId,
nota.categorias,
nota.subcategorias,
nota.notaEspecifica
)
}
// Actualizar sabor
const { actualizarSabor: actualizarSaborCatacion } = useCatacion()
const actualizarSabor = async (nota: NotaSeleccionada) => {
await actualizarSaborCatacion(
props.muestra.muestraId,
nota.categorias,
nota.subcategorias,
nota.notaEspecifica
)
}
// Actualizar tazas
const { actualizarTazasNoUniformes: actualizarTazasNoUniformesCatacion, actualizarTazasDefectuosas: actualizarTazasDefectuosasCatacion } = useCatacion()
const actualizarTazasNoUniformes = async (tazas: number[]) => {
await actualizarTazasNoUniformesCatacion(props.muestra.muestraId, tazas)
}
const actualizarTazasDefectuosas = async (tazas: number[]) => {
await actualizarTazasDefectuosasCatacion(props.muestra.muestraId, tazas)
}
// Actualizar defecto
const { actualizarDefecto: actualizarDefectoCatacion } = useCatacion()
const actualizarDefecto = async (defecto: TipoDefecto) => {
await actualizarDefectoCatacion(props.muestra.muestraId, defecto)
}
// Toggle sensación en boca
const { actualizarSensacionBoca } = useCatacion()
const toggleSensacionBoca = async (sensacion: SensacionBoca) => {
const sensaciones = [...props.muestra.sensacionEnBoca]
const index = sensaciones.indexOf(sensacion)
if (index > -1) {
sensaciones.splice(index, 1)
} else {
sensaciones.push(sensacion)
}
await actualizarSensacionBoca(props.muestra.muestraId, sensaciones)
}
// Toggle gusto predominante
const { actualizarGustosPredominantes } = useCatacion()
const toggleGustoPredominante = async (gusto: GustoPredominante) => {
const gustos = [...props.muestra.gustosPredominantes]
const index = gustos.indexOf(gusto)
if (index > -1) {
gustos.splice(index, 1)
} else {
if (gustos.length >= 2) return // Máximo 2
gustos.push(gusto)
}
if (gustos.length === 0) return // Mínimo 1
await actualizarGustosPredominantes(props.muestra.muestraId, gustos)
}
// Actualizar otras notas
const { actualizarOtrasNotas: actualizarOtrasNotasCatacion } = useCatacion()
const actualizarOtrasNotas = async () => {
const notas = otrasNotasLocal.value.trim()
await actualizarOtrasNotasCatacion(props.muestra.muestraId, notas)
}
// Sincronizar otras notas cuando cambia la muestra
watch(() => props.muestra.otrasNotas, (newVal) => {
if (newVal !== otrasNotasLocal.value) {
otrasNotasLocal.value = newVal
}
})
</script>
<style scoped>
.formulario-muestra {
width: 100%;
}
.tab-section-title {
font-size: 1.125rem;
font-weight: 600;
border-bottom: 1px solid;
padding-bottom: 0.5rem;
border-color: color-mix(in srgb, var(--cata-primary) 30%, transparent);
}
.global-section-title {
font-size: 1rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
opacity: 0.9;
}
.form-section {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.form-section-title {
font-size: 0.875rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.form-subsection-title {
font-size: 0.8125rem;
font-weight: 700;
margin-bottom: 0.5rem;
}
.puntaje-final {
text-align: center;
}
/* Responsive */
@media (max-width: 768px) {
.formulario-muestra {
padding: 0.75rem;
display: flex;
flex-direction: column;
gap: 1rem;
}
.tab-section-title {
font-size: 1rem;
}
.global-section-title {
font-size: 0.9rem;
}
}
</style>