diff --git a/frontend/src/lib/components/dashboard/goods/classes/forms/FixedAssetClassForm.svelte b/frontend/src/lib/components/dashboard/goods/classes/forms/FixedAssetClassForm.svelte index d5578a0c..3fb918c1 100644 --- a/frontend/src/lib/components/dashboard/goods/classes/forms/FixedAssetClassForm.svelte +++ b/frontend/src/lib/components/dashboard/goods/classes/forms/FixedAssetClassForm.svelte @@ -109,6 +109,10 @@ formData.import_tariff_type = snap.import_tariff_type ?? ''; formData.export_tariff_code = snap.export_tariff_code ?? ''; formData.export_tariff_type = snap.export_tariff_type ?? ''; + + // Reiniciar banderas de error al cargar datos + showErrors = false; + validationErrors = {}; } else { // Reset form when initialData is null (new class) formData.class_code = ''; @@ -128,12 +132,17 @@ formData.import_tariff_type = ''; formData.export_tariff_code = ''; formData.export_tariff_type = ''; + + // Reiniciar banderas de error al limpiar formulario + showErrors = false; + validationErrors = {}; } }); // Estado de validación let validationErrors = $state>({}); let showErrors = $state(false); + let isSubmitting = $state(false); // Estado de diálogos let showMaterialDialog = $state(false); @@ -262,6 +271,37 @@ searchMaterial = ''; } + // Fix 1: Buscar descripción de Tipo de Activo Fijo al perder el foco + async function handleMaterialBlur() { + validateField('material_key'); + const code = formData.material_key?.trim().toUpperCase(); + if (!code) { + formData.material_description = ''; + return; + } + // Primero buscar en la caché local (si ya se cargó el catálogo) + if (materialTypes.length > 0) { + const found = materialTypes.find((m) => m.key.toUpperCase() === code); + if (found) { + formData.material_key = found.key; + formData.material_description = found.description; + return; + } + } + // Si no está en caché, consultar el API + try { + const response = await materialTypesApi.get(code); + if (response.data) { + formData.material_key = response.data.key; + formData.material_description = response.data.description; + } else { + formData.material_description = '(Código no encontrado)'; + } + } catch { + formData.material_description = '(Código no encontrado)'; + } + } + async function openUnitOfMeasureSearch() { showUnitDialog = true; searchUnit = ''; @@ -276,6 +316,39 @@ searchUnit = ''; } + // Fix 1: Buscar descripción de U.M. Comercial al perder el foco + async function handleUnitBlur() { + validateField('unit_of_measure'); + const code = formData.unit_of_measure?.trim().toUpperCase(); + if (!code) { + formData.unit_of_measure_description = ''; + return; + } + // Primero buscar en la caché local (si ya se cargó el catálogo) + await loadUnitsOfMeasure(); + const found = unitsOfMeasureData.find((u) => u.code.toUpperCase() === code); + if (found) { + formData.unit_of_measure = found.code; + formData.unit_of_measure_description = found.description; + formData.unit_measure_key = found.claveMexicana; + } else { + formData.unit_of_measure_description = '(Código no encontrado)'; + } + } + + // Fix 2: Máscara para Fracción Americana (formato 0000.00.00.00 = 13 chars) + function formatUSFraction(event: Event) { + const input = event.target as HTMLInputElement; + // Extraer solo dígitos + const digits = input.value.replace(/\D/g, '').slice(0, 10); + // Construir la máscara insertando puntos en las posiciones correctas + let masked = digits; + if (digits.length > 4) masked = digits.slice(0, 4) + '.' + digits.slice(4); + if (digits.length > 6) masked = digits.slice(0, 4) + '.' + digits.slice(4, 6) + '.' + digits.slice(6); + if (digits.length > 8) masked = digits.slice(0, 4) + '.' + digits.slice(4, 6) + '.' + digits.slice(6, 8) + '.' + digits.slice(8); + formData.us_fraction = masked; + } + async function openUSFractionSearch() { showUSFractionDialog = true; searchUSFraction = ''; @@ -451,7 +524,8 @@ // Validar campo individual (para validación en blur) function validateField(fieldName: string) { - if (!showErrors) return; // Solo validar si ya se intentó guardar + // Bloquear validación repetida si: a) no se ha intentado guardar o b) está guardando + if (!showErrors || isSubmitting) return; const errors = { ...validationErrors }; @@ -496,22 +570,28 @@ validationErrors = errors; } - function handleSave() { - // Activar visualización de errores - showErrors = true; - + async function handleSave() { // Validar formulario if (!validateForm()) { + showErrors = true; toast.error('Por favor, complete todos los campos obligatorios'); return; } + // Activar bandera the submitting para que no se validen inputs al azar en el blur + isSubmitting = true; + // Tomamos una copia muerta de los datos actuales const dataToSave = $state.snapshot(formData); - // Ejecutamos el onSave pasándole la copia - if (onSave) { - onSave(dataToSave); + try { + // Ejecutamos el onSave pasándole la copia. Await en caso de que devuelva promesa. + if (onSave) { + await onSave(dataToSave); + } + } finally { + // Liberar el estado de subida solo después de que acabe todo el flujo + isSubmitting = false; } } @@ -522,9 +602,17 @@ } // Escuchar el evento de guardado del padre + import { onDestroy } from 'svelte'; + if (typeof document !== 'undefined') { document.addEventListener('save-form', handleSave); } + + onDestroy(() => { + if (typeof document !== 'undefined') { + document.removeEventListener('save-form', handleSave); + } + });
@@ -569,7 +657,7 @@ ? 'border-red-500 focus-visible:ring-red-500' : ''}" maxlength={10} - onblur={() => validateField('material_key')} + onblur={handleMaterialBlur} />
diff --git a/frontend/src/routes/dashboard/pedimentos/+page.svelte b/frontend/src/routes/dashboard/pedimentos/+page.svelte index cb07499a..d8283960 100644 --- a/frontend/src/routes/dashboard/pedimentos/+page.svelte +++ b/frontend/src/routes/dashboard/pedimentos/+page.svelte @@ -532,10 +532,6 @@
-