|
|
|
|
@@ -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<Record<string, string>>({});
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<div class="space-y-3">
|
|
|
|
|
@@ -569,7 +657,7 @@
|
|
|
|
|
? 'border-red-500 focus-visible:ring-red-500'
|
|
|
|
|
: ''}"
|
|
|
|
|
maxlength={10}
|
|
|
|
|
onblur={() => validateField('material_key')}
|
|
|
|
|
onblur={handleMaterialBlur}
|
|
|
|
|
/>
|
|
|
|
|
<Button type="button" variant="outline" size="icon" onclick={openMaterialSearch}>
|
|
|
|
|
<Folder class="h-4 w-4" />
|
|
|
|
|
@@ -629,7 +717,7 @@
|
|
|
|
|
? 'border-red-500 focus-visible:ring-red-500'
|
|
|
|
|
: ''}"
|
|
|
|
|
maxlength={5}
|
|
|
|
|
onblur={() => validateField('unit_of_measure')}
|
|
|
|
|
onblur={handleUnitBlur}
|
|
|
|
|
/>
|
|
|
|
|
<Button type="button" variant="outline" size="icon" onclick={openUnitOfMeasureSearch}>
|
|
|
|
|
<Folder class="h-4 w-4" />
|
|
|
|
|
@@ -676,9 +764,10 @@
|
|
|
|
|
<Input
|
|
|
|
|
id="us_fraction"
|
|
|
|
|
bind:value={formData.us_fraction}
|
|
|
|
|
placeholder="Fracción americana"
|
|
|
|
|
class="flex-1"
|
|
|
|
|
maxlength={16}
|
|
|
|
|
placeholder="0000.00.00.00"
|
|
|
|
|
class="flex-1 font-mono tracking-wider"
|
|
|
|
|
maxlength={13}
|
|
|
|
|
oninput={formatUSFraction}
|
|
|
|
|
/>
|
|
|
|
|
<Button type="button" variant="outline" size="icon" onclick={openUSFractionSearch}>
|
|
|
|
|
<Folder class="h-4 w-4" />
|
|
|
|
|
|