feat: actualizar nombres de columnas y mejorar gestión de seriales en componentes

This commit is contained in:
Juan Felipe Zapata Moreno 2026-02-19 21:45:23 -06:00
parent 2bb50c48c9
commit e51f3fad0f
5 changed files with 57 additions and 20 deletions

View File

@ -94,7 +94,7 @@ onMounted(() => {
<th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">NOMBRE</th> <th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">NOMBRE</th>
<th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">SKU</th> <th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">SKU</th>
<th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">COMPONENTES</th> <th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">COMPONENTES</th>
<th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">STOCK</th> <th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">PAQ. ESTIMADO</th>
<th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">PRECIO</th> <th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">PRECIO</th>
<th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">ACCIONES</th> <th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">ACCIONES</th>
</template> </template>

View File

@ -88,6 +88,11 @@ const serialsArray = computed(() => {
.filter(s => s.length > 0); .filter(s => s.length > 0);
}); });
// Si tiene seriales y no permite decimales, la cantidad se controla por seriales
const quantityLockedBySerials = computed(() => {
return hasSerials.value && !allowsDecimals.value;
});
const serialsValidation = computed(() => { const serialsValidation = computed(() => {
if (!hasSerials.value) return { valid: true, message: '' }; if (!hasSerials.value) return { valid: true, message: '' };
@ -114,6 +119,13 @@ const serialsValidation = computed(() => {
return { valid: true, message: '' }; return { valid: true, message: '' };
}); });
// Actualizar cantidad automáticamente cuando cambian los seriales
const updateQuantityFromSerials = () => {
if (!quantityLockedBySerials.value) return;
const count = serialsArray.value.length;
form.quantity = count > 0 ? count : 1;
};
/** Métodos */ /** Métodos */
const loadWarehouses = () => { const loadWarehouses = () => {
loading.value = true; loading.value = true;
@ -305,11 +317,15 @@ watch(() => props.show, (isShown) => {
<FormInput <FormInput
v-model="form.quantity" v-model="form.quantity"
type="number" type="number"
:min="allowsDecimals ? '0.001' : '1'" min="1"
:step="allowsDecimals ? '0.001' : '1'" step="1"
:placeholder="allowsDecimals ? '0.000' : '0'" placeholder="0"
:disabled="quantityLockedBySerials"
required required
/> />
<p v-if="quantityLockedBySerials" class="mt-1 text-xs text-gray-500 dark:text-gray-400">
Controlado por seriales
</p>
<FormError :message="form.errors?.quantity" /> <FormError :message="form.errors?.quantity" />
</div> </div>
@ -321,7 +337,7 @@ watch(() => props.show, (isShown) => {
NÚMEROS DE SERIE NÚMEROS DE SERIE
</label> </label>
</div> </div>
<SerialInputList v-model="serialsList" /> <SerialInputList v-model="serialsList" @update:model-value="updateQuantityFromSerials" />
<!-- Validación --> <!-- Validación -->
<div class="mt-2 flex items-center justify-between"> <div class="mt-2 flex items-center justify-between">

View File

@ -479,18 +479,15 @@ watch(() => form.warehouse_id, (newWarehouseId, oldWarehouseId) => {
<input <input
v-model="item.quantity" v-model="item.quantity"
type="number" type="number"
:min="item.allows_decimals ? '0.001' : '1'" min="1"
:step="item.allows_decimals ? '0.001' : '1'" step="1"
:placeholder="item.allows_decimals ? '0.000' : '0'" placeholder="0"
:disabled="item.track_serials && canUseSerials(item)" :disabled="item.track_serials && canUseSerials(item)"
class="w-full px-2 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed" class="w-full px-2 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed"
/> />
<p v-if="item.track_serials && canUseSerials(item)" class="mt-1 text-xs text-gray-500 dark:text-gray-400"> <p v-if="item.track_serials && canUseSerials(item)" class="mt-1 text-xs text-gray-500 dark:text-gray-400">
Controlado por seriales Controlado por seriales
</p> </p>
<p v-else-if="item.allows_decimals" class="mt-1 text-xs text-gray-500 dark:text-gray-400">
Permite hasta 3 decimales (ej: 25.750 {{ item.unit_of_measure?.abbreviation }})
</p>
</div> </div>
<!-- Botón eliminar --> <!-- Botón eliminar -->

View File

@ -144,7 +144,7 @@ watch(() => props.show, (val) => {
<div class="flex items-center justify-between mb-5"> <div class="flex items-center justify-between mb-5">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<GoogleIcon name="assignment" class="text-2xl text-emerald-600" /> <GoogleIcon name="assignment" class="text-2xl text-emerald-600" />
<h3 class="text-lg font-bold text-gray-900 dark:text-gray-100">Exportar Kardex</h3> <h3 class="text-lg font-bold text-gray-900 dark:text-gray-100">Exportar reporte</h3>
</div> </div>
<button @click="emit('close')" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"> <button @click="emit('close')" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
<GoogleIcon name="close" class="text-xl" /> <GoogleIcon name="close" class="text-xl" />

View File

@ -292,7 +292,8 @@ const ticketService = {
quantity: 1, quantity: 1,
unit_price: bundlePrice, unit_price: bundlePrice,
serials: data.serials, serials: data.serials,
is_bundle: true is_bundle: true,
components: data.components
}; };
}) })
]; ];
@ -333,18 +334,41 @@ const ticketService = {
yPosition += 4; yPosition += 4;
// Números de serie (si existen) // Componentes del paquete (mostrar productos con SKU y sus seriales)
const serials = item.serials || item.serial_numbers || []; if (item.is_bundle && item.components && item.components.length > 0) {
if (serials.length > 0) { doc.setFontSize(7);
doc.setFontSize(6);
doc.setFont('helvetica', 'normal'); doc.setFont('helvetica', 'normal');
doc.setTextColor(...darkGrayColor); doc.setTextColor(...darkGrayColor);
serials.forEach((serial) => { item.components.forEach((comp) => {
const serialNumber = typeof serial === 'string' ? serial : serial.serial_number; const compName = comp.product_name || comp.name || comp.inventory?.name || '';
doc.text(` S/N: ${serialNumber}`, leftMargin, yPosition); const compSku = comp.sku || comp.inventory?.sku || '';
const label = compSku ? ` - ${compSku}` : ` - ${compName}`;
doc.text(label, leftMargin, yPosition);
yPosition += 3; yPosition += 3;
// Seriales del componente
const compSerials = comp.serial_numbers || comp.serials || [];
compSerials.forEach((serial) => {
const serialNumber = typeof serial === 'string' ? serial : serial.serial_number;
doc.text(`S/N: ${serialNumber}`, leftMargin, yPosition);
yPosition += 3;
});
}); });
} else {
// Números de serie para productos individuales (no bundle)
const serials = item.serials || item.serial_numbers || [];
if (serials.length > 0) {
doc.setFontSize(7);
doc.setFont('helvetica', 'normal');
doc.setTextColor(...darkGrayColor);
serials.forEach((serial) => {
const serialNumber = typeof serial === 'string' ? serial : serial.serial_number;
doc.text(` S/N: ${serialNumber}`, leftMargin, yPosition);
yPosition += 3;
});
}
} }
yPosition += 2; yPosition += 2;