Se cambiaron los nombres de los archivos a uno mas estandarizado, se puso el ymal como estaba y se quitaron notificaciones

This commit is contained in:
2025-12-30 13:58:56 -06:00
parent 0e3d8654b6
commit 91d68cb1ef
49 changed files with 205 additions and 717 deletions

View File

@@ -3,33 +3,21 @@ import type { ApiResponse } from '$lib/api';
export interface CustomsBrokerConcept {
id: number;
code: string;
description?: string;
description_en?: string;
detailed_description?: string;
priority?: number;
first_total?: boolean;
type?: string;
is_printed?: boolean;
section?: number;
classification?: string;
tenant_id: string;
company_id?: string;
broker_key: string;
concept: string;
amount?: number | null;
priority?: number | null;
company_id: number;
tenant_id: number;
created_at: string;
updated_at?: string;
updated_at?: string | null;
}
export interface CustomsBrokerConceptCreate {
code: string;
description?: string;
description_en?: string;
detailed_description?: string;
priority?: number;
first_total?: boolean;
type?: string;
is_printed?: boolean;
section?: number;
classification?: string;
broker_key: string;
concept: string;
amount?: number | null;
priority?: number | null;
}
export interface CustomsBrokerConceptUpdate extends Partial<CustomsBrokerConceptCreate> {}

View File

@@ -1,75 +0,0 @@
import { api } from '$lib/api';
import type { ApiResponse } from '$lib/api';
export interface CustomsBrokerConcept {
id: number;
code: string;
description?: string;
description_en?: string;
detailed_description?: string;
priority?: number;
first_total?: boolean;
type?: string;
is_printed?: boolean;
section?: number;
classification?: string;
tenant_id: string;
company_id?: string;
created_at: string;
updated_at?: string;
}
export interface CustomsBrokerConceptCreate {
code: string;
description?: string;
description_en?: string;
detailed_description?: string;
priority?: number;
first_total?: boolean;
type?: string;
is_printed?: boolean;
section?: number;
classification?: string;
}
export interface CustomsBrokerConceptUpdate extends Partial<CustomsBrokerConceptCreate> {}
export interface CustomsBrokerConceptListResponse {
items: CustomsBrokerConcept[];
total: number;
page: number;
page_size: number;
pages: number;
}
export async function getCustomsBrokerConcepts(
page: number = 1,
pageSize: number = 50,
companyId: number,
filters: Record<string, any> = {},
): Promise<ApiResponse<CustomsBrokerConceptListResponse>> {
const params = new URLSearchParams({
page: page.toString(),
page_size: pageSize.toString(),
company_id: companyId.toString(),
...filters
});
return await api.get(`/v1/a76/customs-broker-concepts?${params.toString()}`);
}
export async function getCustomsBrokerConcept(id: number, companyId: number): Promise<ApiResponse<CustomsBrokerConcept>> {
return await api.get(`/v1/a76/customs-broker-concepts/${id}?company_id=${companyId}`);
}
export async function createCustomsBrokerConcept(data: CustomsBrokerConceptCreate, companyId: number): Promise<ApiResponse<CustomsBrokerConcept>> {
return await api.post(`/v1/a76/customs-broker-concepts?company_id=${companyId}`, data);
}
export async function updateCustomsBrokerConcept(id: number, data: CustomsBrokerConceptUpdate, companyId: number): Promise<ApiResponse<CustomsBrokerConcept>> {
return await api.put(`/v1/a76/customs-broker-concepts/${id}?company_id=${companyId}`, data);
}
export async function deleteCustomsBrokerConcept(id: number, companyId: number): Promise<ApiResponse<void>> {
return await api.delete(`/v1/a76/customs-broker-concepts/${id}?company_id=${companyId}`);
}

View File

@@ -24,7 +24,6 @@
}
if (!companyStore.activeCompany) {
alert('❌ Error: No hay una compañía seleccionada');
return;
}
@@ -35,20 +34,17 @@
const response = await deleteClassificationConcept(item.id, companyStore.activeCompany.id);
if (response.error) {
alert(`❌ Error al eliminar:\n\n${response.error}`);
return;
}
// Éxito
if (response.status === 204 || response.status === 200 || !response.error) {
alert(`✅ Clasificación "${item.classification}" eliminada correctamente`);
if (onSuccess) {
onSuccess();
}
}
} catch (e) {
const errorMsg = e instanceof Error ? e.message : 'Error desconocido al eliminar el registro';
alert(`❌ Error al eliminar:\n\n${errorMsg}`);
} finally {
loading = false;
}

View File

@@ -30,13 +30,11 @@
// Si hay error en la respuesta
if (response.error) {
alert(`❌ Error al eliminar:\n\n${response.error}`);
return;
}
// Éxito (status 204 o 200)
if (response.status === 204 || response.status === 200 || !response.error) {
alert(`✅ Empresa "${item.name}" eliminada correctamente`);
if (onSuccess) {
onSuccess();
@@ -44,7 +42,6 @@
}
} catch (e) {
const errorMsg = e instanceof Error ? e.message : 'Error desconocido al eliminar el registro';
alert(`❌ Error al eliminar:\n\n${errorMsg}`);
} finally {
loading = false;
}

