Merge pull request 'fix/catalogo-agente-aduanal' (#300) from fix/catalogo-agente-aduanal into development

Reviewed-on: ADUANASOFT/anexo76#300
This commit is contained in:
2026-04-22 23:13:56 +00:00
3 changed files with 174 additions and 18 deletions

View File

@@ -59,5 +59,5 @@ export async function updateCustomsBrokerConcept(id: number, data: CustomsBroker
}
export async function deleteCustomsBrokerConcept(id: number, companyId: number): Promise<ApiResponse<void>> {
return await api.delete(`/v1/a76/customs-broker-concepts/${id}/?company_id=${companyId}`);
return await api.delete(`/v1/a76/customs-broker-concepts/${id}?company_id=${companyId}`);
}

View File

@@ -3,6 +3,12 @@
import * as Dialog from '$lib/components/ui/dialog';
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import { FolderSearch } from 'lucide-svelte';
import BrokerSelectorDialog from '$lib/components/dashboard/export/manifest/modals/broker-selector-dialog.svelte';
import {
customsBrokersApi,
type CustomsBroker
} from '$lib/api/dashboard/a76/customs-brokers';
import {
createCustomsBrokerConcept,
updateCustomsBrokerConcept,
@@ -38,6 +44,8 @@
let loading = $state(false);
let error = $state<string | null>(null);
let showBrokerSelector = $state(false);
let selectedBrokerLabel = $state('');
// Cargar datos al editar
$effect(() => {
@@ -48,6 +56,7 @@
amount: item.amount,
priority: item.priority
};
selectedBrokerLabel = item.broker_key || '';
} else {
formData = {
broker_key: '',
@@ -55,9 +64,36 @@
amount: undefined,
priority: undefined
};
selectedBrokerLabel = '';
}
});
$effect(() => {
if (!open || !formData.broker_key || !companyId || selectedBrokerLabel) return;
(async () => {
try {
const response = await customsBrokersApi.get(formData.broker_key, companyId.toString());
if (response.data) {
selectedBrokerLabel = response.data.name || response.data.broker_key;
}
} catch (e) {
selectedBrokerLabel = formData.broker_key;
}
})();
});
function handleBrokerSelect(broker: CustomsBroker) {
formData.broker_key = broker.broker_key || '';
selectedBrokerLabel = broker.name || broker.broker_key || '';
}
const brokerDisplayValue = $derived(
selectedBrokerLabel && selectedBrokerLabel !== formData.broker_key
? `${selectedBrokerLabel} (${formData.broker_key})`
: formData.broker_key
);
async function handleSubmit() {
error = null;
loading = true;
@@ -118,13 +154,28 @@
<div class="grid grid-cols-2 gap-4">
<div class="grid gap-2 col-span-1">
<Label for="broker_key">Clave del Agente <span class="text-destructive">*</span></Label>
<Input
id="broker_key"
bind:value={formData.broker_key}
placeholder="Ej: 01001"
maxlength={5}
disabled={isEdit}
/>
<div class="flex gap-2">
<Input
id="broker_key"
value={brokerDisplayValue}
placeholder="Seleccionar agente aduanal"
readonly
disabled={isEdit}
class={!isEdit ? 'cursor-pointer' : ''}
onclick={() => {
if (!isEdit) showBrokerSelector = true;
}}
/>
<Button
type="button"
variant="secondary"
size="icon"
onclick={() => (showBrokerSelector = true)}
disabled={isEdit}
>
<FolderSearch class="h-4 w-4" />
</Button>
</div>
</div>
<div class="grid gap-2 col-span-1">
@@ -166,3 +217,5 @@
</form>
</Dialog.Content>
</Dialog.Root>
<BrokerSelectorDialog bind:open={showBrokerSelector} onSelect={handleBrokerSelect} />

View File

@@ -6,26 +6,34 @@
import CreateEditDialog from '$lib/components/dashboard/general_catalogs/customs_broker_concepts/create-edit-dialog.svelte';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Plus, RefreshCw } from 'lucide-svelte';
import { goto } from '$app/navigation';
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
import { browser } from '$app/environment';
import { useShortcuts } from '$lib/hooks/use-shortcuts';
import { obtenerAtajosListaConceptosAA } from '$lib/config/shortcuts/dashboard/general_catalogs/customs_broker_concepts/list';
import { companyStore } from '$lib/stores/company.svelte';
import { getCustomsBrokerConcepts } from '$lib/api/dashboard/a76/general_catalogs/customs-broker-concepts';
import {
deleteCustomsBrokerConcept,
getCustomsBrokerConcepts,
type CustomsBrokerConcept
} from '$lib/api/dashboard/a76/general_catalogs/customs-broker-concepts';
let { data } = $props();
const activeCompanyId = $derived(companyStore.activeCompany?.id);
let dialogOpen = $state(false);
let editingItem = $state<CustomsBrokerConcept | null>(null);
let error = $state<string | null>(data.error || null);
let selectedIds = $state<(string | number)[]>([]);
// Atajos
useShortcuts(
'Lista Conceptos AA',
obtenerAtajosListaConceptosAA({
manejarNuevo: () => (dialogOpen = true),
manejarActualizar: () => goto($page.url, { invalidateAll: true }),
manejarNuevo: () => {
editingItem = null;
dialogOpen = true;
},
manejarActualizar: reloadData,
})
);
@@ -42,6 +50,9 @@
let totalItems = $state(data.concepts?.total || 0);
let loading = $state(false);
let hasMore = $derived(allItems.length < totalItems);
const selectedConcept = $derived(
selectedIds.length === 1 ? allItems.find((item) => item.id === selectedIds[0]) ?? null : null
);
$effect(() => {
if (data.concepts) {
@@ -150,8 +161,62 @@
}
function handleSuccess() {
dialogOpen = false;
editingItem = null;
selectedIds = [];
reloadData();
}
function handleRowClick(row: CustomsBrokerConcept) {
const id = row.id;
selectedIds = selectedIds.includes(id) ? [] : [id];
}
function handleRowDoubleClick(row: CustomsBrokerConcept) {
editingItem = row;
dialogOpen = true;
}
function handleEditSelected() {
if (!selectedConcept) return;
editingItem = selectedConcept;
dialogOpen = true;
}
async function handleDeleteSelected() {
if (!selectedConcept || !companyStore.activeCompany) return;
if (
!confirm(
`¿Estás seguro de eliminar el concepto "${selectedConcept.concept}" del agente "${selectedConcept.broker_key}"?`
)
) {
return;
}
loading = true;
error = null;
try {
const response = await deleteCustomsBrokerConcept(
selectedConcept.id,
companyStore.activeCompany.id
);
if (response.error) {
error = response.error;
return;
}
selectedIds = [];
await reloadData();
} catch (err) {
error = 'Error al eliminar el concepto';
console.error('Error deleting broker concept:', err);
} finally {
loading = false;
}
}
</script>
<div class="flex flex-col gap-6 p-6 h-[calc(100svh-4rem)] group-has-data-[collapsible=icon]/sidebar-wrapper:h-[calc(100svh-3rem)] overflow-hidden">
@@ -161,11 +226,13 @@
<p class="text-muted-foreground">Gestión del catálogo de conceptos de agente aduanal</p>
</div>
<div class="flex items-center gap-3">
<Button variant="outline" size="sm" class="h-9" onclick={reloadData}>
<RefreshCw class="mr-2 h-4 w-4" />
Actualizar
</Button>
<Button class="h-9" onclick={() => (dialogOpen = true)}>
<Button
class="h-9"
onclick={() => {
editingItem = null;
dialogOpen = true;
}}
>
<Plus class="mr-2 h-4 w-4" />
Nuevo Concepto
</Button>
@@ -206,6 +273,10 @@
{loading}
{hasMore}
{loadMore}
{selectedIds}
onSelectedIdsChange={(ids) => (selectedIds = ids)}
onRowClick={handleRowClick}
onRowDoubleClick={handleRowDoubleClick}
/>
</div>
</Card.Content>
@@ -215,9 +286,41 @@
Mostrando {allItems.length} de {totalItems} registros
</div>
<div class="h-20"></div>
<div
id="customs-broker-concepts-list-footer"
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
>
<div class="mx-auto max-w-[1400px] px-4 py-4">
<div class="flex justify-end gap-2">
<Button
variant="outline"
size="sm"
onclick={handleEditSelected}
disabled={selectedIds.length !== 1}
>
<Pencil size={16} class="mr-2" />
Editar
</Button>
<Button
variant="outline"
size="sm"
onclick={handleDeleteSelected}
disabled={selectedIds.length !== 1}
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<Trash2 size={16} class="mr-2" />
Eliminar
</Button>
</div>
</div>
</div>
{#if activeCompanyId}
<CreateEditDialog
bind:open={dialogOpen}
item={editingItem}
companyId={activeCompanyId}
onSuccess={handleSuccess}
/>