Merge development into feature/table-invoice
This commit is contained in:
34
frontend/src/lib/api/dashboard/a76/app-settings.ts
Normal file
34
frontend/src/lib/api/dashboard/a76/app-settings.ts
Normal 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();
|
||||
}
|
||||
};
|
||||
@@ -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}`);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
@@ -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');
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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">Sí</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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
]
|
||||
};
|
||||
@@ -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}
|
||||
|
||||
@@ -518,6 +518,10 @@ export function getSidebarData(): SidebarData {
|
||||
title: m["sidebar.reference_data.usuarios"](),
|
||||
url: "",
|
||||
},
|
||||
{
|
||||
title: "General",
|
||||
url: "/dashboard/settings/general",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -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>
|
||||
|
||||
187
frontend/src/routes/dashboard/settings/general/+page.svelte
Normal file
187
frontend/src/routes/dashboard/settings/general/+page.svelte
Normal 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>
|
||||
1
frontend/src/routes/dashboard/settings/general/+page.ts
Normal file
1
frontend/src/routes/dashboard/settings/general/+page.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const ssr = false;
|
||||
Reference in New Issue
Block a user