feat: replace SVG icons with Lucide icons in reference data pages

- Updated the "Nueva Sección" button in customs_sections, customs_warehouses, incoterms, invoice_types, material_types, payment_methods, pedimento_codes, pedimento_regimens, sectors, states, transport_modes, and transport_types pages to use the Plus icon from Lucide.
- Updated the "Actualizar" button in the same pages to use the RefreshCw icon from Lucide.
- Added a new edit dialog component for customs brokers with a comprehensive form for editing broker details, including validation and loading states.
This commit is contained in:
2025-11-24 16:43:53 -06:00
parent cdc3788a05
commit dcd42583d9
63 changed files with 1232 additions and 1810 deletions

View File

@@ -30,8 +30,6 @@
original_date: datesData.original_date ? datesData.original_date.substring(0, 10) : '',
start_date: datesData.start_date ? datesData.start_date.substring(0, 10) : '',
end_date: datesData.end_date ? datesData.end_date.substring(0, 10) : '',
capture_date: datesData.capture_date ? datesData.capture_date.substring(0, 10) : '',
capture_time: datesData.capture_time || ''
};
}
} else {
@@ -48,7 +46,6 @@
original_date: '',
start_date: '',
end_date: '',
capture_date: '',
capture_time: ''
};
}
@@ -163,27 +160,7 @@
type="date"
bind:value={formData.end_date}
/>
</div>
<!-- Fecha de Captura -->
<div class="space-y-2">
<Label for="capture_date">Fecha de Captura</Label>
<Input
id="capture_date"
type="date"
bind:value={formData.capture_date}
/>
</div>
<!-- Hora de Captura -->
<div class="space-y-2">
<Label for="capture_time">Hora de Captura</Label>
<Input
id="capture_time"
type="time"
bind:value={formData.capture_time}
/>
</div>
</div>
</div>
</div>
</Card.Content>

View File

