Juan Felipe Zapata Moreno 32949fe13a feat: búsqueda, tickets y gestión UI de paquetes- Integra búsqueda y visualización de paquetes en el POS.
- Desglosa detalles de paquetes en la generación de tickets.
- Mejora modales de edición y eliminación.
2026-02-16 23:34:22 -06:00

194 lines
7.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup>
import { computed } from 'vue';
import GoogleIcon from '@Shared/GoogleIcon.vue';
/** Props */
const props = defineProps({
item: {
type: Object,
required: true
}
});
/** Emits */
const emit = defineEmits(['update-quantity', 'remove', 'select-serials']);
/** Computados */
const formattedUnitPrice = computed(() => {
return new Intl.NumberFormat('es-MX', {
style: 'currency',
currency: 'MXN'
}).format(props.item.unit_price);
});
const formattedSubtotal = computed(() => {
const subtotal = props.item.unit_price * props.item.quantity;
return new Intl.NumberFormat('es-MX', {
style: 'currency',
currency: 'MXN'
}).format(subtotal);
});
const canIncrement = computed(() => {
// Si tiene seriales, no permitir incremento directo
if (props.item.track_serials) return false;
// Si permite decimales, incrementar en 1
const incrementValue = props.item.allows_decimals ? 1 : 1;
return (props.item.quantity + incrementValue) <= props.item.max_stock;
});
const canEditQuantity = computed(() => {
// No permitir edición directa si tiene seriales
return !props.item.track_serials;
});
const quantityStep = computed(() => {
return props.item.allows_decimals ? '0.001' : '1';
});
const quantityMin = computed(() => {
return props.item.allows_decimals ? '0.001' : '1';
});
const hasSerials = computed(() => props.item.track_serials);
const serialsSelected = computed(() => {
return props.item.serial_numbers && props.item.serial_numbers.length > 0;
});
const needsSerialSelection = computed(() => {
return hasSerials.value && (!serialsSelected.value || props.item.serial_numbers.length !== props.item.quantity);
});
/** Métodos */
const increment = () => {
if (canIncrement.value) {
const incrementValue = props.item.allows_decimals ? 1 : 1;
const newQuantity = props.item.quantity + incrementValue;
emit('update-quantity', props.item.item_key, newQuantity);
}
};
const decrement = () => {
const decrementValue = props.item.allows_decimals ? 1 : 1;
const minValue = props.item.allows_decimals ? 0.001 : 1;
if (props.item.quantity > minValue) {
const newQuantity = Math.max(minValue, props.item.quantity - decrementValue);
emit('update-quantity', props.item.item_key, newQuantity);
} else {
emit('remove', props.item.item_key);
}
};
const handleQuantityInput = (event) => {
const value = event.target.value;
const numValue = parseFloat(value);
if (!isNaN(numValue) && numValue > 0) {
emit('update-quantity', props.item.item_key, numValue);
}
};
const remove = () => {
emit('remove', props.item.item_key);
};
</script>
<template>
<div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 hover:border-indigo-300 dark:hover:border-indigo-600 transition-colors p-3">
<!-- Fila principal: Nombre y botón eliminar -->
<div class="flex items-start justify-between mb-2">
<div class="flex items-center gap-1.5 pr-2 min-w-0">
<span
v-if="item.is_bundle"
class="shrink-0 px-1.5 py-0.5 text-xs font-bold rounded bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400"
>
KIT
</span>
<h4 class="text-sm font-semibold text-gray-800 dark:text-gray-200 line-clamp-2">
{{ item.product_name }}
</h4>
</div>
<button
type="button"
class="shrink-0 w-6 h-6 flex items-center justify-center rounded-full bg-red-50 hover:bg-red-100 dark:bg-red-900/20 dark:hover:bg-red-900/30 text-red-600 dark:text-red-400 transition-colors"
@click="remove"
>
<GoogleIcon name="close" class="text-base" />
</button>
</div>
<!-- SKU y precio unitario -->
<div class="flex items-center gap-2 mb-1">
<span class="text-xs text-gray-500 dark:text-gray-400 font-mono">
{{ item.sku }}
</span>
<span class="text-xs text-gray-400 dark:text-gray-500"></span>
<span class="text-xs font-medium text-indigo-600 dark:text-indigo-400">
{{ formattedUnitPrice }}
</span>
</div>
<!-- Mensaje para productos con decimales -->
<div v-if="item.allows_decimals && item.unit_of_measure" class="mb-2">
<p class="text-xs text-gray-500 dark:text-gray-400">
{{ item.unit_of_measure.name }} ({{ item.unit_of_measure.abbreviation }}) - Permite decimales
</p>
</div>
<!-- Fila inferior: Controles de cantidad y subtotal -->
<div class="flex items-center justify-between">
<!-- Controles de cantidad -->
<div class="flex items-center gap-2">
<button
type="button"
class="w-7 h-7 flex items-center justify-center rounded-full bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-600 dark:text-gray-300 transition-colors"
@click="decrement"
>
<GoogleIcon
:name="item.quantity <= (item.allows_decimals ? 0.001 : 1) ? 'delete' : 'remove'"
class="text-base"
/>
</button>
<input
v-if="canEditQuantity"
type="number"
:value="item.quantity"
:min="quantityMin"
:step="quantityStep"
:max="item.max_stock"
@change="handleQuantityInput"
class="w-16 text-center text-base font-bold text-gray-800 dark:text-gray-200 bg-transparent border border-gray-300 dark:border-gray-600 rounded px-1 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
/>
<span v-else class="w-16 text-center text-base font-bold text-gray-800 dark:text-gray-200">
{{ item.quantity }}
</span>
<button
type="button"
class="w-7 h-7 flex items-center justify-center rounded-full transition-colors"
:class="{
'bg-indigo-100 hover:bg-indigo-200 dark:bg-indigo-900/30 dark:hover:bg-indigo-900/50 text-indigo-600 dark:text-indigo-400': canIncrement,
'bg-gray-100 dark:bg-gray-700 text-gray-400 dark:text-gray-500 cursor-not-allowed': !canIncrement
}"
:disabled="!canIncrement"
@click="increment"
>
<GoogleIcon name="add" class="text-base" />
</button>
</div>
<!-- Subtotal -->
<div class="text-right">
<p class="text-lg font-bold text-gray-900 dark:text-gray-100">
{{ formattedSubtotal }}
</p>
<p class="text-xs text-gray-500 dark:text-gray-400">
{{ item.quantity }} × {{ formattedUnitPrice }}
</p>
</div>
</div>
</div>
</template>