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">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">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">ACCIONES</th>
</template>

View File

@ -88,6 +88,11 @@ const serialsArray = computed(() => {
.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(() => {
if (!hasSerials.value) return { valid: true, message: '' };
@ -114,6 +119,13 @@ const serialsValidation = computed(() => {
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 */
const loadWarehouses = () => {
loading.value = true;
@ -305,11 +317,15 @@ watch(() => props.show, (isShown) => {
<FormInput
v-model="form.quantity"
type="number"
:min="allowsDecimals ? '0.001' : '1'"
:step="allowsDecimals ? '0.001' : '1'"
:placeholder="allowsDecimals ? '0.000' : '0'"
min="1"
step="1"
placeholder="0"
:disabled="quantityLockedBySerials"
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" />
</div>
@ -321,7 +337,7 @@ watch(() => props.show, (isShown) => {
NÚMEROS DE SERIE
</label>
</div>
<SerialInputList v-model="serialsList" />
<SerialInputList v-model="serialsList" @update:model-value="updateQuantityFromSerials" />
<!-- Validación -->
<div class="mt-2 flex items-center justify-between">

View File

@ -479,18 +479,15 @@ watch(() => form.warehouse_id, (newWarehouseId, oldWarehouseId) => {
<input
v-model="item.quantity"
type="number"
:min="item.allows_decimals ? '0.001' : '1'"
:step="item.allows_decimals ? '0.001' : '1'"
:placeholder="item.allows_decimals ? '0.000' : '0'"
min="1"
step="1"
placeholder="0"
: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"
/>
<p v-if="item.track_serials && canUseSerials(item)" class="mt-1 text-xs text-gray-500 dark:text-gray-400">
Controlado por seriales
</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>
<!-- 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 gap-2">
<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>
<button @click="emit('close')" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
<GoogleIcon name="close" class="text-xl" />

View File

@ -292,7 +292,8 @@ const ticketService = {
quantity: 1,
unit_price: bundlePrice,
serials: data.serials,
is_bundle: true
is_bundle: true,
components: data.components
};
})
];
@ -333,10 +334,32 @@ const ticketService = {
yPosition += 4;
// Números de serie (si existen)
// Componentes del paquete (mostrar productos con SKU y sus seriales)
if (item.is_bundle && item.components && item.components.length > 0) {
doc.setFontSize(7);
doc.setFont('helvetica', 'normal');
doc.setTextColor(...darkGrayColor);
item.components.forEach((comp) => {
const compName = comp.product_name || comp.name || comp.inventory?.name || '';
const compSku = comp.sku || comp.inventory?.sku || '';
const label = compSku ? ` - ${compSku}` : ` - ${compName}`;
doc.text(label, leftMargin, yPosition);
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(6);
doc.setFontSize(7);
doc.setFont('helvetica', 'normal');
doc.setTextColor(...darkGrayColor);
@ -346,6 +369,7 @@ const ticketService = {
yPosition += 3;
});
}
}
yPosition += 2;
});