Merge development into feature/table-invoice

This commit is contained in:
2026-04-13 11:53:03 -06:00
33 changed files with 6840 additions and 491 deletions

View File

@@ -0,0 +1,34 @@
import { BACKEND_URL } from '$lib/config/backend';
export interface AppSettingsRequest {
tenant_id?: number | null;
company_id?: number | null;
settings: Record<string, any>;
}
export const appSettingsApi = {
/**
* Resolves settings merging Global -> Tenant -> Company hierarchy
*/
async getResolved(tenantId: number, companyId: number): Promise<Record<string, any>> {
const response = await fetch(`${BACKEND_URL}/v1/a76/app-settings/resolved?tenant_id=${tenantId}&company_id=${companyId}`);
if (!response.ok) throw new Error('Error al obtener configuraciones');
return response.json();
},
/**
* Upserts an override at a specific level
*/
async upsert(payload: AppSettingsRequest): Promise<any> {
const response = await fetch(`${BACKEND_URL}/v1/a76/app-settings/upsert`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'Error al guardar configuración');
}
return response.json();
}
};

View File

@@ -77,5 +77,5 @@ export async function updatePackage(
}
export async function deletePackage(id: number, companyId: number): Promise<ApiResponse<void>> {
return await api.delete(`/v1/a76/packages/${id}/?company_id=${companyId}`);
return await api.delete(`/v1/a76/packages/${id}?company_id=${companyId}`);
}

View File

