Files
plantillas-proyectos/frontend/src/lib/components/dashboard/pedimentos/edit/general-tab-form.svelte
2026-03-17 16:35:27 -06:00

1385 lines
48 KiB
Svelte

<script lang="ts">
import { onMount, untrack } from 'svelte';
import * as Card from '$lib/components/ui/card';
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import * as Select from '$lib/components/ui/select';
import { getLocale } from '$lib/paraglide/runtime';
import { getExchangeRateByDate } from '$lib/api/dashboard/a76/exchange-rate';
import { companyStore } from '$lib/stores/company.svelte';
import type { Pedimento } from '$lib/api/dashboard/a76/pedimentos';
import type { PedimentoCode } from '$lib/api/dashboard/reference_data/pedimento_codes';
import type { CustomsSection } from '$lib/api/dashboard/reference_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/reference_data/code_pedimento_regimens';
import IdentificadoresTabForm from './identifiers-tab-form.svelte';
import ExchangeRateDialog from '$lib/components/dashboard/exchange_rate/create-edit-dialog.svelte';
import { Calendar, Clock } from 'lucide-svelte';
import {
loadServerDate,
addDaysLocal,
getCurrentLocalYear,
getCurrentLocalDate,
getCurrentLocalTime
} from '$lib/date-utils';
import { useShortcuts } from '$lib/hooks/use-shortcuts';
import { obtenerAtajosPestanaGeneral } from '$lib/config/shortcuts/dashboard/pedimentos/tabs/general';
import { shortcutStore } from '$lib/stores/shortcut-store';
import { focusStore, interactionMode } from '$lib/stores/focus-store';
type PedimentoTransportCatalog = {
code: string;
transport_en: string;
transport_es: string;
payment_date_code: 'E' | 'P' | string;
};
let {
pedimento,
formData = $bindable(),
identificadoresFormData = $bindable(),
pedimentoCodes = [],
customsSections = [],
customsBrokers = [],
clients = [],
codePedimentoRegimens = [],
pedimentoTransportCatalog = [],
isActive = false
}: {
pedimento: Pedimento | null;
formData?: any;
identificadoresFormData?: any;
pedimentoCodes?: PedimentoCode[];
customsSections?: CustomsSection[];
customsBrokers?: CustomsBroker[];
clients?: ClientProvider[];
codePedimentoRegimens?: CodePedimentoRegimen[];
pedimentoTransportCatalog?: PedimentoTransportCatalog[];
isActive?: boolean;
} = $props();
// Active navigation section state
let activeSection = $state('fechas');
// Focus first input when switching sections via mouse
// Shortcuts will override this by focusing the trigger themselves
$effect(() => {
// Accedemos a activeSection para que el efecto dependa de él
if (activeSection) {
// Usamos untrack para que no dependa de interactionMode
// Esto evita que se dispare el foco al empezar a escribir (cambio de mouse a keyboard)
const mode = untrack(() => {
let currentMode;
interactionMode.subscribe((v) => (currentMode = v))();
return currentMode;
});
if (mode === 'keyboard') {
focusStore.request('first-input');
}
}
});
$effect(() => {
if (isActive) {
shortcutStore.register(
'General Tab Navigation',
obtenerAtajosPestanaGeneral({
cambiarSeccion: (seccion) => (activeSection = seccion)
})
);
return () => {
shortcutStore.clear('General Tab Navigation');
};
}
});
// Exchange Rate Check State
let showExchangeRateDialog = $state(false);
let missingExchangeRateDate = $state('');
// 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 (exp/imp)
function typeCodeToOperationType(typeCode: string | null | undefined): string | null {
if (!typeCode) return null;
// E = Exportación = exp, I = Importación = imp
if (typeCode.toUpperCase() === 'E') return 'exp';
if (typeCode.toUpperCase() === 'I') return 'imp';
return null;
}
function operationTypeToTypeCode(operationType: string | null | undefined): string | null {
if (operationType === null || operationType === undefined) return null;
// exp = Exportación = E, imp = Importación = I
if (operationType === 'exp') return 'E';
if (operationType === 'imp') return 'I';
return null;
}
// Opciones constantes
const operationOptions = [
{ value: 'exp', label: 'Exportación' },
{ value: 'imp', label: 'Importación' }
];
// Track previous values to detect changes
let previousPedimentoCode = $state('');
let previousOperationType = $state<string | null>(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.by(() => {
if (!formData) return uniqueRegimens;
// Si hay clave o tipo de operación seleccionado, filtrar
if (
formData.pedimento_code ||
(formData.operation_type !== null && formData.operation_type !== undefined)
) {
const expectedTypeCode = operationTypeToTypeCode(formData.operation_type);
const matches = codePedimentoRegimens.filter((r) => {
const matchesCode =
!formData.pedimento_code || r.pedimento_code === formData.pedimento_code;
const matchesType = !expectedTypeCode || r.type_code === expectedTypeCode;
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;
});
// Determinar si el régimen debe ser seleccionable o readonly
const hasMultipleRegimens = $derived.by(() => {
if (!formData) return false;
return filteredRegimens.length > 1;
});
const filteredOperationTypes = $derived.by(() => {
if (!formData) return operationOptions;
// Solo filtrar por la clave del pedimento (no por régimen)
// Esto permite cambiar entre Exportación e Importación libremente
if (formData.pedimento_code) {
const matches = codePedimentoRegimens.filter(
(r) => r.pedimento_code === formData.pedimento_code
);
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(() => {
if (!formData) return;
const currentCode = formData.pedimento_code;
// Detectar si la clave cambió
const codeChanged = currentCode !== previousPedimentoCode;
// Solo procesar si la clave cambió
if (!codeChanged) return;
// Actualizar el tracking
previousPedimentoCode = currentCode;
if (!currentCode) return;
const matches = codePedimentoRegimens.filter((r) => r.pedimento_code === currentCode);
if (matches.length === 0) return;
// Auto-llenar con el primer match
const firstMatch = matches[0];
if (firstMatch.regimen_code) {
formData.regime = firstMatch.regimen_code;
}
const expectedOpType = typeCodeToOperationType(firstMatch.type_code);
if (expectedOpType !== null) {
formData.operation_type = expectedOpType;
}
});
// Cuando cambia el Régimen
$effect(() => {
if (!formData) return;
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
// Optimizado: solo ejecutar cuando cambien los campos específicos relevantes
$effect(() => {
if (!formData) return;
// Solo rastrear los campos que realmente importan
const currentType = formData.operation_type;
const currentCode = formData.pedimento_code;
const currentRegime = formData.regime;
// Detectar si el tipo de operación cambió
const typeChanged = currentType !== previousOperationType;
previousOperationType = currentType;
// Si no hay clave seleccionada o tipo no definido, salir temprano
if (!currentCode || currentType === null || currentType === undefined) return;
const expectedTypeCode = operationTypeToTypeCode(currentType);
// Buscar matches para esta combinación de clave + tipo de operación
const matches = codePedimentoRegimens.filter(
(r) => r.pedimento_code === currentCode && r.type_code === expectedTypeCode
);
if (matches.length === 0) return;
// Obtener regímenes válidos para esta combinación
const validRegimens = new Set(
matches.map((m) => m.regimen_code).filter((code): code is string => code !== null)
);
// Si el tipo de operación cambió O el régimen actual no es válido, actualizar el régimen
if (typeChanged || !currentRegime || !validRegimens.has(currentRegime)) {
const firstMatch = matches[0];
if (firstMatch?.regimen_code) {
formData.regime = firstMatch.regimen_code;
}
}
});
// --- Inicialización ---
const currentYear = getCurrentLocalYear();
const currentDate = getCurrentLocalDate();
const currentTime = getCurrentLocalTime();
const end_date = addDaysLocal(currentDate, 4);
const payment_date = addDaysLocal(end_date, 2);
// Inicializar formData con los valores del pedimento (o vacío si es null)
if (!formData) {
const datesData = pedimento?.pedimento_dates;
const incrementablesData = pedimento?.pedimento_incrementables;
const decrementablesData = pedimento?.pedimento_decrementables;
const indexesData = pedimento?.pedimento_indexes;
const configAdditionalData = pedimento?.pedimento_config_additional;
formData = {
year: pedimento?.year || currentYear,
customs_office: pedimento?.customs_office || '',
license: pedimento?.license || '',
pedimento_number: pedimento?.pedimento_number || '',
client_id: pedimento?.client_id ?? null,
operation_type: pedimento?.operation_type ?? null,
pedimento_type: pedimento?.pedimento_type || 'consolidated',
pedimento_code: pedimento?.pedimento_code || '',
regime: pedimento?.regime || '',
status: pedimento?.status || 'MODIFICABLE',
usd_value: pedimento?.usd_value ?? null,
paid_price: pedimento?.paid_price ?? null,
gross_weight: pedimento?.gross_weight ?? null,
exchange_rate: pedimento?.exchange_rate ?? null,
// Date fields - Cargamos convirtiendo a local para el usuario
entry_date: loadServerDate(datesData?.entry_date) || currentDate,
end_date: loadServerDate(datesData?.end_date) || end_date,
payment_date: loadServerDate(datesData?.payment_date) || payment_date,
extraction_date: loadServerDate(datesData?.entry_date) || null,
original_date: loadServerDate(datesData?.original_date) || null,
// Mapeos de sub-recursos
pedimento_customs_offices: {
dispatch_customs: pedimento?.pedimento_customs_offices?.dispatch_customs || '',
entry_exit_customs: pedimento?.pedimento_customs_offices?.entry_exit_customs || ''
},
pedimento_transport_means: {
destination: pedimento?.pedimento_transport_means?.destination || null,
entry_exit: pedimento?.pedimento_transport_means?.entry_exit || '',
arrival: pedimento?.pedimento_transport_means?.arrival || '',
departure: pedimento?.pedimento_transport_means?.departure || ''
},
// Campos de captura automática (Local)
fecha_captura: currentDate,
hora_captura: currentTime
};
}
// Flag para evitar loops en actualización de año
let yearInitialized = false;
// Asegurar que el año siempre esté actualizado con el año actual (solo una vez)
$effect(() => {
if (formData && !pedimento?.year && !yearInitialized) {
formData.year = currentYear;
yearInitialized = true;
}
});
// Track para evitar loops en obtención de tipo de cambio
let lastFetchedDate: string | null = null;
let lastCompanyId: number | null = null;
function getTransportByCode(code: string | null | undefined): PedimentoTransportCatalog | undefined {
if (!code) return undefined;
return pedimentoTransportCatalog.find((m) => m.code === code);
}
function getTransportLabel(mode: PedimentoTransportCatalog | undefined): string {
if (!mode) return '';
const locale = getLocale();
return locale === 'en' ? mode.transport_en : mode.transport_es;
}
function getEffectiveExchangeDate(): string | null {
const entryMethod = getTransportByCode(formData?.pedimento_transport_means?.entry_exit);
const paymentCode = (entryMethod?.payment_date_code || 'E').toUpperCase();
if (paymentCode === 'P') {
return formData?.payment_date || null;
}
return formData?.entry_date || null;
}
function getEffectiveDateLabel(): string {
const entryMethod = getTransportByCode(formData?.pedimento_transport_means?.entry_exit);
const paymentCode = (entryMethod?.payment_date_code || 'E').toUpperCase();
return paymentCode === 'P' ? 'fecha de pago' : 'fecha de entrada';
}
// Obtener automáticamente el tipo de cambio cuando cambie la fecha efectiva
$effect(() => {
const effectiveDate = getEffectiveExchangeDate();
const companyId = companyStore.activeCompany?.id;
// Solo ejecutar si los valores clave cambiaron
if (
formData &&
effectiveDate &&
companyId &&
(effectiveDate !== lastFetchedDate || companyId !== lastCompanyId)
) {
lastFetchedDate = effectiveDate;
lastCompanyId = companyId;
getExchangeRateByDate(effectiveDate, companyId)
.then((usdRate) => {
if (usdRate && formData) {
formData.exchange_rate = usdRate.value;
} else {
console.warn('⚠️ [TIPO CAMBIO] No encontrado para fecha:', effectiveDate);
}
})
.catch((err) => {
console.error('❌ [TIPO CAMBIO] Error:', err);
});
}
});
const pedimentoTypeOptions = [
{ value: 'automobile', label: 'Automóvil' },
{ value: 'complementary', label: 'Complementario' },
{ value: 'consolidated', label: 'Consolidado' },
{ value: 'normal', label: 'Normal (Individual)' }
];
const tipoOperacionOptions = [
{ value: 'incrementables', label: 'Incrementables' },
{ value: 'identificadores', label: 'Identificadores' },
{ value: 'indices', label: 'Indices' },
{ value: 'adicional', label: 'Adicional' },
{ value: 'decrementable', label: 'Decrementable' }
];
export async function checkPaymentDateRate(date: string): Promise<boolean> {
const effectiveDate = date || getEffectiveExchangeDate();
if (!effectiveDate || !companyStore.activeCompany?.id) return true;
try {
const rate = await getExchangeRateByDate(effectiveDate, companyStore.activeCompany.id);
if (!rate) {
// Abrir modal preventivamente
missingExchangeRateDate = effectiveDate;
showExchangeRateDialog = true;
return false;
}
return true;
} catch (error) {
console.error('Error checking payment date rate:', error);
// Si hay error de red, asumimos que falta para forzar reintento/captura segura
missingExchangeRateDate = effectiveDate;
showExchangeRateDialog = true;
return false;
}
}
export function openExchangeRateDialog(date: string) {
missingExchangeRateDate = date;
showExchangeRateDialog = true;
}
</script>
<Card.Root>
<Card.Header>
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div class="space-y-1">
<Card.Title>Información General</Card.Title>
<Card.Description>Edita los datos principales del pedimento</Card.Description>
</div>
<!-- Captura Info Badge -->
<div
class="flex flex-row items-center gap-4 rounded-md border border-border/40 bg-secondary/50 px-3 py-2 text-sm text-muted-foreground"
>
<div class="flex items-center gap-2" title="Fecha de Captura">
<Calendar class="h-4 w-4 text-primary/70" />
<span class="font-medium tabular-nums">{formData.fecha_captura}</span>
</div>
<div class="h-4 w-px bg-border"></div>
<div class="flex items-center gap-2" title="Hora de Captura">
<Clock class="h-4 w-4 text-primary/70" />
<span class="font-medium tabular-nums">{formData.hora_captura}</span>
</div>
</div>
</div>
</Card.Header>
<Card.Content>
<div class="space-y-6">
<div
class="grid grid-cols-1 gap-4 md:grid-cols-[auto_auto_auto_auto_auto_auto_auto_auto_auto_1fr_auto] md:items-end md:gap-2"
>
<!-- Año -->
<div class="space-y-2">
<Label for="year">Año <span class="text-red-500">*</span></Label>
<Input
id="year"
bind:value={formData.year}
placeholder="25"
maxlength={4}
class="text-center md:w-20"
/>
</div>
<!-- Separador -->
<div class="hidden pb-2 text-2xl font-semibold text-muted-foreground md:block">-</div>
<!-- Aduana -->
<div class="space-y-2">
<Label for="customs_office">Aduana <span class="text-red-500">*</span></Label>
<Select.Root
type="single"
value={formData.customs_office || ''}
onValueChange={(v: string) => (formData.customs_office = v ?? '')}
>
<Select.Trigger class="w-full md:w-20">
<span class="truncate">
{formData.customs_office ? formData.customs_office.substring(0, 2) : 'Sel...'}
</span>
</Select.Trigger>
<Select.Content class="max-h-[300px] max-w-[300px]">
{#if customsSections.length === 0}
<div class="px-2 py-1.5 text-sm text-muted-foreground">
No hay aduanas disponibles
</div>
{:else}
{#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}
{/if}
</Select.Content>
</Select.Root>
</div>
<!-- Separador -->
<div class="hidden pb-2 text-2xl font-semibold text-muted-foreground md:block">-</div>
<!-- Patente -->
<div class="space-y-2">
<Label for="license">Patente <span class="text-red-500">*</span></Label>
<Select.Root
type="single"
value={formData.license || ''}
onValueChange={(v: string | undefined) => (formData.license = v || null)}
>
<Select.Trigger class="w-full md:w-24">
<span class="truncate">
{formData.license || 'Sel...'}
</span>
</Select.Trigger>
<Select.Content class="max-h-[300px] max-w-[300px]">
{@const validBrokers = customsBrokers.filter((b) => b.license)}
{#if customsBrokers.length === 0}
<div class="px-2 py-1.5 text-sm text-muted-foreground">
No hay agentes aduanales registrados
</div>
{:else if validBrokers.length === 0}
<div class="px-2 py-1.5 text-sm text-muted-foreground">
No hay patentes válidas. Edita el agente aduanal para agregar su patente.
</div>
{:else}
{#each validBrokers as broker}
{@const displayName = broker.name || broker.broker_key || ''}
<Select.Item value={broker.license}>
<span
class="truncate overflow-hidden text-ellipsis whitespace-nowrap"
title={displayName ? `${displayName} - ${broker.license}` : broker.license}
>
{displayName ? `${displayName} - ${broker.license}` : broker.license}
</span>
</Select.Item>
{/each}
{/if}
</Select.Content>
</Select.Root>
</div>
<!-- Separador -->
<div class="hidden pb-2 text-2xl font-semibold text-muted-foreground md:block">-</div>
<!-- Número de Pedimento -->
<div class="space-y-2">
<Label for="pedimento_number"
>Número de Pedimento <span class="text-red-500">*</span></Label
>
<Input
id="pedimento_number"
bind:value={formData.pedimento_number}
placeholder="0000001"
maxlength={7}
class="text-left"
/>
</div>
<!-- Tipo de Cambio -->
<div class="space-y-2">
<Label for="exchange_rate">Tipo de Cambio <span class="text-red-500">*</span></Label>
<Input
id="exchange_rate"
type="text"
value={formData.exchange_rate ? Number(formData.exchange_rate).toFixed(6) : ''}
placeholder={`Se obtiene automáticamente de la ${getEffectiveDateLabel()}`}
readonly
disabled
class="cursor-not-allowed bg-muted"
/>
<p class="text-xs text-muted-foreground">
Tipo de fecha para TC: {getEffectiveDateLabel() === 'fecha de pago'
? 'FECHA PAGO'
: 'FECHA ENTRADA'}
</p>
</div>
</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-3 md:gap-2">
<!-- Clave del Pedimento -->
<div class="space-y-2">
<Label for="pedimento_code">Clave <span class="text-red-500">*</span></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-h-[300px] max-w-[400px]">
{#each pedimentoCodes as code}
<Select.Item value={code.code}>
<div class="flex items-center gap-2">
<span class="font-medium">{code.code}</span>
<span class="text-muted-foreground">-</span>
<span class="flex-1 truncate">{code.description}</span>
</div>
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<!-- Tipo de Operación -->
<div class="space-y-2">
<Label for="operation_type">Tipo de Operación <span class="text-red-500">*</span></Label>
<Select.Root
type="single"
value={formData.operation_type || ''}
onValueChange={(v: string) => (formData.operation_type = 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={option.value} label={option.label} />
{/each}
</Select.Content>
</Select.Root>
</div>
<!-- Régimen -->
<div class="space-y-2">
<Label for="regime">Régimen <span class="text-red-500">*</span></Label>
{#if hasMultipleRegimens}
<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>
{:else}
<Input
id="regime"
bind:value={formData.regime}
placeholder="Automático"
class="text-left"
readonly
disabled
/>
{/if}
</div>
</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<!-- ID del Cliente (opcional) -->
<div class="space-y-2">
<Label for="client_id">Cliente</Label>
<Select.Root
type="single"
value={String(formData.client_id ?? '')}
onValueChange={(v: string) => (formData.client_id = v ? Number(v) : null)}
>
<Select.Trigger class="w-full">
<span class="truncate">
{clients.find((c) => c.id === formData.client_id)?.name || 'Seleccionar cliente...'}
</span>
</Select.Trigger>
<Select.Content class="max-h-[300px]">
{#if clients.length === 0}
<div class="px-2 py-1.5 text-sm text-muted-foreground">
No hay clientes disponibles
</div>
{:else}
{#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}
{/if}
</Select.Content>
</Select.Root>
</div>
<!-- Tipo de Pedimento -->
<div class="space-y-2">
<Label for="pedimento_type">Tipo de Pedimento <span class="text-red-500">*</span></Label>
<Select.Root
type="single"
value={formData.pedimento_type || ''}
onValueChange={(v: string) => (formData.pedimento_type = v ?? '')}
>
<Select.Trigger class="w-full">
{pedimentoTypeOptions.find((o) => o.value === formData.pedimento_type)?.label ||
'Seleccionar...'}
</Select.Trigger>
<Select.Content>
{#each pedimentoTypeOptions as option}
<Select.Item value={option.value} label={option.label} />
{/each}
</Select.Content>
</Select.Root>
</div>
</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<!-- Valor USD -->
<div class="space-y-2">
<Label for="usd_value">Valor USD</Label>
<Input
id="usd_value"
type="number"
step="0.01"
bind:value={formData.usd_value}
placeholder="Ej: 1000.00"
/>
</div>
<!-- Precio Pagado -->
<div class="space-y-2">
<Label for="paid_price">Precio Pagado</Label>
<Input
id="paid_price"
type="number"
step="0.01"
bind:value={formData.paid_price}
placeholder="Ej: 1000.00"
/>
</div>
<!-- Peso Bruto -->
<div class="space-y-2">
<Label for="gross_weight">Peso Bruto (Kg)</Label>
<Input
id="gross_weight"
type="number"
step="0.01"
bind:value={formData.gross_weight}
placeholder="Ej: 100.00"
/>
</div>
<!-- Despacho -->
<div class="space-y-2">
<Label for="despacho">Despacho</Label>
<Select.Root
type="single"
value={formData.pedimento_customs_offices.dispatch_customs || ''}
onValueChange={(v: string) =>
(formData.pedimento_customs_offices.dispatch_customs = v || '')}
>
<Select.Trigger id="despacho" class="w-full">
{formData.pedimento_customs_offices.dispatch_customs || 'Sel...'}
</Select.Trigger>
<Select.Content>
{#each customsSections.filter((s) => !formData.customs_office || s.customs_code.startsWith(formData.customs_office.substring(0, 2))) as section}
<Select.Item value={section.customs_code}>
{section.customs_code} - {section.section_name}
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<!-- Destino -->
<div class="space-y-2">
<Label for="destino">Destino <span class="text-red-500">*</span></Label>
<Select.Root
type="single"
value={formData.pedimento_transport_means.destination
? String(formData.pedimento_transport_means.destination)
: ''}
onValueChange={(v: string) =>
(formData.pedimento_transport_means.destination = v ? Number(v) : null)}
>
<Select.Trigger id="destino" class="w-full">
{formData.pedimento_transport_means.destination === 1
? '1 - REGION FRONTERIZA'
: formData.pedimento_transport_means.destination === 8
? '8 - RECTIFICACION REGION FRONTERIZA'
: formData.pedimento_transport_means.destination === 9
? '9 - INTERIOR DEL PAIS'
: 'Seleccionar...'}
</Select.Trigger>
<Select.Content>
<Select.Item value="1">1 - REGION FRONTERIZA</Select.Item>
<Select.Item value="8">8 - RECTIFICACION REGION FRONTERIZA</Select.Item>
<Select.Item value="9">9 - INTERIOR DEL PAIS</Select.Item>
</Select.Content>
</Select.Root>
</div>
<!-- Aduana E/S -->
<div class="space-y-2">
<Label for="entry_exit_customs">Aduana E/S</Label>
<Input
id="entry_exit_customs"
type="text"
maxlength={3}
bind:value={formData.pedimento_customs_offices.entry_exit_customs}
placeholder="000"
/>
</div>
<!-- Método de Entrada/Salida -->
<div class="space-y-2">
<Label for="entry_exit">Método de Entrada</Label>
<Select.Root
type="single"
value={formData.pedimento_transport_means.entry_exit || ''}
onValueChange={(v: string) => (formData.pedimento_transport_means.entry_exit = v || '')}
>
<Select.Trigger id="entry_exit" class="w-full">
<span class="truncate">
{getTransportLabel(
getTransportByCode(formData.pedimento_transport_means.entry_exit)
) ||
formData.pedimento_transport_means.entry_exit ||
'Seleccionar...'}
</span>
</Select.Trigger>
<Select.Content class="max-h-[300px]">
{#each pedimentoTransportCatalog as mode}
<Select.Item value={mode.code}>{mode.code} - {getTransportLabel(mode)}</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<!-- Arribo -->
<div class="space-y-2">
<Label for="arrival">Arribo</Label>
<Select.Root
type="single"
value={formData.pedimento_transport_means.arrival || ''}
onValueChange={(v: string) => (formData.pedimento_transport_means.arrival = v || '')}
>
<Select.Trigger id="arrival" class="w-full">
<span class="truncate">
{getTransportLabel(getTransportByCode(formData.pedimento_transport_means.arrival)) ||
formData.pedimento_transport_means.arrival ||
'Seleccionar...'}
</span>
</Select.Trigger>
<Select.Content class="max-h-[300px]">
{#each pedimentoTransportCatalog as mode}
<Select.Item value={mode.code}>{mode.code} - {getTransportLabel(mode)}</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<!-- Salida -->
<div class="space-y-2">
<Label for="departure">Salida</Label>
<Select.Root
type="single"
value={formData.pedimento_transport_means.departure || ''}
onValueChange={(v: string) => (formData.pedimento_transport_means.departure = v || '')}
>
<Select.Trigger id="departure" class="w-full">
<span class="truncate">
{getTransportLabel(getTransportByCode(formData.pedimento_transport_means.departure)) ||
formData.pedimento_transport_means.departure ||
'Seleccionar...'}
</span>
</Select.Trigger>
<Select.Content class="max-h-[300px]">
{#each pedimentoTransportCatalog as mode}
<Select.Item value={mode.code}>{mode.code} - {getTransportLabel(mode)}</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
</div>
<!-- Lista de navegación rápida -->
<div class="w-full overflow-x-auto pb-2">
<div
class="inline-flex rounded-md bg-muted p-1 text-muted-foreground md:grid md:w-full md:grid-cols-6"
>
<button
type="button"
onclick={() => (activeSection = 'fechas')}
data-state={activeSection === 'fechas' ? 'active' : ''}
class="inline-flex cursor-pointer items-center justify-center rounded-sm px-3 py-1.5 text-sm font-medium whitespace-nowrap ring-offset-background transition-all hover:bg-background/50 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none data-[state=active]:border data-[state=active]:border-white/60 data-[state=active]:text-foreground"
>
Fechas
</button>
<button
type="button"
onclick={() => (activeSection = 'incrementables')}
data-state={activeSection === 'incrementables' ? 'active' : ''}
class="inline-flex cursor-pointer items-center justify-center rounded-sm px-3 py-1.5 text-sm font-medium whitespace-nowrap ring-offset-background transition-all hover:bg-background/50 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none data-[state=active]:border data-[state=active]:border-white/60 data-[state=active]:text-foreground"
>
Incrementables
</button>
<button
type="button"
onclick={() => (activeSection = 'identificadores')}
data-state={activeSection === 'identificadores' ? 'active' : ''}
class="inline-flex cursor-pointer items-center justify-center rounded-sm px-3 py-1.5 text-sm font-medium whitespace-nowrap ring-offset-background transition-all hover:bg-background/50 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none data-[state=active]:border data-[state=active]:border-white/60 data-[state=active]:text-foreground"
>
Identificadores
</button>
<button
type="button"
onclick={() => (activeSection = 'indices')}
data-state={activeSection === 'indices' ? 'active' : ''}
class="inline-flex cursor-pointer items-center justify-center rounded-sm px-3 py-1.5 text-sm font-medium whitespace-nowrap ring-offset-background transition-all hover:bg-background/50 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none data-[state=active]:border data-[state=active]:border-white/60 data-[state=active]:text-foreground"
>
Indices
</button>
<button
type="button"
onclick={() => (activeSection = 'adicional')}
data-state={activeSection === 'adicional' ? 'active' : ''}
class="inline-flex cursor-pointer items-center justify-center rounded-sm px-3 py-1.5 text-sm font-medium whitespace-nowrap ring-offset-background transition-all hover:bg-background/50 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none data-[state=active]:border data-[state=active]:border-white/60 data-[state=active]:text-foreground"
>
Adicional
</button>
<button
type="button"
onclick={() => (activeSection = 'decrementable')}
data-state={activeSection === 'decrementable' ? 'active' : ''}
class="inline-flex cursor-pointer items-center justify-center rounded-sm px-3 py-1.5 text-sm font-medium whitespace-nowrap ring-offset-background transition-all hover:bg-background/50 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none data-[state=active]:border data-[state=active]:border-white/60 data-[state=active]:text-foreground"
>
Decrementable
</button>
</div>
</div>
<!-- Contenido dinámico según la sección activa -->
{#if activeSection === 'fechas'}
<!-- Fechas -->
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
<!-- Fecha de Entrada -->
<div class="space-y-2">
<Label for="entry_date">Fecha de Entrada <span class="text-red-500">*</span></Label>
<Input id="entry_date" type="date" bind:value={formData.entry_date} />
</div>
<!-- Fecha Fin (Solo Consolidado) -->
{#if formData.pedimento_type === 'consolidated'}
<div class="space-y-2">
<Label for="end_date">Fecha Fin <span class="text-red-500">*</span></Label>
<Input id="end_date" type="date" bind:value={formData.end_date} />
</div>
{/if}
<!-- Fecha de Pago -->
<div class="space-y-2">
<Label for="payment_date">Fecha de Pago <span class="text-red-500">*</span></Label>
<Input id="payment_date" type="date" bind:value={formData.payment_date} />
</div>
<!-- Fecha de Presentación -->
<div class="space-y-2">
<Label for="pedimento_date">Fecha de Presentación</Label>
<Input id="pedimento_date" type="date" bind:value={formData.pedimento_date} />
</div>
<!-- Fecha de Extracción -->
<div class="space-y-2">
<Label for="extraction_date">Fecha de Extracción</Label>
<Input id="extraction_date" type="date" bind:value={formData.extraction_date} />
</div>
{#if formData.pedimento_code === 'R1'}
<!-- Fecha de Pago Rectificación -->
<div class="space-y-2">
<Label for="rectification_payment_date">Fecha de Pago R1</Label>
<Input
id="rectification_payment_date"
type="date"
bind:value={formData.rectification_payment_date}
/>
</div>
{/if}
<!-- Fecha Original -->
<div class="space-y-2">
<Label for="original_date">Fecha de Pago Original</Label>
<Input id="original_date" type="date" bind:value={formData.original_date} />
</div>
</div>
{:else if activeSection === 'incrementables'}
<!-- Incrementables -->
<div class="space-y-4">
<!-- Factor informativo -->
<div class="text-sm font-medium">Factor: 1.00000</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
<!-- Valor Seguro -->
<div class="space-y-2">
<Label for="valor_seguro">Valor Seguro</Label>
<Input
id="valor_seguro"
type="number"
bind:value={formData.valor_seguro}
placeholder="0.00"
/>
</div>
<!-- Embalajes -->
<div class="space-y-2">
<Label for="embalajes">Embalajes</Label>
<Input
id="embalajes"
type="number"
bind:value={formData.embalajes}
placeholder="0.00"
/>
</div>
<!-- Fletes -->
<div class="space-y-2">
<Label for="fletes">Fletes</Label>
<Input id="fletes" type="number" bind:value={formData.fletes} placeholder="0.00" />
</div>
<!-- Deducibles -->
<div class="space-y-2">
<Label for="deducibles">Deducibles</Label>
<Input
id="deducibles"
type="number"
bind:value={formData.deducibles}
placeholder="0.00"
/>
</div>
<!-- Moneda -->
<div class="space-y-2">
<Label for="moneda_incrementables">Moneda</Label>
<Input
id="moneda_incrementables"
type="text"
maxlength={3}
bind:value={formData.moneda_incrementables}
placeholder="USD"
/>
</div>
<!-- Checkboxes agrupados -->
<div class="space-y-2">
<Label class="invisible">Opciones</Label>
<div class="space-y-2">
<div class="flex items-center space-x-2">
<input
id="no_afectar_valor_dolares_inc"
type="checkbox"
bind:checked={formData.no_afectar_valor_dolares_inc}
class="h-4 w-4 rounded border-gray-300 text-primary focus:ring-0"
/>
<Label for="no_afectar_valor_dolares_inc" class="!m-0 cursor-pointer">
No Afectar Valor en Dólares del Pedimento
</Label>
</div>
<div class="flex items-center space-x-2">
<input
id="no_afectar_valor_aduana"
type="checkbox"
bind:checked={formData.no_afectar_valor_aduana}
class="h-4 w-4 rounded border-gray-300 text-primary focus:ring-0"
/>
<Label for="no_afectar_valor_aduana" class="!m-0 cursor-pointer">
No Afectar Valor Aduana del Pedimento
</Label>
</div>
</div>
</div>
</div>
</div>
{:else if activeSection === 'identificadores'}
<!-- Identificadores -->
<IdentificadoresTabForm bind:formData={identificadoresFormData} />
{:else if activeSection === 'indices'}
<!-- Indices -->
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
<!-- Tipo de Factor a Actualizar -->
<div class="space-y-2">
<Label for="tipo_factor">Tipo de Factor a Actualizar</Label>
<Select.Root
type="single"
value={formData.tipo_factor || ''}
onValueChange={(v: string) => (formData.tipo_factor = v ?? '')}
>
<Select.Trigger id="tipo_factor" class="w-full">
<span class="truncate">
{formData.tipo_factor === 'INPC'
? 'I.N.P.C'
: formData.tipo_factor === 'variacion_cambiaria'
? 'Variación Cambiaria'
: 'Seleccionar'}
</span>
</Select.Trigger>
<Select.Content>
<Select.Item value="INPC">I.N.P.C</Select.Item>
<Select.Item value="variacion_cambiaria">Variación Cambiaria</Select.Item>
</Select.Content>
</Select.Root>
</div>
<!-- Factor Actualización -->
<div class="space-y-2">
<Label for="factor_actualizacion">Factor Actualización</Label>
<Input
id="factor_actualizacion"
type="number"
bind:value={formData.factor_actualizacion}
placeholder="0.00"
/>
</div>
<!-- Factor Actualización Manual -->
<div class="space-y-2">
<Label for="factor_actualizacion_manual" class="invisible">Checkbox</Label>
<div class="flex h-10 items-center space-x-2">
<input
id="factor_actualizacion_manual"
type="checkbox"
bind:checked={formData.factor_actualizacion_manual}
class="h-4 w-4 rounded border-gray-300 text-primary focus:ring-0"
/>
<Label for="factor_actualizacion_manual" class="!m-0 cursor-pointer">
Factor Actualización Manual
</Label>
</div>
</div>
</div>
{:else if activeSection === 'adicional'}
<!-- Adicional -->
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
<!-- Año para la impresión del pedimento -->
<div class="space-y-2">
<Label for="anio_impresion">Año para la Impresión del Pedimento</Label>
<Input
id="anio_impresion"
type="text"
bind:value={formData.anio_impresion}
placeholder="25"
maxlength={2}
class="w-40 text-center focus-visible:ring-0 focus-visible:ring-offset-0"
/>
</div>
<!-- Agregar Identificador PO Automáticamente -->
<div class="space-y-2">
<Label for="agregar_po_auto" class="invisible">Checkbox</Label>
<div class="flex h-10 items-center space-x-2">
<input
id="agregar_po_auto"
type="checkbox"
bind:checked={formData.agregar_po_auto}
class="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"
/>
<Label for="agregar_po_auto" class="!m-0 cursor-pointer">
Agregar Identificador PO Automáticamente
</Label>
</div>
</div>
<!-- No Eximir Normas ComplementoX -->
<div class="space-y-2">
<Label for="no_eximir_normas" class="invisible">Checkbox</Label>
<div class="flex h-10 items-center space-x-2">
<input
id="no_eximir_normas"
type="checkbox"
bind:checked={formData.no_eximir_normas}
class="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"
/>
<Label for="no_eximir_normas" class="!m-0 cursor-pointer">
No Eximir Normas ComplementoX
</Label>
</div>
</div>
<!-- Activar Destinatario en Facturas -->
<div class="space-y-2">
<Label for="activar_destinatario" class="invisible">Checkbox</Label>
<div class="flex h-10 items-center space-x-2">
<input
id="activar_destinatario"
type="checkbox"
bind:checked={formData.activar_destinatario}
class="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"
/>
<Label for="activar_destinatario" class="!m-0 cursor-pointer">
Activar Destinatario en Facturas
</Label>
</div>
</div>
<!-- Agregar Registro 502 en Archivo de Validación (Consolidados) -->
<div class="space-y-2">
<Label for="agregar_registro_502" class="invisible">Checkbox</Label>
<div class="flex h-10 items-center space-x-2">
<input
id="agregar_registro_502"
type="checkbox"
bind:checked={formData.agregar_registro_502}
class="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"
/>
<Label for="agregar_registro_502" class="!m-0 cursor-pointer">
Agregar Registro 502 en Archivo de Validación (Consolidados)
</Label>
</div>
</div>
<!-- Agregar y Quitar Normas -->
<div class="space-y-2">
<Label for="agregar_quitar_normas" class="invisible">Checkbox</Label>
<div class="flex h-10 items-center space-x-2">
<input
id="agregar_quitar_normas"
type="checkbox"
bind:checked={formData.agregar_quitar_normas}
class="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"
/>
<Label for="agregar_quitar_normas" class="!m-0 cursor-pointer">
Agregar y Quitar Normas
</Label>
</div>
</div>
<!-- Elegir Facturar con COVE en Partidas -->
<div class="space-y-2">
<Label for="facturar_cove" class="invisible">Checkbox</Label>
<div class="flex h-10 items-center space-x-2">
<input
id="facturar_cove"
type="checkbox"
bind:checked={formData.facturar_cove}
class="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"
/>
<Label for="facturar_cove" class="!m-0 cursor-pointer">
Elegir Facturar con COVE en Partidas
</Label>
</div>
</div>
</div>
{:else if activeSection === 'decrementable'}
<!-- Decrementable -->
<div class="space-y-4">
<!-- Factor informativo -->
<div class="text-sm font-medium">Factor: 0.05000</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
<!-- Fletes -->
<div class="space-y-2">
<Label for="fletes_decrementable">Fletes</Label>
<Input
id="fletes_decrementable"
type="number"
bind:value={formData.fletes_decrementable}
placeholder="0.00"
/>
</div>
<!-- Seguros -->
<div class="space-y-2">
<Label for="seguros">Seguros</Label>
<Input id="seguros" type="number" bind:value={formData.seguros} placeholder="0.00" />
</div>
<!-- Carga -->
<div class="space-y-2">
<Label for="carga">Carga</Label>
<Input id="carga" type="number" bind:value={formData.carga} placeholder="0.00" />
</div>
<!-- Descarga -->
<div class="space-y-2">
<Label for="descarga">Descarga</Label>
<Input
id="descarga"
type="number"
bind:value={formData.descarga}
placeholder="0.00"
/>
</div>
<!-- Otros -->
<div class="space-y-2">
<Label for="otros">Otros</Label>
<Input id="otros" type="number" bind:value={formData.otros} placeholder="0.00" />
</div>
<!-- Moneda -->
<div class="space-y-2">
<Label for="moneda_decrementable">Moneda</Label>
<Input
id="moneda_decrementable"
type="text"
bind:value={formData.moneda_decrementable}
placeholder="USD"
/>
</div>
<!-- Afectar Valor en Dólares del Pedimento -->
<div class="space-y-2">
<Label for="afectar_valor_dolares" class="invisible">Checkbox</Label>
<div class="flex h-10 items-center space-x-2">
<input
id="afectar_valor_dolares"
type="checkbox"
bind:checked={formData.afectar_valor_dolares}
class="h-4 w-4 rounded border-gray-300 text-primary focus:ring-0"
/>
<Label for="afectar_valor_dolares" class="!m-0 cursor-pointer">
Afectar Valor en Dólares del Pedimento
</Label>
</div>
</div>
</div>
</div>
{/if}
</div>
</Card.Content>
</Card.Root>
<ExchangeRateDialog
bind:open={showExchangeRateDialog}
initialDate={missingExchangeRateDate}
overlayClass="bg-black/20"
onSuccess={() => {
/* Optional: maybe refresh something or just let user continue */
}}
/>