@@ -5,26 +5,175 @@
import * as Select from '$lib/components/ui/select';
import type { Pedimento } from '$lib/api/dashboard/a76/pedimentos';
import type { PedimentoCode } from '$lib/api/dashboard/refrence_data/pedimento_codes';
import type { CustomsSection } from '$lib/api/dashboard/refrence_data/customs_sections';
import type { CustomsBroker } from '$lib/api/dashboard/a76/customs-brokers';
import type { ClientProvider } from '$lib/api/dashboard/a76/clients-providers';
import type { CodePedimentoRegimen } from '$lib/api/dashboard/refrence_data/code_pedimento_regimens';
let {
pedimento,
formData = $bindable(),
pedimentoCodes = []
pedimentoCodes = [],
customsSections = [],
customsBrokers = [],
clients = [],
codePedimentoRegimens = []
}: {
pedimento: Pedimento | null;
formData?: any;
pedimentoCodes?: PedimentoCode[];
customsSections?: CustomsSection[];
customsBrokers?: CustomsBroker[];
clients?: ClientProvider[];
codePedimentoRegimens?: CodePedimentoRegimen[];
} = $props();
// Debug: verificar que los datos llegan
$effect(() => {
console.log('pedimentoCodes:', pedimentoCodes.length, 'items');
// Extraer regímenes únicos de codePedimentoRegimens
const uniqueRegimens = $derived(
Array.from(new Set(codePedimentoRegimens.map(r => r.regimen_code).filter((code): code is string => code !== null)))
.sort()
.map(code => ({ code, label: code }))
);
// Funciones de mapeo entre type_code (E/I) y operation_type (1/2)
function typeCodeToOperationType(typeCode: string | null | undefined): number | null {
if (!typeCode) return null;
// E = Exportación = 1, I = Importación = 2
if (typeCode.toUpperCase() === 'E') return 1;
if (typeCode.toUpperCase() === 'I') return 2;
return null;
}
function operationTypeToTypeCode(operationType: number | null | undefined): string | null {
if (operationType === null || operationType === undefined) return null;
// 1 = Exportación = E, 2 = Importación = I
if (operationType === 1) return 'E';
if (operationType === 2) return 'I';
return null;
}
// Opciones filtradas para Régimen y Tipo de Operación basadas en las selecciones actuales
// NOTA: La Clave NO se filtra, siempre muestra todas las opciones
const filteredRegimens = $derived(() => {
// Si hay clave o tipo de operación seleccionado, filtrar
if (formData.pedimento_code || formData.operation_type !== null) {
const matches = codePedimentoRegimens.filter(r => {
const matchesCode = !formData.pedimento_code || r.pedimento_code === formData.pedimento_code;
const matchesType = formData.operation_type === null || r.type_code === operationTypeToTypeCode(formData.operation_type);
return matchesCode && matchesType;
});
const validRegimens = new Set(matches.map(m => m.regimen_code).filter((code): code is string => code !== null));
return Array.from(validRegimens).sort().map(code => ({ code, label: code }));
}
return uniqueRegimens;
});
const filteredOperationTypes = $derived(() => {
// Si hay clave o régimen seleccionado, filtrar
if (formData.pedimento_code || formData.regime) {
const matches = codePedimentoRegimens.filter(r => {
const matchesCode = !formData.pedimento_code || r.pedimento_code === formData.pedimento_code;
const matchesRegime = !formData.regime || r.regimen_code === formData.regime;
return matchesCode && matchesRegime;
});
const validTypes = new Set(matches.map(m => typeCodeToOperationType(m.type_code)).filter(t => t !== null));
return operationOptions.filter(opt => validTypes.has(opt.value));
}
return operationOptions;
});
// Reactive synchronization between Clave, Régimen, and Tipo de Operación
// REGLA: La Clave es el campo principal y NUNCA se modifica automáticamente
// Solo se auto-llenan Régimen y Tipo de Operación basándose en la Clave
// Cuando cambia la Clave del Pedimento
$effect(() => {
const currentCode = formData.pedimento_code;
if (!currentCode) return;
const matches = codePedimentoRegimens.filter(r => r.pedimento_code === currentCode);
if (matches.length === 0) return;
// Verificar si los valores actuales de régimen y tipo son válidos para esta clave
const currentIsValid = matches.some(m => {
const matchesRegime = !formData.regime || m.regimen_code === formData.regime;
const matchesType = formData.operation_type === null || m.type_code === operationTypeToTypeCode(formData.operation_type);
return matchesRegime && matchesType;
});
// Si los valores actuales son válidos, NO auto-llenar
if (currentIsValid && (formData.regime || formData.operation_type !== null)) {
return;
}
// Si solo hay un match y no hay valores válidos, auto-llenar
if (matches.length === 1) {
const match = matches[0];
if (match.regimen_code && formData.regime !== match.regimen_code) {
formData.regime = match.regimen_code;
}
const expectedOpType = typeCodeToOperationType(match.type_code);
if (expectedOpType !== null && formData.operation_type !== expectedOpType) {
formData.operation_type = expectedOpType;
}
}
});
// Cuando cambia el Régimen
$effect(() => {
const currentRegime = formData.regime;
if (!currentRegime) return;
const matches = codePedimentoRegimens.filter(r => r.regimen_code === currentRegime);
if (matches.length === 0) return;
// Si hay clave seleccionada, solo validar (NO auto-llenar tipo de operación)
if (formData.pedimento_code) {
const exactMatch = matches.find(m => m.pedimento_code === formData.pedimento_code);
}
// Si hay tipo de operación pero no clave, no hacer nada
// (el usuario debe seleccionar la clave primero)
});
// Cuando cambia el Tipo de Operación
$effect(() => {
const currentType = formData.operation_type;
if (currentType === null || currentType === undefined) return;
const expectedTypeCode = operationTypeToTypeCode(currentType);
const matches = codePedimentoRegimens.filter(r => r.type_code === expectedTypeCode);
if (matches.length === 0) return;
// Si hay clave seleccionada, actualizar régimen (forzar si no hay match exacto)
if (formData.pedimento_code) {
const exactMatch = matches.find(m => m.pedimento_code === formData.pedimento_code);
if (exactMatch?.regimen_code && formData.regime !== exactMatch.regimen_code) {
formData.regime = exactMatch.regimen_code;
} else if (!exactMatch) {
// No hay match exacto - buscar cualquier match con la clave actual
const allMatchesForClave = codePedimentoRegimens.filter(r => r.pedimento_code === formData.pedimento_code);
if (allMatchesForClave.length > 0) {
// Forzar el régimen al primer match disponible para esta clave
const firstMatch = allMatchesForClave[0];
if (firstMatch.regimen_code) formData.regime = firstMatch.regimen_code;
}
}
}
// Si hay régimen pero no clave, no hacer nada
// (el usuario debe seleccionar la clave primero)
});
// Obtener el año actual (últimos 2 dígitos)
const currentYear = String(new Date().getFullYear()).slice(-2);
// Inicializar formData con los valores del pedimento (o vacío si es null)
if (!formData) {
formData = {
year: pedimento?.year || '',
year: pedimento?.year || currentYear,
customs_office: pedimento?.customs_office || '',
license: pedimento?.license || '',
pedimento_number: pedimento?.pedimento_number || '',
@@ -41,6 +190,13 @@
};
}
// Asegurar que el año siempre esté actualizado con el año actual
$effect(() => {
if (formData && !pedimento?.year) {
formData.year = currentYear;
}
});
const operationOptions = [
{ value: 1, label: 'Exportación' },
{ value: 2, label: 'Importación' },
@@ -85,6 +241,8 @@
placeholder="23"
maxlength={2}
class="text-center"
disabled
readonly
/>
</div>
@@ -92,15 +250,28 @@
<div class="pb-2 text-2xl font-semibold text-muted-foreground">-</div>
<!-- Aduana -->
<div class="space-y-2 w-16">
<div class="space-y-2 w-20">
<Label for="customs_office">Aduana</Label>
<Input
id="customs_office"
bind:value={formData.customs_office}
placeholder="01"
maxlength={2}
class="text-center"
/>
<Select.Root
type="single"
value={formData.customs_office || ''}
onValueChange={(v: string) => formData.customs_office = v ?? ''}
>
<Select.Trigger class="w-full">
<span class="truncate">
{formData.customs_office || 'Sel...'}
</span>
</Select.Trigger>
<Select.Content class="max-w-[300px] max-h-[300px]">
{#each customsSections as section}
<Select.Item value={section.customs_code}>
<span class="truncate overflow-hidden text-ellipsis whitespace-nowrap" title={`${section.customs_code} - ${section.section_name}`}>
{section.customs_code} - {section.section_name}
</span>
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<!-- Separador -->
@@ -109,13 +280,26 @@
<!-- Patente -->
<div class="space-y-2 w-24">
<Label for="license">Patente</Label>
<Input
id="license"
bind:value={formData.license}
placeholder="1234"
maxlength={4}
class="text-center"
/>
<Select.Root
type="single"
value={formData.license || ''}
onValueChange={(v: string) => formData.license = v ?? ''}
>
<Select.Trigger class="w-full">
<span class="truncate">
{formData.license || 'Sel...'}
</span>
</Select.Trigger>
<Select.Content class="max-w-[300px] max-h-[300px]">
{#each customsBrokers as broker}
<Select.Item value={broker.license}>
<span class="truncate overflow-hidden text-ellipsis whitespace-nowrap" title={`${broker.broker_key} - ${broker.name || ''}`}>
{broker.broker_key} - {broker.name || ''}
</span>
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<!-- Separador -->
@@ -137,29 +321,24 @@
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- ID del Cliente -->
<div class="space-y-2">
<Label for="client_id">ID del Cliente</Label>
<Input
id="client_id"
type="number"
bind:value={formData.client_id}
placeholder="Ej: 123"
/>
</div>
<!-- Tipo de Operación -->
<div class="space-y-2">
<Label for="operation_type">Tipo de Operación</Label>
<Label for="client_id">Cliente</Label>
<Select.Root
type="single"
value={String(formData.operation_type ?? '')}
onValueChange={(v: string) => formData.operation_type = v ? Number(v) : null}
value={String(formData.client_id ?? '')}
onValueChange={(v: string) => formData.client_id = v ? Number(v) : null}
>
<Select.Trigger class="w-full">
{operationOptions.find(o => o.value === formData.operation_type)?.label || 'Seleccionar...'}
<span class="truncate">
{clients.find(c => c.id === formData.client_id)?.name || 'Seleccionar cliente...'}
</span>
</Select.Trigger>
<Select.Content>
{#each operationOptions as option}
<Select.Item value={String(option.value)} label={option.label} />
<Select.Content class="max-h-[300px]">
{#each clients as client}
<Select.Item value={String(client.id)} label={client.name}>
<span class="truncate overflow-hidden text-ellipsis whitespace-nowrap" title={client.name}>
{client.name}
</span>
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
@@ -176,40 +355,81 @@
/>
</div>
<!-- Clave del Pedimento -->
<div class="space-y-2">
<Label for="pedimento_code">Clave del Pedimento</Label>
<Select.Root
type="single"
value={formData.pedimento_code || ''}
onValueChange={(v: string) => formData.pedimento_code = v ?? ''}
>
<Select.Trigger class="w-full">
<span class="truncate">
{pedimentoCodes.find(o => o.code === formData.pedimento_code)?.code || 'Seleccionar...'}
</span>
</Select.Trigger>
<Select.Content class="max-w-[600px]">
{#each pedimentoCodes as code}
<Select.Item value={code.code}>
<span class="truncate overflow-hidden text-ellipsis whitespace-nowrap" title={`${code.code} - ${code.description}`}>
{code.code} - {code.description}
</span>
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
</div>
<!-- Régimen -->
<div class="space-y-2">
<Label for="regime">Régimen</Label>
<Input
id="regime"
bind:value={formData.regime}
placeholder="Ej: IMD"
/>
</div>
<!-- Fila: Clave (2), Régimen (3), Tipo de Operación (11) -->
<div class="flex items-end gap-2">
<!-- Clave del Pedimento -->
<div class="space-y-2 w-20">
<Label for="pedimento_code">Clave</Label>
<Select.Root
type="single"
value={formData.pedimento_code || ''}
onValueChange={(v: string) => formData.pedimento_code = v ?? ''}
>
<Select.Trigger class="w-full">
<span class="truncate">
{pedimentoCodes.find(o => o.code === formData.pedimento_code)?.code || 'Sel...'}
</span>
</Select.Trigger>
<Select.Content class="max-w-[200px]">
{#each pedimentoCodes as code}
<Select.Item value={code.code}>
<span class="truncate overflow-hidden text-ellipsis whitespace-nowrap" title={`${code.code} - ${code.description}`}>
{code.code} - {code.description}
</span>
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<!-- Régimen -->
<div class="space-y-2 w-24">
<Label for="regime">Régimen</Label>
<Select.Root
type="single"
value={formData.regime || ''}
onValueChange={(v: string) => formData.regime = v ?? ''}
>
<Select.Trigger class="w-full">
<span class="truncate">
{formData.regime || 'Sel...'}
</span>
</Select.Trigger>
<Select.Content class="max-w-[200px]">
{#each filteredRegimens() as regimen}
<Select.Item value={regimen.code}>
<span class="truncate overflow-hidden text-ellipsis whitespace-nowrap" title={regimen.code}>
{regimen.code}
</span>
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<!-- Tipo de Operación -->
<div class="space-y-2 flex-1">
<Label for="operation_type">Tipo de Operación</Label>
<Select.Root
type="single"
value={String(formData.operation_type ?? '')}
onValueChange={(v: string) => formData.operation_type = v ? Number(v) : null}
>
<Select.Trigger class="w-full">
{operationOptions.find(o => o.value === formData.operation_type)?.label || 'Seleccionar...'}
</Select.Trigger>
<Select.Content>
{#each filteredOperationTypes() as option}
<Select.Item value={String(option.value)} label={option.label} />
{/each}
</Select.Content>
</Select.Root>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- Estado -->
<div class="space-y-2">