View File

@@ -24,7 +24,6 @@
}
if (!companyStore.activeCompany) {
alert('❌ Error: No hay una compañía seleccionada');
return;
}
@@ -35,20 +34,17 @@
const response = await deleteConcept(item.id, companyStore.activeCompany.id);
if (response.error) {
alert(`❌ Error al eliminar:\n\n${response.error}`);
return;
}
// Éxito
if (response.status === 204 || response.status === 200 || !response.error) {
alert(`✅ Concepto "${item.code}" eliminado correctamente`);
if (onSuccess) {
onSuccess();
}
}
} catch (e) {
const errorMsg = e instanceof Error ? e.message : 'Error desconocido al eliminar el registro';
alert(`❌ Error al eliminar:\n\n${errorMsg}`);
} finally {
loading = false;
}

View File

@@ -6,24 +6,24 @@ import DataTableActions from './data-table-actions.svelte';
export function createColumns(onSuccess?: () => void): ColumnDef<CustomsBrokerConcept>[] {
return [
{
accessorKey: 'code',
header: 'Código',
cell: ({ row }) => row.original.code || '-'
accessorKey: 'broker_key',
header: 'Clave Agente',
cell: ({ row }) => row.original.broker_key || '-'
},
{
accessorKey: 'description',
header: 'Descripción',
cell: ({ row }) => row.original.description || '-'
accessorKey: 'concept',
header: 'Concepto',
cell: ({ row }) => row.original.concept || '-'
},
{
accessorKey: 'type',
header: 'Tipo',
cell: ({ row }) => row.original.type || '-'
accessorKey: 'amount',
header: 'Importe',
cell: ({ row }) => row.original.amount ? `$${row.original.amount.toFixed(2)}` : '-'
},
{
accessorKey: 'section',
header: 'Sección',
cell: ({ row }) => row.original.section?.toString() || '-'
accessorKey: 'priority',
header: 'Prioridad',
cell: ({ row }) => row.original.priority?.toString() || '-'
},
{
id: 'actions',

View File

@@ -6,49 +6,51 @@
import {
createCustomsBrokerConcept,
updateCustomsBrokerConcept,
type CustomsBrokerConcept
type CustomsBrokerConcept,
type CustomsBrokerConceptCreate
} from "$lib/api/dashboard/a76/general_catalogs/customs-broker-concepts";
import { companyStore } from "$lib/stores/company.svelte";
let {
open = $bindable(false),
item = null,
companyId,
onSuccess
}: {
open: boolean;
item?: CustomsBrokerConcept | null;
item?: CustomsBrokerConcept | null;
companyId: number;
onSuccess?: () => void;
} = $props();
const isEdit = $derived(!!item);
const title = $derived(isEdit ? "Editar Concepto AA" : "Nuevo Concepto AA");
// 3. Estado alineado al modelo de BD
let formData = $state({
// Estado alineado a CustomsBrokerConceptCreate
let formData = $state<CustomsBrokerConceptCreate>({
broker_key: '',
concept: '',
amount: null as number | null,
priority: null as number | null
amount: undefined,
priority: undefined
});
let loading = $state(false);
let error = $state<string | null>(null);
// 4. Cargar datos al editar
// Cargar datos al editar
$effect(() => {
if (item) {
formData = {
broker_key: item.broker_key || '',
concept: item.concept || '',
amount: item.amount || null,
priority: item.priority || null
amount: item.amount,
priority: item.priority
};
} else {
formData = {
broker_key: '',
concept: '',
amount: null,
priority: null
amount: undefined,
priority: undefined
};
}
});
@@ -58,34 +60,30 @@
loading = true;
try {
const companyId = companyStore.activeCompany?.id;
if (!companyId) throw new Error('No hay una compañía seleccionada');
// Validaciones
if (!formData.broker_key.trim()) throw new Error('La Clave AA es requerida');
if (!formData.broker_key.trim()) throw new Error('La Clave del Agente es requerida');
if (!formData.concept.trim()) throw new Error('El Concepto es requerido');
// 5. Preparar datos con los tipos correctos (Números)
const dataToSend = {
// Limpiar y enviar datos
const dataToSend: CustomsBrokerConceptCreate = {
broker_key: formData.broker_key.trim(),
concept: formData.concept.trim(),
amount: formData.amount ? Number(formData.amount) : undefined,
priority: formData.priority ? Number(formData.priority) : undefined
};
// 6. Corregida la sintaxis de llamada a la API
if (isEdit && item) {
// UPDATE: (id, data, companyId)
await updateCustomsBrokerConcept(item.id, dataToSend, companyId);
} else {
// CREATE: (data, companyId)
await createCustomsBrokerConcept(dataToSend, companyId);
}
open = false;
if (onSuccess) onSuccess();
} catch (e) {
error = e instanceof Error ? e.message : 'Error al guardar';
} catch (e: any) {
error = e.message || 'Error al guardar';
} finally {
loading = false;
}
@@ -93,11 +91,11 @@
</script>
<Dialog.Root bind:open>
<Dialog.Content class="max-w-md max-h-[90vh] overflow-y-auto">
<Dialog.Content class="max-w-lg max-h-[90vh] overflow-y-auto">
<Dialog.Header>
<Dialog.Title>{title}</Dialog.Title>
<Dialog.Description>
{isEdit ? 'Modifica el concepto del agente aduanal' : 'Crea un nuevo concepto'}
{isEdit ? 'Modifica los detalles del concepto' : 'Ingresa los datos para el nuevo concepto'}
</Dialog.Description>
</Dialog.Header>
@@ -108,29 +106,30 @@
</div>
{/if}
<div class="grid gap-4">
<div class="grid gap-2">
<Label for="broker_key">Clave AA <span class="text-destructive">*</span></Label>
<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: 550"
maxlength={5}
placeholder="Ej: 01001"
maxlength="5"
disabled={isEdit}
/>
</div>
<div class="grid gap-2">
<div class="grid gap-2 col-span-1">
<Label for="concept">Concepto <span class="text-destructive">*</span></Label>
<Input
id="concept"
bind:value={formData.concept}
placeholder="Ej: FLETE"
maxlength={15}
placeholder="Ej: 001"
maxlength="15"
disabled={isEdit}
/>
</div>
<div class="grid gap-2">
<div class="grid gap-2 col-span-1">
<Label for="amount">Importe</Label>
<Input
id="amount"
@@ -141,13 +140,13 @@
/>
</div>
<div class="grid gap-2">
<div class="grid gap-2 col-span-1">
<Label for="priority">Prioridad</Label>
<Input
id="priority"
type="number"
bind:value={formData.priority}
placeholder="Ej: 1"
placeholder="0"
/>
</div>
</div>

View File

@@ -24,7 +24,6 @@
}
if (!companyStore.activeCompany) {
alert('❌ Error: No hay una compañía seleccionada');
return;
}
@@ -35,20 +34,17 @@
const response = await deleteCustomsBrokerConcept(item.id, companyStore.activeCompany.id);
if (response.error) {
alert(`❌ Error al eliminar:\n\n${response.error}`);
return;
}
// Éxito
if (response.status === 204 || response.status === 200 || !response.error) {
alert(`✅ Concepto "${item.code}" eliminado correctamente`);
if (onSuccess) {
onSuccess();
}
}
} catch (e) {
const errorMsg = e instanceof Error ? e.message : 'Error desconocido al eliminar el registro';
alert(`❌ Error al eliminar:\n\n${errorMsg}`);
} finally {
loading = false;
}

View File

@@ -27,7 +27,6 @@
const companyId = companyStore.activeCompany?.id;
if (!companyId) {
alert('❌ Error: No hay compañía seleccionada');
return;
}
@@ -35,11 +34,9 @@
try {
await deleteDoda(item.id, companyId);
alert('✅ Registro eliminado correctamente');
if (onSuccess) onSuccess();
} catch (err: any) {
error = err.message || 'Error al eliminar el registro';
alert(`❌ Error: ${error}`);
console.error('Error deleting doda:', err);
} finally {
loading = false;

View File

@@ -27,7 +27,6 @@
const companyId = companyStore.activeCompany?.id;
if (!companyId) {
alert('❌ Error: No hay compañía seleccionada');
return;
}
@@ -35,11 +34,9 @@
try {
await deleteElectronicNotice(item.id, companyId);
alert('✅ Aviso electrónico eliminado correctamente');
if (onSuccess) onSuccess();
} catch (err: any) {
error = err.message || 'Error al eliminar el aviso electrónico';
alert(`❌ Error: ${error}`);
console.error('Error deleting electronic notice:', err);
} finally {
loading = false;

View File

@@ -27,7 +27,6 @@
const companyId = companyStore.activeCompany?.id;
if (!companyId) {
alert('❌ Error: No hay compañía seleccionada');
return;
}
@@ -40,11 +39,9 @@
throw new Error(response.error);
}
alert('✅ Equivalencia eliminada correctamente');
if (onSuccess) onSuccess();
} catch (err: any) {
error = err.message || 'Error al eliminar la equivalencia';
alert(`❌ Error: ${error}`);
console.error('Error deleting equivalency:', err);
} finally {
loading = false;

View File

@@ -83,14 +83,12 @@
if (isEdit && item) {
await updateErrorCatalog(item.id, basePayload, companyId);
alert("✅ Error actualizado correctamente");
} else {
const createPayload = {
code: formData.code.trim(),
...basePayload
};
await createErrorCatalog(createPayload, companyId);
alert("✅ Error creado correctamente");
}
open = false;

View File

@@ -27,7 +27,6 @@
const companyId = companyStore.activeCompany?.id;
if (!companyId) {
alert('❌ Error: No hay compañía seleccionada');
return;
}
@@ -35,13 +34,11 @@
try {
await deleteErrorCatalog(item.id, companyId);
alert('✅ Error eliminado correctamente');
if (onSuccess) {
onSuccess();
}
} catch (err: any) {
error = err.message || 'Error al eliminar el error';
alert(`❌ Error: ${error}`);
console.error('Error deleting:', err);
} finally {
loading = false;

View File

@@ -27,7 +27,6 @@
const companyId = companyStore.activeCompany?.id;
if (!companyId) {
alert('❌ Error: No hay compañía seleccionada');
return;
}
@@ -35,11 +34,9 @@
try {
await deleteIdentifier(item.id, companyId);
alert('✅ Identificador eliminado correctamente');
if (onSuccess) onSuccess();
} catch (err: any) {
error = err.message || 'Error al eliminar el identificador';
alert(`❌ Error: ${error}`);
console.error('Error deleting identifier:', err);
} finally {
loading = false;

View File

@@ -27,7 +27,6 @@
const companyId = companyStore.activeCompany?.id;
if (!companyId) {
alert('❌ Error: No hay compañía seleccionada');
return;
}
@@ -35,11 +34,9 @@
try {
await deleteINPC(item.id, companyId);
alert('✅ Registro eliminado correctamente');
if (onSuccess) onSuccess();
} catch (err: any) {
error = err.message || 'Error al eliminar el registro';
alert(`❌ Error: ${error}`);
console.error('Error deleting INPC:', err);
} finally {
loading = false;

View File

@@ -25,7 +25,6 @@
}
if (!companyStore.activeCompany) {
alert('❌ Error: No hay una compañía seleccionada');
return;
}
@@ -36,20 +35,17 @@
const response = await deleteLegend(item.id, companyStore.activeCompany.id);
if (response.error) {
alert(`❌ Error al eliminar:\n\n${response.error}`);
return;
}
// Éxito
if (response.status === 204 || response.status === 200 || !response.error) {
alert(`✅ Leyenda "${item.code}" eliminada correctamente`);
if (onSuccess) {
onSuccess();
}
}
} catch (e) {
const errorMsg = e instanceof Error ? e.message : 'Error desconocido al eliminar el registro';
alert(`❌ Error al eliminar:\n\n${errorMsg}`);
} finally {
loading = false;
}

View File

@@ -60,14 +60,12 @@
if (isEdit && item) {
await updateLocation(item.id, basePayload, companyId);
alert("✅ Ubicación actualizada correctamente");
} else {
const createPayload = {
location_code: formData.location_code.trim(),
...basePayload
};
await createLocation(createPayload, companyId);
alert("✅ Ubicación creada correctamente");
}
open = false;

View File

@@ -93,10 +93,8 @@
if (isEdit && item) {
await updateMultiCurrencyType(item.id, dataToSend, companyId);
alert(`✅ Tipo de moneda múltiple actualizado correctamente`);
} else {
await createMultiCurrencyType(dataToSend, companyId);
alert(`✅ Tipo de moneda múltiple creado correctamente`);
}
open = false;

View File

@@ -27,7 +27,6 @@
const companyId = companyStore.activeCompany?.id;
if (!companyId) {
alert('❌ Error: No hay compañía seleccionada');
return;
}
@@ -35,11 +34,9 @@
try {
await deletePrevalidator(item.id, companyId);
alert('✅ Prevalidador eliminado correctamente');
if (onSuccess) onSuccess();
} catch (err: any) {
error = err.message || 'Error al eliminar el prevalidador';
alert(`❌ Error: ${error}`);
console.error('Error deleting prevalidator:', err);
} finally {
loading = false;

View File

@@ -27,7 +27,6 @@
const companyId = companyStore.activeCompany?.id;
if (!companyId) {
alert('❌ Error: No hay compañía seleccionada');
return;
}
@@ -35,11 +34,9 @@
try {
await deleteSignature(item.id, companyId);
alert('✅ Firma eliminada correctamente');
if (onSuccess) onSuccess();
} catch (err: any) {
error = err.message || 'Error al eliminar la firma';
alert(`❌ Error: ${error}`);
console.error('Error deleting signature:', err);
} finally {
loading = false;

View File

@@ -70,10 +70,8 @@
if (isEdit && conversion) {
await updateUnitConversion(conversion.id, dataToSend, companyId);
alert(`✅ Conversión "${dataToSend.from_unit_code}${dataToSend.to_unit_code}" actualizada correctamente`);
} else {
await createUnitConversion(dataToSend, companyId);
alert(`✅ Conversión "${dataToSend.from_unit_code}${dataToSend.to_unit_code}" creada correctamente`);
}
open = false;

View File

@@ -23,7 +23,6 @@
}
if (!companyStore.activeCompany) {
alert('❌ Error: No hay una compañía seleccionada');
return;
}
@@ -31,13 +30,11 @@
try {
await deleteUnitConversion(conversion.id, companyStore.activeCompany.id);
alert(`✅ Conversión "${conversion.from_unit_code}${conversion.to_unit_code}" eliminada correctamente`);
if (onSuccess) {
onSuccess();
}
} catch (e) {
const errorMsg = e instanceof Error ? e.message : 'Error desconocido al eliminar el registro';
alert(`❌ Error al eliminar:\n\n${errorMsg}`);
} finally {
loading = false;
}

View File

@@ -23,7 +23,6 @@
}
if (!companyStore.activeCompany) {
alert('❌ Error: No hay una compañía seleccionada');
return;
}
@@ -33,19 +32,16 @@
const response = await deleteUnitOfMeasureACE(item.id, companyStore.activeCompany.id);
if (response.error) {
alert(`❌ Error al eliminar:\n\n${response.error}`);
return;
}
if (response.status === 204 || response.status === 200 || !response.error) {
alert(`✅ Unidad ACE "${item.code}" eliminada correctamente`);
if (onSuccess) {
onSuccess();
}
}
} catch (e) {
const errorMsg = e instanceof Error ? e.message : 'Error desconocido al eliminar el registro';
alert(`❌ Error al eliminar:\n\n${errorMsg}`);
} finally {
loading = false;
}

View File

@@ -45,7 +45,6 @@
const activeCompanyId = companyStore.activeCompany?.id;
if (!activeCompanyId) {
alert('No hay una compañía activa seleccionada');
return;
}
@@ -59,9 +58,7 @@
: await createUnitOfMeasureAmerican(data, activeCompanyId);
if (response.error) {
alert(response.error.detail || 'Error al guardar');
} else {
alert(unit ? 'Unidad actualizada' : 'Unidad creada');
open = false;
onSuccess?.();
}

View File

@@ -20,15 +20,12 @@
const activeCompanyId = companyStore.activeCompany?.id;
if (!activeCompanyId) {
alert('No hay una compañía activa seleccionada');
return;
}
const response = await deleteUnitOfMeasureAmerican(unit.id, activeCompanyId);
if (response.error) {
alert(response.error.detail || 'Error al eliminar');
} else if (response.status === 204 || response.status === 200) {
alert('Unidad eliminada');
onSuccess?.();
}
}

View File

@@ -47,7 +47,6 @@
const activeCompanyId = companyStore.activeCompany?.id;
if (!activeCompanyId) {
alert('No hay una compañía activa seleccionada');
return;
}
@@ -62,9 +61,7 @@
: await createUnitOfMeasureCustoms(data, activeCompanyId);
if (response.error) {
alert(response.error.detail || 'Error al guardar');
} else {
alert(unit ? 'Unidad actualizada' : 'Unidad creada');
open = false;
onSuccess?.();
}

View File

@@ -20,15 +20,12 @@
const activeCompanyId = companyStore.activeCompany?.id;
if (!activeCompanyId) {
alert('No hay una compañía activa seleccionada');
return;
}
const response = await deleteUnitOfMeasureCustoms(unit.id, activeCompanyId);
if (response.error) {
alert(response.error.detail || 'Error al eliminar');
} else if (response.status === 204 || response.status === 200) {
alert('Unidad eliminada');
onSuccess?.();
}
}

View File

@@ -45,7 +45,6 @@
const activeCompanyId = companyStore.activeCompany?.id;
if (!activeCompanyId) {
alert('No hay una compañía activa seleccionada');
return;
}
@@ -59,9 +58,7 @@
: await createUnitOfMeasureGeneral(data, activeCompanyId);
if (response.error) {
alert(response.error.detail || 'Error al guardar');
} else {
alert(unit ? 'Unidad actualizada' : 'Unidad creada');
open = false;
onSuccess?.();
}

View File

@@ -20,15 +20,12 @@
const activeCompanyId = companyStore.activeCompany?.id;
if (!activeCompanyId) {
alert('No hay una compañía activa seleccionada');
return;
}
const response = await deleteUnitOfMeasureGeneral(unit.id, activeCompanyId);
if (response.error) {
alert(response.error.detail || 'Error al eliminar');
} else if (response.status === 204 || response.status === 200) {
alert('Unidad eliminada');
onSuccess?.();
}
}

View File

@@ -45,7 +45,6 @@
const activeCompanyId = companyStore.activeCompany?.id;
if (!activeCompanyId) {
alert('No hay una compañía activa seleccionada');
return;
}
@@ -59,9 +58,7 @@
: await createUnitOfMeasureOMA(data, activeCompanyId);
if (response.error) {
alert(response.error.detail || 'Error al guardar');
} else {
alert(unit ? 'Unidad actualizada' : 'Unidad creada');
open = false;
onSuccess?.();
}

View File

@@ -20,15 +20,12 @@
const activeCompanyId = companyStore.activeCompany?.id;
if (!activeCompanyId) {
alert('No hay una compañía activa seleccionada');
return;
}
const response = await deleteUnitOfMeasureOMA(unit.id, activeCompanyId);
if (response.error) {
alert(response.error.detail || 'Error al eliminar');
} else if (response.status === 204 || response.status === 200) {
alert('Unidad eliminada');
onSuccess?.();
}
}

View File

@@ -14,7 +14,7 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
companies: parentData.companies || []
};
}
/////HOLAAA
try {
// Obtener company_id de múltiples fuentes (en orden de prioridad):
// 1. URL query param (permite cambiar vía navegación)

View File

@@ -12,7 +12,7 @@
import { browser } from '$app/environment';
import { companyStore } from '$lib/stores/company.svelte';
import { Plus, Search, Trash2 } from 'lucide-svelte';
//HOLAA
// Los datos iniciales vienen del servidor
let { data }: { data: PageData } = $props();

View File

@@ -1,395 +0,0 @@
<script lang="ts">
import { Button } from "$lib/components/ui/button";
import { Input } from "$lib/components/ui/input";
import { Label } from "$lib/components/ui/label";
import * as Card from "$lib/components/ui/card";
import { customsBrokersApi, type CreateCustomsBrokerData } from "$lib/api/dashboard/a76/customs-brokers";
import { companyStore } from "$lib/stores/company.svelte";
import { goto } from "$app/navigation";
import { ArrowLeft } from "lucide-svelte";
// Eliminar props de modal ya que ahora es una página
// let { open = $bindable(false), onSuccess }: { open: boolean; onSuccess?: () => void; } = $props();
let formData = $state({
broker_key: "",
name: "",
type: "",
address: "",
postal_code: "",
city: "",
state: "",
phone: "",
fax: "",
email: "",
country: "",
tax_id: "",
personal_id: "",
position: "",
license: "",
company: "",
contact: ""
});
let loading = $state(false);
let error = $state<string | null>(null);
async function handleSubmit(e: Event) {
e.preventDefault();
if (!companyStore.activeCompany) {
error = "No hay compañía seleccionada";
return;
}
loading = true;
error = null;
try {
const payload: CreateCustomsBrokerData = {
broker_key: formData.broker_key,
name: formData.name || null,
type: formData.type || null,
address: formData.address || null,
postal_code: formData.postal_code || null,
city: formData.city || null,
state: formData.state || null,
phone: formData.phone || null,
fax: formData.fax || null,
email: formData.email || null,
country: formData.country || null,
tax_id: formData.tax_id || null,
personal_id: formData.personal_id || null,
position: formData.position || null,
license: formData.license || null,
company: formData.company || null,
contact: formData.contact || null,
tenant_id: "1", // TODO: Get from user context
company_id: companyStore.activeCompany.id.toString()
};
const response = await customsBrokersApi.create(payload);
if (response.error) {
if (response.status === 409) {
error = 'Ya existe un agente aduanal con la clave proporcionada.';
} else
if (response.status === 401) {
error = 'Sesión expirada. Recargando página...';
setTimeout(() => {
window.location.reload();
}, 1500);
} else {
error = response.error;
}
return;
}
// Éxito - redirigir a la lista
goto('/dashboard/customs_brokers');
} catch (e) {
error = e instanceof Error ? e.message : "Error al guardar";
console.error("Error saving:", e);
} finally {
loading = false;
}
}
function resetForm() {
// Limpiar formulario
formData = {
broker_key: "",
name: "",
type: "",
address: "",
postal_code: "",
city: "",
state: "",
phone: "",
fax: "",
email: "",
country: "",
tax_id: "",
personal_id: "",
position: "",
license: "",
company: "",
contact: ""
};
error = null;
}
</script>
<div class="space-y-6">
<!-- Header -->
<div class="flex items-center gap-4">
<Button variant="ghost" size="icon" onclick={() => goto('/dashboard/customs_brokers')}>
<ArrowLeft class="h-4 w-4" />
</Button>
<div>
<h1 class="text-3xl font-bold tracking-tight">Nuevo Agente Aduanal</h1>
<p class="text-muted-foreground">
Completa los datos para crear un nuevo agente aduanal
</p>
</div>
</div>
<!-- Formulario -->
<Card.Root>
<Card.Header>
<Card.Title>Información del Agente Aduanal</Card.Title>
<Card.Description>
Todos los campos marcados con * son obligatorios
</Card.Description>
</Card.Header>
<Card.Content>
<form onsubmit={handleSubmit} class="space-y-6">
{#if error}
<div class="rounded-lg border border-destructive bg-destructive/10 p-3 text-sm text-destructive">
{error}
</div>
{/if}
<!-- Información básica -->
<div class="space-y-4">
<h3 class="text-sm font-semibold">Información Básica</h3>
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="broker_key">Clave *</Label>
<Input
id="broker_key"
bind:value={formData.broker_key}
placeholder="Ej: 12345"
maxlength={5}
required
disabled={loading}
/>
</div>
<div class="space-y-2">
<Label for="type">Tipo</Label>
<Input
id="type"
bind:value={formData.type}
placeholder="Tipo de agente"
maxlength={9}
disabled={loading}
/>
</div>
</div>
<div class="space-y-2">
<Label for="name">Nombre</Label>
<Input
id="name"
bind:value={formData.name}
placeholder="Nombre del agente aduanal"
maxlength={80}
disabled={loading}
/>
</div>
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="license">Patente</Label>
<Input
id="license"
bind:value={formData.license}
placeholder="Número de patente"
maxlength={4}
disabled={loading}
/>
</div>
<div class="space-y-2">
<Label for="company">Empresa</Label>
<Input
id="company"
bind:value={formData.company}
placeholder="Empresa del agente"
maxlength={200}
disabled={loading}
/>
</div>
</div>
</div>
<!-- Información de contacto -->
<div class="space-y-4">
<h3 class="text-sm font-semibold">Información de Contacto</h3>
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="phone">Teléfono</Label>
<Input
id="phone"
bind:value={formData.phone}
placeholder="Número telefónico"
maxlength={30}
disabled={loading}
/>
</div>
<div class="space-y-2">
<Label for="fax">Fax</Label>
<Input
id="fax"
bind:value={formData.fax}
placeholder="Número de fax"
maxlength={30}
disabled={loading}
/>
</div>
</div>
<div class="space-y-2">
<Label for="email">Email</Label>
<Input
id="email"
type="email"
bind:value={formData.email}
placeholder="correo@ejemplo.com"
maxlength={100}
disabled={loading}
/>
</div>
<div class="space-y-2">
<Label for="contact">Contacto</Label>
<Input
id="contact"
bind:value={formData.contact}
placeholder="Nombre del contacto"
maxlength={80}
disabled={loading}
/>
</div>
</div>
<!-- Dirección -->
<div class="space-y-4">
<h3 class="text-sm font-semibold">Dirección</h3>
<div class="space-y-2">
<Label for="address">Dirección</Label>
<Input
id="address"
bind:value={formData.address}
placeholder="Calle y número"
maxlength={1500}
disabled={loading}
/>
</div>
<div class="grid grid-cols-3 gap-4">
<div class="space-y-2">
<Label for="postal_code">Código Postal</Label>
<Input
id="postal_code"
bind:value={formData.postal_code}
placeholder="C.P."
maxlength={15}
disabled={loading}
/>
</div>
<div class="space-y-2">
<Label for="city">Ciudad</Label>
<Input
id="city"
bind:value={formData.city}
placeholder="Ciudad"
maxlength={30}
disabled={loading}
/>
</div>
<div class="space-y-2">
<Label for="state">Estado</Label>
<Input
id="state"
bind:value={formData.state}
placeholder="Estado"
maxlength={30}
disabled={loading}
/>
</div>
</div>
<div class="space-y-2">
<Label for="country">País</Label>
<Input
id="country"
bind:value={formData.country}
placeholder="País"
maxlength={3}
disabled={loading}
/>
</div>
</div>
<!-- Información fiscal -->
<div class="space-y-4">
<h3 class="text-sm font-semibold">Información Fiscal</h3>
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="tax_id">RFC</Label>
<Input
id="tax_id"
bind:value={formData.tax_id}
placeholder="RFC"
maxlength={30}
disabled={loading}
/>
</div>
<div class="space-y-2">
<Label for="personal_id">CURP</Label>
<Input
id="personal_id"
bind:value={formData.personal_id}
placeholder="CURP"
maxlength={20}
disabled={loading}
/>
</div>
</div>
<div class="space-y-2">
<Label for="position">Posición</Label>
<Input
id="position"
bind:value={formData.position}
placeholder="Cargo o posición"
maxlength={30}
disabled={loading}
/>
</div>
</div>
<div class="fixed bottom-0 right-0 left-0 md:left-64 py-2 px-4 border-t bg-background/95 backdrop-blur z-50 flex justify-end gap-4 shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.1)]">
<Button type="button" variant="outline" onclick={() => goto('/dashboard/customs_brokers')} disabled={loading}>
Cancelar
</Button>
<Button type="button" variant="outline" onclick={resetForm} disabled={loading}>
Limpiar
</Button>
<Button type="submit" disabled={loading}>
{#if loading}
<div class="flex items-center gap-2">
<div class="h-4 w-4 animate-spin rounded-full border-2 border-primary-foreground border-t-transparent"></div>
Guardando...
</div>
{:else}
Crear Agente Aduanal
{/if}
</Button>
</div>
</form>
</Card.Content>
</Card.Root>
</div>

View File

@@ -6,48 +6,44 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
const { accessToken } = getAuthTokens(cookies);
if (!accessToken) {
return { error: 'No authenticated', concepts: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
return { error: 'No authenticated', concepts: { items: [], total: 0, page: 1, page_size: 50, pages: 0 }, activeCompanyId: null };
}
try {
const page = Number(url.searchParams.get('page')) || 1;
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
const filters: Record<string, string> = {};
const code = url.searchParams.get('code');
const description = url.searchParams.get('description');
if (code) filters.code = code;
if (description) filters.description = description;
// Obtener company_id de la cookie o usar el primero disponible
const parentData = await parent();
const cookieCompanyId = cookies.get('active_company_id');
const companyId = cookieCompanyId
? parseInt(cookieCompanyId)
: parentData.companies?.[0]?.id;
const companyId = url.searchParams.get('company_id') || cookies.get('active_company_id');
if (!companyId) {
return {
error: 'No se encontró una compañía seleccionada',
concepts: { items: [], total: 0, page: 1, page_size: 50, pages: 0 }
};
return { error: 'No company selected', concepts: { items: [], total: 0, page: 1, page_size: 50, pages: 0 }, activeCompanyId: null };
}
const filters: Record<string, string> = {};
const brokerKey = url.searchParams.get('broker_key');
const concept = url.searchParams.get('concept');
if (brokerKey) filters.broker_key = brokerKey;
if (concept) filters.concept = concept;
const queryParams = new URLSearchParams({
company_id: companyId.toString(),
page: page.toString(),
page_size: pageSize.toString(),
company_id: companyId,
...filters
});
const response = await authenticatedFetch(`v1/a76/customs-broker-concepts?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
if (!response.ok) {
return { error: 'Failed to load', concepts: { items: [], total: 0, page, page_size: pageSize, pages: 0 } };
return { error: 'Failed to load', concepts: { items: [], total: 0, page, page_size: pageSize, pages: 0 }, activeCompanyId: companyId };
}
return { concepts: await response.json() };
const data = await response.json();
// Calculate pages if not provided by API
const pages = data.pages || Math.ceil(data.total / pageSize);
return { concepts: { ...data, pages }, activeCompanyId: parseInt(companyId) };
} catch (error) {
console.error('Error loading customs broker concepts:', error);
return { error: 'Error loading', concepts: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
return { error: 'Error loading', concepts: { items: [], total: 0, page: 1, page_size: 50, pages: 0 }, activeCompanyId: null };
}
};

View File

@@ -13,8 +13,8 @@
let dialogOpen = $state(false);
// Filtros
let searchCode = $state($page.url.searchParams.get('code') || '');
let searchDesc = $state($page.url.searchParams.get('description') || '');
let searchBrokerKey = $state($page.url.searchParams.get('broker_key') || '');
let searchConcept = $state($page.url.searchParams.get('concept') || '');
let timeout: ReturnType<typeof setTimeout>;
// Solo necesitamos una declaración de handleSuccess
@@ -31,11 +31,11 @@
clearTimeout(timeout);
timeout = setTimeout(() => {
const url = new URL($page.url);
if (searchCode) url.searchParams.set('code', searchCode);
else url.searchParams.delete('code');
if (searchBrokerKey) url.searchParams.set('broker_key', searchBrokerKey);
else url.searchParams.delete('broker_key');
if (searchDesc) url.searchParams.set('description', searchDesc);
else url.searchParams.delete('description');
if (searchConcept) url.searchParams.set('concept', searchConcept);
else url.searchParams.delete('concept');
url.searchParams.set('page', '1');
goto(url, { keepFocus: true, noScroll: true });
@@ -60,15 +60,15 @@
<div class="flex gap-4 items-end">
<div class="grid w-full max-w-sm items-center gap-1.5">
<Input
placeholder="Buscar por código..."
bind:value={searchCode}
placeholder="Buscar por clave de agente..."
bind:value={searchBrokerKey}
oninput={handleSearch}
/>
</div>
<div class="grid w-full max-w-sm items-center gap-1.5">
<Input
placeholder="Buscar por descripción..."
bind:value={searchDesc}
placeholder="Buscar por concepto..."
bind:value={searchConcept}
oninput={handleSearch}
/>
</div>
@@ -85,6 +85,7 @@
<CreateEditDialog
bind:open={dialogOpen}
companyId={data.activeCompanyId}
onSuccess={handleSuccess}
/>
</div>

View File

@@ -2,9 +2,9 @@
import { page } from '$app/stores';
import { goto } from '$app/navigation';
import { browser } from '$app/environment';
import DataTable from '$lib/components/dashboard/general_catalogs/electronic-notices/data-table.svelte';
import { createColumns } from '$lib/components/dashboard/general_catalogs/electronic-notices/columns';
import CreateEditDialog from '$lib/components/dashboard/general_catalogs/electronic-notices/create-edit-dialog.svelte';
import DataTable from '$lib/components/dashboard/general_catalogs/electronic_notices/data-table.svelte';
import { createColumns } from '$lib/components/dashboard/general_catalogs/electronic_notices/columns.js';
import CreateEditDialog from '$lib/components/dashboard/general_catalogs/electronic_notices/create-edit-dialog.svelte';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Plus } from 'lucide-svelte';

View File

@@ -2,9 +2,9 @@
import { page } from '$app/stores';
import { goto } from '$app/navigation';
import { browser } from '$app/environment';
import { createColumns } from '$lib/components/dashboard/exchange-rate/columns';
import CreateEditDialog from '$lib/components/dashboard/exchange-rate/create-edit-dialog.svelte';
import DataTable from '$lib/components/dashboard/exchange-rate/data-table.svelte';
import { createColumns } from '$lib/components/dashboard/exchange_rate/columns';
import CreateEditDialog from '$lib/components/dashboard/exchange_rate/create-edit-dialog.svelte';
import DataTable from '$lib/components/dashboard/exchange_rate/data-table.svelte';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Plus } from 'lucide-svelte';