Feat: Agregar calculadora SCAA en página principal
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m6s
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m6s
Implementado flujo de asignación rápida reutilizable en modo calculadora para la página principal, permitiendo calcular SCAA Score sin sesión. Cambios: - ModalAsignacionRapida: Nuevo modo 'calculadora' con paso 3 - Paso 3 incluye ResumenMuestra y penalizaciones configurables - Función generadora de muestra genérica para vista previa - Botón "Calculadora SCAA" en página principal - Navegación mejorada entre pasos (Volver/Continuar)
This commit is contained in:
@@ -15,7 +15,7 @@
|
||||
|
||||
<!-- Título personalizado -->
|
||||
<template #title>
|
||||
<span class="cata-text">Asignación Rápida de Puntajes</span>
|
||||
<span class="cata-text">{{ tituloModal }}</span>
|
||||
</template>
|
||||
|
||||
<!-- Contenido del modal -->
|
||||
@@ -106,6 +106,72 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Paso 3: Vista previa con ResumenMuestra (solo en modo calculadora) -->
|
||||
<div v-if="paso === 3">
|
||||
<div class="space-y-4">
|
||||
<!-- Inputs de penalizaciones -->
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label class="block text-xs cata-text opacity-75 mb-1">
|
||||
Tazas No Uniformes
|
||||
</label>
|
||||
<UInputNumber
|
||||
v-model="tazasNoUniformes"
|
||||
:min="0"
|
||||
:max="5"
|
||||
:step="1"
|
||||
placeholder="0-5"
|
||||
class="w-full cata-input-number"
|
||||
variant="outline"
|
||||
:ui="{
|
||||
base: 'cata-input text-center',
|
||||
}"
|
||||
/>
|
||||
<p class="text-xs cata-text opacity-60 mt-1 text-center">
|
||||
-2 puntos cada una
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs cata-text opacity-75 mb-1">
|
||||
Tazas Defectuosas
|
||||
</label>
|
||||
<UInputNumber
|
||||
v-model="tazasDefectuosas"
|
||||
:min="0"
|
||||
:max="5"
|
||||
:step="1"
|
||||
placeholder="0-5"
|
||||
class="w-full cata-input-number"
|
||||
variant="outline"
|
||||
:ui="{
|
||||
base: 'cata-input text-center',
|
||||
}"
|
||||
/>
|
||||
<p class="text-xs cata-text opacity-60 mt-1 text-center">
|
||||
-4 puntos cada una
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Resumen de la muestra -->
|
||||
<div class="mt-6">
|
||||
<h4 class="text-sm font-semibold cata-text mb-2 opacity-75">
|
||||
Vista Previa
|
||||
</h4>
|
||||
<div class="cata-outline-box p-4 rounded-lg">
|
||||
<ResumenMuestra :muestra="muestraGenerica" tab-activa="impresion-global" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Información adicional -->
|
||||
<div class="text-xs cata-text opacity-60 text-center">
|
||||
<p>Esta es una muestra de ejemplo para calcular el SCAA Score</p>
|
||||
<p>Los valores descriptivos y organolépticos están ocultos</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -113,21 +179,33 @@
|
||||
<template #footer>
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<!-- Puntaje target a la izquierda -->
|
||||
<div v-if="paso === 2" class="flex items-center gap-2 cata-text font-semibold">
|
||||
<div v-if="paso === 2 || paso === 3" class="flex items-center gap-2 cata-text font-semibold">
|
||||
<UIcon name="i-lucide-target" class="w-5 h-5" :style="{ color: 'var(--cata-primary)' }" />
|
||||
<span>{{ puntajeDeseado }}</span>
|
||||
<span>Σ {{ puntajeDeseado }}</span>
|
||||
</div>
|
||||
<div v-else></div>
|
||||
|
||||
<!-- Botones a la derecha -->
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- Botón Volver (pasos 2 y 3) -->
|
||||
<button
|
||||
v-if="paso === 2 || paso === 3"
|
||||
class="cata-button px-4 py-2"
|
||||
@click="pasoAnterior"
|
||||
>
|
||||
Volver
|
||||
</button>
|
||||
|
||||
<!-- Botón Cancelar (solo paso 1) -->
|
||||
<button
|
||||
v-if="paso === 1"
|
||||
class="cata-button px-4 py-2"
|
||||
@click="cerrar"
|
||||
>
|
||||
Cancelar
|
||||
</button>
|
||||
|
||||
<!-- Botón Continuar (paso 1) -->
|
||||
<button
|
||||
v-if="paso === 1"
|
||||
class="cata-button px-4 py-2"
|
||||
@@ -138,14 +216,24 @@
|
||||
Continuar
|
||||
</button>
|
||||
|
||||
<!-- Botón Aplicar/Continuar (paso 2) -->
|
||||
<button
|
||||
v-if="paso === 2"
|
||||
class="cata-button px-4 py-2"
|
||||
:disabled="categoriasSeleccionadas.length !== diferencia"
|
||||
:class="{ 'opacity-50 cursor-not-allowed': categoriasSeleccionadas.length !== diferencia }"
|
||||
@click="aplicarAsignacion"
|
||||
@click="siguientePasoOAplicar"
|
||||
>
|
||||
Aplicar
|
||||
{{ modo === 'calculadora' ? 'Continuar' : 'Aplicar' }}
|
||||
</button>
|
||||
|
||||
<!-- Botón Cerrar (paso 3 - solo modo calculadora) -->
|
||||
<button
|
||||
v-if="paso === 3"
|
||||
class="cata-button px-4 py-2"
|
||||
@click="cerrar"
|
||||
>
|
||||
Cerrar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -154,15 +242,18 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Muestra } from '~/types/catacion'
|
||||
import type { Muestra, IntensidadValor } from '~/types/catacion'
|
||||
import { scaaASumatoriaAfectiva, sumatoriaAfectivaASCAA, redondearA025 } from '~/types/catacion'
|
||||
|
||||
interface Props {
|
||||
modelValue: boolean
|
||||
muestra: Muestra
|
||||
muestra?: Muestra
|
||||
modo?: 'calculadora' | 'asignacion'
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
modo: 'asignacion',
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: boolean]
|
||||
@@ -193,9 +284,18 @@ const puntajeDeseado = ref<number>(40)
|
||||
const scaaDeseado = ref<number>(sumatoriaAfectivaASCAA(40))
|
||||
const categoriasSeleccionadas = ref<string[]>([])
|
||||
|
||||
// Estado de penalizaciones (paso 3)
|
||||
const tazasNoUniformes = ref<number>(0)
|
||||
const tazasDefectuosas = ref<number>(0)
|
||||
|
||||
// Flag para evitar loops infinitos en la sincronización
|
||||
const actualizandoInput = ref(false)
|
||||
|
||||
// Título del modal según el modo
|
||||
const tituloModal = computed(() => {
|
||||
return props.modo === 'calculadora' ? 'Calculadora SCAA Score' : 'Asignación Rápida de Puntajes'
|
||||
})
|
||||
|
||||
// Límites de SCAA Score (basados en sumatoria afectiva 8-72)
|
||||
// 8 categorías × 1 punto mínimo = 8
|
||||
// 8 categorías × 9 puntos máximo = 72
|
||||
@@ -209,6 +309,60 @@ watch(isOpen, (newValue) => {
|
||||
puntajeDeseado.value = 40
|
||||
scaaDeseado.value = sumatoriaAfectivaASCAA(40)
|
||||
categoriasSeleccionadas.value = []
|
||||
tazasNoUniformes.value = 0
|
||||
tazasDefectuosas.value = 0
|
||||
}
|
||||
})
|
||||
|
||||
// Crear muestra genérica para vista previa (modo calculadora)
|
||||
const muestraGenerica = computed<Muestra>(() => {
|
||||
// Primero calculamos los puntajes afectivos
|
||||
const puntajes: Record<string, number> = {}
|
||||
const puntajeBase = multiploMasCercano.value / 8
|
||||
const ajuste = multiploMasCercano.value < puntajeDeseado.value ? 1 : -1
|
||||
|
||||
categoriasDisponibles.forEach(cat => {
|
||||
const esSeleccionada = categoriasSeleccionadas.value.includes(cat.key)
|
||||
puntajes[cat.key] = puntajeBase + (esSeleccionada ? ajuste : 0)
|
||||
})
|
||||
|
||||
// Crear objeto de intensidades
|
||||
const intensidades: Muestra['intensidades'] = {
|
||||
fragancia: { descriptiva: null, afectiva: puntajes.fragancia || puntajeBase },
|
||||
aroma: { descriptiva: null, afectiva: puntajes.aroma || puntajeBase },
|
||||
sabor: { descriptiva: null, afectiva: puntajes.sabor || puntajeBase },
|
||||
saborResidual: { descriptiva: null, afectiva: puntajes.saborResidual || puntajeBase },
|
||||
acidez: { descriptiva: null, afectiva: puntajes.acidez || puntajeBase },
|
||||
dulzor: { descriptiva: null, afectiva: puntajes.dulzor || puntajeBase },
|
||||
sensacionBoca: { descriptiva: null, afectiva: puntajes.sensacionBoca || puntajeBase },
|
||||
impresionGlobal: { descriptiva: null, afectiva: puntajes.impresionGlobal || puntajeBase },
|
||||
}
|
||||
|
||||
// Crear arrays de tazas con defectos según las cantidades
|
||||
const tazasNoUniformesArray: number[] = []
|
||||
const tazasDefectuosasArray: number[] = []
|
||||
|
||||
for (let i = 1; i <= tazasNoUniformes.value; i++) {
|
||||
tazasNoUniformesArray.push(i)
|
||||
}
|
||||
|
||||
for (let i = 1; i <= tazasDefectuosas.value; i++) {
|
||||
tazasDefectuosasArray.push(i)
|
||||
}
|
||||
|
||||
return {
|
||||
muestraId: 1,
|
||||
nombre: 'Muestra de Ejemplo',
|
||||
intensidades,
|
||||
fraganciaAromaNotas: { categorias: [], subcategorias: [], notaEspecifica: null },
|
||||
saborNotas: { categorias: [], subcategorias: [], notaEspecifica: null },
|
||||
tazasNoUniformes: tazasNoUniformesArray,
|
||||
tazasDefectuosas: tazasDefectuosasArray,
|
||||
defecto: null,
|
||||
sensacionEnBoca: null,
|
||||
gustosPredominantes: [],
|
||||
otrasNotas: '',
|
||||
puntajeFinal: 0,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -345,14 +499,39 @@ const diferencia = computed(() => {
|
||||
const siguientePaso = () => {
|
||||
if (!puntajeValido.value) return
|
||||
|
||||
// Si el múltiplo es exacto (diferencia = 0), aplicar directamente
|
||||
// Si el múltiplo es exacto (diferencia = 0)
|
||||
if (diferencia.value === 0) {
|
||||
aplicarAsignacionDirecta()
|
||||
if (props.modo === 'calculadora') {
|
||||
// En modo calculadora, ir directo al paso 3
|
||||
paso.value = 3
|
||||
} else {
|
||||
// En modo asignación, aplicar directamente
|
||||
aplicarAsignacionDirecta()
|
||||
}
|
||||
} else {
|
||||
// Hay diferencia, ir al paso 2
|
||||
paso.value = 2
|
||||
}
|
||||
}
|
||||
|
||||
const pasoAnterior = () => {
|
||||
if (paso.value > 1) {
|
||||
paso.value--
|
||||
}
|
||||
}
|
||||
|
||||
const siguientePasoOAplicar = () => {
|
||||
if (categoriasSeleccionadas.value.length !== diferencia.value) return
|
||||
|
||||
if (props.modo === 'calculadora') {
|
||||
// En modo calculadora, ir al paso 3
|
||||
paso.value = 3
|
||||
} else {
|
||||
// En modo asignación, aplicar
|
||||
aplicarAsignacion()
|
||||
}
|
||||
}
|
||||
|
||||
const toggleCategoria = (key: string) => {
|
||||
const index = categoriasSeleccionadas.value.indexOf(key)
|
||||
if (index >= 0) {
|
||||
|
||||
Reference in New Issue
Block a user