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 -->
|
<!-- Título personalizado -->
|
||||||
<template #title>
|
<template #title>
|
||||||
<span class="cata-text">Asignación Rápida de Puntajes</span>
|
<span class="cata-text">{{ tituloModal }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- Contenido del modal -->
|
<!-- Contenido del modal -->
|
||||||
@@ -106,6 +106,72 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -113,21 +179,33 @@
|
|||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="flex items-center justify-between w-full">
|
<div class="flex items-center justify-between w-full">
|
||||||
<!-- Puntaje target a la izquierda -->
|
<!-- 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)' }" />
|
<UIcon name="i-lucide-target" class="w-5 h-5" :style="{ color: 'var(--cata-primary)' }" />
|
||||||
<span>{{ puntajeDeseado }}</span>
|
<span>Σ {{ puntajeDeseado }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-else></div>
|
<div v-else></div>
|
||||||
|
|
||||||
<!-- Botones a la derecha -->
|
<!-- Botones a la derecha -->
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
|
<!-- Botón Volver (pasos 2 y 3) -->
|
||||||
<button
|
<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"
|
class="cata-button px-4 py-2"
|
||||||
@click="cerrar"
|
@click="cerrar"
|
||||||
>
|
>
|
||||||
Cancelar
|
Cancelar
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<!-- Botón Continuar (paso 1) -->
|
||||||
<button
|
<button
|
||||||
v-if="paso === 1"
|
v-if="paso === 1"
|
||||||
class="cata-button px-4 py-2"
|
class="cata-button px-4 py-2"
|
||||||
@@ -138,14 +216,24 @@
|
|||||||
Continuar
|
Continuar
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<!-- Botón Aplicar/Continuar (paso 2) -->
|
||||||
<button
|
<button
|
||||||
v-if="paso === 2"
|
v-if="paso === 2"
|
||||||
class="cata-button px-4 py-2"
|
class="cata-button px-4 py-2"
|
||||||
:disabled="categoriasSeleccionadas.length !== diferencia"
|
:disabled="categoriasSeleccionadas.length !== diferencia"
|
||||||
:class="{ 'opacity-50 cursor-not-allowed': 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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -154,15 +242,18 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Muestra } from '~/types/catacion'
|
import type { Muestra, IntensidadValor } from '~/types/catacion'
|
||||||
import { scaaASumatoriaAfectiva, sumatoriaAfectivaASCAA, redondearA025 } from '~/types/catacion'
|
import { scaaASumatoriaAfectiva, sumatoriaAfectivaASCAA, redondearA025 } from '~/types/catacion'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
modelValue: boolean
|
modelValue: boolean
|
||||||
muestra: Muestra
|
muestra?: Muestra
|
||||||
|
modo?: 'calculadora' | 'asignacion'
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<Props>()
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
modo: 'asignacion',
|
||||||
|
})
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
'update:modelValue': [value: boolean]
|
'update:modelValue': [value: boolean]
|
||||||
@@ -193,9 +284,18 @@ const puntajeDeseado = ref<number>(40)
|
|||||||
const scaaDeseado = ref<number>(sumatoriaAfectivaASCAA(40))
|
const scaaDeseado = ref<number>(sumatoriaAfectivaASCAA(40))
|
||||||
const categoriasSeleccionadas = ref<string[]>([])
|
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
|
// Flag para evitar loops infinitos en la sincronización
|
||||||
const actualizandoInput = ref(false)
|
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)
|
// Límites de SCAA Score (basados en sumatoria afectiva 8-72)
|
||||||
// 8 categorías × 1 punto mínimo = 8
|
// 8 categorías × 1 punto mínimo = 8
|
||||||
// 8 categorías × 9 puntos máximo = 72
|
// 8 categorías × 9 puntos máximo = 72
|
||||||
@@ -209,6 +309,60 @@ watch(isOpen, (newValue) => {
|
|||||||
puntajeDeseado.value = 40
|
puntajeDeseado.value = 40
|
||||||
scaaDeseado.value = sumatoriaAfectivaASCAA(40)
|
scaaDeseado.value = sumatoriaAfectivaASCAA(40)
|
||||||
categoriasSeleccionadas.value = []
|
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 = () => {
|
const siguientePaso = () => {
|
||||||
if (!puntajeValido.value) return
|
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) {
|
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 {
|
} else {
|
||||||
|
// Hay diferencia, ir al paso 2
|
||||||
paso.value = 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 toggleCategoria = (key: string) => {
|
||||||
const index = categoriasSeleccionadas.value.indexOf(key)
|
const index = categoriasSeleccionadas.value.indexOf(key)
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
|
|||||||
@@ -22,6 +22,20 @@
|
|||||||
Sistema de Catación de Café
|
Sistema de Catación de Café
|
||||||
</p>
|
</p>
|
||||||
<hr class="cata-divider my-6 max-w-xs mx-auto">
|
<hr class="cata-divider my-6 max-w-xs mx-auto">
|
||||||
|
|
||||||
|
<!-- Botón Calculadora SCAA -->
|
||||||
|
<div class="mt-6">
|
||||||
|
<button
|
||||||
|
class="cata-button px-6 py-3"
|
||||||
|
@click="mostrarCalculadora = true"
|
||||||
|
>
|
||||||
|
<UIcon name="i-lucide-calculator" class="w-5 h-5 inline mr-2" />
|
||||||
|
Calculadora SCAA
|
||||||
|
</button>
|
||||||
|
<p class="text-xs cata-text opacity-60 mt-2">
|
||||||
|
Calcula el SCAA Score sin iniciar sesión
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Loading State -->
|
<!-- Loading State -->
|
||||||
@@ -194,6 +208,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal Calculadora SCAA -->
|
||||||
|
<CataModalAsignacionRapida
|
||||||
|
v-model="mostrarCalculadora"
|
||||||
|
modo="calculadora"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -215,6 +234,7 @@ const { inicializar, tieneSecion } = useIndexedDB()
|
|||||||
const mostrarFormulario = ref(false)
|
const mostrarFormulario = ref(false)
|
||||||
const mostrarDialogoNueva = ref(false)
|
const mostrarDialogoNueva = ref(false)
|
||||||
const creandoSesion = ref(false)
|
const creandoSesion = ref(false)
|
||||||
|
const mostrarCalculadora = ref(false)
|
||||||
const formData = reactive({
|
const formData = reactive({
|
||||||
catador: '',
|
catador: '',
|
||||||
cantidadMuestras: 5,
|
cantidadMuestras: 5,
|
||||||
|
|||||||
Reference in New Issue
Block a user