Files
cataRio/nuxt4/app/components/cata/FormularioMuestra.vue
josedario87 ab47142966
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m6s
Fix: Validar estructura de datos de colores personalizados
- Agregar validación completa en loadCustomColors para detectar formato incompatible
- Proteger setCustomColors asegurando que la estructura existe antes de asignar
- Validar estructura en getCurrentColors y retornar defaults si no existe
- Validar estructura en hasCustomColors y retornar false si no existe
- Validar estructura en inicializar antes de aplicar colores
- Limpiar localStorage automáticamente si detecta formato antiguo o corrupto

Soluciona error: TypeError Cannot create property 'primary' on string
cuando localStorage contiene datos de versión anterior
2025-10-18 03:25:28 -06:00

890 lines
35 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 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 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 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 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 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 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 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 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 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 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 } from '~/composables/useCatacion'
import { SENSACIONES_BOCA, GUSTOS_PREDOMINANTES, TIPOS_DEFECTOS } from '~/types/catacion'
interface FormularioMuestraProps {
/** Muestra a editar */
muestra: Muestra
/** Tab activa */
tabActiva: TabCatacion
}
const props = defineProps<FormularioMuestraProps>()
const { actualizarIntensidad: actualizarIntensidadCatacion } = useCatacion()
const { getCategoryColor } = useCategoryColors()
// Listas para los selectores
const sensacionesBoca = SENSACIONES_BOCA
const gustosPredominantes = GUSTOS_PREDOMINANTES
const tiposDefectos = TIPOS_DEFECTOS
// 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>