@@ -13,20 +13,22 @@
formData = $bindable(),
exists = $bindable(),
operationType = undefined,
invoiceType = undefined
invoiceType = undefined,
isSettings = false
}: {
invoice: Invoice | null;
formData?: any;
exists?: boolean;
operationType?: number;
invoiceType?: string;
isSettings?: boolean;
} = $props();
if (!formData && invoice) {
formData = {
// Campos de esta pestaña
numero_tipo_transporte:
invoice.logistics?.transport_num || invoice.logistics?.vehicle_num || '',
invoice.logistics?.trailer_num || invoice.logistics?.transport_num || invoice.logistics?.vehicle_num || '',
es_ferrocarril: invoice.logistics?.is_rail ? 'si' : 'no',
numero_bl: invoice.logistics?.bill_number || '',
cantidad_guias_embarque: invoice.logistics?.guide_number || null,
@@ -90,6 +92,23 @@
};
exists = false;
}
$effect.pre(() => {
if (formData) {
if (formData.es_ferrocarril === undefined) formData.es_ferrocarril = 'no';
if (formData.is_mixed === undefined) formData.is_mixed = false;
if (formData.reason_export === undefined) formData.reason_export = '1';
if (formData.fue_revisado_equipo === undefined) formData.fue_revisado_equipo = false;
if (formData.sub_division === undefined) formData.sub_division = false;
if (formData.funge_como_cd === undefined) formData.funge_como_cd = false;
if (formData.llego_pedimento === undefined) formData.llego_pedimento = false;
if (formData.semaforo_verde_aduana_mexicana === undefined) formData.semaforo_verde_aduana_mexicana = false;
if (formData.semaforo_verde_aduana_americana === undefined) formData.semaforo_verde_aduana_americana = false;
if (formData.semaforo_rojo_aduana_mexicana === undefined) formData.semaforo_rojo_aduana_mexicana = false;
if (formData.semaforo_rojo_aduana_americana === undefined) formData.semaforo_rojo_aduana_americana = false;
}
});
let showPortModal = $state(false);
function handlePortSelect(section: any) {
@@ -119,7 +138,7 @@
<Input id="vehicle_data" bind:value={formData.vehicle_data} class="mb-2 h-7 text-xs" />
<div class="space-y-1.5">
<Label class="text-xs">Es Ferrocarril?</Label>
<RadioGroup bind:value={formData.es_ferrocarril} class="flex gap-4">
<RadioGroup value={formData.es_ferrocarril} onValueChange={(v) => formData.es_ferrocarril = v} class="flex gap-4">
<div class="flex items-center gap-2">
<RadioGroupItem value="si" id="ferrocarril_si" />
<Label for="ferrocarril_si" class="text-xs">SI</Label>
@@ -203,10 +222,10 @@
{#if operationType === 1 || invoiceType === 'CR'}
<div class="space-y-3 pt-1">
<div class="space-y-1.5">
<div class="space-y-1.5 pt-4">
<Label class="text-xs">Razón de exportación:</Label>
<RadioGroup bind:value={formData.reason_export} class="flex flex-wrap gap-4">
<div class="flex items-center gap-2">
<RadioGroup value={formData.reason_export} onValueChange={(v) => formData.reason_export = v} class="flex flex-wrap gap-4">
<div class="flex items-center space-x-2">
<RadioGroupItem value="1" id="reason_vendido" />
<Label for="reason_vendido" class="text-xs text-muted-foreground">Vendido</Label>
</div>

View File

@@ -33,7 +33,8 @@
operationType = undefined,
defaultOperationType = undefined,
exchangeRate = undefined,
invoiceType = undefined
invoiceType = undefined,
isSettings = false
}: {
invoice: Invoice | null;
formData?: any;
@@ -54,6 +55,7 @@
operationType?: number | null;
exchangeRate?: number | null;
invoiceType?: string;
isSettings?: boolean;
} = $props();
let showManifestModal = $state(false);
@@ -88,7 +90,7 @@
// RIGHT fields
currency_type: invoice.financials?.currency_type || '',
currency: invoice.financials?.currency || 'foreign', // foreign, local, manual
exchange_rate: invoice.financials?.exchange_rate || null, // Added exchange_rate
exchange_rate: invoice.financials?.exchange_rate || null,
weight_type: 'kgs',
iva_factor: invoice.financials?.iva_factor || null,
carrier_id: invoice.logistics?.carrier_id || null,
@@ -120,7 +122,7 @@
// RIGHT fields
currency_type: '',
currency: 'foreign', // foreign, local, manual
exchange_rate: null, // Added exchange_rate
exchange_rate: null,
weight_type: 'kgs',
iva_factor: null,
carrier_id: null,
@@ -135,37 +137,27 @@
electronic_signature: ''
};
}
} else {
// Si formData ya existe, asegurar que tiene valores por defecto
if (formData.currency === undefined) {
formData.currency = 'foreign';
}
$effect.pre(() => {
if (formData) {
if (formData.currency === undefined) formData.currency = 'foreign';
if (!formData.provider_header) formData.provider_header = 'proveedor';
if (!formData.sold_to_header) formData.sold_to_header = 'consignado_a';
if (!formData.shipped_to_header) formData.shipped_to_header = 'enviado_a';
if (!formData.shipped_by_header) formData.shipped_by_header = 'enviado_por';
if (formData.manifest_number === undefined) formData.manifest_number = '';
if (formData.code_signature === undefined) formData.code_signature = '';
if (formData.electronic_signature === undefined) formData.electronic_signature = '';
// Migración de transport_num a trailer_num (Upstream change)
if (formData.trailer_num === undefined && (formData as any).transport_num) {
formData.trailer_num = (formData as any).transport_num;
delete (formData as any).transport_num;
}
if (formData.trailer_num === undefined) formData.trailer_num = '';
}
if (!formData.provider_header) {
formData.provider_header = 'proveedor';
}
if (!formData.sold_to_header) {
formData.sold_to_header = 'consignado_a';
}
if (!formData.shipped_to_header) {
formData.shipped_to_header = 'enviado_a';
}
if (!formData.shipped_by_header) {
formData.shipped_by_header = 'enviado_por';
}
if (formData.manifest_number === undefined) {
formData.manifest_number = '';
}
if (formData.code_signature === undefined) {
formData.code_signature = '';
}
if (formData.electronic_signature === undefined) {
formData.electronic_signature = '';
}
if (formData.trailer_num === undefined && (formData as any).transport_num) {
formData.trailer_num = (formData as any).transport_num;
delete (formData as any).transport_num;
}
}
});
// Opciones de tipo de peso
const weightTypeOptions = [
@@ -352,12 +344,12 @@
</script>
<!-- Layout de 2 columnas compacto -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<!-- Columna Izquierda: Clientes - Proveedores - Agente Aduanal -->
<div class="space-y-3 rounded-md border p-3">
{#if invoiceType !== 'MEX'}
<h4 class="text-xs font-semibold text-muted-foreground uppercase">Datos del pedimento</h4>
<div class="grid grid-cols-2 md:grid-cols-4 gap-3 text-xs">
<div class="grid grid-cols-2 gap-3 text-xs md:grid-cols-4">
<div>
<span class="text-muted-foreground">Fecha del:</span>
<p class="font-medium">{formData.fecha_pedimento_del || '-'}</p>
@@ -380,7 +372,7 @@
<h4 class="text-xs font-semibold text-muted-foreground uppercase">
Clientes - Proveedores - Agente Aduanal
</h4>
<div class="grid grid-cols-[1fr_4fr_4fr] items-center gap-x-2 gap-y-2 min-w-0">
<div class="grid min-w-0 grid-cols-[1fr_4fr_4fr] items-center gap-x-2 gap-y-2">
<!-- Row 1: Proveedor / Exportador -->
<Select.Root
type="single"
@@ -428,7 +420,7 @@
{/each}
</Select.Content>
</Select.Root>
<span class="text-red-500 font-bold">*</span>
{#if !isSettings}<span class="font-bold text-red-500">*</span>{/if}
<!-- Row 2: Consignado a / Vendido a / Importador -->
<Select.Root
@@ -477,7 +469,7 @@
{/each}
</Select.Content>
</Select.Root>
<span class="text-red-500 font-bold">*</span>
{#if !isSettings}<span class="font-bold text-red-500">*</span>{/if}
<!-- Row 3: Enviado a -->
<Select.Root
@@ -527,12 +519,12 @@
{/each}
</Select.Content>
</Select.Root>
<span class="text-red-500 font-bold">*</span>
{#if !isSettings}<span class="font-bold text-red-500">*</span>{/if}
</div>
<div class="space-y-1.5">
<Label for="customs_broker_id" class="text-xs"
>Agente Aduanal Mex: <span class="text-red-500">*</span></Label
>Agente Aduanal Mex: {#if !isSettings}<span class="text-red-500">*</span>{/if}</Label
>
<Select.Root
type="single"
@@ -541,7 +533,7 @@
formData.customs_broker_id = v ? parseInt(v) : null;
}}
>
<Select.Trigger id="customs_broker_id" class="h-7 text-xs min-w-[150px] max-w-[300px]">
<Select.Trigger id="customs_broker_id" class="h-7 max-w-[300px] min-w-[150px] text-xs">
<span class="truncate">
{formData.customs_broker_id
? customsBrokers.find((cb) => cb.id === formData.customs_broker_id)?.name ||
@@ -590,14 +582,14 @@
<!-- Columna Derecha: Tipo de Moneda y Transportista -->
<div class="space-y-3">
<!-- Tipo de Moneda - Pesos Netos y Brutos -->
<div class="border rounded-md p-3 space-y-2">
<div class="space-y-2 rounded-md border p-3">
<div class="flex flex-wrap justify-between gap-1">
<h4 class="text-xs font-semibold text-muted-foreground uppercase">
Tipo de Moneda - Pesos Netos y Brutos
</h4>
<h4 class="text-xs font-semibold text-muted-foreground uppercase">
Tipo de cambio:
<span class="text-primary ml-1">
<span class="ml-1 text-primary">
{exchangeRate !== undefined && exchangeRate !== null
? exchangeRate === 0
? 'N/A'
@@ -614,19 +606,19 @@
<RadioGroup.Root bind:value={formData.currency} class="flex flex-wrap gap-x-4 gap-y-2">
<div class="flex items-center space-x-2">
<RadioGroup.Item value="foreign" id="currency-foreign" class="h-4 w-4" />
<Label for="currency-foreign" class="text-xs font-normal cursor-pointer"
<Label for="currency-foreign" class="cursor-pointer text-xs font-normal"
>Extranjera (Dlls)</Label
>
</div>
<div class="flex items-center space-x-2">
<RadioGroup.Item value="local" id="currency-local" class="h-4 w-4" />
<Label for="currency-local" class="text-xs font-normal cursor-pointer"
<Label for="currency-local" class="cursor-pointer text-xs font-normal"
>Nacional (Pesos)</Label
>
</div>
<div class="flex items-center space-x-2">
<RadioGroup.Item value="manual" id="currency-manual" class="h-4 w-4" />
<Label for="currency-manual" class="text-xs font-normal cursor-pointer"
<Label for="currency-manual" class="cursor-pointer text-xs font-normal"
>De Captura</Label
>
</div>
@@ -642,12 +634,12 @@
formData.currency_type = v ?? '';
}}
>
<Select.Trigger id="currency_type" class="h-7 text-xs min-w-[80px]">
<Select.Trigger id="currency_type" class="h-7 min-w-[80px] text-xs">
<span class="truncate">
{formData.currency_type || '...'}
</span>
</Select.Trigger>
<Select.Content class="min-w-[80px] max-h-[300px]">
<Select.Content class="max-h-[300px] min-w-[80px]">
{#each currencyTypes as currencyType}
<Select.Item value={currencyType.code}>
{currencyType.code}
@@ -657,7 +649,7 @@
</Select.Root>
</div>
{/if}
<div class="grid grid-cols-1 sm:grid-cols-3 gap-2">
<div class="grid grid-cols-1 gap-2 sm:grid-cols-3">
<div class="space-y-1.5">
<Label for="weight_type" class="text-xs">Tipo Peso:</Label>
<Select.Root
@@ -667,7 +659,7 @@
formData.weight_type = v ?? 'kgs';
}}
>
<Select.Trigger id="weight_type" class="min-w-[150px] h-7 text-xs">
<Select.Trigger id="weight_type" class="h-7 min-w-[150px] text-xs">
<span class="truncate">
{weightTypeOptions.find((w) => w.value === formData.weight_type)?.label ||
'Kilogramos (kg)'}
@@ -721,10 +713,10 @@
</div>
<!-- Transportista -->
<div class="border rounded-md p-3 space-y-2">
<div class="space-y-2 rounded-md border p-3">
<h4 class="text-xs font-semibold text-muted-foreground uppercase">Transportista</h4>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-2">
<div class="grid grid-cols-1 gap-2 sm:grid-cols-3">
{#if invoiceType !== 'MEX'}
<div class="space-y-1.5">
<Label for="carrier_id" class="text-xs">Transportista:</Label>
@@ -737,7 +729,7 @@
formData.trailer_num = '';
}}
>
<Select.Trigger id="carrier_id" class="h-7 text-xs min-w-[120px] max-w-[250px]">
<Select.Trigger id="carrier_id" class="h-7 max-w-[250px] min-w-[120px] text-xs">
<span class="truncate">
{#if formData.carrier_id}
{transporters.find(
@@ -830,7 +822,7 @@
</Select.Root>
</div>
<div class="grid grid-cols-2 sm:grid-cols-4 gap-2">
<div class="grid grid-cols-2 gap-2 sm:grid-cols-4">
<div class="space-y-1.5">
<Label for="transport_type" class="text-xs">Tipo Transporte:</Label>
<Select.Root
@@ -856,7 +848,7 @@
</Select.Root>
</div>
<div class="col-span-1 sm:col-span-3 space-y-1.5">
<div class="col-span-1 space-y-1.5 sm:col-span-3">
<Label for="trailer_num" class="text-xs">
Remolque:{#if (formData.transport_type || 'none') !== 'none'}
<span class="text-red-500"> *</span>
@@ -872,8 +864,8 @@
<Select.Trigger id="trailer_num" class="h-7 w-full text-xs">
<span class="truncate">
{#if formData.trailer_num}
{trailers.find((t) => t.trailer_number === formData.trailer_num)
?.plate_number || formData.trailer_num}
{trailers.find((t) => t.trailer_number === formData.trailer_num)?.plate_number ||
formData.trailer_num}
{:else if invoiceType !== 'MEX' && !formData.carrier_id}
Primero elige transportista...
{:else if trailers.length > 0}
@@ -904,7 +896,7 @@
formData.aduana = v ?? '';
}}
>
<Select.Trigger id="aduana" class="h-7 text-xs w-full">
<Select.Trigger id="aduana" class="h-7 w-full text-xs">
<span class="truncate">
{#if formData.aduana}
{customsSections.find((cs) => cs.customs_code === formData.aduana)
@@ -939,7 +931,7 @@
formData.document_type = v ?? '';
}}
>
<Select.Trigger id="document_type" class="h-7 text-xs w-full">
<Select.Trigger id="document_type" class="h-7 w-full text-xs">
<span class="truncate">
{#if formData.document_type}
{codePedimentoRegimens.find((r) => r.regimen_code === formData.document_type)

View File

@@ -14,7 +14,8 @@
pedimentos = [],
defaultOperationType = undefined,
defaultInvoiceType = undefined,
invoiceType = undefined
invoiceType = undefined,
isSettings = false
}: {
invoice: Invoice | null;
formData?: any;
@@ -23,6 +24,7 @@
defaultOperationType?: string | null;
defaultInvoiceType?: string | null;
invoiceType?: string;
isSettings?: boolean;
} = $props();
function handlePedimentoChange(pedimentoId: string) {
@@ -68,47 +70,47 @@
});
if (!formData) {
let operationType: string | null = null;
let opType: string | null = null;
if (invoice?.operation_type) {
operationType = invoice.operation_type;
opType = invoice.operation_type;
} else if (defaultOperationType !== undefined) {
operationType = defaultOperationType ?? null;
opType = (defaultOperationType as string) ?? null;
}
formData = {
is_pedimento_pending: false,
pedimento_id: invoice?.compliance_mx?.pedimento_id || '',
remesa: invoice?.compliance_mx?.remesa || '',
invoice_number: invoice?.invoice_number || '',
invoice_date: invoice?.invoice_date || new Date().toISOString().split('T')[0],
emission_date: new Date().toISOString().split('T')[0],
operation_type: operationType,
operation_type: opType,
invoice_type: invoice?.invoice_type || (defaultInvoiceType ?? ''),
// Campos del pedimento (se llenarán al seleccionar un pedimento)
fecha_pedimento_del: '',
fecha_pedimento_al: '',
clave_pedimento: '',
regimen_pedimento: '',
// Campos específicos para MEX
iva_factor: invoice?.financials?.iva_factor || '',
alternate_invoice: invoice?.alternate_invoice || ''
};
} else {
// Si formData ya existe pero operation_type está vacío, usar defaultOperationType
if (
!formData.operation_type &&
defaultOperationType !== undefined &&
defaultOperationType !== null
) {
formData.operation_type = defaultOperationType;
}
$effect.pre(() => {
if (formData) {
if (
!formData.operation_type &&
defaultOperationType !== undefined &&
defaultOperationType !== null
) {
formData.operation_type = defaultOperationType;
}
if (formData.iva_factor === undefined)
formData.iva_factor = invoice?.financials?.iva_factor || '';
if (formData.alternate_invoice === undefined)
formData.alternate_invoice = invoice?.alternate_invoice || '';
}
// Ensure new fields exist if formData was created before
if (formData.iva_factor === undefined)
formData.iva_factor = invoice?.financials?.iva_factor || '';
if (formData.alternate_invoice === undefined)
formData.alternate_invoice = invoice?.alternate_invoice || '';
}
});
// Filter invoice types based on operation type
let filteredInvoiceTypes = $derived(
invoiceTypes.filter((type) => {
@@ -148,53 +150,55 @@
<!-- Datos Principales en una fila compacta (reusable across tabs) -->
<div class="flex flex-wrap items-end gap-3 pb-3">
<div class="min-w-[100px] flex-1 space-y-1">
<Label for="operation_type" class="text-xs"
>Tipo de Operación<span class="text-red-500">*</span></Label
>
<Select.Root
type="single"
value={formData.operation_type || ''}
onValueChange={(v) => {
formData.operation_type = v;
}}
>
<Select.Trigger id="operation_type" class="h-8 text-sm">
<span class="truncate">
{formData.operation_type ? (formData.operation_type === 'exp' ? 'Exp' : 'Imp') : '...'}
</span>
</Select.Trigger>
<Select.Content>
<Select.Item value="exp">Exportación</Select.Item>
<Select.Item value="imp">Importación</Select.Item>
</Select.Content>
</Select.Root>
</div>
<div class="min-w-[100px] flex-1 space-y-1">
<Label for="operation_type" class="text-xs"
>Tipo de factura <span class="text-red-500">*</span></Label
>
<Select.Root
type="single"
value={formData.invoice_type || ''}
onValueChange={(v) => {
formData.invoice_type = v ?? '';
}}
>
<Select.Trigger id="invoice_type" class="h-7 text-xs">
<span class="truncate">
{formData.invoice_type ? `${formData.invoice_type}` : '...'}
</span>
</Select.Trigger>
<Select.Content class="max-h-[300px]">
{#each filteredInvoiceTypes as type}
<Select.Item value={type.key}>
{type.key} - {type.description}
</Select.Item>
{#if !isSettings}
<div class="min-w-[100px] flex-1 space-y-1">
<Label for="operation_type" class="text-xs"
>Tipo de Operación<span class="text-red-500">*</span></Label
>
<Select.Root
type="single"
value={formData.operation_type || ''}
onValueChange={(v) => {
formData.operation_type = v;
}}
>
<Select.Trigger id="operation_type" class="h-8 text-sm">
<span class="truncate">
{formData.operation_type ? (formData.operation_type === 'exp' ? 'Exp' : 'Imp') : '...'}
</span>
</Select.Trigger>
<Select.Content>
<Select.Item value="exp">Exportación</Select.Item>
<Select.Item value="imp">Importación</Select.Item>
</Select.Content>
</Select.Root>
</div>
<div class="min-w-[100px] flex-1 space-y-1">
<Label for="operation_type" class="text-xs"
>Tipo de factura <span class="text-red-500">*</span></Label
>
<Select.Root
type="single"
value={formData.invoice_type || ''}
onValueChange={(v) => {
formData.invoice_type = v ?? '';
}}
>
<Select.Trigger id="invoice_type" class="h-7 text-xs">
<span class="truncate">
{formData.invoice_type ? `${formData.invoice_type}` : '...'}
</span>
</Select.Trigger>
<Select.Content class="max-h-[300px]">
{#each filteredInvoiceTypes as type}
<Select.Item value={type.key}>
{type.key} - {type.description}
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
</Select.Content>
</Select.Root>
</div>
{/if}
{#if invoiceType !== 'MEX'}
<div class="flex flex-col items-center space-y-1 pb-1">
@@ -253,34 +257,36 @@
</div>
{/if}
<div class="min-w-[140px] flex-[2] space-y-1">
<Label for="invoice_number" class="text-xs"
>Núm. Factura <span class="text-red-500">*</span></Label
>
<Input
id="invoice_number"
bind:value={formData.invoice_number}
class="h-8 text-sm font-medium"
required
/>
</div>
{#if !isSettings}
<div class="min-w-[140px] flex-[2] space-y-1">
<Label for="invoice_number" class="text-xs"
>Núm. Factura <span class="text-red-500">*</span></Label
>
<Input
id="invoice_number"
bind:value={formData.invoice_number}
class="h-8 text-sm font-medium"
required={!isSettings}
/>
</div>
<div class="min-w-[140px] flex-[2] space-y-1">
<Label for="invoice_date" class="text-xs">
{formData.operation_type === 'exp' || invoiceType === 'CR'
? 'Fecha'
: invoiceType === 'MEX'
? 'Fecha de Entrada'
: 'Fecha Factura'}
<span class="text-red-500">*</span>
</Label>
<Input id="invoice_date" type="date" bind:value={formData.invoice_date} class="h-8 text-sm" />
</div>
<div class="min-w-[140px] flex-[2] space-y-1">
<Label for="invoice_date" class="text-xs">
{formData.operation_type === 'exp' || invoiceType === 'CR'
? 'Fecha'
: invoiceType === 'MEX'
? 'Fecha de Entrada'
: 'Fecha Factura'}
<span class="text-red-500">*</span>
</Label>
<Input id="invoice_date" type="date" bind:value={formData.invoice_date} class="h-8 text-sm" />
</div>
<div class="min-w-[140px] flex-[2] space-y-1">
<Label for="emission_date" class="text-xs">Fecha Emisión</Label>
<Input id="emission_date" type="date" bind:value={formData.emission_date} class="h-8 text-sm" />
</div>
<div class="min-w-[140px] flex-[2] space-y-1">
<Label for="emission_date" class="text-xs">Fecha Emisión</Label>
<Input id="emission_date" type="date" bind:value={formData.emission_date} class="h-8 text-sm" />
</div>
{/if}
{#if invoiceType === 'MEX'}
<div class="min-w-[90px] flex-1 space-y-1">

View File

@@ -15,7 +15,8 @@
legends = [],
enclosure = [],
operationType = undefined,
invoiceType = undefined
invoiceType = undefined,
isSettings = false
}: {
invoice: Invoice | null;
formData?: any;
@@ -27,6 +28,7 @@
enclosure?: any[];
operationType?: number;
invoiceType?: string;
isSettings?: boolean;
} = $props();
let selectedLegendCode = $state<string>('');
@@ -110,6 +112,16 @@
};
exists = false;
}
$effect.pre(() => {
if (formData) {
if (formData.sub_division === undefined) formData.sub_division = 'no';
if (formData.acts_as_cd === undefined) formData.acts_as_cd = false;
if (formData.is_mixed === undefined) formData.is_mixed = false;
if (formData.incoterm === undefined) formData.incoterm = '';
if (formData.valuation_method === undefined) formData.valuation_method = '';
}
});
</script>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">

View File

@@ -16,7 +16,8 @@
exists = $bindable(),
transportModes = [],
operationType = undefined,
invoiceType = undefined
invoiceType = undefined,
isSettings = false
}: {
invoice: Invoice | null;
formData?: any;
@@ -24,6 +25,7 @@
transportModes?: any[];
operationType?: number;
invoiceType?: string;
isSettings?: boolean;
} = $props();
if (!formData && invoice) {
@@ -96,6 +98,18 @@
exists = false;
}
$effect.pre(() => {
if (formData) {
if (formData.transport_mode === undefined) formData.transport_mode = 'TRUCK';
if (formData.is_mixed === undefined) formData.is_mixed = false;
if (formData.print_stamp === undefined) formData.print_stamp = false;
if (formData.rule_3121_parties_ii === undefined) formData.rule_3121_parties_ii = false;
if (formData.contingency_mode === undefined) formData.contingency_mode = false;
if (formData.delivered_status === undefined) formData.delivered_status = false;
if (formData.option_iv18 === undefined) formData.option_iv18 = '';
}
});
// Campos que no están en el backend
let rfc = $state('');
let curp = $state('');

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,28 @@
--- frontend/src/lib/components/dashboard/invoices/settings/sscr-settings-form.svelte
+++ frontend/src/lib/components/dashboard/invoices/settings/sscr-settings-form.svelte
@@ -126,15 +126,17 @@
ActualizarPartePartida: false
};
- // Initialize defaults safely once during component setup to prevent infinite $effect reactivity loops
- if (!formData) {
- formData = { ...defaultFormData };
- } else {
- for (const key in defaultFormData) {
- backend/if (formData[key] === undefined) {
- docs/formData[key] = defaultFormData[key as keyof typeof defaultFormData];
+// Initialize defaults safely after component mounts to prevent render-phase mutations
+let initialized = false;
+$effect(() => {
c++ if (!initialized && formData) {
c++filt for (const key in defaultFormData) {
Dism++ARM64.exe if (formData[key] === undefined) {
Dism++x64.exe formData[key] = defaultFormData[key as keyof typeof defaultFormData];
Dism++ARM64.exe }
}
c++filt initialized = true;
}
- }
+});
let activeSubTab = $state('general');

View File

@@ -0,0 +1,448 @@
<script lang="ts">
import * as Tabs from '$lib/components/ui/tabs';
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import { Checkbox } from '$lib/components/ui/checkbox';
import { Textarea } from '$lib/components/ui/textarea';
import { RadioGroup, RadioGroupItem } from '$lib/components/ui/radio-group';
import { Select, SelectContent, SelectItem, SelectTrigger } from '$lib/components/ui/select';
import { Button } from '$lib/components/ui/button';
import { Search } from 'lucide-svelte';
let {
formData = $bindable(),
drivers = [],
activeSystem = 'ssisgen'
}: {
formData: any;
drivers?: any[];
activeSystem?: 'ssisgen' | 'qsisgen';
} = $props();
const transportOptions = [
{ value: 'transporte', label: 'Transporte' },
{ value: 'caja', label: 'Caja' },
{ value: 'placas', label: 'Placas' },
{ value: 'camion', label: 'Camión' },
{ value: 'buque', label: 'Buque' },
{ value: 'ferrobarcaza', label: 'Ferrobarcaza' },
{ value: 'contenedor', label: 'Contenedor' },
{ value: 'avion', label: 'Avión' }
];
const defaultFormData = {
// General
Conductor: '',
Transporte: '',
ImpOrdenComp: false,
DecimalesPeso: null,
DecimalesCant: null,
DecimalesCosto: null,
DecimalesValor: null,
// Mexicana
PorParteClaseMex: 'parte',
NumParteMex: false,
TipoFraccMex: false,
TasaFraccMex: false,
UMAlternaMex: false,
PaisOrigenMex: false,
FraccionImp: false,
UMEquivalenteMex: false,
AgregarVAenProdTerminados: false,
OcultarFechaHora: false,
ImprimirLote: false,
ImprimirNumEntrada: false,
ObservacionFMex: '',
FirmaFMex: '',
// Americana (legacy/SCAII)
PorParteClaseAMe: 'parte',
NumParteAme: false,
FraccionAme: false,
PaisOrigenAme: false,
UMEquivalenteAme: false,
LeyendaFacAme: '',
FirmaFAme: '',
// Packing List
PackingOrdenCompra: false,
IncluirLineaDelPO: false
};
if (!formData) {
formData = { ...defaultFormData };
}
$effect.pre(() => {
if (formData) {
for (const key in defaultFormData) {
if (formData[key] === undefined) {
formData[key] = defaultFormData[key as keyof typeof defaultFormData];
}
}
}
});
let activeSubTab = $state('general');
// Conductor picker modal
let showConductorPicker = $state(false);
function selectConductor(driver: any) {
formData.Conductor = driver.name ?? driver.driver_name ?? driver.full_name ?? String(driver);
showConductorPicker = false;
}
</script>
<!-- Conductor picker modal -->
{#if showConductorPicker}
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
<div class="bg-background border rounded-xl shadow-xl p-6 w-full max-w-md space-y-4">
<h3 class="text-base font-semibold">Seleccionar Conductor</h3>
{#if drivers && drivers.length > 0}
<ul class="max-h-72 overflow-y-auto divide-y rounded-md border">
{#each drivers as driver}
<li>
<button
class="w-full text-left px-4 py-2 text-sm hover:bg-muted transition-colors"
onclick={() => selectConductor(driver)}
>
{driver.name ?? driver.driver_name ?? driver.full_name ?? JSON.stringify(driver)}
</button>
</li>
{/each}
</ul>
{:else}
<p class="text-sm text-muted-foreground text-center py-6">No hay conductores registrados.</p>
{/if}
<div class="flex justify-end">
<Button variant="outline" size="sm" onclick={() => showConductorPicker = false}>Cerrar</Button>
</div>
</div>
</div>
{/if}
<div class="w-full">
<Tabs.Root bind:value={activeSubTab} class="flex flex-col">
<div class="w-full overflow-x-auto pb-2 mb-4">
<Tabs.List class="inline-flex md:flex w-full bg-muted/60 p-1 rounded-md">
<Tabs.Trigger value="general" class="flex-1">Generales</Tabs.Trigger>
<Tabs.Trigger value="mexicana" class="flex-1">Factura Mexicana Bilingüe</Tabs.Trigger>
{#if activeSystem !== 'qsisgen'}
<Tabs.Trigger value="americana" class="flex-1">Factura Americana</Tabs.Trigger>
{/if}
<Tabs.Trigger value="packing" class="flex-1">Packing List</Tabs.Trigger>
</Tabs.List>
</div>
<!-- GENERAL TAB -->
<Tabs.Content value="general" class="space-y-6">
<!-- Conductor y Transporte -->
<div class="space-y-4 border rounded-md p-4 bg-muted/10">
<h4 class="text-sm font-semibold text-muted-foreground uppercase border-b pb-2">Datos de Transporte</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 pt-2">
<!-- Conductor -->
<div class="space-y-1.5">
<Label class="text-xs">Conductor</Label>
<div class="flex gap-2">
<Input
id="cm_Conductor"
bind:value={formData.Conductor}
placeholder="Nombre del conductor..."
class="h-8 text-sm flex-1"
/>
<Button
variant="outline"
size="sm"
class="h-8 px-2"
onclick={() => showConductorPicker = true}
title="Buscar conductor del catálogo"
>
<Search size={14} />
</Button>
</div>
</div>
<!-- Tipo de Transporte -->
<div class="space-y-1.5">
<Label class="text-xs">Tipo de Transporte</Label>
<Select
type="single"
value={formData.Transporte}
onValueChange={(v) => (formData.Transporte = v)}
>
<SelectTrigger class="h-8 text-sm">
{transportOptions.find((o) => o.value === formData.Transporte)?.label || 'Seleccionar...'}
</SelectTrigger>
<SelectContent>
{#each transportOptions as opt}
<SelectItem value={opt.value}>{opt.label}</SelectItem>
{/each}
</SelectContent>
</Select>
</div>
</div>
<!-- Imprimir orden de compra por partida -->
<div class="flex items-center gap-2 pt-2">
<Checkbox id="cm_ImpOrdenComp" bind:checked={formData.ImpOrdenComp} />
<Label for="cm_ImpOrdenComp" class="text-sm">Imprimir la orden de compra por partida</Label>
</div>
</div>
<!-- Número de decimales -->
<div class="space-y-4 border rounded-md p-4 bg-muted/10">
<h4 class="text-sm font-semibold text-muted-foreground uppercase border-b pb-2">Número de Decimales</h4>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 pt-2">
<div class="space-y-1.5">
<Label class="text-xs">Peso Neto y Bruto</Label>
<Input type="number" bind:value={formData.DecimalesPeso} class="h-8 text-sm" />
</div>
<div class="space-y-1.5">
<Label class="text-xs">En Cantidades</Label>
<Input type="number" bind:value={formData.DecimalesCant} class="h-8 text-sm" />
</div>
<div class="space-y-1.5">
<Label class="text-xs">Costos Unitarios</Label>
<Input type="number" bind:value={formData.DecimalesCosto} class="h-8 text-sm" />
</div>
<div class="space-y-1.5">
<Label class="text-xs">En Valores</Label>
<Input type="number" bind:value={formData.DecimalesValor} class="h-8 text-sm" />
</div>
</div>
</div>
</Tabs.Content>
<!-- MEXICANA TAB -->
<Tabs.Content value="mexicana" class="space-y-6">
{#if activeSystem === 'qsisgen'}
<!-- SCAF DESIGN (Based on image) -->
<div class="space-y-6">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- Incluir en la descripción (Panel Izquierdo) -->
<div class="space-y-4 border rounded-md p-4 bg-muted/10 h-full">
<h4 class="text-xs font-bold text-red-600/80 dark:text-red-400 uppercase border-b pb-2 tracking-tight">
Incluir en la columna de descripción
</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-3 pt-2">
<div class="flex items-center gap-2">
<Checkbox id="cm_TipoFraccMex_scaf" bind:checked={formData.TipoFraccMex} />
<Label for="cm_TipoFraccMex_scaf" class="text-sm">El Tipo de Fracción.</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="cm_PaisOrigenMex_scaf" bind:checked={formData.PaisOrigenMex} />
<Label for="cm_PaisOrigenMex_scaf" class="text-sm">El país de origen.</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="cm_TasaFraccMex_scaf" bind:checked={formData.TasaFraccMex} />
<Label for="cm_TasaFraccMex_scaf" class="text-sm">La tasa de la Fracción.</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="cm_FraccionImp_scaf" bind:checked={formData.FraccionImp} />
<Label for="cm_FraccionImp_scaf" class="text-sm">La fracción Arancelaria.</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="cm_ImprimirLote_scaf" bind:checked={formData.ImprimirLote} />
<Label for="cm_ImprimirLote_scaf" class="text-sm">Lote</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="cm_ImprimirNumEntrada_scaf" bind:checked={formData.ImprimirNumEntrada} />
<Label for="cm_ImprimirNumEntrada_scaf" class="text-sm">Num. Entrada</Label>
</div>
</div>
</div>
<!-- Firma (Panel Derecho) -->
<div class="space-y-4 border rounded-md p-4 bg-muted/10 h-full">
<Label for="cm_FirmaFMex_scaf" class="text-sm font-semibold block mb-2">Firma en factura:</Label>
<Textarea
id="cm_FirmaFMex_scaf"
bind:value={formData.FirmaFMex}
class="h-[calc(100%-2rem)] min-h-[120px] resize-none"
/>
</div>
</div>
<!-- Observaciones (Panel Inferior) -->
<div class="space-y-2 border rounded-md p-4 bg-muted/10">
<Label for="cm_ObservacionFMex_scaf" class="text-sm font-semibold">
Observación de la Factura Mexicana y Bilingüe:
</Label>
<Textarea
id="cm_ObservacionFMex_scaf"
bind:value={formData.ObservacionFMex}
class="h-32 resize-y"
/>
</div>
</div>
{:else}
<!-- SCAII DESIGN (Traditional) -->
<div class="space-y-6">
<!-- Imprimir por -->
<div class="space-y-4 border rounded-md p-4 bg-muted/10">
<h4 class="text-sm font-semibold text-muted-foreground uppercase">Imprimir por</h4>
<RadioGroup
value={formData.PorParteClaseMex}
onValueChange={(v) => (formData.PorParteClaseMex = v)}
class="flex gap-4"
>
<div class="flex items-center gap-2">
<RadioGroupItem value="parte" id="cm_mex_imprimir_parte" />
<Label for="cm_mex_imprimir_parte" class="text-sm">Parte</Label>
</div>
<div class="flex items-center gap-2">
<RadioGroupItem value="clase" id="cm_mex_imprimir_clase" />
<Label for="cm_mex_imprimir_clase" class="text-sm">Clase</Label>
</div>
</RadioGroup>
</div>
<!-- Incluir en la columna de descripción -->
<div class="space-y-4 border rounded-md p-4 bg-muted/10">
<h4 class="text-sm font-semibold text-muted-foreground uppercase border-b pb-2">
Incluir en la columna de descripción
</h4>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 pt-2">
<div class="flex items-center gap-2">
<Checkbox id="cm_NumParteMex" bind:checked={formData.NumParteMex} />
<Label for="cm_NumParteMex" class="text-sm">El número de parte</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="cm_TipoFraccMex" bind:checked={formData.TipoFraccMex} />
<Label for="cm_TipoFraccMex" class="text-sm">El tipo de fracción</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="cm_TasaFraccMex" bind:checked={formData.TasaFraccMex} />
<Label for="cm_TasaFraccMex" class="text-sm">La tasa de fracción</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="cm_UMAlternaMex" bind:checked={formData.UMAlternaMex} />
<Label for="cm_UMAlternaMex" class="text-sm">Cantidad alterna</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="cm_PaisOrigenMex" bind:checked={formData.PaisOrigenMex} />
<Label for="cm_PaisOrigenMex" class="text-sm">El país de origen</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="cm_FraccionImp" bind:checked={formData.FraccionImp} />
<Label for="cm_FraccionImp" class="text-sm">La fracción arancelaria</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="cm_UMEquivalenteMex" bind:checked={formData.UMEquivalenteMex} />
<Label for="cm_UMEquivalenteMex" class="text-sm">La U.M. Equivalente</Label>
</div>
</div>
<!-- Opciones adicionales fuera de la sub-sección -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 pt-4 border-t mt-4">
<div class="flex items-center gap-2">
<Checkbox id="cm_AgregarVAenProdTerminados" bind:checked={formData.AgregarVAenProdTerminados} />
<Label for="cm_AgregarVAenProdTerminados" class="text-sm">Agregar VA en Prod. Terminados</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="cm_OcultarFechaHora" bind:checked={formData.OcultarFechaHora} />
<Label for="cm_OcultarFechaHora" class="text-sm">Ocultar Fecha y Hora</Label>
</div>
</div>
</div>
<!-- Firma -->
<div class="space-y-4 border rounded-md p-4 bg-muted/10">
<h4 class="text-sm font-semibold text-muted-foreground uppercase border-b pb-2">Firma</h4>
<div class="space-y-2 pt-2">
<Textarea id="cm_FirmaFMex" bind:value={formData.FirmaFMex} class="h-24 resize-y" />
</div>
</div>
</div>
{/if}
</Tabs.Content>
<!-- AMERICANA TAB -->
{#if activeSystem !== 'qsisgen'}
<Tabs.Content value="americana" class="space-y-6">
<!-- Imprimir por -->
<div class="space-y-4 border rounded-md p-4 bg-muted/10">
<h4 class="text-sm font-semibold text-muted-foreground uppercase">Imprimir por</h4>
<RadioGroup
value={formData.PorParteClaseAMe}
onValueChange={(v) => (formData.PorParteClaseAMe = v)}
class="flex gap-4"
>
<div class="flex items-center gap-2">
<RadioGroupItem value="parte" id="cm_ame_imprimir_parte" />
<Label for="cm_ame_imprimir_parte" class="text-sm">Parte</Label>
</div>
<div class="flex items-center gap-2">
<RadioGroupItem value="clase" id="cm_ame_imprimir_clase" />
<Label for="cm_ame_imprimir_clase" class="text-sm">Clase</Label>
</div>
</RadioGroup>
</div>
<!-- Incluir en la columna de descripción -->
<div class="space-y-4 border rounded-md p-4 bg-muted/10">
<h4 class="text-sm font-semibold text-muted-foreground uppercase border-b pb-2">
Incluir en la columna de descripción
</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 pt-2">
<div class="flex items-center gap-2">
<Checkbox id="cm_NumParteAme" bind:checked={formData.NumParteAme} />
<Label for="cm_NumParteAme" class="text-sm">El número de parte</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="cm_FraccionAme" bind:checked={formData.FraccionAme} />
<Label for="cm_FraccionAme" class="text-sm">La fracción americana</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="cm_PaisOrigenAme" bind:checked={formData.PaisOrigenAme} />
<Label for="cm_PaisOrigenAme" class="text-sm">El país de origen</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="cm_UMEquivalenteAme" bind:checked={formData.UMEquivalenteAme} />
<Label for="cm_UMEquivalenteAme" class="text-sm">La U.M. N. Equivalente</Label>
</div>
</div>
</div>
<!-- Textos libres -->
<div class="space-y-4 border rounded-md p-4 bg-muted/10">
<div class="space-y-2">
<Label for="cm_LeyendaFacAme" class="text-sm font-semibold text-muted-foreground">
Leyenda factura
</Label>
<Textarea id="cm_LeyendaFacAme" bind:value={formData.LeyendaFacAme} class="h-24 resize-y" />
</div>
<div class="space-y-2 pt-4">
<Label for="cm_FirmaFAme" class="text-sm font-semibold text-muted-foreground">
Firma en factura americana
</Label>
<Textarea id="cm_FirmaFAme" bind:value={formData.FirmaFAme} class="h-24 resize-y" />
</div>
</div>
</Tabs.Content>
{/if}
<!-- PACKING LIST TAB -->
<Tabs.Content value="packing" class="space-y-6">
<div class="space-y-4 border rounded-md p-4 bg-muted/10">
<h4 class="text-sm font-semibold text-muted-foreground uppercase border-b pb-2">
Opciones de Packing List
</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 pt-2">
<div class="flex items-center gap-2">
<Checkbox id="cm_PackingOrdenCompra" bind:checked={formData.PackingOrdenCompra} />
<Label for="cm_PackingOrdenCompra" class="text-sm">Imprimir orden de compra</Label>
</div>
{#if activeSystem !== 'qsisgen'}
<div class="flex items-center gap-2">
<Checkbox id="cm_IncluirLineaDelPO" bind:checked={formData.IncluirLineaDelPO} />
<Label for="cm_IncluirLineaDelPO" class="text-sm">Incluir línea de PO</Label>
</div>
{/if}
</div>
</div>
</Tabs.Content>
</Tabs.Root>
</div>

View File

@@ -0,0 +1,392 @@
<script lang="ts">
import * as Tabs from '$lib/components/ui/tabs';
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import { Checkbox } from '$lib/components/ui/checkbox';
import { Textarea } from '$lib/components/ui/textarea';
import { RadioGroup, RadioGroupItem } from '$lib/components/ui/radio-group';
import { Button } from '$lib/components/ui/button';
let {
formData = $bindable(),
activeSystem = 'ssisgen'
}: {
formData: any;
activeSystem?: 'ssisgen' | 'qsisgen';
} = $props();
const defaultFormData = {
DecimalesPeso: null,
DecimalesCant: null,
DecimalesValor: null,
DecimalesCosto: null,
ControlRemesa: false,
RemesaInicio: null,
RemesaFinal: null,
CantLimiteMin: null,
PesoLimiteMin: null,
ValorLimiteMin: null,
ValorLimiteParMin: null,
CantLimite: null,
PesoLimite: null,
ValorLimite: null,
ValorLimitePar: null,
RestringeImpoPT: false,
ActualizarPartePartida: false,
CalcIGIBaseCapt: false,
LocAPOrigen: false,
SolicitarContrasenaAdministrador: false,
// SCAF specific (backend persist)
pagoimpuesto: 'no',
formapago: '',
// Mexicana
PorParteClaseMex: 'parte',
NumParteMex: false,
PaisOrigenMex: false,
UMEquivalenteMex: false,
UMAlternaMex: false,
FraccionImp: false,
TasaFraccMex: false,
TipoFraccMex: false,
ImpOrdenCom: false,
PerPagRenglon: false,
ParteComplementariaMex: false,
ParteCompleMexPartida: false,
ImprimirLote: false,
ImprimirNumEntrada: false,
CodigoBarras: false,
UMAuxiliarMex: false,
AgregarVAenProdTerminados: false,
OcultarFechaHora: false,
FirmaFMex: '',
// Americana
PorParteClaseAMe: 'parte',
NumParteAme: false,
FraccionAme: false,
PaisOrigenAme: false,
UMEquivalenteAme: false,
LeyendaFacAme: '',
FirmaFAme: '',
// Packing list
PackingPedClave: false,
PackingOrdenCompra: false,
PorOrdenPorOCPack: 'captura',
IncluirLineaDelPO: false
};
if (!formData) {
formData = { ...defaultFormData };
}
$effect.pre(() => {
if (formData) {
for (const key in defaultFormData) {
if (formData[key] === undefined) {
formData[key] = defaultFormData[key as keyof typeof defaultFormData];
}
}
}
});
let activeSubTab = $state('general');
</script>
<div class="w-full">
<Tabs.Root bind:value={activeSubTab} class="flex flex-col">
<div class="w-full overflow-x-auto pb-2 mb-4">
<Tabs.List class="inline-flex md:flex w-full bg-muted/60 p-1 rounded-md">
<Tabs.Trigger value="general" class="flex-1">General</Tabs.Trigger>
<Tabs.Trigger value="mexicana" class="flex-1">Factura Mexicana y Bilingüe</Tabs.Trigger>
<Tabs.Trigger value="americana" class="flex-1">Factura Americana</Tabs.Trigger>
<Tabs.Trigger value="packing" class="flex-1">Packing List</Tabs.Trigger>
</Tabs.List>
</div>
<!-- ========================================================================= -->
<!-- TAB: GENERAL -->
<!-- ========================================================================= -->
<Tabs.Content value="general" class="space-y-6">
<!-- Configuración de Decimales -->
<div class="space-y-4 border rounded-md p-4 bg-muted/10">
<h4 class="text-sm font-semibold text-muted-foreground uppercase">Configuración de Decimales</h4>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div class="space-y-1.5">
<Label class="text-xs">Decimales (Peso Neto y Bruto)</Label>
<Input type="number" bind:value={formData.DecimalesPeso} class="h-8 text-sm" />
</div>
<div class="space-y-1.5">
<Label class="text-xs">Decimales (Cantidades)</Label>
<Input type="number" bind:value={formData.DecimalesCant} class="h-8 text-sm" />
</div>
<div class="space-y-1.5">
<Label class="text-xs">Decimales (Valores)</Label>
<Input type="number" bind:value={formData.DecimalesValor} class="h-8 text-sm" />
</div>
<div class="space-y-1.5">
<Label class="text-xs">Decimales (Costos Unitarios)</Label>
<Input type="number" bind:value={formData.DecimalesCosto} class="h-8 text-sm" />
</div>
</div>
</div>
<!-- SCAII: Control Remesa -->
{#if activeSystem === 'ssisgen'}
<div class="space-y-4 border rounded-md p-4 bg-muted/10">
<div class="flex items-center gap-2 mb-2 border-b pb-2">
<Checkbox id="ControlRemesa" bind:checked={formData.ControlRemesa} />
<Label for="ControlRemesa" class="font-semibold text-muted-foreground uppercase">Control Remesa por Rangos</Label>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4" class:opacity-50={!formData.ControlRemesa}>
<div class="space-y-1.5">
<Label class="text-xs">De la Remesa</Label>
<Input type="number" bind:value={formData.RemesaInicio} disabled={!formData.ControlRemesa} class="h-8 text-sm" />
</div>
<div class="space-y-1.5">
<Label class="text-xs">A la Remesa</Label>
<Input type="number" bind:value={formData.RemesaFinal} disabled={!formData.ControlRemesa} class="h-8 text-sm" />
</div>
</div>
</div>
{/if}
<!-- SCAF: Sub sección Partidas & Formas de Pago -->
{#if activeSystem === 'qsisgen'}
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="space-y-4 border rounded-md p-4 bg-muted/10">
<h4 class="text-sm font-semibold text-muted-foreground uppercase border-b pb-2">Sub sección Partidas</h4>
<div class="space-y-3">
<Label class="text-sm font-medium">¿Se pagó el impuesto?</Label>
<RadioGroup value={formData.pagoimpuesto} onValueChange={(v) => formData.pagoimpuesto = v} class="flex gap-4">
<div class="flex items-center gap-2">
<RadioGroupItem value="si" id="pago_si" />
<Label for="pago_si" class="text-sm cursor-pointer">Si</Label>
</div>
<div class="flex items-center gap-2">
<RadioGroupItem value="no" id="pago_no" />
<Label for="pago_no" class="text-sm cursor-pointer">No</Label>
</div>
</RadioGroup>
</div>
</div>
<div class="space-y-4 border rounded-md p-4 bg-muted/10">
<h4 class="text-sm font-semibold text-muted-foreground uppercase border-b pb-2">Formas de Pago</h4>
<div class="space-y-2">
<Label class="text-sm font-medium">Seleccionar Forma de Pago</Label>
<div class="flex gap-2">
<Input bind:value={formData.formapago} placeholder="Ej. 0, 21..." class="h-9" />
<Button variant="outline" size="sm" class="h-9 px-3 shrink-0">Catálogo</Button>
</div>
</div>
</div>
</div>
{/if}
<!-- Limites Min/Max (Shared) -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="space-y-4 border rounded-md p-4 bg-muted/10">
<h4 class="text-sm font-semibold text-muted-foreground uppercase border-b pb-2">Mínimos límites para actualizar</h4>
<div class="grid grid-cols-2 gap-4 pt-2">
<div class="space-y-1"><Label class="text-[10px]">Cantidad</Label><Input type="number" bind:value={formData.CantLimiteMin} class="h-7 text-xs" /></div>
<div class="space-y-1"><Label class="text-[10px]">Peso</Label><Input type="number" bind:value={formData.PesoLimiteMin} class="h-7 text-xs" /></div>
<div class="space-y-1"><Label class="text-[10px]">Valor</Label><Input type="number" bind:value={formData.ValorLimiteMin} class="h-7 text-xs" /></div>
<div class="space-y-1"><Label class="text-[10px]">Valor Part.</Label><Input type="number" bind:value={formData.ValorLimiteParMin} class="h-7 text-xs" /></div>
</div>
</div>
<div class="space-y-4 border rounded-md p-4 bg-muted/10">
<h4 class="text-sm font-semibold text-muted-foreground uppercase border-b pb-2">Máximos límites para actualizar</h4>
<div class="grid grid-cols-2 gap-4 pt-2">
<div class="space-y-1"><Label class="text-[10px]">Cantidad</Label><Input type="number" bind:value={formData.CantLimite} class="h-7 text-xs" /></div>
<div class="space-y-1"><Label class="text-[10px]">Peso</Label><Input type="number" bind:value={formData.PesoLimite} class="h-7 text-xs" /></div>
<div class="space-y-1"><Label class="text-[10px]">Valor</Label><Input type="number" bind:value={formData.ValorLimite} class="h-7 text-xs" /></div>
<div class="space-y-1"><Label class="text-[10px]">Valor Part.</Label><Input type="number" bind:value={formData.ValorLimitePar} class="h-7 text-xs" /></div>
</div>
</div>
</div>
<!-- Otras Opciones -->
<div class="space-y-4 border rounded-md p-4 bg-muted/10">
<h4 class="text-sm font-semibold text-muted-foreground uppercase border-b pb-2">Otras Opciones</h4>
{#if activeSystem === 'qsisgen'}
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="flex items-center gap-2">
<Checkbox id="scaf_ActualizarParte" bind:checked={formData.ActualizarPartePartida} />
<Label for="scaf_ActualizarParte" class="text-sm">Actualizar datos del número de parte en base de a la captura de las partidas</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="scaf_SolicitarAuth" bind:checked={formData.SolicitarContrasenaAdministrador} />
<Label for="scaf_SolicitarAuth" class="text-sm font-bold">
Solicitar autorizacion para pasar por alto la validacion de tipo de cambio de fecha de pago de pedimento
</Label>
</div>
</div>
{:else}
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<div class="flex items-center gap-2"><Checkbox id="RestringeImpoPT" bind:checked={formData.RestringeImpoPT} /><Label for="RestringeImpoPT" class="text-sm">Restringe la importación de Productos Terminados</Label></div>
<div class="flex items-center gap-2"><Checkbox id="ActualizarPartePartida" bind:checked={formData.ActualizarPartePartida} /><Label for="ActualizarPartePartida" class="text-sm">Actualiza datos del número de parte en base a captura de partidas</Label></div>
<div class="flex items-center gap-2"><Checkbox id="CalcIGIBaseCapt" bind:checked={formData.CalcIGIBaseCapt} /><Label for="CalcIGIBaseCapt" class="text-sm">Cálculo del Valor IGI en Base a lo capturado</Label></div>
<div class="flex items-center gap-2"><Checkbox id="LocAPOrigen" bind:checked={formData.LocAPOrigen} /><Label for="LocAPOrigen" class="text-sm">Advertencia País de Origen</Label></div>
<div class="flex items-center gap-2 col-span-2"><Checkbox id="SolicitarContrasenaAdministrador" bind:checked={formData.SolicitarContrasenaAdministrador} /><Label for="SolicitarContrasenaAdministrador" class="text-sm">Solicitar Autorización para Pasar por Alto la Validacion de Temporalidad en Base a Fecha de Pago de Pedimento</Label></div>
</div>
{/if}
</div>
</Tabs.Content>
<!-- ========================================================================= -->
<!-- TAB: MEXICANA -->
<!-- ========================================================================= -->
<Tabs.Content value="mexicana" class="space-y-6">
{#if activeSystem === 'qsisgen'}
<!-- SCAF LAYOUT (Modern Style) -->
<div class="space-y-6">
<div class="space-y-4 border rounded-md p-4 bg-muted/10">
<h4 class="text-sm font-semibold text-muted-foreground uppercase border-b pb-2">Factura Mexicana y Bilingüe</h4>
<div class="flex items-center gap-2 mb-4">
<Checkbox id="scaf_CodigoBarras" bind:checked={formData.CodigoBarras} />
<Label for="scaf_CodigoBarras" class="text-sm">Imprimir Código de Barras en la parte inferior de la factura.</Label>
</div>
<div class="space-y-4 border rounded-md p-4 bg-muted/5">
<h4 class="text-sm font-semibold text-muted-foreground uppercase border-b pb-2">Incluir en la columna de descripción</h4>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 pt-2">
<!-- Column 1 -->
<div class="space-y-3">
<div class="flex items-center gap-2">
<Checkbox id="m1_pais" bind:checked={formData.PaisOrigenMex} />
<Label for="m1_pais" class="text-sm">El País de Origen.</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="m1_perm" bind:checked={formData.PerPagRenglon} />
<Label for="m1_perm" class="text-sm">Permiso y la Página - Renglón.</Label>
</div>
<div class="flex flex-col gap-0.5">
<div class="flex items-center gap-2">
<Checkbox id="m1_ord" bind:checked={formData.ImpOrdenCom} />
<Label for="m1_ord" class="text-sm">Orden de Compra.</Label>
</div>
<span class="text-[10px] text-muted-foreground ml-6 font-medium">(Facturas y Packing List)</span>
</div>
</div>
<!-- Column 2 -->
<div class="space-y-3">
<div class="flex items-center gap-2">
<Checkbox id="m2_frac" bind:checked={formData.FraccionImp} />
<Label for="m2_frac" class="text-sm">Fracción Arancelaria.</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="m2_tasa" bind:checked={formData.TasaFraccMex} />
<Label for="m2_tasa" class="text-sm">Tasa de la Fracción Arancelaria.</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="m2_pref" bind:checked={formData.TipoFraccMex} />
<Label for="m2_pref" class="text-sm">La Preferencia de la Fracción Arancelaria.</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="m2_num" bind:checked={formData.NumParteMex} />
<Label for="m2_num" class="text-sm">Número de Parte.</Label>
</div>
</div>
<!-- Column 3 -->
<div class="space-y-3">
<div class="flex items-center gap-2">
<Checkbox id="m3_lote" bind:checked={formData.ImprimirLote} />
<Label for="m3_lote" class="text-sm">Lote</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="m3_ent" bind:checked={formData.ImprimirNumEntrada} />
<Label for="m3_ent" class="text-sm">Num. Entrada</Label>
</div>
</div>
</div>
</div>
<div class="space-y-2 pt-4 flex flex-col items-center">
<Label for="scaf_Firma" class="text-xs font-semibold text-muted-foreground">Firma en Factura Mexicana:</Label>
<Textarea id="scaf_Firma" bind:value={formData.FirmaFMex} class="h-24 w-full max-w-xl resize-y" />
</div>
</div>
</div>
{:else}
<!-- SCAII LAYOUT -->
<div class="space-y-6">
<div class="space-y-4 border rounded-md p-4 bg-muted/10">
<h4 class="text-sm font-semibold text-muted-foreground uppercase border-b pb-2">Imprimir por</h4>
<RadioGroup value={formData.PorParteClaseMex} onValueChange={(v) => formData.PorParteClaseMex = v} class="flex gap-6">
<div class="flex items-center gap-2"><RadioGroupItem value="parte" id="s_p" /><Label for="s_p" class="text-sm">Parte</Label></div>
<div class="flex items-center gap-2"><RadioGroupItem value="clase" id="s_c" /><Label for="s_c" class="text-sm">Clase</Label></div>
</RadioGroup>
</div>
<div class="space-y-4 border rounded-md p-4 bg-muted/10">
<h4 class="text-sm font-semibold text-muted-foreground uppercase border-b pb-2">Incluir en descripción</h4>
<div class="grid grid-cols-2 md:grid-cols-3 gap-4">
<div class="flex items-center gap-2"><Checkbox id="sn_p" bind:checked={formData.NumParteMex} /><Label for="sn_p" class="text-xs">Número de parte</Label></div>
<div class="flex items-center gap-2"><Checkbox id="sn_o" bind:checked={formData.PaisOrigenMex} /><Label for="sn_o" class="text-xs">País de origen</Label></div>
<div class="flex items-center gap-2"><Checkbox id="sn_e" bind:checked={formData.UMEquivalenteMex} /><Label for="sn_e" class="text-xs">U.M. Equivalente</Label></div>
<div class="flex items-center gap-2"><Checkbox id="sn_a" bind:checked={formData.UMAlternaMex} /><Label for="sn_a" class="text-xs">Cantidad Alterna</Label></div>
<div class="flex items-center gap-2"><Checkbox id="sn_f" bind:checked={formData.FraccionImp} /><Label for="sn_f" class="text-xs">Fracción Arancelaria</Label></div>
<div class="flex items-center gap-2"><Checkbox id="sn_t" bind:checked={formData.TasaFraccMex} /><Label for="sn_t" class="text-xs">Tasa Fracción</Label></div>
</div>
</div>
<div class="space-y-2 border rounded-md p-4 bg-muted/10">
<Label class="text-sm font-semibold">Firma Mexicana</Label>
<Textarea bind:value={formData.FirmaFMex} class="h-20" />
</div>
</div>
{/if}
</Tabs.Content>
<!-- ========================================================================= -->
<!-- TAB: AMERICANA -->
<!-- ========================================================================= -->
<Tabs.Content value="americana" class="space-y-6">
<div class="space-y-4 border rounded-md p-4 bg-muted/10">
<h4 class="text-sm font-semibold text-muted-foreground uppercase border-b pb-2">Incluir en descripción</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="flex items-center gap-2"><Checkbox id="a_p" bind:checked={formData.NumParteAme} /><Label for="a_p" class="text-sm">Número de parte</Label></div>
<div class="flex items-center gap-2"><Checkbox id="a_f" bind:checked={formData.FraccionAme} /><Label for="a_f" class="text-sm">Fracción Americana</Label></div>
<div class="flex items-center gap-2"><Checkbox id="a_o" bind:checked={formData.PaisOrigenAme} /><Label for="a_o" class="text-sm">País de origen</Label></div>
{#if activeSystem === 'ssisgen'}
<div class="flex items-center gap-2"><Checkbox id="a_e" bind:checked={formData.UMEquivalenteAme} /><Label for="a_e" class="text-sm">U.M. Equivalente</Label></div>
{/if}
</div>
</div>
<div class="space-y-4 border rounded-md p-4 bg-muted/10">
{#if activeSystem === 'ssisgen'}
<div class="space-y-2">
<Label class="text-xs font-semibold text-muted-foreground uppercase">Leyenda factura</Label>
<Textarea bind:value={formData.LeyendaFacAme} class="h-20" />
</div>
{/if}
<div class="space-y-2">
<Label class="text-xs font-semibold text-muted-foreground uppercase">Firma en factura americana</Label>
<Textarea bind:value={formData.FirmaFAme} class="h-24 resize-y" />
</div>
</div>
</Tabs.Content>
<!-- ========================================================================= -->
<!-- TAB: PACKING -->
<!-- ========================================================================= -->
<Tabs.Content value="packing" class="space-y-6">
<div class="space-y-4 border rounded-md p-4 bg-muted/10">
<h4 class="text-sm font-semibold text-muted-foreground uppercase border-b pb-2">Opciones</h4>
<div class="space-y-3">
<div class="flex items-center gap-2">
<Checkbox id="p_oc" bind:checked={formData.PackingOrdenCompra} />
<Label for="p_oc" class="text-sm">
{activeSystem === 'qsisgen' ? 'imprimir orden de compra' : 'Imprimir orden de compra'}
</Label>
</div>
{#if activeSystem === 'ssisgen'}
<div class="flex items-center gap-2"><Checkbox id="p_pc" bind:checked={formData.PackingPedClave} /><Label for="p_pc" class="text-sm">Muestra pedimento y clave</Label></div>
<div class="flex items-center gap-2"><Checkbox id="p_pl" bind:checked={formData.IncluirLineaDelPO} /><Label for="p_pl" class="text-sm">Incluir línea de PO</Label></div>
{/if}
</div>
</div>
</Tabs.Content>
</Tabs.Root>
</div>

View File

@@ -0,0 +1,535 @@
<script lang="ts">
import * as Tabs from '$lib/components/ui/tabs';
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import { Checkbox } from '$lib/components/ui/checkbox';
import { Textarea } from '$lib/components/ui/textarea';
import { RadioGroup, RadioGroupItem } from '$lib/components/ui/radio-group';
import { Button } from '$lib/components/ui/button';
let {
formData = $bindable(),
activeSystem = 'ssisgen'
}: {
formData: any;
activeSystem?: 'ssisgen' | 'qsisgen';
} = $props();
const defaultFormData = {
DecimalesPeso: null,
DecimalesCant: null,
DecimalesValor: null,
DecimalesCosto: null,
CantLimiteMin: null,
PesoLimiteMin: null,
ValorLimiteMin: null,
ValorLimiteParMin: null,
CantLimite: null,
PesoLimite: null,
ValorLimite: null,
ValorLimitePar: null,
RestringeImpoPT: false,
ActualizarPartePartida: false,
LocAPOrigen: false,
SolicitarContrasenaAdministrador: false,
// SCAF specific
pagoimpuesto: 'no',
formapago: '',
// Mexicana
PorParteClaseMex: 'parte',
NumParteMex: false,
PaisOrigenMex: false,
PerPagRenglon: false,
UMEquivalenteMex: false,
UMAlternaMex: false,
FraccionImp: false,
TasaFraccMex: false,
ImpOrdenCom: false,
ParteComplementariaMex: false,
ParteCompleMexPartida: false,
ImprimirLote: false,
ImprimirNumEntrada: false,
CodigoBarras: false,
UMAuxiliarMex: false,
AgregarVAenProdTerminados: false,
OcultarFechaHora: false,
FirmaFMex: '',
// Americana
PorParteClaseAMe: 'parte',
NumParteAme: false,
FraccionAme: false,
PaisOrigenAme: false,
UMEquivalenteAme: false,
LeyendaFacAme: '',
FirmaFAme: '',
// Packing list
PackingPedClave: false,
PackingOrdenCompra: false,
PorOrdenPorOCPack: 'captura',
IncluirLineaDelPO: false
};
if (!formData) {
formData = { ...defaultFormData };
}
$effect.pre(() => {
if (formData) {
for (const key in defaultFormData) {
if (formData[key] === undefined) {
formData[key] = defaultFormData[key as keyof typeof defaultFormData];
}
}
}
});
let activeSubTab = $state('general');
</script>
<div class="w-full">
<Tabs.Root bind:value={activeSubTab} class="flex flex-col">
<div class="w-full overflow-x-auto pb-2 mb-4">
<Tabs.List class="inline-flex md:flex w-full bg-muted/60 p-1 rounded-md">
<Tabs.Trigger value="general" class="flex-1">General</Tabs.Trigger>
<Tabs.Trigger value="mexicana" class="flex-1">Factura Mexicana y Bilingüe</Tabs.Trigger>
<Tabs.Trigger value="americana" class="flex-1">Factura Americana</Tabs.Trigger>
<Tabs.Trigger value="packing" class="flex-1">Packing List</Tabs.Trigger>
</Tabs.List>
</div>
<!-- GENERAL TAB -->
<Tabs.Content value="general" class="space-y-6">
{#if activeSystem === 'qsisgen'}
<!-- SCAF General Tab -->
<div class="space-y-6">
<!-- Se Pago Impuesto (Partidas) -->
<div class="space-y-4 rounded-md border bg-muted/10 p-4">
<h4 class="border-b pb-2 text-sm font-semibold text-muted-foreground uppercase">Partidas</h4>
<div class="flex flex-col gap-2">
<Label class="text-xs font-semibold text-muted-foreground uppercase">
¿Se pagó el impuesto?
</Label>
<RadioGroup
value={formData.pagoimpuesto}
onValueChange={(v) => (formData.pagoimpuesto = v)}
class="flex gap-6"
>
<div class="flex items-center gap-2">
<RadioGroupItem value="si" id="def_pago_si" />
<Label for="def_pago_si" class="text-sm"></Label>
</div>
<div class="flex items-center gap-2">
<RadioGroupItem value="no" id="def_pago_no" />
<Label for="def_pago_no" class="text-sm">No</Label>
</div>
</RadioGroup>
</div>
</div>
<!-- Formas de Pago -->
<div class="space-y-4 rounded-md border bg-muted/10 p-4">
<h4 class="border-b pb-2 text-sm font-semibold text-muted-foreground uppercase">
Formas de Pago
</h4>
<div class="flex items-end gap-3">
<div class="flex-1 space-y-2">
<Label for="def_formapago" class="text-xs font-semibold text-muted-foreground uppercase">
SisImp: FormaPago
</Label>
<Input id="def_formapago" bind:value={formData.formapago} class="h-9" readonly />
</div>
<Button variant="outline" class="h-9 px-4 uppercase text-xs font-bold tracking-tight">
Catálogo...
</Button>
</div>
</div>
<!-- Otras Opciones (SCAF) -->
<div class="space-y-4 rounded-md border bg-muted/10 p-4">
<h4 class="border-b pb-2 text-sm font-semibold text-muted-foreground uppercase">
Otras Opciones
</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 pt-2">
<div class="flex items-center gap-2">
<Checkbox id="def_ActualizarParte_scaf" bind:checked={formData.ActualizarPartePartida} />
<Label for="def_ActualizarParte_scaf" class="text-sm">Actualizar datos del número de parte en base de a la captura de las partidas</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="def_SolicitarAuth_scaf" bind:checked={formData.SolicitarContrasenaAdministrador} />
<Label for="def_SolicitarAuth_scaf" class="text-sm font-bold">
Solicitar autorizacion para pasar por alto la validacion de tipo de cambio de fecha de pago de pedimento
</Label>
</div>
</div>
</div>
</div>
{:else}
<!-- SCAII General Tab (Legacy) -->
<div class="space-y-6">
<!-- Configuración de Decimales -->
<div class="space-y-4 border rounded-md p-4 bg-muted/10">
<h4 class="text-sm font-semibold text-muted-foreground uppercase">Configuración de Decimales</h4>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div class="space-y-1.5">
<Label class="text-xs">Decimales (Peso Neto y Bruto)</Label>
<Input type="number" bind:value={formData.DecimalesPeso} class="h-8 text-sm" />
</div>
<div class="space-y-1.5">
<Label class="text-xs">Decimales (Cantidades)</Label>
<Input type="number" bind:value={formData.DecimalesCant} class="h-8 text-sm" />
</div>
<div class="space-y-1.5">
<Label class="text-xs">Decimales (Valores)</Label>
<Input type="number" bind:value={formData.DecimalesValor} class="h-8 text-sm" />
</div>
<div class="space-y-1.5">
<Label class="text-xs">Decimales (Costos Unitarios)</Label>
<Input type="number" bind:value={formData.DecimalesCosto} class="h-8 text-sm" />
</div>
</div>
</div>
<!-- Limites Minimos -->
<div class="space-y-4 border rounded-md p-4 bg-muted/10">
<h4 class="text-sm font-semibold text-muted-foreground uppercase border-b pb-2">Mínimos límites para actualizar las facturas</h4>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 pt-2">
<div class="space-y-1.5">
<Label class="text-xs">Cantidad Mínima</Label>
<Input type="number" bind:value={formData.CantLimiteMin} class="h-8 text-sm" />
</div>
<div class="space-y-1.5">
<Label class="text-xs">Peso Mínimo</Label>
<Input type="number" bind:value={formData.PesoLimiteMin} class="h-8 text-sm" />
</div>
<div class="space-y-1.5">
<Label class="text-xs">Valor Mínimo</Label>
<Input type="number" bind:value={formData.ValorLimiteMin} class="h-8 text-sm" />
</div>
<div class="space-y-1.5">
<Label class="text-xs">Valor Mínimo por Partida</Label>
<Input type="number" bind:value={formData.ValorLimiteParMin} class="h-8 text-sm" />
</div>
</div>
</div>
<!-- Limites Maximos -->
<div class="space-y-4 border rounded-md p-4 bg-muted/10">
<h4 class="text-sm font-semibold text-muted-foreground uppercase border-b pb-2">Máximos límites para actualizar las facturas</h4>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 pt-2">
<div class="space-y-1.5">
<Label class="text-xs">Cantidad Máxima</Label>
<Input type="number" bind:value={formData.CantLimite} class="h-8 text-sm" />
</div>
<div class="space-y-1.5">
<Label class="text-xs">Peso Máximo</Label>
<Input type="number" bind:value={formData.PesoLimite} class="h-8 text-sm" />
</div>
<div class="space-y-1.5">
<Label class="text-xs">Valor Máximo</Label>
<Input type="number" bind:value={formData.ValorLimite} class="h-8 text-sm" />
</div>
<div class="space-y-1.5">
<Label class="text-xs">Valor Máximo por Partida</Label>
<Input type="number" bind:value={formData.ValorLimitePar} class="h-8 text-sm" />
</div>
</div>
</div>
<!-- Otras Opciones (Footer) -->
<div class="space-y-4 border rounded-md p-4 bg-muted/10">
<h4 class="text-sm font-semibold text-muted-foreground uppercase border-b pb-2">Otras Opciones</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 pt-2">
<div class="flex items-center gap-2">
<Checkbox id="def_RestringeImpoPT" bind:checked={formData.RestringeImpoPT} />
<Label for="def_RestringeImpoPT" class="text-sm">Restringe la importación de Productos Terminados</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="def_ActualizarPartePartida" bind:checked={formData.ActualizarPartePartida} />
<Label for="def_ActualizarPartePartida" class="text-sm">Actualiza datos del número de parte en base a captura de partidas</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="def_LocAPOrigen" bind:checked={formData.LocAPOrigen} />
<Label for="def_LocAPOrigen" class="text-sm">Advertencia País de Origen</Label>
</div>
<div class="flex items-center gap-2 md:col-span-2">
<Checkbox id="def_SolicitarContrasenaAdministrador" bind:checked={formData.SolicitarContrasenaAdministrador} />
<Label for="def_SolicitarContrasenaAdministrador" class="text-sm">Solicitar Autorización para Pasar por Alto la Validacion de Temporalidad en Base a Fecha de Pago de Pedimento</Label>
</div>
</div>
</div>
</div>
{/if}
</Tabs.Content>
<!-- MEXICANA TAB -->
<Tabs.Content value="mexicana" class="space-y-6">
{#if activeSystem === 'qsisgen'}
<!-- SCAF Mexicana (Modern Grid) -->
<div class="space-y-6">
<div class="space-y-4 rounded-md border bg-muted/10 p-4">
<h4 class="border-b pb-2 text-sm font-semibold text-muted-foreground uppercase">
Opciones de Impresión
</h4>
<div class="flex items-center gap-2 py-2">
<Checkbox id="def_CodigoBarras_scaf" bind:checked={formData.CodigoBarras} />
<Label for="def_CodigoBarras_scaf" class="text-sm font-semibold">
Imprimir código de barras en la parte inferior de la factura
</Label>
</div>
<div class="space-y-4 pt-2">
<h5 class="flex items-center gap-2 text-xs font-bold tracking-wider text-primary uppercase">
<span class="h-px flex-1 bg-border"></span>
Incluir en la columna de descripción
<span class="h-px flex-1 bg-border"></span>
</h5>
<div class="grid grid-cols-1 gap-x-8 gap-y-4 md:grid-cols-2 lg:grid-cols-3">
<div class="flex items-center gap-2">
<Checkbox id="def_PaisOrigenMex_scaf" bind:checked={formData.PaisOrigenMex} />
<Label for="def_PaisOrigenMex_scaf" class="text-sm">El país de origen</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="def_PerPagRenglon_scaf" bind:checked={formData.PerPagRenglon} />
<Label for="def_PerPagRenglon_scaf" class="text-sm">Permiso y la Página - Renglón</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="def_ImpOrdenCom_scaf" bind:checked={formData.ImpOrdenCom} />
<Label for="def_ImpOrdenCom_scaf" class="text-sm">Orden de compra (Facturas y Packing List)</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="def_FraccionImp_scaf" bind:checked={formData.FraccionImp} />
<Label for="def_FraccionImp_scaf" class="text-sm">Fracción arancelaria</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="def_TasaFraccMex_scaf" bind:checked={formData.TasaFraccMex} />
<Label for="def_TasaFraccMex_scaf" class="text-sm">Tasa de la fracción</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="def_ImprimirLote_scaf" bind:checked={formData.ImprimirLote} />
<Label for="def_ImprimirLote_scaf" class="text-sm font-bold text-primary">Lote</Label>
</div>
</div>
</div>
</div>
<!-- Firma (SCAF Mexicana) -->
<div class="space-y-4 rounded-md border bg-muted/10 p-4">
<h4 class="border-b pb-2 text-sm font-semibold text-muted-foreground uppercase">Firma</h4>
<div class="space-y-2">
<Label for="def_FirmaFMex_scaf" class="text-xs font-semibold text-muted-foreground uppercase">Firma en factura mexicana</Label>
<Textarea id="def_FirmaFMex_scaf" bind:value={formData.FirmaFMex} class="h-24 resize-y" />
</div>
</div>
</div>
{:else}
<!-- SCAII Mexicana (Legacy) -->
<div class="space-y-6">
<!-- Imprimir por -->
<div class="space-y-4 border rounded-md p-4 bg-muted/10">
<h4 class="text-sm font-semibold text-muted-foreground uppercase">Imprimir por</h4>
<RadioGroup value={formData.PorParteClaseMex} onValueChange={(v) => formData.PorParteClaseMex = v} class="flex gap-4">
<div class="flex items-center gap-2">
<RadioGroupItem value="parte" id="def_mex_imprimir_parte" />
<Label for="def_mex_imprimir_parte" class="text-sm">Parte</Label>
</div>
<div class="flex items-center gap-2">
<RadioGroupItem value="clase" id="def_mex_imprimir_clase" />
<Label for="def_mex_imprimir_clase" class="text-sm">Clase</Label>
</div>
</RadioGroup>
</div>
<!-- Incluir en la columna de descripción -->
<div class="space-y-4 border rounded-md p-4 bg-muted/10">
<h4 class="text-sm font-semibold text-muted-foreground uppercase border-b pb-2">Incluir en la columna de descripción</h4>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 pt-2">
<div class="flex items-center gap-2">
<Checkbox id="def_NumParteMex" bind:checked={formData.NumParteMex} />
<Label for="def_NumParteMex" class="text-sm">El número de parte</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="def_PaisOrigenMex" bind:checked={formData.PaisOrigenMex} />
<Label for="def_PaisOrigenMex" class="text-sm">El país de origen</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="def_PerPagRenglon" bind:checked={formData.PerPagRenglon} />
<Label for="def_PerPagRenglon" class="text-sm">Permiso y la Página - Renglón</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="def_UMEquivalenteMex" bind:checked={formData.UMEquivalenteMex} />
<Label for="def_UMEquivalenteMex" class="text-sm">La U.M. Equivalente</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="def_UMAlternaMex" bind:checked={formData.UMAlternaMex} />
<Label for="def_UMAlternaMex" class="text-sm">Cantidad Alterna</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="def_FraccionImp" bind:checked={formData.FraccionImp} />
<Label for="def_FraccionImp" class="text-sm">Fracción Arancelaria</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="def_TasaFraccMex" bind:checked={formData.TasaFraccMex} />
<Label for="def_TasaFraccMex" class="text-sm">Tasa de la Fracción Arancelaria</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="def_ImpOrdenCom" bind:checked={formData.ImpOrdenCom} />
<Label for="def_ImpOrdenCom" class="text-sm">Orden de Compra (Solo OC)</Label>
</div>
</div>
<!-- Parte Complementaria -->
<div class="mt-4 pt-4 border-t space-y-2">
<h5 class="text-xs font-semibold text-muted-foreground uppercase mb-2">Parte Complementaria de:</h5>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 pl-2">
<div class="flex items-center gap-2">
<Checkbox id="def_ParteComplementariaMex" bind:checked={formData.ParteComplementariaMex} />
<Label for="def_ParteComplementariaMex" class="text-sm">Número de parte</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="def_ParteCompleMexPartida" bind:checked={formData.ParteCompleMexPartida} />
<Label for="def_ParteCompleMexPartida" class="text-sm">La partida</Label>
</div>
</div>
</div>
<!-- Más opciones -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 pt-4 border-t mt-4">
<div class="flex items-center gap-2">
<Checkbox id="def_ImprimirLote" bind:checked={formData.ImprimirLote} />
<Label for="def_ImprimirLote" class="text-sm">Lote</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="def_ImprimirNumEntrada" bind:checked={formData.ImprimirNumEntrada} />
<Label for="def_ImprimirNumEntrada" class="text-sm">Núm. Entrada</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="def_CodigoBarras" bind:checked={formData.CodigoBarras} />
<Label for="def_CodigoBarras" class="text-sm">Imprimir Código de Barras en parte inferior</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="def_UMAuxiliarMex" bind:checked={formData.UMAuxiliarMex} />
<Label for="def_UMAuxiliarMex" class="text-sm">Muestra UM Auxiliar por UM Comercial</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="def_AgregarVAenProdTerminados" bind:checked={formData.AgregarVAenProdTerminados} />
<Label for="def_AgregarVAenProdTerminados" class="text-sm">Agregar VA en Prod. Terminados</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="def_OcultarFechaHora" bind:checked={formData.OcultarFechaHora} />
<Label for="def_OcultarFechaHora" class="text-sm">Ocultar Fecha y Hora</Label>
</div>
</div>
</div>
<!-- Firma (SCAII Moderna) -->
<div class="space-y-4 border rounded-md p-4 bg-muted/10">
<h4 class="text-sm font-semibold text-muted-foreground uppercase border-b pb-2">Firmas</h4>
<div class="space-y-2 pt-2">
<Label for="def_FirmaFMex" class="text-sm uppercase text-xs font-bold text-muted-foreground">Firma en Factura Mexicana:</Label>
<Textarea id="def_FirmaFMex" bind:value={formData.FirmaFMex} class="h-24 resize-y" />
</div>
</div>
</div>
{/if}
</Tabs.Content>
<!-- AMERICANA TAB -->
<Tabs.Content value="americana" class="space-y-6">
<!-- Opciones Compartidas -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- Imprimir por -->
<div class="space-y-4 border rounded-md p-4 bg-muted/10 h-full">
<h4 class="text-sm font-semibold text-muted-foreground uppercase">Imprimir por</h4>
<RadioGroup value={formData.PorParteClaseAMe} onValueChange={(v) => formData.PorParteClaseAMe = v} class="flex gap-4">
<div class="flex items-center gap-2">
<RadioGroupItem value="parte" id="def_ame_imprimir_parte" />
<Label for="def_ame_imprimir_parte" class="text-sm">Parte</Label>
</div>
<div class="flex items-center gap-2">
<RadioGroupItem value="clase" id="def_ame_imprimir_clase" />
<Label for="def_ame_imprimir_clase" class="text-sm">Clase</Label>
</div>
</RadioGroup>
</div>
<!-- Incluir en la descripción -->
<div class="space-y-4 border rounded-md p-4 bg-muted/10 h-full">
<h4 class="text-sm font-semibold text-muted-foreground uppercase border-b pb-2">Incluir en la descripción</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 pt-2">
<div class="flex items-center gap-2">
<Checkbox id="def_NumParteAme" bind:checked={formData.NumParteAme} />
<Label for="def_NumParteAme" class="text-sm">El número de parte</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="def_FraccionAme" bind:checked={formData.FraccionAme} />
<Label for="def_FraccionAme" class="text-sm">La fracción americana</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="def_PaisOrigenAme" bind:checked={formData.PaisOrigenAme} />
<Label for="def_PaisOrigenAme" class="text-sm">El país de origen</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="def_UMEquivalenteAme" bind:checked={formData.UMEquivalenteAme} />
<Label for="def_UMEquivalenteAme" class="text-sm">La U.M. N. Equivalente</Label>
</div>
</div>
</div>
</div>
<!-- Firma y Notas -->
<div class="space-y-4 border rounded-md p-4 bg-muted/10">
<h4 class="text-sm font-semibold text-muted-foreground uppercase border-b pb-2">Firma y Notas</h4>
<div class="space-y-2">
<Label for="def_FirmaFAme" class="text-xs font-semibold text-muted-foreground uppercase">Firma en factura americana</Label>
<Textarea id="def_FirmaFAme" bind:value={formData.FirmaFAme} class="h-24 resize-y" />
</div>
<div class="space-y-2 pt-2">
<Label for="def_LeyendaFacAme" class="text-xs font-semibold text-muted-foreground uppercase">Leyenda factura</Label>
<Textarea id="def_LeyendaFacAme" bind:value={formData.LeyendaFacAme} class="h-20 resize-y" />
</div>
</div>
</Tabs.Content>
<!-- PACKING LIST TAB -->
<Tabs.Content value="packing" class="space-y-6">
<!-- Opciones de Packing List -->
<div class="space-y-4 border rounded-md p-4 bg-muted/10">
<h4 class="text-sm font-semibold text-muted-foreground uppercase border-b pb-2">Opciones de Packing List</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 pt-2">
<div class="flex items-center gap-2">
<Checkbox id="def_PackingPedClave" bind:checked={formData.PackingPedClave} />
<Label for="def_PackingPedClave" class="text-sm">Muestra el número de pedimento y su clave de pedimento en el Packing List</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="def_PackingOrdenCompra" bind:checked={formData.PackingOrdenCompra} />
<Label for="def_PackingOrdenCompra" class="text-sm">Imprimir orden de compra</Label>
</div>
<div class="flex items-center gap-2">
<Checkbox id="def_IncluirLineaDelPO" bind:checked={formData.IncluirLineaDelPO} />
<Label for="def_IncluirLineaDelPO" class="text-sm">Incluir línea de PO</Label>
</div>
</div>
</div>
<!-- Imprimir por -->
<div class="space-y-4 border rounded-md p-4 bg-muted/10">
<h4 class="text-sm font-semibold text-muted-foreground uppercase">Imprimir por</h4>
<RadioGroup value={formData.PorOrdenPorOCPack} onValueChange={(v) => formData.PorOrdenPorOCPack = v} class="flex gap-4">
<div class="flex items-center gap-2">
<RadioGroupItem value="captura" id="def_pack_imprimir_captura" />
<Label for="def_pack_imprimir_captura" class="text-sm">Orden de captura</Label>
</div>
<div class="flex items-center gap-2">
<RadioGroupItem value="compra" id="def_pack_imprimir_compra" />
<Label for="def_pack_imprimir_compra" class="text-sm">Orden de compra</Label>
</div>
</RadioGroup>
</div>
</Tabs.Content>
</Tabs.Root>
</div>

View File

@@ -0,0 +1,79 @@
<script lang="ts">
import { SETTINGS_METADATA } from './settings-metadata';
import SettingFormField from './SettingFormField.svelte';
import { Input } from '$lib/components/ui/input';
import { Search, FilterX } from 'lucide-svelte';
import { fade } from 'svelte/transition';
export let category: string;
export let currentData: any = {};
export let onUpdate: (data: any) => void;
let searchQuery = '';
// Local state to manage edits before final save
let formData: any = { ...currentData };
// Update local state when category changes
$: {
formData = { ...currentData };
}
function handleFieldChange(key: string, value: any) {
formData[key] = value;
onUpdate(formData); // Propagate changes upward
}
// Filter fields based on metadata and search query
$: availableFields = SETTINGS_METADATA[category] || Object.keys(currentData);
$: filteredFields = availableFields.filter(f =>
f.toLowerCase().includes(searchQuery.toLowerCase())
);
// Derived grouped fields for UI organization
$: hasResults = filteredFields.length > 0;
</script>
<div class="space-y-6 flex flex-col h-full overflow-hidden">
<!-- Search/Filter Bar -->
<div class="relative group mx-2">
<Search class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-white/20 group-focus-within:text-primary transition-colors" />
<Input
placeholder="Buscar parámetro en {category}..."
bind:value={searchQuery}
class="pl-10 bg-black/40 border-white/5 h-10 focus-visible:ring-primary/20"
/>
</div>
<!-- Fields Grid -->
<div class="flex-1 overflow-y-auto px-2 custom-scrollbar pr-4">
{#if hasResults}
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 pb-8">
{#each filteredFields as field (field)}
<div in:fade={{ duration: 150 }}>
<SettingFormField
key={field}
value={formData[field]}
onChange={(val) => handleFieldChange(field, val)}
/>
</div>
{/each}
</div>
{:else}
<div class="flex flex-col items-center justify-center h-48 text-white/20 border-2 border-dashed border-white/5 rounded-2xl mx-2">
<FilterX class="w-12 h-12 mb-4 opacity-50" />
<p class="text-xs uppercase tracking-widest">No se encontraron parámetros</p>
</div>
{/if}
</div>
</div>
<style>
.custom-scrollbar::-webkit-scrollbar {
width: 4px;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.05);
border-radius: 10px;
}
</style>

View File

@@ -0,0 +1,526 @@
<script lang="ts">
import * as Tabs from '$lib/components/ui/tabs';
import SettingFormField from './SettingFormField.svelte';
import { Button } from '$lib/components/ui/button';
import { AlertTriangle, CreditCard, FileEdit, RefreshCw } from 'lucide-svelte';
import { onMount } from 'svelte';
interface Props {
currentData: any;
onUpdate: (data: any) => void;
}
let { currentData = {}, onUpdate }: Props = $props();
// Labels — mismos que SCAII más los específicos de SCAF
const FIELD_LABELS: Record<string, string> = {
// General (heredados de SsisGen)
factoriva: 'Factor IVA (Aplica en compras)',
dta: 'DTA',
actseguridad: 'Activar la seguridad del sistema',
// SIN mensajevenc / diavencimiento (no aplica en SCAF)
calvalbasetcped: 'Cálculo en base al TC fecha pago (Importación)',
calvalbasetcpedexpo: 'Cálculo en base al TC fecha pago (Exportación)',
controldes: 'Asignar fecha límite para desactualizar facturas',
diadesactual: 'Días para desactualizar facturas (Impo/Expo)',
decimalespeso: 'Peso neto y bruto',
decimalescant: 'Cantidades',
decimalesvalor: 'Valores y costos',
filtrocantidad: 'Omitir Cantidades con balance menor a',
muestraarchcodbarras: 'Muestra archivo TXT al generar factura/remesa',
// SCAF extra en código de barras
codigobarrasesp: 'Imprimir 3 en código de barras',
// Archivos electrónicos
patharch: 'Generación de los archivos PDF en',
patharchtransmision: 'Generación de archivos electrónicos para transmitir en',
pathrespuesta: 'Dirección de espera de la respuesta del broker americano',
patharchped: 'Generación de archivos electrónicos para pedimentos en',
// Continuación 1
pathgenimpotemp: 'Ruta de los archivos CSV para importación temporal',
pathgenexpo: 'Ruta de los archivos CSV para exportación',
tipovenro: 'Tipo de validación para regla octava',
cantvenro: 'Cantidad (Porcentaje/Días)',
calcdepreciacion: 'Cálculo de la depreciación',
firmapacking: 'Esconder la Firma en el Packing List',
escondamepacking: 'Esconde el A. A. Mexicano en Packing List',
advertenciatm: 'Advertencia el Tipo de Moneda en Facturas',
valmanifusado: 'Activar Control de Manifiesto',
repdescargolineal: 'Generar Reporte de Descargo por Orden de factura',
muestra_copias_codbarras: 'Imprimir Código de Barras en las demás copias, aparte de la copia del transportista',
// Continuación 2
validadecencant: 'Invalida la Captura de Decimales en Cantidades con Unidad de Medida Pieza',
mostraradvertenciaro: 'Mostrar Advertencia de Valores de Regla Octava en Partidas',
usartranspamedocame: 'Utilizar Transportista Americano en el Packing List y en Bill Of Lading de Exportación',
cantvscantseries: 'Validar la Cantidad Contra la Cantidad de Series',
covefechaemision: 'Tomar la Fecha de Emision para la Generación del COVE',
usarcoveenarchsaaim3: 'Usar E-Document en Lugar del Número de Factura en el Archivo de Transferencia SAAIM3',
asignainfoparte: 'Asigna en Impo. Temporal, Definitiva y Compras Mex. la información completa del número de parte',
interfaceaatcfpff: 'En Interfaces (WINSAAI, SAAIM3), usar tipo de cambio de:',
incluirobscoveobsimpo: 'Incluir las Observaciones del COVE en las Observaciones en Importación',
agregarincreimpo: 'Agregar Incrementables, Fecha de Emisión y Factor de Multimoneda en las Facturas de Importación',
// Continuación 3
identificadornodoseriecove: 'Omitir datos de identificación en COVE (Regla 7.3.3 OEA)',
pathnaftaccs: 'Ruta NAFTA CSS',
actpdfreportes: 'Activa para que se generen los PDFs al momento de Actualización',
patharchpdfimpo: 'Generar Archivos PDF de Importación en',
patharchpdfexpo: 'Generar Archivos PDF de Exportación en',
mensajesvurfc: 'Desactivar mensajes de RFC en Transmision de VU',
mostrarpackinglistingles: 'Mostrar Packing List en Inglés',
enviarsubpartidascove: 'Enviar Sub Partidas a XML COVE',
utilizarfechapagopeddeundiaanterior: 'Utilizar Tipo de Cambio de la Fecha de Pago de Pedimento De Un Dia Anterior',
utilizartitulosalternativosimpresionfactura: 'Utilizar Títulos Alternativos en la Impresión de las Facturas',
// Continuación 4
usartcdelafechapagopedimpoendescarga: 'Usar Tipo de Cambio de la Fecha de Pago de Pedimento de Importación en Impresion de la Descarga',
usarvude128o256: 'Utilizar Ventanilla Unica con Encriptacion de 256',
agregarnumeroembarque: 'Agregar Numero Embarque',
afostrofe: 'Imprimir Caracter Especial en Columna Clase / Num. Parte (Exportar a Excel)',
utilizarumdeexistenciaentransmisionvu: 'Utilizar UM de Existencia En Transmision VU',
utilizarcodigodebrokerdeclienteenmainx30: 'Utilizar Codigo De Broker De Cliente En Main X 30',
hojacalculosepararincrementablesanexo3: 'En la Hoja de Calculo Separar los Incrementables en el Anexo 3',
hojacalculodesglosefacturaanexo3: 'En la Hoja de Calculo Desglosar las Facturas en el Anexo 3',
utilizarnombregenericomainx30: 'Utilizar Nombre Generico Mainx 30',
utilizartcrespectoatipoped: 'Utilizar TC Respecto Al Tipo de Pedimento',
utilizarsolopartesnaftaenco: 'Utilizar Solo Partes Nafta En CO',
cambiarpesosporcostounitario: 'Cambiar Pesos Por Costo Unitario En Exportación',
bloqueodeediciondefacturas: 'Bloqueo De Edición De Facturas',
impresionfacturaalterna: 'Imprimir Factura Alterna En Hoja De Calculo Y Manifestación Al Valor',
// Continuación 5
transmitirfacalterna: 'Transmitir Factura Alterna en VU',
validarseries: 'Omitir validación series en facturas',
emailas: 'Activar ventana EmailAS',
};
const canonicalize = (data: any) => {
const result: any = {};
const recognizedKeys = Object.keys(FIELD_LABELS);
for (const rawKey in data) {
const canonicalKey = recognizedKeys.find((k) => k.toLowerCase() === rawKey.toLowerCase());
if (canonicalKey) {
result[canonicalKey] = data[rawKey];
} else {
result[rawKey] = data[rawKey];
}
}
return result;
};
let formData: any = $state({
downloadftp: 'No',
descargarftpolocal: 'FTP',
minsdownlftp: 0,
usarfechaemisionfactura: 0,
campo18valsaaim3: 0,
...canonicalize(currentData)
});
// Guarda la última versión recibida del padre para detectar cambios reales vs. echo circular
let lastExternalData: string = JSON.stringify({ ...currentData });
$effect(() => {
const newData = { ...currentData };
const newDataStr = JSON.stringify(newData);
// Solo sincroniza si el dato cambió externamente (no es un echo de nuestro propio onUpdate)
if (newDataStr === lastExternalData) return;
lastExternalData = newDataStr;
const mapped = canonicalize(newData);
for (const key in mapped) {
formData[key] = mapped[key];
}
});
// Notifica al padre del estado completo al montar (incluyendo defaults)
onMount(() => {
lastExternalData = JSON.stringify({ ...currentData });
onUpdate({ ...formData });
});
function handleFieldChange(key: string, value: any) {
formData[key] = value;
onUpdate({ ...formData });
}
// Tab General — igual que SCAII pero SIN mensajevenc/diavencimiento, más codigobarrasEsp
const GENERAL_SECTIONS = [
{
title: 'Parámetros del Sistema',
fields: ['factoriva', 'dta', 'actseguridad']
},
{
title: 'Tipos de Cambio',
fields: ['calvalbasetcped', 'calvalbasetcpedexpo']
},
{
title: 'Desactualización de Facturas',
fields: ['controldes', 'diadesactual']
},
{
title: 'Número de decimales en reportes generales en',
fields: ['decimalespeso', 'decimalescant', 'decimalesvalor']
},
{
title: 'Filtrar por la cantidad',
fields: ['filtrocantidad']
},
{
title: 'Código de barras',
fields: ['muestraarchcodbarras', 'codigobarrasesp']
}
];
// Tab Archivos — solo 4 rutas (sin pathgenimpotemp/pathgenexpo/patharchpedconsm)
const ARCHIVOS_SECTIONS = [
{
title: 'Gestión de Archivos PDF y Electrónicos',
fields: [
'patharch',
'patharchtransmision',
'pathrespuesta',
'patharchped',
]
}
];
// Continuación 1
const CONTINUACION_SECTIONS = [
{
title: 'Rutas CSV',
fields: ['pathgenimpotemp', 'pathgenexpo']
},
{
title: 'Tipo de validación para regla octava',
fields: ['tipovenro', 'cantvenro']
},
{
title: 'Cálculo de la depreciación',
fields: ['calcdepreciacion']
},
{
title: 'Parámetros Adicionales y Visualización',
fields: [
'firmapacking',
'escondamepacking',
'advertenciatm',
'valmanifusado',
'repdescargolineal',
'muestra_copias_codbarras',
]
}
];
// Continuación 2
const CONT2_SECTIONS = [
{
title: 'Configuraciones de Pantalla y Validación',
fields: [
'validadecencant',
'mostraradvertenciaro',
'usartranspamedocame',
'cantvscantseries',
'covefechaemision',
'usarcoveenarchsaaim3',
]
},
{
title: 'SCAF.INI',
fields: ['asignainfoparte']
},
{
title: 'En Interfaces (WINSAAI, SAAIM3), usar tipo de cambio de:',
fields: ['interfaceaatcfpff']
},
{
title: 'COVE e Importación',
fields: ['incluirobscoveobsimpo', 'agregarincreimpo']
}
];
// Continuación 3
const CONT3_SECTIONS = [
{
id: 'rutas_pdf',
title: 'Rutas y Archivos PDF',
fields: [
'identificadornodoseriecove',
'pathnaftaccs',
'actpdfreportes',
'patharchpdfimpo',
'patharchpdfexpo',
]
},
{
id: 'opciones',
title: 'Opciones',
fields: [
'mensajesvurfc',
'mostrarpackinglistingles',
'enviarsubpartidascove',
'utilizarfechapagopeddeundiaanterior',
'utilizartitulosalternativosimpresionfactura',
]
}
];
// Continuación 4
const CONT4_FIELDS = [
'usartcdelafechapagopedimpoendescarga',
'usarvude128o256',
'agregarnumeroembarque',
'afostrofe',
'utilizarumdeexistenciaentransmisionvu',
'utilizarcodigodebrokerdeclienteenmainx30',
'hojacalculosepararincrementablesanexo3',
'hojacalculodesglosefacturaanexo3',
'utilizarnombregenericomainx30',
'utilizartcrespectoatipoped',
'utilizarsolopartesnaftaenco',
'cambiarpesosporcostounitario',
'bloqueodeediciondefacturas',
'impresionfacturaalterna',
];
// Continuación 5
const CONT5_FIELDS = [
'transmitirfacalterna',
'validarseries',
'emailas',
];
const TABS = [
{ id: 'general', label: 'General' },
{ id: 'archivos', label: 'Archivos electrónicos' },
{ id: 'continuacion', label: 'Continuación' },
{ id: 'cont2', label: 'Cont 2' },
{ id: 'cont3', label: 'Cont 3' },
{ id: 'cont4', label: 'Cont 4' },
{ id: 'cont5', label: 'Cont 5' },
];
const RULE_VALIDATOR_OPTIONS = [
{ value: 'Porcentaje', label: 'Porcentaje' },
{ value: 'Dias', label: 'Días' },
{ value: 'No aplica', label: 'No aplica' }
];
const DEPRECIACION_OPTIONS = [
{ value: 'MES', label: 'MES' },
{ value: 'DIA', label: 'DÍA' }
];
const INTERFACE_TC_OPTIONS = [
{ value: 'P', label: 'Fecha de Pago' },
{ value: 'F', label: 'Fecha de Factura' }
];
</script>
<div class="w-full">
<Tabs.Root value="general" class="flex flex-col h-full">
<Tabs.List class="grid w-full grid-cols-3 md:grid-cols-4 lg:grid-cols-7 bg-muted rounded-md p-1 mb-4">
{#each TABS as tab}
<Tabs.Trigger
value={tab.id}
class="w-full px-3 py-1.5 text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm"
>
{tab.label}
</Tabs.Trigger>
{/each}
</Tabs.List>
<!-- GENERAL -->
<Tabs.Content value="general" class="space-y-6">
<div class="space-y-6 py-4">
{#each GENERAL_SECTIONS as section}
<div class="space-y-4">
<div class="flex items-center gap-4">
<h3 class="font-medium text-lg">{section.title}</h3>
<div class="h-px w-full bg-border mt-2"></div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{#each section.fields as field}
<SettingFormField
key={field}
label={FIELD_LABELS[field] || field}
value={formData[field]}
onChange={(val) => handleFieldChange(field, val)}
type={field === 'DTA' ? 'number' : undefined}
/>
{/each}
</div>
</div>
{/each}
</div>
</Tabs.Content>
<!-- ARCHIVOS ELECTRÓNICOS — solo 4 rutas -->
<Tabs.Content value="archivos" class="space-y-6">
<div class="space-y-6 py-4">
{#each ARCHIVOS_SECTIONS as section}
<div class="space-y-6 max-w-3xl mx-auto">
<div class="flex items-center gap-4">
<h3 class="font-medium text-lg">{section.title}</h3>
<div class="h-px w-full bg-border mt-2"></div>
</div>
<div class="grid grid-cols-1 gap-4">
{#each section.fields as field}
<SettingFormField
key={field}
label={FIELD_LABELS[field] || field}
value={formData[field]}
onChange={(val) => handleFieldChange(field, val)}
/>
{/each}
</div>
</div>
{/each}
</div>
</Tabs.Content>
<!-- CONTINUACIÓN 1 -->
<Tabs.Content value="continuacion" class="space-y-6">
<div class="space-y-6 py-4">
{#each CONTINUACION_SECTIONS as section}
<div class="space-y-4">
<div class="flex items-center gap-4">
<h3 class="font-medium text-lg">{section.title}</h3>
<div class="h-px w-full bg-border mt-2"></div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{#each section.fields as field}
<SettingFormField
key={field}
label={FIELD_LABELS[field] || field}
value={formData[field]}
onChange={(val) => handleFieldChange(field, val)}
options={
field === 'TipoVenRO' ? RULE_VALIDATOR_OPTIONS :
field === 'CalcDepreciacion' ? DEPRECIACION_OPTIONS :
undefined
}
type={
field === 'TipoVenRO' ? 'select' :
field === 'CalcDepreciacion' ? 'radio' :
field === 'CantVenRO' ? 'number' :
undefined
}
/>
{/each}
</div>
</div>
{/each}
</div>
</Tabs.Content>
<!-- CONTINUACIÓN 2 -->
<Tabs.Content value="cont2" class="space-y-6">
<div class="space-y-6 py-4">
{#each CONT2_SECTIONS as section}
<div class="space-y-6 max-w-4xl mx-auto">
<div class="flex items-center gap-4">
<h3 class="font-medium text-lg">{section.title}</h3>
<div class="h-px w-full bg-border mt-2"></div>
</div>
<div class="grid grid-cols-1 gap-4">
{#each section.fields as field}
<SettingFormField
key={field}
label={FIELD_LABELS[field] || field}
value={formData[field]}
onChange={(val) => handleFieldChange(field, val)}
type="switch"
/>
{/each}
</div>
</div>
{/each}
</div>
</Tabs.Content>
<!-- CONTINUACIÓN 3 -->
<Tabs.Content value="cont3" class="space-y-6">
<div class="space-y-6 py-4">
{#each CONT3_SECTIONS as section}
<div class="space-y-6 max-w-4xl mx-auto">
<div class="flex items-center gap-4">
<h3 class="font-medium text-lg">{section.title}</h3>
<div class="h-px w-full bg-border mt-2"></div>
</div>
<div class="grid grid-cols-1 gap-4">
{#each section.fields as field}
<SettingFormField
key={field}
label={FIELD_LABELS[field] || field}
value={formData[field]}
onChange={(val) => handleFieldChange(field, val)}
type={
field.toLowerCase().includes('path') || field.toLowerCase().includes('nafta')
? undefined
: 'switch'
}
/>
{/each}
</div>
</div>
{/each}
</div>
</Tabs.Content>
<!-- CONTINUACIÓN 4 -->
<Tabs.Content value="cont4" class="space-y-6">
<div class="space-y-6 py-4 max-w-4xl mx-auto">
<div class="flex items-center gap-4">
<h3 class="font-medium text-lg">Opciones Adicionales</h3>
<div class="h-px w-full bg-border mt-2"></div>
</div>
<div class="grid grid-cols-1 gap-4">
{#each CONT4_FIELDS as field}
<SettingFormField
key={field}
label={FIELD_LABELS[field] || field}
value={formData[field]}
onChange={(val) => handleFieldChange(field, val)}
type="switch"
/>
{/each}
</div>
</div>
</Tabs.Content>
<!-- CONTINUACIÓN 5 -->
<Tabs.Content value="cont5" class="space-y-6">
<div class="space-y-6 py-4 max-w-4xl mx-auto">
<div class="flex items-center gap-4">
<h3 class="font-medium text-lg">Configuraciones Adicionales</h3>
<div class="h-px w-full bg-border mt-2"></div>
</div>
<div class="grid grid-cols-1 gap-4">
{#each CONT5_FIELDS as field}
<SettingFormField
key={field}
label={FIELD_LABELS[field] || field}
value={formData[field]}
onChange={(val) => handleFieldChange(field, val)}
type="switch"
/>
{/each}
</div>
</div>
</Tabs.Content>
</Tabs.Root>
</div>
<style>
:global([data-radix-scroll-area-viewport]) {
scrollbar-width: none;
-ms-overflow-style: none;
}
:global([data-radix-scroll-area-viewport]::-webkit-scrollbar) {
display: none;
}
</style>

View File

@@ -0,0 +1,72 @@
<script lang="ts">
import {
Settings, Package, Truck, Construction,
TrendingUp, FileText, ClipboardList, Database,
ChevronRight, Layers
} from 'lucide-svelte';
import { fade, slide } from 'svelte/transition';
export let activeCategory: string;
export let onSelect: (cat: string) => void;
const groups = [
{
title: 'SCAII (Inventarios)',
icon: Package,
items: [
{ id: 'ssisgen', label: 'Parámetros Generales', icon: Settings },
{ id: 'ssisgen2', label: 'Valor Agregado', icon: TrendingUp },
{ id: 'ssisgen3', label: 'Otros Parámetros', icon: ClipboardList },
{ id: 'ssismex', label: 'Nacional (MEX)', icon: FileText },
{ id: 'ssisdef', label: 'Imp. Definitiva', icon: Truck },
{ id: 'ssisimpo', label: 'Imp. Temporal', icon: Database },
{ id: 'ssisexpo', label: 'Exportación', icon: Construction },
]
},
{
title: 'SCAF (Activos Fijos)',
icon: Layers,
items: [
{ id: 'qsisgen', label: 'General Activos', icon: Settings },
{ id: 'qsiscmex', label: 'Mantenimiento EX', icon: FileText },
{ id: 'qsisdef', label: 'Imp. Definitiva', icon: Truck },
{ id: 'qsisimpo', label: 'Imp. Temporal', icon: Database },
{ id: 'qsisexpo', label: 'Exportación', icon: Construction },
{ id: 'qsisimporep', label: 'Rep. Importación', icon: ClipboardList },
{ id: 'qsisexporep', label: 'Rep. Exportación', icon: ClipboardList },
]
}
];
</script>
<div class="space-y-6">
{#each groups as group}
<div class="space-y-2">
<h3 class="text-[10px] font-bold uppercase tracking-widest text-muted-foreground px-4 flex items-center gap-2">
<svelte:component this={group.icon} class="w-3 h-3" />
{group.title}
</h3>
<div class="space-y-1">
{#each group.items as item}
<button
class="w-full flex items-center justify-between gap-3 px-4 py-2.5 rounded-lg transition-all group
{activeCategory === item.id
? 'bg-primary/10 text-primary border border-primary/20 shadow-lg shadow-primary/5'
: 'text-white/60 hover:bg-white/5 border border-transparent hover:border-white/5'}"
onclick={() => onSelect(item.id)}
>
<div class="flex items-center gap-3">
<svelte:component this={item.icon} class="w-4 h-4 {activeCategory === item.id ? 'text-primary' : 'text-white/40 group-hover:text-white/60'}" />
<span class="text-sm font-medium">{item.label}</span>
</div>
{#if activeCategory === item.id}
<div in:fade>
<ChevronRight class="w-4 h-4 text-primary" />
</div>
{/if}
</button>
{/each}
</div>
</div>
{/each}
</div>

View File

@@ -0,0 +1,176 @@
<script lang="ts">
import * as Select from '$lib/components/ui/select';
import * as RadioGroup from '$lib/components/ui/radio-group';
import { Switch } from '$lib/components/ui/switch';
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import { Button } from '$lib/components/ui/button';
import { FolderOpen } from 'lucide-svelte';
interface Props {
key: string;
label?: string;
value: any;
onChange: (val: any) => void;
options?: { value: any, label: string }[];
type?: 'switch' | 'input' | 'select' | 'path' | 'radio' | 'password' | 'number';
hint?: string;
disabled?: boolean;
}
let { key, label, value, onChange, options, type, hint, disabled }: Props = $props();
// Heuristic to detect toggleable (boolean-like) legacy fields (0/1)
const isToggleable = (k: string, v: any) => {
const toggles = [
'act', 'valid', 'mostr', 'usar', 'utiliz', 'restring', 'bloqueo',
'omiti', 'oculta', 'resaltar', 'cal', 'mues', 'mens', 'cont', 'firm',
'esc', 'adv', 'val', 'tom', 'nom', 'rev', 'imp', 'temp', 'costo',
'des', 'asig', 'calc', 'param', 'fracc', 'valor', 'cove', 'loc', 'inter', 'inclu', 'actvalor', 'trans', 'cambiar', 'reasigna'
];
// Be more lenient: if it has the prefix and is 0, 1, or null, it's likely a toggle
return (v === 0 || v === 1 || v === null || v === undefined) &&
toggles.some(t => k.toLowerCase().startsWith(t));
}
const isPath = $derived(
key.toLowerCase().includes('path') ||
key.toLowerCase().includes('arch') ||
key.toLowerCase().includes('ruta')
);
// Heuristic or explicit type
let mode = $derived(type || (options ? 'select' : (isToggleable(key, value) ? 'switch' : (isPath ? 'path' : 'input'))));
function handleBrowse() {
try {
if (typeof window !== 'undefined' && 'showDirectoryPicker' in window) {
// @ts-ignore
window.showDirectoryPicker().then((handle: any) => {
onChange(handle.name);
}).catch((err: any) => console.log("Browse cancelled:", err));
}
} catch (err) {
console.log("Browse failed:", err);
}
}
let selectValue = $state(String(value));
$effect(() => {
selectValue = String(value);
});
function handleSelectChange(v: string) {
selectValue = v;
onChange(v);
}
</script>
<div class="flex flex-col gap-2.5">
<div class="flex flex-col gap-2 {mode === 'switch' ? 'flex-row items-center justify-between' : ''}">
<Label for={key} class="text-sm font-medium {disabled ? 'opacity-50 cursor-not-allowed' : ''} {mode === 'switch' ? 'cursor-pointer' : ''}">
{label || key}
</Label>
{#if mode === 'select' && options}
<Select.Root
type="single"
bind:value={selectValue}
onValueChange={handleSelectChange}
disabled={disabled}
>
<Select.Trigger class="w-full">
<span class="truncate">
{options.find(opt => String(opt.value) === selectValue)?.label || "Seleccionar..."}
</span>
</Select.Trigger>
<Select.Content>
{#each options as opt}
<Select.Item value={String(opt.value)} label={opt.label}>
{opt.label}
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
{:else if mode === 'radio'}
<RadioGroup.Root
value={String(value)}
onValueChange={onChange}
disabled={disabled}
class="flex flex-col gap-3"
>
{#each options || [] as opt}
<div class="flex items-center space-x-3">
<RadioGroup.Item value={String(opt.value)} id={`${key}-${opt.value}`} />
<Label for={`${key}-${opt.value}`} class="text-sm font-medium cursor-pointer {disabled ? 'cursor-not-allowed' : ''}">
{opt.label}
</Label>
</div>
{/each}
</RadioGroup.Root>
{:else if mode === 'switch'}
<Switch
id={key}
checked={value === 1 || value === '1' || value === true || value === 'true'}
onCheckedChange={(val) => onChange(val ? 1 : 0)}
disabled={disabled}
/>
{:else}
<div class="space-y-1.5">
{#if mode === 'path'}
<div class="flex gap-2">
<Input
id={key}
value={value || ""}
oninput={(e: any) => onChange(e.target.value)}
placeholder="Ruta del directorio..."
disabled={disabled}
class="font-mono text-xs text-foreground bg-background"
/>
<Button
variant="outline"
size="icon"
disabled={disabled}
onclick={handleBrowse}
class="shrink-0"
>
<FolderOpen class="w-4 h-4" />
</Button>
</div>
{:else if mode === 'password'}
<Input
id={key}
type="password"
value={value || ""}
oninput={(e: any) => onChange(e.target.value)}
placeholder="••••••••"
disabled={disabled}
class="text-foreground bg-background"
/>
{:else if mode === 'number'}
<Input
id={key}
type="number"
value={value || ""}
oninput={(e: any) => onChange(Number(e.target.value))}
disabled={disabled}
class="text-right text-foreground bg-background"
/>
{:else}
<Input
id={key}
value={value || ""}
oninput={(e: any) => onChange(e.target.value)}
disabled={disabled}
class="text-foreground bg-background"
/>
{/if}
{#if hint}
<p class="text-[10px] text-muted-foreground ml-1 font-medium italic">
{hint}
</p>
{/if}
</div>
{/if}
</div>
</div>

View File

@@ -0,0 +1,31 @@
<script lang="ts">
import { Globe, Database, Layers } from 'lucide-svelte';
export let level: 'global' | 'tenant' | 'company';
export let size: 'sm' | 'md' = 'md';
const config = {
global: {
label: 'Global',
icon: Globe,
color: 'text-primary bg-primary/10 border-primary/20'
},
tenant: {
label: 'Tenant',
icon: Database,
color: 'text-blue-400 bg-blue-400/10 border-blue-400/20'
},
company: {
label: 'Company',
icon: Layers,
color: 'text-purple-400 bg-purple-400/10 border-purple-400/20'
}
};
$: current = config[level];
</script>
<div class="inline-flex items-center gap-1.5 px-2 py-1 rounded-md border {current.color} {size === 'sm' ? 'text-[10px]' : 'text-xs'} font-medium">
<svelte:component this={current.icon} class={size === 'sm' ? 'w-3 h-3' : 'w-3.5 h-3.5'} />
{current.label.toUpperCase()}
</div>

View File

@@ -0,0 +1,884 @@
<script lang="ts">
import { fade } from 'svelte/transition';
import * as Tabs from '$lib/components/ui/tabs';
import SettingFormField from './SettingFormField.svelte';
import { Button } from '$lib/components/ui/button';
import {
AlertTriangle,
CreditCard,
FileEdit,
RefreshCw,
Layers
} from 'lucide-svelte';
import { onMount } from 'svelte';
interface Props {
currentData: any;
onUpdate: (data: any) => void;
}
let { currentData = {}, onUpdate }: Props = $props();
// Translation Mapping for SSisGen
const FIELD_LABELS: Record<string, string> = {
consecutivo: "Número Consecutivo",
dta: "DTA (Derecho Trámite Aduanero)",
dtaexpo: "DTA Exportación",
subempresa: "Sub-Empresa / División",
patharch: "Generación de los archivos PDF por",
patharchtransmision: "Generación de archivos electrónicos por transmitir en",
pathtransexpo: "Ruta: Transmisión Exportación",
pathrespuesta: "Ruta: Respuestas SAAI",
patharchped: "Generación de archivos electrónicos para pedimentos en",
patharchpedconsm: "Ruta de los archivos TXT de los archivos previos para pedimentos consolidados",
pathgenimpotemp: "Ruta de los archivos CSV para importaciones",
pathgenexpo: "Ruta de los archivos CSV para exportación",
actseguridad: "Activar la seguridad del sistema",
controldes: "Asignar fecha límite para desactualizar facturas",
diadesactual: "Días para desactualizar facturas (Impo/Expo)",
diavencimiento: "Días de anticipación (vencimiento)",
mensajevenc: "Activar la advertencia de saldos vencidos",
fechades: "Fecha de Desactualización Automática",
factoriva: "Factor IVA (Aplica en compras)",
validasifra: "Validar Fracciones con SIFRA",
decimalespeso: "Peso neto y bruto",
decimalescant: "Cantidades",
decimalesvalor: "Valores y costos",
calvalbasetcped: "Cálculo en base al TC fecha pago (Importación)",
calvalbasetcpedexpo: "Cálculo en base al TC fecha pago (Exportación)",
filtrocantidad: "Omitir Cantidades con balance menor a",
muestraarchcodbarras: "Muestra archivo TXT al generar factura/remesa",
datoshist: "Permitir Datos Históricos",
tipovenro: "Tipo de validación para regla octava",
cantvenro: "Cantidad para validación (Porcentaje/Días)",
costoplanta: "Costo Directo en Planta",
firmapacking: "Esconder la firma en el packing list",
escondaamexpacking: "Esconde el agente aduanal mexicano en el packing list",
advertenciatm: "Advertencia del uso del tipo de moneda en facturas",
costoimpofijo: "Valida como inconsistencia si el costo de la parte es diferente al costo de la partida al actualizar la factura de importación",
tomarsaldosvenc: "Activar si desea tomar saldos temporales o vencidos para descargar",
valmanifusado: "Activar control de asignación de manifiestos en factura",
valparteexiste: "No mostrar ventanas de auto selección de partes y marcar error en caso de que no exista",
temporalfechapago: "Tomar temporalidad en base a la fecha de pago del pedimento",
muestra_copias_codbarras: "Imprimir código de barras en las demas copias, aparte de la copia del transportista",
mostrarpackinglistingles: "Mostrar packing list en inglés",
activar_revision_fracciones: "Activar revisión de fracciones",
transmitirfacalterna: "Transmitir Factura Alterna en VU",
deshabilitardescparte: "Deshabilitar la Descripción en Español del Número de Parte",
deshabilitardescparteing: "Deshabilitar la Descripción en Inglés del Número de Parte",
asignafracameparte: "Asignar Automáticamente Fracción Americana de Expo US Dutible en Base a la información de la Clase y Parte",
validadecencant: "En las Partidas de Factura se Restringe la Captura de Decimales en el Campo de Unidad de Medida Pza",
mostraradvertenciaro: "Mostrar Advertencia si el Costo del Permiso de Regla Octava es Diferente al Costo de la Partida al Momento de Guardarla",
mostraradvertenciarovalor: "Mostrar Advertencia si el Valor del Permiso de Regla Octava es Diferente al Valor de la Partida al Momento de Guardarla",
usartranspamedocame: "Mostrar Transportista Americano en vez del Mexicano en Packing List y Bill Of Lading de Exportación",
calcdutypacking: "Agregar el Valor de US Packing como Complemento del Valor Dutible para Exportación.",
parammultiples: "Activar Uso de Parámetros Multiples en Catálogos de Facturas Impo/Expo.",
fraccnivelpais: "Activar la Asignación de Fracción a Nivel Parte-Pais.",
valordllstcfacturaexpo: "Mostrar Valor en dolares conforme al Tipo de Cambio de Exportación (Facturas, Códigos de Barra y COVE)",
covefechaemision: "Tomar la fecha de Emision para la generación del COVE.",
usarcoveenarchsaaim3: "Usar COVE en Lugar del Número de Factura en el Archivo de Transferencia SAAIM3.",
interfaceaaconsolidada: "Generar la Interface Consolidada por Clase para el Agente Aduanal.",
incluirobscoveobsimpo: "Incluir las Observaciones del COVE en las Observaciones en Importación.",
activarcatalogofraccionesamericanassifra: "Activar Catálogo Fracciones Americanas Sifra",
mostrarprogramaimmexprosec: "Mostrar Autorización Prosec en Reportes de Saldos y Descargos",
costounitarioporempaquefac: "Mostrar Costo Unitario Por Empaque en Impresión de Facturas de Exportación",
actvaloragre: "Activar Valor Agregado General",
valoragregadogen: "Valor Agregado General",
interfaceaatcfpff: "En Interfaces (WINSAAI, SAAIM3), Usar Tipo de Cambio de:",
agregarincreimpo: "Agregar Incrementables, Fecha de Emisión y Factor Tipo de Cambio en Facturas de Importación",
componentebom: "No Permitir Capturar Componentes Diferentes en BOMs (Materias Primas)",
limitesubensamble: "Límite Sub Ensamble",
partesypedimentosporcliente: "Agregar y Filtrar Catálogo de Partes y Pedimentos por Cliente",
mensajesvurfc: "Desactivar Mensajes de RFC en transmisión de VU",
omitirempaqueencodigobarras: "Omitir en el Código de Barras el Valor del Empaque",
actpdfreportes: "Generar PDFs al momento de Actualización",
patharchpdfimpo: "Generar Archivos PDF de Importación en:",
patharchpdfexpo: "Generar Archivos PDF de Exportación en:",
downloadftp: "¿Desea Activar Descarga Desde Sitio FTP o Localmente?",
minsdownlftp: "En Intervalos de (Minutos)",
downloadftppath: "Descargar Archivos en",
descargarftpolocal: "Descargar Archivos Desde",
serverftp: "Servidor",
userftp: "Usuario",
passwordftp: "Contraseña",
directorioftp: "Directorio en FTP",
pathlocalparadescde: "Ruta Local",
agregarremplazarautomatico: "Agregar/Remplazar Aut",
activarprocesovequipment: "Activar/Desactivar proceso para JD Edwards",
activarprocesodesperdiciojdedwards: "Activar proceso de desperdicio para JD Edwards",
utilizarnombregenericomainx30: "Utilizar Nombre Genérico Mainx 30",
utilizartcrespectoatipoped: "Utilizar TC Respecto Al Tipo de Pedimento",
tomardecimalescompletos: "Tomar Decimales Completos en las Impresiones de Facturas de Importación y Exportación",
incluirremesaeninterfazaawinsaai: "Incluir Remesa en Interfaz AA (WINSAAI)",
cambiarpesosporcostounitario: "Cambiar Pesos Por Costo Unitario en Exportaciones Mexicanas y Bilingües",
utilizarsolopartesnaftaenco: "Utilizar Solo Partes Nafta En CO",
bloqueodeediciondefacturas: "Bloqueo De Edición De Facturas",
reasignafraccionclase: "Reasigna Fracción en Reporte de Descargos",
calcularcostounitarioenbaseavalortotal: "Calcula Costo Unitario en Base a Valor Total Capturado",
parametroauxiliar: "Seleccionar versión de B.O.M a Descargar",
usarcontroldefechasdeversion: "Control de Versiones de Bom por Fechas",
desactivaciondemodulos: "Activar/Desactivar Módulos",
restringepaisimpo: "Restringir país de Korea para operaciones de Importación.",
restringpaisexpo: "Restringir país de Korea para operaciones de Exportación.",
bloqueoaldesactivarnumerodeparte: "Bloqueo para Desactivación y Activación de Número De Parte",
geninformeanexo31: "Inventario Inicial en Base al Sistema",
usarfechaemisionfactura: "Usar Fecha de Emision Factura en Manifestacion al Valor",
utilizarfechapagopeddeundiaanterior: "Utilizar Tipo de Cambio de la Fecha de Pago de Pedimento De Un Dia Anterior",
utilizartitulosalternativosimpresionfactura: "Utilizar Títulos Alternativos en la Impresión de las Facturas",
utilizarequivalenciasdeumpornumerodeparte: "Utilizar factor de conversión en número de parte para Importación y exportación.",
usarfactorconversionpornumerodeparte: "Usar Factor de Conversión Por Número De Parte",
usartcdelafechapagopedimpoendescarga: "Usar Tipo de Cambio de la Fecha de Pago Pedimento de Importación en Descarga",
usarvude128o256: "Utilizar Ventanilla Unica con Encriptacion de 256",
agregarnumeroembarque: "Agregar Número Embarque",
utilizarumdeexistenciaentransmisionvu: "Utilizar UM de Existencia En Transmision VU",
utilizarcodigodebrokerdeclienteenmainx30: "Utilizar Codigo De Broker De Cliente En Main X 30",
hojacalculosepararincrementablesanexo3: "En la Hoja de Calculo Separar Incrementables en el Anexo 3",
hojacalculodesglosefacturaanexo3: "En la Hoja de Calculo Desglosar Facturas en el Anexo 3",
usarvaloragregadoenfacturaamericana: "Usar Valor Agregado En Factura Americana",
ocultarinformacionfraccion: "Ocultar Información Fracción",
resaltarsaldostempconcolor: "Resaltar Saldos Temporales con Color",
validarsectorprosecr8: "Validar Sector Prosec R8",
agregarsubtotalinterfazaa: "Agregar Sub Total al archivo TXT para la Interfaz del Agente Aduanal",
imprimirfacturaalterna: "Imprimir factura alterna en manifestación al valor y hoja de calculo",
informacionamericanasubtotal: "Mostrar Información Extra En Factura Americana SubTotal",
activarexpedienteelectronico: "Activar Expediente Electrónico",
campo18valsaaim3: "Mostrar Campo 18 en 551 Valsaaim3"
};
const canonicalize = (data: any) => {
const result: any = {};
const recognizedKeys = Object.keys(FIELD_LABELS);
for (const rawKey in data) {
const canonicalKey = recognizedKeys.find((k) => k.toLowerCase() === rawKey.toLowerCase());
if (canonicalKey) {
result[canonicalKey] = data[rawKey];
} else {
result[rawKey] = data[rawKey];
}
}
return result;
};
// Initialize local/loc fields with defaults and keep in sync with currentData prop
let formData: any = $state({
downloadftp: 'No',
descargarftpolocal: 'FTP',
minsdownlftp: 0,
usarfechaemisionfactura: 0,
campo18valsaaim3: 0,
...canonicalize(currentData)
});
// Guarda la última versión recibida del padre para detectar cambios reales vs. echo circular
let lastExternalData: string = JSON.stringify({ ...currentData });
$effect(() => {
// Update state when currentData prop changes from outside
const newData = { ...currentData };
const newDataStr = JSON.stringify(newData);
// Solo sincroniza si el dato cambió externamente (no es un echo de nuestro propio onUpdate)
if (newDataStr === lastExternalData) return;
lastExternalData = newDataStr;
const mapped = canonicalize(newData);
for (const key in mapped) {
formData[key] = mapped[key];
}
});
// Notifica al padre del estado completo al montar (incluyendo defaults)
onMount(() => {
lastExternalData = JSON.stringify({ ...currentData });
onUpdate({ ...formData });
});
function handleFieldChange(key: string, value: any) {
formData[key] = value;
onUpdate({ ...formData });
}
// Grouped sections for the "General" tab
const GENERAL_SECTIONS = [
{
title: 'Parámetros del Sistema',
fields: ['factoriva', 'actseguridad']
},
{
title: 'Advertencias de Vencimiento',
fields: ['mensajevenc', 'diavencimiento']
},
{
title: 'Tipos de Cambio',
fields: ['calvalbasetcped', 'calvalbasetcpedexpo']
},
{
title: 'Desactualización de Facturas',
fields: ['controldes', 'diadesactual']
},
{
title: 'Número de decimales en reportes generales en',
fields: ['decimalespeso', 'decimalescant', 'decimalesvalor']
},
{
title: 'Filtrar por la cantidad',
fields: ['filtrocantidad']
},
{
title: 'Código de barras',
fields: ['muestraarchcodbarras']
}
];
// Grouped sections for "Archivos electrónicos" tab
const ARCHIVOS_SECTIONS = [
{
title: 'Gestión de Archivos PDF y Electrónicos',
fields: [
'patharch',
'patharchtransmision',
'patharchped',
'pathgenimpotemp',
'pathgenexpo',
'patharchpedconsm',
]
}
];
// Grouped sections for "Continuación" tab
const CONTINUACION_SECTIONS = [
{
title: 'Tipo de validación para regla octava',
fields: ['tipovenro', 'cantvenro']
},
{
title: 'Parámetros Adicionales y Visualización',
fields: [
'firmapacking',
'escondaamexpacking',
'advertenciatm',
'costoimpofijo',
'tomarsaldosvenc',
'valmanifusado',
'valparteexiste',
'temporalfechapago',
'muestra_copias_codbarras',
'mostrarpackinglistingles',
'activar_revision_fracciones'
]
}
];
// Grouped sections for "Cont 2" tab
const CONT2_SECTIONS = [
{
title: 'Configuraciones de Pantalla y Validación Avanzada',
fields: [
'deshabilitardescparte',
'deshabilitardescparteing',
'asignafracameparte',
'validadecencant',
'mostraradvertenciaro',
'mostraradvertenciarovalor',
'usartranspamedocame',
'calcdutypacking',
'parammultiples',
'fraccnivelpais',
'valordllstcfacturaexpo',
'covefechaemision',
'usarcoveenarchsaaim3',
'interfaceaaconsolidada',
'incluirobscoveobsimpo'
]
}
];
// Sections for "Cont 3" tab
const CONT3_SECTIONS = [
{
id: 'interface_tc',
title: 'Tipo de Cambio en Interfaces',
fields: ['interfaceaatcfpff']
},
{
id: 'config_general',
title: 'Opciones de Configuración General',
fields: [
'agregarincreimpo',
'componentebom',
'limitesubensamble',
'partesypedimentosporcliente',
'mensajesvurfc',
'omitirempaqueencodigobarras'
]
},
{
id: 'rutas_pdf',
title: 'Rutas y Archivos PDF',
fields: [
'actpdfreportes',
'patharchpdfimpo',
'patharchpdfexpo'
]
}
];
// Groups for Cont 4
const CONT4_SECTIONS = [
{
id: 'ftp_master',
title: 'Activación y Frecuencia',
fields: ['downloadftp', 'minsdownlftp']
},
{
id: 'ftp_destino',
title: 'Destino de Descarga',
fields: ['downloadftppath']
},
{
id: 'ftp_origen',
title: 'Origen de Descarga',
fields: ['descargarftpolocal']
},
{
id: 'ftp_config',
title: 'Configuración FTP',
fields: ['serverftp', 'userftp', 'passwordftp', 'directorioftp']
},
{
id: 'local_config',
title: 'Configuración de Ruta Local',
fields: ['pathlocalparadescde']
},
{
id: 'additional_options',
title: 'Opciones Adicionales',
fields: ['agregarremplazarautomatico', 'activarprocesovequipment', 'activarprocesodesperdiciojdedwards']
}
];
// Sections for redefined "Cont 5" tab
const CONT5_SECTIONS = [
{
id: 'restricciones',
title: 'Restricciones y Bloqueos Iniciales',
fields: ['restringepaisimpo', 'restringpaisexpo', 'bloqueoaldesactivarnumerodeparte']
},
{
id: 'inv_inicial',
title: 'Inventario Inicial (Agrupación)',
fields: ['geninformeanexo31']
},
{
id: 'config_adicionales',
title: 'Configuraciones Adicionales',
fields: [
'usarfechaemisionfactura',
'utilizarfechapagopeddeundiaanterior',
'utilizartitulosalternativosimpresionfactura',
'utilizarequivalenciasdeumpornumerodeparte',
'usarfactorconversionpornumerodeparte',
'usartcdelafechapagopedimpoendescarga',
'usarvude128o256',
'agregarnumeroembarque',
'utilizarumdeexistenciaentransmisionvu',
'utilizarcodigodebrokerdeclienteenmainx30',
'hojacalculosepararincrementablesanexo3',
'hojacalculodesglosefacturaanexo3'
]
}
];
// Sections for redefined "Cont 6" tab
const CONT6_SECTIONS = [
{
id: 'configuracion',
title: 'Opciones de Configuración',
fields: [
'usarvaloragregadoenfacturaamericana',
'ocultarinformacionfraccion',
'resaltarsaldostempconcolor',
'validarsectorprosecr8',
'agregarsubtotalinterfazaa',
'utilizarnombregenericomainx30',
'utilizartcrespectoatipoped',
'tomardecimalescompletos',
'cambiarpesosporcostounitario',
'utilizarsolopartesnaftaenco',
'bloqueodeediciondefacturas',
'reasignafraccionclase',
'calcularcostounitarioenbaseavalortotal',
'parametroauxiliar',
'usarcontroldefechasdeversion',
'imprimirfacturaalterna',
'informacionamericanasubtotal'
]
},
{
id: 'acciones_modulos',
title: 'Acciones y Módulos',
fields: [
'desactivaciondemodulos',
'activarexpedienteelectronico',
'campo18valsaaim3'
]
}
];
// Grouped sections for "Cont 7" tab
const CONT7_SECTIONS = [
{
title: 'Configuraciones de Fracciones y Transmisión',
fields: [
'activarcatalogofraccionesamericanassifra',
'mostrarprogramaimmexprosec',
'costounitarioporempaquefac',
'transmitirfacalterna'
]
}
];
const TABS = [
{ id: 'general', label: 'General' },
{ id: 'archivos', label: 'Archivos electrónicos' },
{ id: 'continuacion', label: 'Continuación' },
{ id: 'cont2', label: 'Cont 2' },
{ id: 'cont3', label: 'Cont 3' },
{ id: 'cont4', label: 'Cont 4' },
{ id: 'cont5', label: 'Cont 5' },
{ id: 'cont6', label: 'Cont 6' },
{ id: 'cont7', label: 'cont 7' }
];
// Options for the rule validator select
const RULE_VALIDATOR_OPTIONS = [
{ value: 'Porcentaje', label: 'Porcentaje' },
{ value: 'Dias', label: 'Días' },
{ value: 'No aplica', label: 'No aplica' }
];
// Options for the interface TC radio buttons
const INTERFACE_TC_OPTIONS = [
{ value: 'P', label: 'Fecha de Pago' },
{ value: 'F', label: 'Fecha de Factura' }
];
// Options for radio groups in Cont 4
const YES_NO_OPTIONS = [
{ value: 'Si', label: 'Si' },
{ value: 'No', label: 'No' }
];
const FTP_LOCAL_OPTIONS = [
{ value: 'FTP', label: 'FTP' },
{ value: 'Ruta Local', label: 'Ruta Local' }
];
// Options for Inventario Inicial in Cont 5
const INICIAL_INV_OPTIONS = [
{ value: 'AMBOS', label: 'AMBOS' },
{ value: 'SCAII', label: 'SCAII' },
{ value: 'SCAF', label: 'SCAF' }
];
</script>
<div class="w-full">
<Tabs.Root value="general" class="flex flex-col h-full">
<Tabs.List class="grid w-full grid-cols-3 md:grid-cols-5 lg:grid-cols-9 bg-muted rounded-md p-1 mb-4">
{#each TABS as tab}
<Tabs.Trigger
value={tab.id}
class="w-full px-3 py-1.5 text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm"
>
{tab.label}
</Tabs.Trigger>
{/each}
</Tabs.List>
<Tabs.Content value="general" class="space-y-6">
<div class="space-y-6 py-4">
{#each GENERAL_SECTIONS as section}
<div class="space-y-4">
<div class="flex items-center gap-4">
<h3 class="font-medium text-lg">
{section.title}
</h3>
<div class="h-px w-full bg-border mt-2"></div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{#each section.fields as field}
<SettingFormField
key={field}
label={FIELD_LABELS[field] || field}
value={formData[field]}
onChange={(val) => handleFieldChange(field, val)}
/>
{/each}
</div>
</div>
{/each}
</div>
</Tabs.Content>
<Tabs.Content value="archivos" class="space-y-6">
<div class="space-y-6 py-4">
{#each ARCHIVOS_SECTIONS as section}
<div class="space-y-6 max-w-3xl mx-auto">
<div class="flex items-center gap-4">
<h3 class="font-medium text-lg">
{section.title}
</h3>
<div class="h-px w-full bg-border mt-2"></div>
</div>
<div class="grid grid-cols-1 gap-4">
{#each section.fields as field}
<SettingFormField
key={field}
label={FIELD_LABELS[field] || field}
value={formData[field]}
onChange={(val) => handleFieldChange(field, val)}
/>
{/each}
</div>
</div>
{/each}
</div>
</Tabs.Content>
<Tabs.Content value="continuacion" class="space-y-6">
<div class="space-y-6 py-4">
{#each CONTINUACION_SECTIONS as section}
<div class="space-y-4">
<div class="flex items-center gap-4">
<h3 class="font-medium text-lg">
{section.title}
</h3>
<div class="h-px w-full bg-border mt-2"></div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{#each section.fields as field}
<SettingFormField
key={field}
label={FIELD_LABELS[field] || field}
value={formData[field]}
onChange={(val) => handleFieldChange(field, val)}
options={field === 'tipovenro' ? RULE_VALIDATOR_OPTIONS : undefined}
/>
{/each}
</div>
</div>
{/each}
</div>
</Tabs.Content>
<Tabs.Content value="cont2" class="space-y-6">
<div class="space-y-6 py-4">
{#each CONT2_SECTIONS as section}
<div class="space-y-6 max-w-4xl mx-auto">
<div class="flex items-center gap-4">
<h3 class="font-medium text-lg">
{section.title}
</h3>
<div class="h-px w-full bg-border mt-2"></div>
</div>
<div class="grid grid-cols-1 gap-4">
{#each section.fields as field}
<SettingFormField
key={field}
label={FIELD_LABELS[field] || field}
value={formData[field]}
onChange={(val) => handleFieldChange(field, val)}
/>
{/each}
</div>
</div>
{/each}
</div>
</Tabs.Content>
<Tabs.Content value="cont3" class="space-y-6">
<div class="space-y-6 py-4">
{#each CONT3_SECTIONS as section}
<div class="space-y-6 max-w-4xl mx-auto">
<div class="flex items-center gap-4">
<h3 class="font-medium text-lg">
{section.title}
</h3>
<div class="h-px w-full bg-border mt-2"></div>
</div>
<div class="grid grid-cols-1 gap-4">
{#each section.fields as field}
<SettingFormField
key={field}
label={FIELD_LABELS[field] || field}
value={formData[field]}
onChange={(val) => handleFieldChange(field, val)}
type={
field === 'interfaceaatcfpff' ? 'radio' :
(field === 'limitesubensamble' ? 'input' :
(['agregarincreimpo', 'componentebom', 'partesypedimentosporcliente'].includes(field) ? 'switch' : undefined))
}
options={field === 'interfaceaatcfpff' ? INTERFACE_TC_OPTIONS : undefined}
/>
{/each}
</div>
</div>
{/each}
</div>
</Tabs.Content>
<Tabs.Content value="cont4" class="space-y-6">
<div class="space-y-6 py-4">
<div class="max-w-4xl mx-auto space-y-6">
<div class="space-y-6 p-6 rounded-2xl border border-border bg-muted/20">
{#each CONT4_SECTIONS.filter(s => s.id !== 'additional_options') as section}
<div class="space-y-6">
<div class="flex items-center gap-4">
<h3 class="font-medium text-lg">
{section.title}
</h3>
<div class="h-px w-full bg-border mt-2"></div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
{#each section.fields as field}
<SettingFormField
key={field}
label={FIELD_LABELS[field] || field}
value={formData[field]}
onChange={(val) => handleFieldChange(field, val)}
disabled={formData.downloadftp !== 'Si' && field !== 'downloadftp'}
type={
field === 'downloadftp' || field === 'descargarftpolocal' ? 'radio' :
field === 'minsdownlftp' ? 'number' :
field === 'passwordftp' ? 'password' :
undefined
}
options={
field === 'downloadftp' ? YES_NO_OPTIONS :
field === 'descargarftpolocal' ? FTP_LOCAL_OPTIONS :
undefined
}
hint={
field === 'directorioftp' ? 'Ejemplo: /Folder 1/SubFolder' :
field === 'pathlocalparadescde' ? 'Ejemplo: C:\\Aduanas\\SCAIISQL' :
undefined
}
{...(section.id === 'ftp_config' && formData.descargarftpolocal === 'Ruta Local' ? { disabled: true } : {})}
{...(section.id === 'local_config' && formData.descargarftpolocal === 'FTP' ? { disabled: true } : {})}
/>
{/each}
</div>
</div>
{/each}
</div>
<!-- Outside Options -->
<div class="space-y-6">
<div class="flex items-center gap-4">
<h3 class="font-medium text-lg">
Opciones Adicionales
</h3>
<div class="h-px w-full bg-border mt-2"></div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{#each CONT4_SECTIONS.find(s => s.id === 'additional_options')?.fields || [] as field}
<SettingFormField
key={field}
label={FIELD_LABELS[field] || field}
value={formData[field]}
onChange={(val) => handleFieldChange(field, val)}
/>
{/each}
</div>
</div>
</div>
</div>
</Tabs.Content>
<Tabs.Content value="cont5" class="space-y-6">
<div class="space-y-6 py-4">
<div class="max-w-4xl mx-auto space-y-6">
{#each CONT5_SECTIONS as section}
<div class="space-y-6 p-6 rounded-2xl border border-border bg-muted/20">
<div class="flex items-center gap-4">
<h3 class="font-medium text-lg">
{section.title}
</h3>
<div class="h-px w-full bg-border mt-2"></div>
</div>
<div class="grid grid-cols-1 {section.id === 'inv_inicial' ? '' : 'md:grid-cols-2 lg:grid-cols-3'} gap-6">
{#each section.fields as field}
<SettingFormField
key={field}
label={FIELD_LABELS[field] || field}
value={formData[field]}
onChange={(val) => handleFieldChange(field, val)}
type={
field === 'geninformeanexo31' ? 'radio' :
(['usarfechaemisionfactura', 'agregarnumeroembarque', 'hojacalculosepararincrementablesanexo3', 'hojacalculodesglosefacturaanexo3'].includes(field) ? 'switch' : undefined)
}
options={field === 'geninformeanexo31' ? INICIAL_INV_OPTIONS : undefined}
/>
{/each}
</div>
</div>
{/each}
</div>
</div>
</Tabs.Content>
<Tabs.Content value="cont6" class="space-y-6">
<div class="space-y-6 py-4">
<div class="max-w-5xl mx-auto grid grid-cols-1 lg:grid-cols-2 gap-12">
<!-- Columna Izquierda: Opciones de Configuración -->
<div class="space-y-8">
<div class="flex items-center gap-4">
<h3 class="font-medium text-lg">
Opciones de Configuración
</h3>
<div class="h-px w-full bg-border mt-2"></div>
</div>
<div class="space-y-4">
{#each CONT6_SECTIONS.find(s => s.id === 'configuracion')?.fields || [] as field}
<div class="{field === 'usarcontroldefechasdeversion' ? 'ml-8 opacity-60' : ''} transition-opacity">
<SettingFormField
key={field}
label={FIELD_LABELS[field] || field}
value={formData[field]}
onChange={(val) => handleFieldChange(field, val)}
disabled={field === 'usarcontroldefechasdeversion' && !formData.parametroauxiliar}
type="switch"
/>
</div>
{/each}
</div>
</div>
<!-- Columna Derecha: Acciones y Módulos -->
<div class="space-y-6">
<!-- Módulos -->
<div class="space-y-8">
<div class="flex items-center gap-4">
<h3 class="font-medium text-lg">
Módulos del Sistema
</h3>
<div class="h-px w-full bg-border mt-2"></div>
</div>
<div class="space-y-4">
{#each CONT6_SECTIONS.find(s => s.id === 'acciones_modulos')?.fields || [] as field}
<SettingFormField
key={field}
label={FIELD_LABELS[field] || field}
value={formData[field]}
onChange={(val) => handleFieldChange(field, val)}
type={field === 'campo18valsaaim3' ? 'switch' : undefined}
/>
{/each}
</div>
</div>
<!-- Acciones -->
<div class="space-y-8">
<div class="flex items-center gap-4">
<h3 class="font-medium text-lg">
Acciones de Gestión
</h3>
<div class="h-px w-full bg-border mt-2"></div>
</div>
<div class="grid grid-cols-1 gap-4">
<Button variant="outline" class="w-full flex items-center justify-start gap-4 h-12 rounded-xl border-border bg-background hover:bg-accent text-foreground group">
<AlertTriangle class="w-5 h-5 text-red-500 group-hover:scale-110 transition-transform" />
Eliminar Accesos Abiertos
</Button>
<Button variant="outline" class="w-full flex items-center justify-start gap-4 h-12 rounded-xl border-border bg-background hover:bg-accent text-foreground group">
<CreditCard class="w-5 h-5 text-primary group-hover:scale-110 transition-transform" />
Asignar Formas de Pago Para Anexo 31
</Button>
<Button variant="outline" class="w-full flex items-center justify-start gap-4 h-12 rounded-xl border-border bg-background hover:bg-accent text-foreground group">
<FileEdit class="w-5 h-5 text-primary group-hover:scale-110 transition-transform" />
Corrección de partidas expo
</Button>
<Button variant="outline" class="w-full flex items-center justify-start gap-4 h-12 rounded-xl border-border bg-background hover:bg-accent text-foreground group">
<RefreshCw class="w-5 h-5 text-primary group-hover:scale-110 transition-transform" />
Actualizar Tarifa
</Button>
</div>
</div>
</div>
</div>
</div>
</Tabs.Content>
<Tabs.Content value="cont7" class="space-y-6">
<div class="space-y-6 py-4">
{#each CONT7_SECTIONS as section}
<div class="space-y-6 max-w-4xl mx-auto">
<div class="flex items-center gap-4">
<h3 class="font-medium text-lg">
{section.title}
</h3>
<div class="h-px w-full bg-border mt-2"></div>
</div>
<div class="grid grid-cols-1 gap-4">
<!-- Especial Row: Activar Valor Agregado Gral + Numeric Input -->
<div class="flex flex-col md:flex-row items-center gap-4 p-4 rounded-xl border border-border bg-background">
<div class="flex-1">
<SettingFormField
key="actvaloragre"
label={FIELD_LABELS.actvaloragre}
value={formData.actvaloragre}
onChange={(val) => handleFieldChange('actvaloragre', val)}
/>
</div>
<div class="w-full md:w-32">
<SettingFormField
key="valoragregadogen"
label={FIELD_LABELS.valoragregadogen}
value={formData.valoragregadogen}
onChange={(val) => handleFieldChange('valoragregadogen', val)}
type="input"
/>
</div>
</div>
{#each section.fields as field}
<SettingFormField
key={field}
label={FIELD_LABELS[field] || field}
value={formData[field]}
onChange={(val) => handleFieldChange(field, val)}
/>
{/each}
</div>
</div>
{/each}
</div>
</Tabs.Content>
</Tabs.Root>
</div>
<style>
:global([data-radix-scroll-area-viewport]) {
scrollbar-width: none;
-ms-overflow-style: none;
}
:global([data-radix-scroll-area-viewport]::-webkit-scrollbar) {
display: none;
}
</style>

View File

@@ -0,0 +1,35 @@
export const SETTINGS_METADATA: Record<string, string[]> = {
ssisgen: [
"consecutivo", "dta", "dtaexpo", "subempresa", "patharch", "patharchtransmision", "pathtransexpo", "pathrespuesta",
"patharchped", "patharchpedconsm", "pathgenimpotemp", "pathgenexpo", "actseguridad", "controldes", "diadesactual",
"diavencimiento", "mensajevenc", "fechades", "factoriva", "validasifra", "decimalespeso", "decimalescant",
"decimalesvalor", "calvalbasetcped", "calvalbasetcpedexpo", "filtrocantidad", "muestraarchcodbarras", "datoshist",
"tipovenro", "cantvenro", "costoplanta", "firmapacking", "advertenciatm", "tomarsaldosvenc", "costoimpofijo",
"valparteexiste", "valmanifusado", "temporalfechapago", "asignadiasantdesc", "diasantdesc", "deshabilitardescparte",
"deshabilitardescparteing", "asignafracameparte", "validadecencant", "usartranspamedocame", "mostraradvertenciaro",
"escondaamexpacking", "calcdutypacking", "parammultiples", "fraccnivelpais", "covefechaemision", "valordllstcfacturaexpo",
"interfaceaaconsolidada", "interfaceaatcfpff", "incluirobscoveobsimpo", "agregarincreimpo", "componentebom",
"limitesubensamble", "actpdfreportes", "patharchpdfimpo", "patharchpdfexpo", "noimprimircons", "mostraradvertenciarovalor",
"partesypedimentosporcliente", "mensajesvurfc", "mostrarpackinglistingles", "omitirempaqueencodigobarras", "restringepaisimpo",
"bloqueoaldesactivarnumerodeparte", "restringpaisexpo", "geninformeanexo31", "utilizarfechapagopeddeundiaanterior",
"utilizarequivalenciasdeumpornumerodeparte", "utilizartitulosalternativosimpresionfactura", "usarfactorconversionpornumerodeparte",
"usarvude128o256", "usartcdelafechapagopedimpoendescarga", "solicitarcontrasenaadministrador", "agregarnumeroembarque",
"utilizarumdeexistenciaentransmisionvu", "utilizarcodigodebrokerdeclienteenmainx30", "hojacalculosepararincrementablesanexo3",
"hojacalculodesglosefacturaanexo3", "usarvaloragregadoenfacturaamericana", "ocultarinformacionfraccion",
"resaltarsaldostempconcolor", "valoragregadoenfacturamexicana", "validarsectorprosecr8", "agregarsubtotalinterfazaa"
],
ssismex: [
"consecutivo", "prefijocm", "consecutivocm", "porparteclasemex", "porparteclaseame", "proveedor", "vendidoconsignado",
"vendidoa", "enviadotransferido", "enviadoa", "flete", "paisorigenmex", "numpartemex", "firmafmex", "fraccionimp",
"tipofraccmex", "tasafraccmex", "umequivalentemex", "numparteame", "fraccioname", "paisorigename", "umequivalenteame",
"firmafame", "impordencomp", "decimalespeso", "decimalescant", "decimalesvalor", "decimalescosto", "tipomoneda",
"clavemoneda", "transportista", "conductor", "transporte", "numtransporte", "observacione", "observacioni",
"leyendamex", "leyendaame", "firmaaamex", "firmapmex", "firmaaaame", "firmapame", "firmaeamex", "firmaeame",
"firmasamex", "firmasame", "claveregimen", "claveregimename", "numfactura", "fechafactura", "numeropedimento",
"fechapedimento", "pedimentomex", "clientemex", "claveregmexo", "claveregmexd", "usarvalorameric", "consecutivoas",
"consecutivops", "usatranspfactu", "ocultarfechahora"
],
qsisgen: [
"consecutivo", "actvaloragre", "valoragregadogen"
]
};

View File

@@ -124,13 +124,11 @@
</script>
<Sheet.Root bind:open={helpStore.isOpen}>
<Sheet.Trigger asChild>
<button
class="fixed right-6 bottom-6 z-50 flex h-14 w-14 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-lg transition-transform hover:scale-110 active:scale-95"
aria-label="Ayuda"
>
<HelpCircle size={28} />
</button>
<Sheet.Trigger
class="fixed right-6 bottom-6 z-50 flex h-14 w-14 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-lg transition-transform hover:scale-110 active:scale-95"
aria-label="Ayuda"
>
<HelpCircle size={28} />
</Sheet.Trigger>
<Sheet.Content side="right" class="w-[400px] sm:w-[540px]">
<Sheet.Header>
@@ -170,7 +168,7 @@
>
<span class="font-semibold">{article.title}</span>
<span class="text-xs text-muted-foreground">
Última edición: {browser ? new Date(article.updated_at).toLocaleDateString() : article.updated_at.split('T')[0]}
Última edición: {article.updated_at ? article.updated_at.split('T')[0] : '...'}
</span>
</button>
{/each}

View File

@@ -518,6 +518,10 @@ export function getSidebarData(): SidebarData {
title: m["sidebar.reference_data.usuarios"](),
url: "",
},
{
title: "General",
url: "/dashboard/settings/general",
},
],
},
],

View File

@@ -1,13 +1,21 @@
<script lang="ts">
import { onMount } from 'svelte';
import { onMount, untrack } from 'svelte';
import { browser } from '$app/environment';
import { page } from '$app/stores';
import * as Tabs from '$lib/components/ui/tabs';
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { Label } from '$lib/components/ui/label';
import { Input } from '$lib/components/ui/input';
import { LoaderCircle, Save, FileText, Eye, DollarSign, Truck, Package } from 'lucide-svelte';
import { Badge } from '$lib/components/ui/badge';
import { Separator } from '$lib/components/ui/separator';
import {
LoaderCircle, Save, FileText, Eye, DollarSign,
Truck, Package, ArrowLeft
} from 'lucide-svelte';
import { toast } from 'svelte-sonner';
import { api } from '$lib/api';
import { goto } from '$app/navigation';
import { Select, SelectContent, SelectItem, SelectTrigger } from '$lib/components/ui/select';
// Import Form Components
import GeneralTabForm from '$lib/components/dashboard/invoices/edit/general-tab-form.svelte';
@@ -16,6 +24,10 @@
import OthersTabForm from '$lib/components/dashboard/invoices/edit/others-tab-form.svelte';
import InvoiceTopFields from '$lib/components/dashboard/invoices/edit/invoice-top-fields.svelte';
import ContinuationTabForm from '$lib/components/dashboard/invoices/edit/continuation-tab-form.svelte';
import SsimpSettingsForm from '$lib/components/dashboard/invoices/settings/ssimp-settings-form.svelte';
import SsisdefSettingsForm from '$lib/components/dashboard/invoices/settings/ssisdef-settings-form.svelte';
import SsicmSettingsForm from '$lib/components/dashboard/invoices/settings/ssicm-settings-form.svelte';
import SscrSettingsForm from '$lib/components/dashboard/invoices/settings/sscr-settings-form.svelte';
// Props
let { data } = $props();
@@ -27,51 +39,40 @@
let isSaving = $state(false);
let activeTab = $state('general');
let companyStore: any = $state(undefined);
// SCAII (ssisgen) | SCAF (qsisgen)
type SystemType = 'ssisgen' | 'qsisgen';
let activeSystem = $state<SystemType>('ssisgen');
// Form Data State
let InvoiceTopFieldsFormData = $state<any>({});
let generalFormData = $state<any>({});
let observationFormData = $state<any>({});
let itemsFormData = $state<any>({});
let othersFormData = $state<any>({
comments_status: '',
transport_mode: 'TRUCK',
is_mixed: null,
print_stamp: false,
rule_3121_parties_ii: false,
related_doc_id: null,
code_signature: '',
electronic_signature: '',
mandatory_person: '',
contingency_mode: false,
cove: '',
operation_num: '',
adendas: '',
observations_vu: '',
certified_number: ''
});
let continuationFormData = $state<any>({
numero_tipo_transporte: '',
es_ferrocarril: 'no',
numero_bl: '',
cantidad_guias_embarque: null,
destino_origen: '',
puerto_entrada: '',
fue_revisado_equipo: false,
sub_division: false,
funge_como_cd: false,
llego_pedimento: false,
errores_facturacion: [],
semaforo_verde_aduana_mexicana: false,
semaforo_verde_aduana_americana: false,
semaforo_rojo_aduana_mexicana: false,
semaforo_rojo_aduana_americana: false
});
// Form Data State - Initialize as null to let components self-initialize
let rawSettings = $state<any>({});
let InvoiceTopFieldsFormData = $state<any>(null);
let generalFormData = $state<any>(null);
let observationFormData = $state<any>(null);
let itemsFormData = $state<any>(null);
let othersFormData = $state<any>(null);
let continuationFormData = $state<any>(null);
let ssimpFormData = $state<any>(null);
let lastLoadedKey = $state<string>('');
// Dummy objects for components
let invoice = $state<any>({
financials: { exchange_rate: 0 }
});
// Derived object for components to consume context
// We use a getter to avoid circular reactivity issues during initialization
let invoice = $derived.by(() => ({
id: 0,
tenant_id: 0,
company_id: companyStore?.activeCompany?.id || 0,
capture_date: new Date().toISOString(),
operation_type: selectedOperationType,
invoice_type: selectedInvoiceType,
financials: { exchange_rate: 0 },
compliance_mx: {
pedimento_id: InvoiceTopFieldsFormData?.pedimento_id || '',
remesa: InvoiceTopFieldsFormData?.remesa || ''
},
invoice_number: InvoiceTopFieldsFormData?.invoice_number || '',
invoice_date: InvoiceTopFieldsFormData?.invoice_date || '',
alternate_invoice: observationFormData?.alternate_invoice || ''
}) as any);
// Existence flags (not strictly needed for settings but required by components)
let observationExists = $state(false);
@@ -94,42 +95,63 @@
});
async function loadSettings() {
if (!selectedInvoiceType || !selectedOperationType || !companyStore?.activeCompany?.id) return;
const cid = companyStore?.activeCompany?.id;
if (!selectedInvoiceType || !selectedOperationType || !cid) {
console.log('LOAD: Skipping - Missing Context', { selectedInvoiceType, selectedOperationType, cid });
return;
}
const currentKey = `${selectedOperationType}:${selectedInvoiceType}:${cid}`;
if (currentKey === lastLoadedKey && !isLoading) {
console.log('LOAD: Skipping - Key match', currentKey);
return;
}
if (isLoading) {
console.log('LOAD: Skipping - Already Loading');
return;
}
console.log('LOAD: Starting...', currentKey);
isLoading = true;
try {
const token = $page.data.user?.token;
const headers: HeadersInit = {
'Content-Type': 'application/json'
};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
const res = await fetch(
`/api/v1/a76/invoice-settings/${selectedInvoiceType}?operation_type=${selectedOperationType}&company_id=${companyStore.activeCompany.id}`,
{ headers }
const res = await api.get(
`/v1/a76/invoice-settings/${selectedInvoiceType}?operation_type=${selectedOperationType}&company_id=${cid}`
);
if (res.ok) {
const settingsData = await res.json();
if (res.status === 200 || res.data) {
const settingsData = res.data;
lastLoadedKey = currentKey;
console.log('LOAD: Success', { hasSettings: !!settingsData?.settings });
if (settingsData && settingsData.settings) {
applySettings(settingsData.settings);
rawSettings = settingsData.settings;
applySettings(rawSettings);
} else {
rawSettings = {};
resetForms();
}
} else {
console.log('LOAD: Response format mismatch or failed', res.status);
lastLoadedKey = currentKey;
rawSettings = {};
resetForms();
}
} catch (error) {
console.error('Error loading settings', error);
console.error('LOAD: Exception', error);
lastLoadedKey = currentKey;
rawSettings = {};
resetForms();
} finally {
console.log('LOAD: Finished');
isLoading = false;
}
}
function applySettings(settings: any) {
// Separation logic: Check if we have nested settings for the active system
const systemData = settings[activeSystem] || (activeSystem === 'ssisgen' ? settings : {});
// If the systemData is empty/new, we still spread defaults above it
InvoiceTopFieldsFormData = {
is_pedimento_pending: false,
pedimento_id: '',
@@ -143,7 +165,7 @@
fecha_pedimento_al: '',
clave_pedimento: '',
regimen_pedimento: '',
...(settings.InvoiceTopFieldsFormData || {})
...(systemData.InvoiceTopFieldsFormData || {})
};
generalFormData = {
provider_header: 'proveedor',
@@ -166,7 +188,7 @@
trailer_num: '',
aduana: '',
document_type: '',
...(settings.generalFormData || {})
...(systemData.generalFormData || {})
};
observationFormData = {
observation_es: '',
@@ -178,19 +200,30 @@
other_increments: null,
total_increments_mn: null,
total_increments_me: null,
incoterm: null,
incoterm: '',
enclosure: null,
num_seals: null,
movement_type: '',
alternate_invoice: '',
valuation_method: null,
...(settings.observationFormData || {})
valuation_method: '',
other_deductibles: null,
proforma_number: '',
sub_division: 'no',
acts_as_cd: false,
identifier_1: '',
complement_1: '',
identifier_2: '',
complement_2: '',
office_document: '',
is_mixed: false,
...(systemData.observationFormData || {})
};
itemsFormData = settings.itemsFormData || {};
itemsFormData = systemData.itemsFormData || {};
ssimpFormData = systemData.ssimpFormData || {};
othersFormData = {
comments_status: '',
transport_mode: 'TRUCK',
is_mixed: null,
is_mixed: false,
print_stamp: false,
rule_3121_parties_ii: false,
related_doc_id: null,
@@ -203,7 +236,16 @@
adendas: '',
observations_vu: '',
certified_number: '',
...(settings.othersFormData || {})
entry_exit_date: '',
payment_date: '',
bill_number: '',
guide_number: '',
shipment_number: '',
option_iv18: '',
delivered_status: false,
received_by: '',
delivery_date: '',
...(systemData.othersFormData || {})
};
continuationFormData = {
numero_tipo_transporte: '',
@@ -212,6 +254,7 @@
cantidad_guias_embarque: null,
destino_origen: '',
puerto_entrada: '',
vehicle_data: '',
fue_revisado_equipo: false,
sub_division: false,
funge_como_cd: false,
@@ -221,16 +264,18 @@
semaforo_verde_aduana_americana: false,
semaforo_rojo_aduana_mexicana: false,
semaforo_rojo_aduana_americana: false,
...(settings.continuationFormData || {})
is_mixed: false,
reason_export: '1',
purchase_order: '',
payment_terms: '',
handling_fees: 0,
cfdi_uuid: '',
path_pdf: '',
path_xml: '',
...(systemData.continuationFormData || {})
};
// Update the 'invoice' dummy object to reflect some top fields if needed for display
invoice = {
...invoice,
...InvoiceTopFieldsFormData,
operation_type: selectedOperationType,
invoice_type: selectedInvoiceType
};
// The derived 'invoice' will update automatically because it spread InvoiceTopFieldsFormData
}
function resetForms() {
@@ -280,18 +325,29 @@
other_increments: null,
total_increments_mn: null,
total_increments_me: null,
incoterm: null,
incoterm: '',
enclosure: null,
num_seals: null,
movement_type: '',
alternate_invoice: '',
valuation_method: null
valuation_method: '',
other_deductibles: null,
proforma_number: '',
sub_division: 'no',
acts_as_cd: false,
identifier_1: '',
complement_1: '',
identifier_2: '',
complement_2: '',
office_document: '',
is_mixed: false
};
itemsFormData = {};
ssimpFormData = {};
othersFormData = {
comments_status: '',
transport_mode: 'TRUCK',
is_mixed: null,
is_mixed: false,
print_stamp: false,
rule_3121_parties_ii: false,
related_doc_id: null,
@@ -303,7 +359,16 @@
operation_num: '',
adendas: '',
observations_vu: '',
certified_number: ''
certified_number: '',
entry_exit_date: '',
payment_date: '',
bill_number: '',
guide_number: '',
shipment_number: '',
option_iv18: '',
delivered_status: false,
received_by: '',
delivery_date: ''
};
continuationFormData = {
numero_tipo_transporte: '',
@@ -312,6 +377,7 @@
cantidad_guias_embarque: null,
destino_origen: '',
puerto_entrada: '',
vehicle_data: '',
fue_revisado_equipo: false,
sub_division: false,
funge_como_cd: false,
@@ -320,9 +386,17 @@
semaforo_verde_aduana_mexicana: false,
semaforo_verde_aduana_americana: false,
semaforo_rojo_aduana_mexicana: false,
semaforo_rojo_aduana_americana: false
semaforo_rojo_aduana_americana: false,
is_mixed: false,
reason_export: '1',
purchase_order: '',
payment_terms: '',
handling_fees: 0,
cfdi_uuid: '',
path_pdf: '',
path_xml: ''
};
invoice = { financials: { exchange_rate: 0 } };
// The derived 'invoice' handles itself
}
function cleanObject(obj: any): any {
@@ -346,6 +420,26 @@
return obj;
}
function handleBack() {
goto('/dashboard/invoices');
}
let operationTypeText = $derived.by(() => {
if (selectedOperationType === 'exp') return 'EXPORTACIÓN';
if (selectedOperationType === 'imp') return 'IMPORTACIÓN';
return 'TIPO NO DEFINIDO';
});
let operationColorClass = $derived.by(() => {
if (selectedOperationType === 'exp') return 'bg-blue-500/10 text-blue-600 border-blue-500/20';
if (selectedOperationType === 'imp') return 'bg-emerald-500/10 text-emerald-600 border-emerald-500/20';
return 'bg-muted text-muted-foreground border-border';
});
// Contexto reactivo para componentes
let invoiceType = $derived(selectedInvoiceType || 'TEM');
let operationTypeNumeric = $derived(selectedOperationType === 'exp' ? 1 : 2);
async function handleSaveSettings() {
if (!selectedInvoiceType || !selectedOperationType || !companyStore?.activeCompany?.id) {
toast.error('Por favor selecciona tipo de factura y operación');
@@ -354,48 +448,42 @@
isSaving = true;
// Use snaphot to get clean data from Svelte 5 proxies
const rawSettings = {
// Use snapshot to get clean data from Svelte 5 proxies
const systemCleanedData = cleanObject({
InvoiceTopFieldsFormData: $state.snapshot(InvoiceTopFieldsFormData),
generalFormData: $state.snapshot(generalFormData),
observationFormData: $state.snapshot(observationFormData),
itemsFormData: $state.snapshot(itemsFormData),
ssimpFormData: $state.snapshot(ssimpFormData),
othersFormData: $state.snapshot(othersFormData),
continuationFormData: $state.snapshot(continuationFormData)
};
});
// Clean the settings to only save what's configured
const cleanedSettings = cleanObject(rawSettings);
// Nest the settings under the active system key
// Merge with existing rawSettings to preserve other system's data
const fullSettings = {
...$state.snapshot(rawSettings),
[activeSystem]: systemCleanedData
};
const settingsPayload = {
invoice_type: selectedInvoiceType,
operation_type: selectedOperationType,
settings: cleanedSettings
settings: fullSettings
};
try {
const token = $page.data.user?.token;
const headers: HeadersInit = {
'Content-Type': 'application/json'
};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
const response = await fetch(
`/api/v1/a76/invoice-settings/?company_id=${companyStore.activeCompany.id}`,
{
method: 'PUT',
headers,
body: JSON.stringify(settingsPayload)
}
const response = await api.put(
`/v1/a76/invoice-settings/?company_id=${companyStore.activeCompany.id}`,
settingsPayload
);
if (response.ok) {
toast.success('Configuración guardada correctamente.');
if (response.status === 200 || response.data) {
// Update local cache
rawSettings = fullSettings;
toast.success(`Configuraciones de ${activeSystem === 'ssisgen' ? 'SCAII' : 'SCAF'} guardadas correctly.`);
} else {
const errorData = await response.json().catch(() => ({}));
console.error('Save error details:', errorData);
console.error('Save error details:', response.error || response.validationErrors);
toast.error('Error al guardar la configuración.');
}
} catch (error) {
@@ -406,180 +494,347 @@
}
}
// Watch for selection changes
// Effect to reload data when system changes
$effect(() => {
if (selectedInvoiceType && selectedOperationType && companyStore?.activeCompany?.id) {
loadSettings();
const sys = activeSystem;
if (rawSettings) {
applySettings(rawSettings);
}
});
// NO MORE reactive loading effects. Manual triggers only to prevent loops.
// Initial mount load.
$effect(() => {
const cid = companyStore?.activeCompany?.id;
if (browser && cid && selectedOperationType && selectedInvoiceType && !lastLoadedKey && !isLoading) {
untrack(() => {
console.log('EFFECT: Initial load triggered');
loadSettings();
});
}
});
// Handle explicit changes
function handleOperationTypeChange(v: string) {
console.log('UI: Operation type change:', v);
selectedOperationType = v;
loadSettings();
}
function handleInvoiceTypeChange(v: string) {
console.log('UI: Invoice type change:', v);
selectedInvoiceType = v;
loadSettings();
}
</script>
<div class="container mx-auto py-6 pb-32">
<!-- Added padding bottom for footer -->
<div class="mb-6 flex items-center justify-between">
<div>
<h1 class="text-2xl font-bold tracking-tight">Configuración de Facturas</h1>
<p class="text-muted-foreground">
Define los valores predeterminados para la creación de facturas.
</p>
</div>
</div>
<Card.Root class="mb-6">
<Card.Header>
<Card.Title>Contexto de Configuración</Card.Title>
<Card.Description
>Selecciona el contexto para editar sus valores por defecto.</Card.Description
>
</Card.Header>
<Card.Content class="flex gap-4">
<div class="w-[250px]">
<Label>Tipo de Operación</Label>
<Select type="single" bind:value={selectedOperationType}>
<SelectTrigger>
{operationTypes.find((t) => t.value === selectedOperationType)?.label ||
'Seleccionar...'}
</SelectTrigger>
<SelectContent>
{#each operationTypes as type}
<SelectItem value={type.value}>{type.label}</SelectItem>
{/each}
</SelectContent>
</Select>
</div>
<div class="w-[350px]">
<Label>Tipo de Factura</Label>
<Select type="single" bind:value={selectedInvoiceType}>
<SelectTrigger>
{@const type = data.invoiceTypes.find((t) => t.key === selectedInvoiceType)}
{type ? `${type.key} - ${type.description}` : 'Seleccionar...'}
</SelectTrigger>
<SelectContent class="max-h-[300px]">
{#each data.invoiceTypes as type}
<SelectItem value={type.key}>{type.key} - {type.description}</SelectItem>
{/each}
</SelectContent>
</Select>
</div>
</Card.Content>
</Card.Root>
{#if selectedInvoiceType && selectedOperationType}
{#if isLoading}
<div class="flex justify-center py-12">
<LoaderCircle class="animate-spin h-8 w-8 text-primary" />
</div>
{:else}
<div class="grid gap-6">
<!-- Top Fields (Header) -->
<div class="bg-background rounded-lg border p-4">
<h3 class="font-medium mb-4">Encabezado</h3>
<InvoiceTopFields
{invoice}
bind:formData={InvoiceTopFieldsFormData}
invoiceTypes={data.invoiceTypes || []}
pedimentos={data.pedimentos || []}
defaultOperationType={selectedOperationType}
defaultInvoiceType={selectedInvoiceType}
/>
<Tabs.Root bind:value={activeTab}>
<div class="space-y-4">
<!-- Header -->
<div class="flex items-center justify-between">
<div class="space-y-1">
<div class="flex items-center gap-3">
<Button variant="ghost" size="icon" onclick={handleBack}>
<ArrowLeft size={20} />
</Button>
<h1 class="text-3xl font-bold tracking-tight">
Configuración de Facturas
</h1>
{#if selectedOperationType}
<Badge variant="outline" class="px-3 py-1 text-sm font-bold {operationColorClass}">
{operationTypeText}
</Badge>
{/if}
{#if selectedInvoiceType}
<Badge variant="secondary">
{data.invoiceTypes.find((t: any) => t.key === selectedInvoiceType)?.description || selectedInvoiceType}
</Badge>
{/if}
</div>
<!-- Tabs for detailed sections -->
<Tabs.Root bind:value={activeTab}>
<Tabs.List class="grid w-full grid-cols-5 bg-muted rounded-md p-1 mb-4">
<Tabs.Trigger value="general">General</Tabs.Trigger>
<Tabs.Trigger value="observations">Observaciones</Tabs.Trigger>
<Tabs.Trigger value="items">Partidas</Tabs.Trigger>
<Tabs.Trigger value="others">Otros</Tabs.Trigger>
<Tabs.Trigger value="continuation">Continuación</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="general">
<GeneralTabForm
{invoice}
bind:formData={generalFormData}
invoiceTypes={data.invoiceTypes || []}
customsBrokers={data.customsBrokers || []}
clients={data.clients || []}
providers={data.providers || []}
currencyTypes={data.currencyTypes || []}
transportTypes={data.transportTypes || []}
transporters={data.transporters || []}
vehicles={data.vehicles || []}
drivers={data.drivers || []}
trailers={data.trailers || []}
customsSections={data.customsSections || []}
codePedimentoRegimens={data.codePedimentoRegimens || []}
operationType={selectedOperationType === 'exp' ? 1 : 2}
exchangeRate={0}
/>
</Tabs.Content>
<Tabs.Content value="observations">
<ObservationsTabForm
{invoice}
bind:formData={observationFormData}
bind:exists={observationExists}
seals={[]}
incoterms={data.incoterms || []}
enclosure={[]}
/>
</Tabs.Content>
<Tabs.Content value="items">
<div class="p-4 border rounded bg-muted/20 text-center">
<p class="text-sm text-muted-foreground">
La configuración predeterminada de partidas es limitada.
</p>
</div>
</Tabs.Content>
<Tabs.Content value="others">
<OthersTabForm
{invoice}
bind:formData={othersFormData}
bind:exists={othersExists}
transportModes={data.transportModes || []}
/>
</Tabs.Content>
<Tabs.Content value="continuation">
<ContinuationTabForm
{invoice}
bind:formData={continuationFormData}
bind:exists={continuationExists}
/>
</Tabs.Content>
</Tabs.Root>
<p class="text-muted-foreground">
Define los valores predeterminados para la creación de facturas.
</p>
</div>
{/if}
{:else}
<div
class="flex flex-col items-center justify-center border-2 border-dashed rounded-lg p-12 text-muted-foreground"
>
<FileText class="h-10 w-10 mb-2 opacity-20" />
<p>Selecciona un tipo de operación y factura para comenzar.</p>
</div>
{/if}
</div>
<!-- Sticky Footer -->
<div
class="fixed bottom-0 left-0 right-0 bg-background/95 backdrop-blur border-t shadow-lg z-10 p-4 transition-all duration-300 group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] ml-[calc(var(--sidebar-width))]"
>
<div class="container mx-auto max-w-[1400px] flex justify-end gap-3">
<Button variant="outline" onclick={resetForms} disabled={isSaving || !selectedInvoiceType}>
Restablecer
</Button>
<Button onclick={handleSaveSettings} disabled={isSaving || !selectedInvoiceType}>
{#if isSaving}
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
Guardando...
<Separator />
<div class="pb-48">
<Card.Root class="mb-8 border-dashed bg-muted/30">
<Card.Header class="py-4">
<Card.Title class="text-lg">Contexto de Configuración</Card.Title>
<Card.Description
>Selecciona el contexto para editar sus valores por defecto.</Card.Description
>
</Card.Header>
<Card.Content class="flex gap-6 py-2">
<div class="space-y-2">
<Label class="text-xs uppercase text-muted-foreground font-bold tracking-wider">Tipo de Operación</Label>
<Select type="single" value={selectedOperationType} onValueChange={handleOperationTypeChange}>
<SelectTrigger class="w-[200px] h-10 bg-background">
{operationTypes.find((t: any) => t.value === selectedOperationType)?.label ||
'Seleccionar...'}
</SelectTrigger>
<SelectContent>
{#each operationTypes as type}
<SelectItem value={type.value}>{type.label}</SelectItem>
{/each}
</SelectContent>
</Select>
</div>
<div class="space-y-2">
<Label class="text-xs uppercase text-muted-foreground font-bold tracking-wider">Tipo de Factura</Label>
<Select type="single" value={selectedInvoiceType} onValueChange={handleInvoiceTypeChange}>
<SelectTrigger class="w-[300px] h-10 bg-background">
{@const type = data.invoiceTypes.find((t: any) => t.key === selectedInvoiceType)}
{type ? `${type.key} - ${type.description}` : 'Seleccionar...'}
</SelectTrigger>
<SelectContent class="max-h-[300px]">
{#each data.invoiceTypes as type}
<SelectItem value={type.key}>{type.key} - {type.description}</SelectItem>
{/each}
</SelectContent>
</Select>
</div>
</Card.Content>
</Card.Root>
{#if selectedInvoiceType && selectedOperationType}
{#if isLoading}
<div class="flex justify-center py-24">
<div class="flex flex-col items-center gap-4">
<LoaderCircle class="animate-spin h-10 w-10 text-primary" />
<p class="text-sm text-muted-foreground animate-pulse">Cargando configuración...</p>
</div>
</div>
{:else}
<div class="grid gap-6">
<!-- Top Fields (Header Defaults) -->
<div class="bg-background rounded-xl border-2 shadow-sm p-6">
<div class="flex items-center gap-2 mb-6 border-b pb-4">
<div class="p-1.5 bg-primary/10 rounded-md">
<FileText class="w-5 h-5 text-primary" />
</div>
<h3 class="text-lg font-bold">Valores Predeterminados del Encabezado</h3>
</div>
<InvoiceTopFields
{invoice}
bind:formData={InvoiceTopFieldsFormData}
invoiceTypes={data.invoiceTypes || []}
pedimentos={data.pedimentos || []}
defaultOperationType={selectedOperationType}
defaultInvoiceType={selectedInvoiceType}
{invoiceType}
isSettings={true}
/>
</div>
<div class="space-y-4">
<Tabs.Content value="general">
<GeneralTabForm
{invoice}
bind:formData={generalFormData}
invoiceTypes={data.invoiceTypes || []}
customsBrokers={data.customsBrokers || []}
clients={data.clients || []}
providers={data.providers || []}
currencyTypes={data.currencyTypes || []}
transportTypes={data.transportTypes || []}
transporters={data.transporters || []}
vehicles={data.vehicles || []}
drivers={data.drivers || []}
trailers={data.trailers || []}
customsSections={data.customsSections || []}
codePedimentoRegimens={data.codePedimentoRegimens || []}
operationType={operationTypeNumeric}
{invoiceType}
exchangeRate={0}
isSettings={true}
/>
</Tabs.Content>
<Tabs.Content value="observations">
<ObservationsTabForm
{invoice}
bind:formData={observationFormData}
bind:exists={observationExists}
seals={[]}
incoterms={data.incoterms || []}
enclosure={[]}
operationType={operationTypeNumeric}
{invoiceType}
isSettings={true}
/>
</Tabs.Content>
<Tabs.Content value="items">
<div class="p-12 border-2 border-dashed rounded-xl bg-muted/20 text-center flex flex-col items-center gap-4">
<div class="p-4 bg-muted rounded-full">
<DollarSign class="w-8 h-8 text-muted-foreground opacity-50" />
</div>
<div class="max-w-md">
<h4 class="font-bold text-lg">Configuración de Partidas</h4>
<p class="text-sm text-muted-foreground mt-1">
La configuración predeterminada de partidas es limitada desde esta sección. Los valores se heredarán de los maestros de productos cuando sea posible.
</p>
</div>
</div>
</Tabs.Content>
<Tabs.Content value="others">
<OthersTabForm
{invoice}
bind:formData={othersFormData}
bind:exists={othersExists}
transportModes={data.transportModes || []}
operationType={operationTypeNumeric}
{invoiceType}
isSettings={true}
/>
</Tabs.Content>
<Tabs.Content value="continuation">
<div class="space-y-6">
{#if invoiceType !== 'MEX'}
<div class="bg-background rounded-xl border p-6">
<h4 class="text-sm font-semibold uppercase text-muted-foreground mb-4">Captura de Valores por Defecto</h4>
<ContinuationTabForm
{invoice}
bind:formData={continuationFormData}
bind:exists={continuationExists}
operationType={operationTypeNumeric}
{invoiceType}
isSettings={true}
/>
</div>
{/if}
</div>
</Tabs.Content>
<Tabs.Content value="ssimp">
<div class="space-y-6">
<div class="bg-background rounded-xl border p-6">
<div class="flex items-center justify-between mb-4 border-b pb-4">
<h4 class="text-sm font-semibold uppercase text-muted-foreground">
{#if selectedOperationType === 'exp'}
Parámetros de Exportación — {data.invoiceTypes?.find((t: any) => t.key === invoiceType)?.description || invoiceType}
{:else}
{#if invoiceType === 'CR' || invoiceType === 'REP' || invoiceType === 'REPAR'}
Parámetros de Cambio de Régimen/Reparación — {invoiceType}
{:else if invoiceType === 'DEF'}
Parámetros Adicionales SSisDef
{:else if invoiceType === 'MEX'}
Parámetros Adicionales SSICM
{:else}
Parámetros Adicionales SSimp
{/if}
{/if}
</h4>
<!-- SCAF/SCAII Switcher -->
<div class="flex items-center bg-muted/30 p-1 rounded-lg border">
<Button
variant={activeSystem === 'ssisgen' ? 'default' : 'ghost'}
size="sm"
class="h-7 text-[10px] uppercase font-bold px-3"
onclick={() => activeSystem = 'ssisgen'}
>
SCAII (Legacy)
</Button>
<Button
variant={activeSystem === 'qsisgen' ? 'default' : 'ghost'}
size="sm"
class="h-7 text-[10px] uppercase font-bold px-3"
onclick={() => activeSystem = 'qsisgen'}
>
SCAF (Activos)
</Button>
</div>
</div>
{#if invoiceType === 'CR' || selectedOperationType === 'exp' || invoiceType === 'REP' || invoiceType === 'REPAR'}
<SscrSettingsForm bind:formData={ssimpFormData} {activeSystem} />
{:else if invoiceType === 'DEF'}
<SsisdefSettingsForm bind:formData={ssimpFormData} {activeSystem} />
{:else if invoiceType === 'MEX'}
<SsicmSettingsForm bind:formData={ssimpFormData} drivers={data.drivers || []} {activeSystem} />
{:else}
<SsimpSettingsForm bind:formData={ssimpFormData} {activeSystem} />
{/if}
</div>
</div>
</Tabs.Content>
</div>
</div>
{/if}
{:else}
<Save class="mr-2 h-4 w-4" />
Guardar Configuración
<div
class="flex flex-col items-center justify-center border-2 border-dashed rounded-xl p-24 text-muted-foreground bg-muted/10 mt-12"
>
<div class="p-6 bg-muted rounded-full mb-6">
<FileText class="h-16 w-16 opacity-20" />
</div>
<h3 class="text-xl font-bold text-foreground">Sin Contexto de Edición</h3>
<p class="max-w-xs text-center mt-2">
Selecciona un tipo de operación y factura para comenzar a editar los valores globales por defecto.
</p>
</div>
{/if}
</Button>
</div>
</div>
</div>
<!-- Sticky Footer -->
<div
class="fixed bottom-0 left-0 right-0 bg-background/95 backdrop-blur border-t shadow-lg z-[10] transition-all duration-300 md:ml-[calc(var(--sidebar-width))] group-has-data-[state=collapsed]/sidebar-wrapper:md:ml-[calc(var(--sidebar-width-icon))]"
>
<div class="container mx-auto max-w-[1400px] space-y-4 p-4">
<!-- Tabs Navigation (Only shown if context selected) -->
{#if selectedInvoiceType && selectedOperationType}
<div class="w-full overflow-x-auto pb-2">
<Tabs.List class="inline-flex md:grid md:w-full md:grid-cols-6 bg-muted rounded-md p-1 h-auto flex-wrap">
<Tabs.Trigger value="general" class="whitespace-nowrap">
<FileText size={16} class="mr-2" />
General
</Tabs.Trigger>
<Tabs.Trigger value="observations" class="whitespace-nowrap">
<Eye size={16} class="mr-2" />
Observaciones
</Tabs.Trigger>
<Tabs.Trigger value="items" class="whitespace-nowrap">
<DollarSign size={16} class="mr-2" />
Partidas
</Tabs.Trigger>
<Tabs.Trigger value="others" class="whitespace-nowrap">
<Truck size={16} class="mr-2" />
Otros
</Tabs.Trigger>
<Tabs.Trigger value="continuation" class="whitespace-nowrap">
<Package size={16} class="mr-2" />
Cont.
</Tabs.Trigger>
<Tabs.Trigger value="ssimp" class="whitespace-nowrap">
<Package size={16} class="mr-2" />
Continuación
</Tabs.Trigger>
</Tabs.List>
</div>
{/if}
<div class="flex justify-end gap-3">
<Button variant="outline" onclick={resetForms} disabled={isSaving || !selectedInvoiceType}>
Restablecer
</Button>
<Button onclick={handleSaveSettings} disabled={isSaving || !selectedInvoiceType}>
{#if isSaving}
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
Guardando...
{:else}
<Save class="mr-2 h-4 w-4" />
Guardar Configuración
{/if}
</Button>
</div>
</div>
</div>
</Tabs.Root>

View File

@@ -0,0 +1,187 @@
<script lang="ts">
import { appSettingsApi } from '$lib/api/dashboard/a76/app-settings';
import { toast } from 'svelte-sonner';
import { Settings2, Save, RefreshCw, Database, Package } from 'lucide-svelte';
import { companyStore } from '$lib/stores/company.svelte';
// Componentes UI
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { Badge } from '$lib/components/ui/badge';
// Custom Components
import SsisGenTabsForm from '$lib/components/dashboard/settings/SsisGenTabsForm.svelte';
import QsisGenTabsForm from '$lib/components/dashboard/settings/QsisGenTabsForm.svelte';
let tenantIdVal = $derived(companyStore.activeCompany?.tenant_id || 0);
let companyIdVal = $derived(companyStore.activeCompany?.id || 0);
// SCAII (ssisgen) | SCAF (qsisgen)
type SystemType = 'ssisgen' | 'qsisgen';
let activeSystem = $state<SystemType>('ssisgen');
let resolvedSettings = $state<any>(null);
let loading = $state(true);
let saving = $state(false);
let currentFormData = $state<any>({});
$effect(() => {
const companyId = companyIdVal;
const tenantId = tenantIdVal;
if (companyId && tenantId) {
handleResolve();
} else {
loading = true;
resolvedSettings = null;
currentFormData = {};
}
});
async function handleSave() {
if (!companyIdVal || !tenantIdVal) {
toast.error('No hay contexto de empresa o tenant activo');
return;
}
saving = true;
try {
const payload = {
tenant_id: tenantIdVal,
company_id: companyIdVal,
settings: {
[activeSystem]: {
...currentFormData,
Type: activeSystem === 'ssisgen' ? 'SCAII' : 'SCAF'
}
}
};
await appSettingsApi.upsert(payload);
toast.success(`Configuraciones de ${activeSystem.toUpperCase()} guardadas correctamente`);
await handleResolve();
} catch (e: any) {
toast.error('Error al guardar: ' + e.message);
} finally {
saving = false;
}
}
async function handleResolve() {
if (!companyIdVal || !tenantIdVal) return;
loading = true;
try {
resolvedSettings = await appSettingsApi.getResolved(tenantIdVal, companyIdVal);
// Carga los datos del sistema activo — sin $effect duplicado
currentFormData = resolvedSettings?.[activeSystem]
? { ...resolvedSettings[activeSystem] }
: {};
} catch (e: any) {
toast.error('Error al cargar configuraciones: ' + e.message);
} finally {
loading = false;
}
}
function handleFormUpdate(newData: any) {
currentFormData = newData;
}
function switchSystem(sys: SystemType) {
if (sys === activeSystem) return;
activeSystem = sys;
// Usa los datos ya cargados en caché; no hace re-fetch
if (resolvedSettings) {
currentFormData = resolvedSettings[sys] ? { ...resolvedSettings[sys] } : {};
} else {
currentFormData = {};
}
}
</script>
<svelte:head>
<title>Configuración General | Anexo 76</title>
</svelte:head>
<div class="container mx-auto py-6 pb-32">
<div class="mb-6 flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<h1 class="text-2xl font-bold tracking-tight">Configuraciones Generales</h1>
<p class="text-muted-foreground">
Configura los parámetros del sistema.
</p>
</div>
<!-- Toggle SCAII / SCAF -->
<div class="flex items-center gap-2 bg-muted rounded-lg p-1 w-fit">
<button
onclick={() => switchSystem('ssisgen')}
class="flex items-center gap-2 px-4 py-2 rounded-md text-sm font-semibold transition-all duration-200
{activeSystem === 'ssisgen'
? 'bg-background text-foreground shadow-sm'
: 'text-muted-foreground hover:text-foreground'}"
>
<Database class="w-4 h-4" />
SCAII
{#if activeSystem === 'ssisgen'}
<Badge variant="secondary" class="text-[10px] px-1.5 py-0 ml-1">inv</Badge>
{/if}
</button>
<button
onclick={() => switchSystem('qsisgen')}
class="flex items-center gap-2 px-4 py-2 rounded-md text-sm font-semibold transition-all duration-200
{activeSystem === 'qsisgen'
? 'bg-background text-foreground shadow-sm'
: 'text-muted-foreground hover:text-foreground'}"
>
<Package class="w-4 h-4" />
SCAF
{#if activeSystem === 'qsisgen'}
<Badge variant="secondary" class="text-[10px] px-1.5 py-0 ml-1">fixed_asset</Badge>
{/if}
</button>
</div>
</div>
{#if loading && !resolvedSettings}
<div class="flex flex-col items-center justify-center py-12 gap-4">
<RefreshCw class="w-8 h-8 animate-spin text-primary" />
<p class="text-muted-foreground">Cargando configuraciones...</p>
</div>
{:else}
<div class="grid gap-6">
<div class="bg-background rounded-lg border p-4">
{#if activeSystem === 'ssisgen'}
<SsisGenTabsForm
currentData={currentFormData}
onUpdate={handleFormUpdate}
/>
{:else}
<QsisGenTabsForm
currentData={currentFormData}
onUpdate={handleFormUpdate}
/>
{/if}
</div>
</div>
{/if}
</div>
<!-- Sticky Footer -->
<div class="fixed bottom-0 left-0 right-0 bg-background/95 backdrop-blur border-t shadow-lg z-10 p-4 transition-all duration-300 group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] ml-[calc(var(--sidebar-width))]">
<div class="container mx-auto max-w-[1400px] flex justify-end items-center gap-4">
{#if loading}
<div class="text-sm text-muted-foreground flex items-center gap-2">
<RefreshCw class="w-4 h-4 animate-spin" />
Cargando...
</div>
{/if}
<Button onclick={handleSave} disabled={saving || loading}>
{#if saving}
<RefreshCw class="mr-2 h-4 w-4 animate-spin" />
Guardando...
{:else}
<Save class="mr-2 h-4 w-4" />
Guardar {activeSystem === 'ssisgen' ? 'SCAII' : 'SCAF'}
{/if}
</Button>
</div>
</div>

View File

@@ -0,0 +1 @@
export const ssr = false;