diff --git a/backend/api/v1/modules/a76/general_catalogs/classification_concepts/routes.py b/backend/api/v1/modules/a76/general_catalogs/classification_concepts/routes.py index 401a5c24..68aa792a 100644 --- a/backend/api/v1/modules/a76/general_catalogs/classification_concepts/routes.py +++ b/backend/api/v1/modules/a76/general_catalogs/classification_concepts/routes.py @@ -11,10 +11,13 @@ router = TenantCRUDRoutes( tags=["a76.general_catalogs.classification_concepts"], resource_name="Classification Concept", enable_list=True, - enable_filters=True, +<<<<<<< HEAD list_permissions=["cat_classification.view"], get_permissions=["cat_classification.view"], create_permissions=["cat_classification.create"], update_permissions=["cat_classification.edit"], delete_permissions=["cat_classification.delete"], +======= + enable_filters=True, +>>>>>>> 88ab6b45df947b2091e7cd5fff7468284a0ceef6 ).router diff --git a/backend/api/v1/modules/a76/general_catalogs/doda/routes.py b/backend/api/v1/modules/a76/general_catalogs/doda/routes.py index 6e5cb50e..85bd05a5 100644 --- a/backend/api/v1/modules/a76/general_catalogs/doda/routes.py +++ b/backend/api/v1/modules/a76/general_catalogs/doda/routes.py @@ -155,6 +155,8 @@ async def export_doda_pedimentos( # Incluir rutas CRUD (contiene GET /{id}, POST /, PUT /{id}, DELETE /{id}). +# Se registra DESPUÉS de los endpoints literales para que /export, /alta-logs, +# /alta-status no sean capturados por el parámetro /{id}. _crud_router = TenantCRUDRoutes( service=DodaService, create_schema=DodaCreateDTO, @@ -167,8 +169,13 @@ _crud_router = TenantCRUDRoutes( enable_list=True, enable_filters=True, ).router +<<<<<<< HEAD +router = crud_router + +======= router.include_router(_crud_router) +>>>>>>> 103363c6315f9235252cc443c8e0093ba401c6c2 @router.get( @@ -192,6 +199,8 @@ async def get_doda_detail( return DodaDetailResponseDTO.model_validate(doda) +<<<<<<< HEAD +======= @router.get( "/{doda_id}/print", summary="Imprimir DODA (PDF)", @@ -273,6 +282,7 @@ async def print_doda_pdf( # ============ CONTAINERS ENDPOINTS ============ +>>>>>>> 103363c6315f9235252cc443c8e0093ba401c6c2 @router.get( "/{doda_id}/containers", response_model=List[DodaContainerResponseDTO], @@ -331,6 +341,8 @@ async def update_container( return DodaContainerResponseDTO.model_validate(container) +<<<<<<< HEAD +======= @router.delete( "/{doda_id}/containers/{container_line}", status_code=status.HTTP_204_NO_CONTENT, @@ -406,6 +418,7 @@ async def delete_container_seal( # ============ AMERICAN PEDIMENTOS ENDPOINTS ============ +>>>>>>> 103363c6315f9235252cc443c8e0093ba401c6c2 @router.get( "/{doda_id}/american-pedimentos", response_model=List[DodaAmericanPedimentoResponseDTO], @@ -441,6 +454,8 @@ async def add_american_pedimento( return DodaAmericanPedimentoResponseDTO.model_validate(pedimento) +<<<<<<< HEAD +======= # ============ AMERICAN PEDIMENTOS (DELETE) ============ @router.delete( "/{doda_id}/american-pedimentos/{pedimento_line}", @@ -458,6 +473,7 @@ async def delete_american_pedimento( # ============ PEDIMENTOS ENDPOINTS ============ +>>>>>>> 103363c6315f9235252cc443c8e0093ba401c6c2 @router.get( "/{doda_id}/pedimentos", response_model=List[DodaPedimentoResponseDTO], diff --git a/backend/api/v1/modules/public/reference_data/containers/routes.py b/backend/api/v1/modules/public/reference_data/containers/routes.py index 6f0bbe4b..6e38729d 100644 --- a/backend/api/v1/modules/public/reference_data/containers/routes.py +++ b/backend/api/v1/modules/public/reference_data/containers/routes.py @@ -16,8 +16,11 @@ router = APIRouter(prefix="/containers") async def list_containers( page: int = Query(1, ge=1, description="Número de página"), page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"), +<<<<<<< HEAD company_id: int = Query(..., description="ID de la empresa"), +======= search: str = Query(None, description="Término de búsqueda"), +>>>>>>> 88ab6b45df947b2091e7cd5fff7468284a0ceef6 db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user), ): diff --git a/frontend/src/lib/components/dashboard/general_catalogs/doda/columns.ts b/frontend/src/lib/components/dashboard/general_catalogs/doda/columns.ts index 438ddb4a..524faca3 100644 --- a/frontend/src/lib/components/dashboard/general_catalogs/doda/columns.ts +++ b/frontend/src/lib/components/dashboard/general_catalogs/doda/columns.ts @@ -2,9 +2,12 @@ import type { ColumnDef } from '@tanstack/table-core'; import type { Doda } from '$lib/api/dashboard/a76/general_catalogs/doda'; import { renderComponent, renderSnippet } from '$lib/components/ui/data-table'; import { createRawSnippet } from 'svelte'; +<<<<<<< HEAD import DataTableActions from './data-table-actions.svelte'; +======= import { getLocale } from '$lib/paraglide/runtime'; import { dodaFormT, type DodaFormKey } from '$lib/i18n/doda-form-strings'; +>>>>>>> 103363c6315f9235252cc443c8e0093ba401c6c2 /** * doda_date se almacena como Integer con formato YYYYMMDD (ej. 20180409). @@ -31,6 +34,12 @@ function formatDodaDate(val?: number | string | null): string { } } +<<<<<<< HEAD +export function createColumns( + onSuccess?: () => void, + { canEdit = true, canDelete = true }: { canEdit?: boolean; canDelete?: boolean } = {} +): ColumnDef[] { +======= const STATUS_CLASSES: Record = { GENERADO: 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300', 'EN PROCESO':'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300', @@ -39,13 +48,9 @@ const STATUS_CLASSES: Record = { ELIMINADO: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300', }; -export function createColumns( - onSuccess?: () => void, - { canEdit = true, canDelete = true }: { canEdit?: boolean; canDelete?: boolean } = {} -): ColumnDef[] { - const loc = getLocale() as 'en' | 'es'; +export function createColumns(loc: 'en' | 'es'): ColumnDef[] { const t = (k: DodaFormKey) => dodaFormT(loc, k); - +>>>>>>> 103363c6315f9235252cc443c8e0093ba401c6c2 return [ { accessorKey: 'id', diff --git a/frontend/src/lib/components/dashboard/general_catalogs/doda/data-table-actions.svelte b/frontend/src/lib/components/dashboard/general_catalogs/doda/data-table-actions.svelte index ac05c077..1564e641 100644 --- a/frontend/src/lib/components/dashboard/general_catalogs/doda/data-table-actions.svelte +++ b/frontend/src/lib/components/dashboard/general_catalogs/doda/data-table-actions.svelte @@ -59,8 +59,9 @@ Acciones +<<<<<<< HEAD {#if canEdit} - goto(`/dashboard/general_catalogs/doda?doda_id=${item.id}`)}> + goto(`/dashboard/general_catalogs/doda/edit/${item.id}`)}> Editar @@ -75,5 +76,20 @@ Eliminar {/if} +======= + goto(`/dashboard/general_catalogs/doda?doda_id=${item.id}`)}> + + Editar + + + {#if loading} + + {:else} + + {/if} + Eliminar + +>>>>>>> 103363c6315f9235252cc443c8e0093ba401c6c2 + diff --git a/frontend/src/lib/components/sidebar/modules.ts b/frontend/src/lib/components/sidebar/modules.ts index c2551d64..2bfb3f13 100644 --- a/frontend/src/lib/components/sidebar/modules.ts +++ b/frontend/src/lib/components/sidebar/modules.ts @@ -13,7 +13,6 @@ import { Hash, LayoutDashboard, Package, - PackageCheck, Settings2, Shield, Ship, @@ -291,6 +290,10 @@ export function getSidebarData(): SidebarData { title: m["sidebar.general_catalogs.customs_warehouses"](), url: "/dashboard/reference_data/customs_warehouses", }, + { + title: m["sidebar.general_catalogs.doda"](), + url: "/dashboard/general_catalogs/doda", + }, { title: m["sidebar.general_catalogs.prevalidators"](), url: "/dashboard/general_catalogs/prevalidators", @@ -448,8 +451,12 @@ export function getSidebarData(): SidebarData { items: [ { title: m["sidebar.export_invoices.exportation"](), - url: "/dashboard/invoices?operation_type=exp&invoice_type=EXDEF", +<<<<<<< HEAD + url: "/dashboard/invoices?operation_type=exp", permission: "invoice.exp.view" +======= + url: "/dashboard/invoices?operation_type=exp&invoice_type=EXDEF", +>>>>>>> 88ab6b45df947b2091e7cd5fff7468284a0ceef6 }, { title: m["sidebar.export_invoices.repair"](), @@ -530,19 +537,10 @@ export function getSidebarData(): SidebarData { ], }, { - title: m["sidebar.despacho.title"](), - url: "#", - icon: PackageCheck, - items: [ - { - title: m["sidebar.despacho.digitalizacion"](), - url: "/dashboard/digitalizacion", - }, - { - title: m["sidebar.despacho.doda"](), - url: "/dashboard/despacho/doda", - }, - ], + title: m["sidebar.digitalizacion.title"](), + url: "/dashboard/digitalizacion", + icon: FolderArchive, + items: [], }, { title: m["sidebar.reference_data.configuracion"](), diff --git a/frontend/src/routes/dashboard/clients_and_providers/+page.svelte b/frontend/src/routes/dashboard/clients_and_providers/+page.svelte index bbab6e7b..8f71723b 100644 --- a/frontend/src/routes/dashboard/clients_and_providers/+page.svelte +++ b/frontend/src/routes/dashboard/clients_and_providers/+page.svelte @@ -12,72 +12,113 @@ import { Input } from '$lib/components/ui/input'; import { Label } from '$lib/components/ui/label'; import * as Select from '$lib/components/ui/select'; - import { Plus, RefreshCw, Users, Building2, MapPin, Mail, Phone, Hash, Pencil, Trash2 } from 'lucide-svelte'; + import { Plus, RefreshCw, Users, Building2, MapPin, Mail, Phone, Hash } from 'lucide-svelte'; import { toast } from 'svelte-sonner'; import { companyStore } from '$lib/stores/company.svelte'; - import { currentUser, userHasPermission } from '$lib/auth'; + import type { ApiError } from '$lib/utils/error-handler'; +<<<<<<< HEAD + import { authStore, userHasPermission } from '$lib/auth'; import ErrorState from '$lib/components/dashboard/common/error-state.svelte'; + +======= import DataTable from '$lib/components/dashboard/clients_and_providers/data-table.svelte'; import { createColumns } from '$lib/components/dashboard/clients_and_providers/columns'; - import { useShortcuts } from '$lib/hooks/use-shortcuts'; - import { obtenerAtajosListaSocios } from '$lib/config/shortcuts/dashboard/clients_and_providers/list'; +>>>>>>> 88ab6b45df947b2091e7cd5fff7468284a0ceef6 - // Props + // Los datos iniciales vienen del servidor let { data }: { data: any } = $props(); // State let items = $state(data.items || []); let selectedItem = $state(null); let isLoading = $state(false); - let error = $state(data.error || null); - let status = $state(200); - // Filtros y Paginación + // Server-side filtering/pagination parameters let currentPage = $state(data.page || 1); let pageSize = $state(50); let totalItems = $state(data.total || 0); let hasMore = $derived(items.length < totalItems); + + // Filter state let searchName = $state(''); let searchRfc = $state(''); let searchType = $state($page.url.searchParams.get('type') || 'both'); let filterDebounce: ReturnType | null = null; - // Permisos - const canView = $derived(userHasPermission($currentUser, 'partners_mgmt.view')); - const canCreate = $derived(userHasPermission($currentUser, 'partners_mgmt.create')); - const canEdit = $derived(userHasPermission($currentUser, 'partners_mgmt.edit')); - const canDelete = $derived(userHasPermission($currentUser, 'partners_mgmt.delete')); + // Estado para el diálogo de crear + let showCreateDialog = $state(false); + let error = $state(data.error || null); - const isError = $derived(!canView || status >= 400 || error); + // Permisos + const canView = $derived(userHasPermission($authStore.user, 'partners_mgmt.view')); + const canCreate = $derived(userHasPermission($authStore.user, 'partners_mgmt.create')); + const canEdit = $derived(userHasPermission($authStore.user, 'partners_mgmt.edit')); + const canDelete = $derived(userHasPermission($authStore.user, 'partners_mgmt.delete')); + + // --- Lifecycle --- onMount(() => { - if (browser && companyStore.activeCompany) { - loadItems(1); + if (browser) { + const getCookie = (name: string): string | null => { + const value = `; ${document.cookie}`; + const parts = value.split(`; ${name}=`); + if (parts.length === 2) return parts.pop()?.split(';').shift() || null; + return null; + }; + + const cookieToken = getCookie('access_token'); + const localToken = localStorage.getItem('access_token'); + + if (cookieToken && cookieToken !== localToken) + localStorage.setItem('access_token', cookieToken); + + // Listen for company changes + const handleCompanyChange = () => loadItems(1); // Reload from page 1 + window.addEventListener('companyChanged', handleCompanyChange); + return () => window.removeEventListener('companyChanged', handleCompanyChange); } }); + // --- Actions --- + async function loadItems(pageToLoad = 1, append = false) { const companyId = companyStore.activeCompany?.id; - if (!companyId || !canView) return; + if (!companyId) return; isLoading = true; try { const filters: any = {}; if (searchType !== 'both') filters.type = searchType; - if (searchName.trim()) filters.name = searchName.trim(); - if (searchRfc.trim()) filters.rfc = searchRfc.trim(); +<<<<<<< HEAD + if (searchName) filters.name = searchName; + if (searchRfc) filters.rfc = searchRfc; +======= + const trimmedName = searchName.trim(); + const trimmedRfc = searchRfc.trim(); + if (trimmedName) filters.name = trimmedName; + if (trimmedRfc) filters.rfc = trimmedRfc; +>>>>>>> 88ab6b45df947b2091e7cd5fff7468284a0ceef6 const response = await clientsProvidersApi.list(companyId, pageToLoad, pageSize, filters); if (response.error) { - error = response.error; - status = response.status || 500; + if (response.status === 401 || response.status === 403) { + toast.error('Sesión expirada. Recargando...'); + setTimeout(() => window.location.reload(), 2000); + return; + } toast.error(response.error); - } else if (response.data) { - items = append ? [...items, ...response.data.items] : response.data.items; + return; + } + + if (response.data) { + if (append) { + items = [...items, ...response.data.items]; + } else { + items = response.data.items; + } totalItems = response.data.total; currentPage = response.data.page; - status = 200; } } catch (e) { console.error('Error loading items:', e); @@ -96,20 +137,32 @@ searchType = value; } - function taxIdOrRfcLabel(cp: ClientProvider | null): string { - if (!cp) return 'RFC'; - return (cp.type_nat_foreign || 'N').toUpperCase() === 'E' ? 'TAX-ID' : 'RFC'; + function selectItem(item: ClientProvider) { + selectedItem = item; } + function taxIdOrRfcLabel(cp: ClientProvider | null): string { + if (!cp) return 'RFC'; + const proc = (cp.type_nat_foreign || 'N').toUpperCase(); + return proc === 'E' ? 'TAX-ID' : 'RFC'; + } + + function handleEdit() { + if (selectedItem) goto(`/dashboard/clients_and_providers/edit/${selectedItem.id}`); + } + + import { useShortcuts } from '$lib/hooks/use-shortcuts'; + import { obtenerAtajosListaSocios } from '$lib/config/shortcuts/dashboard/clients_and_providers/list'; + async function handleDelete() { - if (!selectedItem || !companyStore.activeCompany?.id || !canDelete) return; - if (!confirm(`¿Estás seguro de eliminar a ${selectedItem.name}?`)) return; + if (!selectedItem || !companyStore.activeCompany?.id) return; + if (!confirm('¿Estás seguro de eliminar este registro?')) return; try { await clientsProvidersApi.delete(selectedItem.id, companyStore.activeCompany.id); toast.success('Registro eliminado'); selectedItem = null; - loadItems(1); + loadItems(currentPage); } catch (e) { toast.error('Error al eliminar'); } @@ -121,16 +174,19 @@ filtrarTodos: () => handleTypeChange('both'), filtrarClientes: () => handleTypeChange('client'), filtrarProveedores: () => handleTypeChange('provider'), - nuevo: () => canCreate && goto('/dashboard/clients_and_providers/edit'), + nuevo: () => goto('/dashboard/clients_and_providers/edit'), recargar: () => loadItems(1) }) ); $effect(() => { - if (!browser || !companyStore.activeCompany?.id) return; - - // Trigger on filter changes - void searchName; void searchRfc; void searchType; + const companyId = companyStore.activeCompany?.id; + if (!browser || !companyId) return; + + searchName; + searchRfc; + searchType; + pageSize; if (filterDebounce) clearTimeout(filterDebounce); filterDebounce = setTimeout(() => { @@ -138,53 +194,117 @@ loadItems(1); }, 350); - return () => { if (filterDebounce) clearTimeout(filterDebounce); }; + return () => { + if (filterDebounce) clearTimeout(filterDebounce); + }; }); -
-
-
-

Clientes y Proveedores

-

Administración del catálogo de socios comerciales

-
-
- - {#if canCreate && !isError} - - {/if} -
-
- - {#if isError} +<<<<<<< HEAD +
+ {#if !canView} loadItems(1)} + status={403} + error="No tienes permiso para ver esta sección: partners_mgmt.view" + onRetry={() => window.location.reload()} /> {:else} -
- -
-
+
+
+

Socio Comercial

+

Administración de clientes y proveedores

+
+
+ + {#if canCreate} + + {/if} + {#if canEdit} + + {/if} + {#if canDelete} + +======= +
+ +
+

CLIENTES Y PROVEEDORES

+

+ Gestiona el catálogo de clientes y proveedores de tu empresa +

+
+ + + {#if error} + + + Error + + {typeof error === 'string' ? error : error.detail} + + + + {/if} + +
+ +
+ +
+
+
+

Filtros

+ Busque por nombre, RFC/TAX-ID o tipo +
- - + +
- - + +
- + - {searchType === 'both' ? 'Todos' : searchType === 'client' ? 'Clientes' : 'Proveedores'} + {searchType === 'both' + ? 'Todos' + : searchType === 'client' + ? 'Clientes' + : 'Proveedores'} Todos @@ -195,128 +315,436 @@
+
-
-
-

{totalItems} REGISTROS ENCONTRADOS

+ +
+
+

Listado

+
+ + {totalItems} registros + +
-
- loadItems(1))} - loading={isLoading} - {hasMore} - {loadMore} - onRowClick={(row) => (selectedItem = row as ClientProvider)} - selectedId={selectedItem?.id} - /> +
+ +
+ loadItems(1))} + loading={isLoading} + {hasMore} + {loadMore} + onRowClick={(row) => selectItem(row as ClientProvider)} + selectedId={selectedItem?.id} + /> +
+
+
+ + +
+
+

+ Detalles del Registro +

+

+ {selectedItem?.name || '---'} +

+
+ {taxIdOrRfcLabel(selectedItem)}: + {selectedItem?.rfc || ''} +
+
+ +
+ {#if selectedItem} +
+
+ +

{selectedItem.client_or_provider}

+
+ + {#if selectedItem.address} +
+ +
+

+ {selectedItem.address.streets || ''} + {selectedItem.address.exterior_number || ''} + {selectedItem.address.interior_number + ? 'Int ' + selectedItem.address.interior_number + : ''} +

+

{selectedItem.address.neighborhood || ''}

+

{selectedItem.address.city || ''}, {selectedItem.address.state || ''}

+

+ {selectedItem.address.postal_code || ''}, {selectedItem.address.country || ''} +

+
+
+ +
+ + {#if selectedItem.address.email} +
+ + {selectedItem.address.email} +
+ {/if} + {#if selectedItem.address.phone} +
+ + {selectedItem.address.phone} +
+ {/if} +
+ {:else} +
+

Sin dirección registrada

+
+ {/if} + + {#if selectedItem.programs} +
+ +
+
+ Programa + {selectedItem.programs.program || '-'} +
+
+ Número + {selectedItem.programs.program_number || '-'} +
+
+
+ {/if} +
+ {:else} +
+ +

Selecciona un registro

+
+>>>>>>> 88ab6b45df947b2091e7cd5fff7468284a0ceef6 + {/if} +
+
+ + {#if error} +
+ {typeof error === 'string' ? error : error.detail} +
+ {/if} + +
+ +
+ +
+
+
+

Filtros

+ Busque por nombre, RFC/TAX-ID o tipo +
+
+
+ + e.key === 'Enter' && handleSearch()} + /> +
+
+ + e.key === 'Enter' && handleSearch()} + /> +
+
+ + + + {searchType === 'both' + ? 'Todos' + : searchType === 'client' + ? 'Clientes' + : 'Proveedores'} + + + Todos + Clientes + Proveedores + + +
+
+ +
+
+
+
+ + +
+
+

Listado

+
+ + {totalItems} registros + + +
+
+ +
+ + + + + + + + + + + + {#if isLoading} + + {:else if items.length === 0} + + {:else} + {#each items as item (item.id)} + selectItem(item)} + > + + + + + + + {/each} + {/if} + +
#RFC / TAX-IDNombreTipoEstatus
Cargando...
No se encontraron registros
{item.id}{item.rfc}{item.name} + {#if item.client_or_provider === 'client'} + Cliente + {:else if item.client_or_provider === 'provider'} + Proveedor + {:else} + Ambos + {/if} + + + {item.is_active ? 'Activo' : 'Inactivo'} + +
+
+ +
+ + + Página {currentPage} de {Math.ceil(totalItems / pageSize)} + +
- -
-
-

Detalles del Socio

-

+ +
+
+

+ Detalles del Registro +

+

{selectedItem?.name || '---'}

-
+
{taxIdOrRfcLabel(selectedItem)}: - {selectedItem?.rfc || ''} + {selectedItem?.rfc || ''}
+ + {#if selectedItem} + + {/if}
-
+
{#if selectedItem} -
-
-
\ No newline at end of file diff --git a/frontend/src/routes/dashboard/general_catalogs/doda/+page.server.ts b/frontend/src/routes/dashboard/general_catalogs/doda/+page.server.ts index e0aba56c..2b36895e 100644 --- a/frontend/src/routes/dashboard/general_catalogs/doda/+page.server.ts +++ b/frontend/src/routes/dashboard/general_catalogs/doda/+page.server.ts @@ -52,6 +52,7 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => { const response = await authenticatedFetch(`v1/a76/doda?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch); if (!response.ok) { +<<<<<<< HEAD let errorMsg = 'Failed to load'; try { const errorData = await response.json(); @@ -62,19 +63,33 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => { return { error: errorMsg, status: response.status, - dodas: { items: [], total: 0, page, page_size: pageSize, pages: 0 }, - defaultLastUser + dodas: { items: [], total: 0, page, page_size: pageSize, pages: 0 } }; } - return { dodas: await response.json(), status: 200, defaultLastUser }; + return { dodas: await response.json(), status: 200 }; } catch (error: any) { console.error('Error loading DODAs:', error); return { error: error.message || 'Error loading', status: error.status || 500, + dodas: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } +======= + return { + error: 'Failed to load', + dodas: { items: [], total: 0, page, page_size: pageSize, pages: 0 }, + defaultLastUser + }; + } + + return { dodas: await response.json(), defaultLastUser }; + } catch (error) { + console.error('Error loading DODAs:', error); + return { + error: 'Error loading', dodas: { items: [], total: 0, page: 1, page_size: 50, pages: 0 }, defaultLastUser +>>>>>>> 103363c6315f9235252cc443c8e0093ba401c6c2 }; } }; diff --git a/frontend/src/routes/dashboard/general_catalogs/doda/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/doda/+page.svelte index ab9f19c2..ef86f87b 100644 --- a/frontend/src/routes/dashboard/general_catalogs/doda/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/doda/+page.svelte @@ -1,22 +1,22 @@
@@ -164,85 +164,75 @@ const columns = $derived(createColumns(handleSuccess, {{ canEdit, canDelete }}))

Catálogo de DODA

- -{{#if !isError && canCreate}} - -{{/if}} +{/if}
-{{#if isError}} +{#if isError} -{{else}} +{:else}
Listado
- - + +
(selectedIds = ids)}} -onRowClick={{(row) => selectedIds = selectedIds.includes(row.id) ? [] : [row.id]}} -onRowDoubleClick={{(row) => {{ if(canEdit) {{ editingItem = row; dialogOpen = true; }} }}}} +data={allItems} {columns} {loading} {hasMore} {loadMore} +{selectedIds} onSelectedIdsChange={(ids) => (selectedIds = ids)} +onRowClick={(row) => selectedIds = selectedIds.includes(row.id) ? [] : [row.id]} +onRowDoubleClick={(row) => { if(canEdit) { editingItem = row; dialogOpen = true; } }} />
-Mostrando {{allItems.length}} de {{totalItems}} registros +Mostrando {allItems.length} de {totalItems} registros
-<-- 1. Primero borramos las relaciones de usuarios con tenants -DELETE FROM user_tenant; --- 2. Luego borramos las compañías (que dependen de los tenants) -DELETE FROM company; - --- 3. Finalmente borramos los tenants (inquilinos) -DELETE FROM tenant; - --- (Opcional) Si tienes una tabla de usuarios locales, también límpiala para evitar duplicados -DELETE FROM Footer Actions -->
-{{#if canEdit}} - -{{/if}} -{{#if canDelete}} - -{{/if}} +{/if}
-{{/if}} +{/if} - +
\ No newline at end of file diff --git a/frontend/src/routes/dashboard/general_catalogs/drivers/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/drivers/+page.svelte index 5e1286f1..f5caebe1 100644 --- a/frontend/src/routes/dashboard/general_catalogs/drivers/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/drivers/+page.svelte @@ -1,22 +1,22 @@
@@ -164,85 +164,75 @@ const columns = $derived(createColumns(handleSuccess, {{ canEdit, canDelete }}))

Catálogo de Choferes

- -{{#if !isError && canCreate}} - -{{/if}} +{/if}
-{{#if isError}} +{#if isError} -{{else}} +{:else}
Listado
- - + +
(selectedIds = ids)}} -onRowClick={{(row) => selectedIds = selectedIds.includes(row.id) ? [] : [row.id]}} -onRowDoubleClick={{(row) => {{ if(canEdit) {{ editingItem = row; dialogOpen = true; }} }}}} +data={allItems} {columns} {loading} {hasMore} {loadMore} +{selectedIds} onSelectedIdsChange={(ids) => (selectedIds = ids)} +onRowClick={(row) => selectedIds = selectedIds.includes(row.id) ? [] : [row.id]} +onRowDoubleClick={(row) => { if(canEdit) { editingItem = row; dialogOpen = true; } }} />
-Mostrando {{allItems.length}} de {{totalItems}} registros +Mostrando {allItems.length} de {totalItems} registros
-<-- 1. Primero borramos las relaciones de usuarios con tenants -DELETE FROM user_tenant; --- 2. Luego borramos las compañías (que dependen de los tenants) -DELETE FROM company; - --- 3. Finalmente borramos los tenants (inquilinos) -DELETE FROM tenant; - --- (Opcional) Si tienes una tabla de usuarios locales, también límpiala para evitar duplicados -DELETE FROM Footer Actions -->
-{{#if canEdit}} - -{{/if}} -{{#if canDelete}} - -{{/if}} +{/if}
-{{/if}} +{/if} - +
\ No newline at end of file diff --git a/frontend/src/routes/dashboard/general_catalogs/electronic_notices/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/electronic_notices/+page.svelte index cdce2225..54bf9241 100644 --- a/frontend/src/routes/dashboard/general_catalogs/electronic_notices/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/electronic_notices/+page.svelte @@ -1,22 +1,22 @@
@@ -163,85 +163,75 @@ const columns = $derived(createColumns(handleSuccess, {{ canEdit, canDelete }}))

Catálogo de Avisos Electrónicos del sistema

- -{{#if !isError && canCreate}} - -{{/if}} +{/if}
-{{#if isError}} +{#if isError} -{{else}} +{:else}
Listado de Avisos Electrónicos
- - + +
(selectedIds = ids)}} -onRowClick={{(row) => selectedIds = selectedIds.includes(row.id) ? [] : [row.id]}} -onRowDoubleClick={{(row) => {{ if(canEdit) {{ editingItem = row; createDialogOpen = true; }} }}}} +data={allItems} {columns} {loading} {hasMore} {loadMore} +{selectedIds} onSelectedIdsChange={(ids) => (selectedIds = ids)} +onRowClick={(row) => selectedIds = selectedIds.includes(row.id) ? [] : [row.id]} +onRowDoubleClick={(row) => { if(canEdit) { editingItem = row; createDialogOpen = true; } }} />
-Mostrando {{allItems.length}} de {{totalItems}} registros +Mostrando {allItems.length} de {totalItems} registros
-<-- 1. Primero borramos las relaciones de usuarios con tenants -DELETE FROM user_tenant; --- 2. Luego borramos las compañías (que dependen de los tenants) -DELETE FROM company; - --- 3. Finalmente borramos los tenants (inquilinos) -DELETE FROM tenant; - --- (Opcional) Si tienes una tabla de usuarios locales, también límpiala para evitar duplicados -DELETE FROM Footer Actions -->
-{{#if canEdit}} - -{{/if}} -{{#if canDelete}} - -{{/if}} +{/if}
-{{/if}} +{/if} - +

\ No newline at end of file diff --git a/frontend/src/routes/dashboard/general_catalogs/equivalencies/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/equivalencies/+page.svelte index e6a0a2e3..a3f4355b 100644 --- a/frontend/src/routes/dashboard/general_catalogs/equivalencies/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/equivalencies/+page.svelte @@ -2,61 +2,52 @@ import { page } from '$app/stores'; import { goto } from '$app/navigation'; import { browser } from '$app/environment'; - import { RefreshCw, Pencil, Plus, Trash2 } from 'lucide-svelte'; import type { Equivalency } from '$lib/api/dashboard/a76/general_catalogs/equivalencies'; import { createCatalogColumns } from '$lib/components/dashboard/general_catalogs/equivalencies/catalog-columns'; import InfiniteDataTable from '$lib/components/dashboard/common/infinite-data-table.svelte'; import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; + import { Pencil, Plus, Trash2 } from 'lucide-svelte'; import { useShortcuts } from '$lib/hooks/use-shortcuts'; import { obtenerAtajosListaEquivalencias } from '$lib/config/shortcuts/dashboard/general_catalogs/equivalencies/list'; import DataEquivalenciesDialog from '$lib/components/dashboard/general_catalogs/equivalencies/data-equivalencies-dialog.svelte'; +<<<<<<< HEAD + import { getEquivalencies } from '$lib/api/dashboard/a76/general_catalogs/equivalencies'; +======= + import type { PageData } from './$types'; import { getEquivalencies, deleteEquivalency } from '$lib/api/dashboard/a76/general_catalogs/equivalencies'; +>>>>>>> 88ab6b45df947b2091e7cd5fff7468284a0ceef6 import { companyStore } from '$lib/stores/company.svelte'; import { currentUser, userHasPermission } from '$lib/auth'; import ErrorState from '$lib/components/dashboard/common/error-state.svelte'; let { data } = $props(); - - // Estados básicos let error = $state(data.error || null); let status = $state(data.status || 200); + let dataDialogOpen = $state(false); let selectedEquivalency = $state(null); let dataMode = $state<'create' | 'edit'>('edit'); + let selectedIds = $state<(string | number)[]>([]); + const singleSelected = $derived( + selectedIds.length === 1 ? allItems.find((item) => item.id === selectedIds[0]) ?? null : null + ); - // Estados de datos - let allItems = $state(data.equivalencies?.items || []); - let currentPage = $state(data.equivalencies?.page || 1); - let pageSize = $state(data.equivalencies?.page_size || 50); - let totalItems = $state(data.equivalencies?.total || 0); - let loading = $state(false); - - // Filtros - let searchFrom = $state($page.url.searchParams.get('from_unit_code') || ''); - let timeout: ReturnType; - - // Derivados + // Permisos const canView = $derived(userHasPermission($currentUser, 'cat_equivalencies.view')); const canCreate = $derived(userHasPermission($currentUser, 'cat_equivalencies.create')); const canEdit = $derived(userHasPermission($currentUser, 'cat_equivalencies.edit')); const canDelete = $derived(userHasPermission($currentUser, 'cat_equivalencies.delete')); + const isError = $derived(!canView || status >= 400 || error); - const hasMore = $derived(allItems.length < totalItems); - const singleSelected = $derived( - selectedIds.length === 1 - ? allItems.find((item) => String(item.id) === String(selectedIds[0])) ?? null - : null - ); // Atajos useShortcuts( 'Lista Equivalencias', obtenerAtajosListaEquivalencias({ manejarNuevo: () => { - if (!canCreate) return; selectedEquivalency = null; dataMode = 'create'; dataDialogOpen = true; @@ -65,6 +56,17 @@ }) ); + // Filtros + let searchFrom = $state($page.url.searchParams.get('from_unit_code') || ''); + let timeout: ReturnType; + + let allItems = $state(data.equivalencies?.items || []); + let currentPage = $state(data.equivalencies?.page || 1); + let pageSize = $state(data.equivalencies?.page_size || 50); + let totalItems = $state(data.equivalencies?.total || 0); + let loading = $state(false); + let hasMore = $derived(allItems.length < totalItems); + $effect(() => { if (data.equivalencies) { allItems = data.equivalencies.items || []; @@ -82,15 +84,20 @@ loading = true; error = null; try { - const response = await getEquivalencies(companyStore.activeCompany.id, 1, pageSize, { - from_unit_code: searchFrom || undefined - }); - if (response.error) throw new Error(response.error); - - if (response.data?.items) { - allItems = response.data.items; - currentPage = 1; - totalItems = response.data.total; + const filters: Record = {}; + if (searchFrom) filters.from_unit_code = searchFrom; + + const response = await getEquivalencies( + 1, + pageSize, + companyStore.activeCompany.id, + filters + ); + const payload = (response.data || response) as any; + if (payload?.items) { + allItems = payload.items; + currentPage = payload.page || 1; + totalItems = payload.total; } } catch (err) { error = 'Error aplicando filtros'; @@ -112,18 +119,20 @@ loading = true; error = null; try { + const filters: Record = {}; + if (searchFrom) filters.from_unit_code = searchFrom; + const response = await getEquivalencies( - companyStore.activeCompany.id, currentPage + 1, pageSize, - { from_unit_code: searchFrom || undefined } + companyStore.activeCompany.id, + filters ); - if (response.error) throw new Error(response.error); - - if (response.data?.items) { - allItems = [...allItems, ...response.data.items]; - currentPage += 1; - totalItems = response.data.total; + const payload = (response.data || response) as any; + if (payload?.items) { + allItems = [...allItems, ...payload.items]; + currentPage = payload.page || currentPage + 1; + totalItems = payload.total; } } catch (err) { error = 'Error cargando mas datos'; @@ -138,19 +147,20 @@ loading = true; error = null; try { - const response = await getEquivalencies(companyStore.activeCompany.id, 1, pageSize, { - from_unit_code: searchFrom || undefined - }); - if (response.error) throw new Error(response.error); - - if (response.data?.items) { - allItems = response.data.items; - currentPage = 1; - totalItems = response.data.total; + const filters: Record = {}; + if (searchFrom) filters.from_unit_code = searchFrom; + + const response = await getEquivalencies(1, pageSize, companyStore.activeCompany.id, filters); + const payload = (response.data || response) as any; + if (payload?.items) { + allItems = payload.items; + currentPage = payload.page || 1; + totalItems = payload.total; } - } catch (err) { - error = 'Error al recargar datos'; + } catch (err: any) { + error = err.message || 'Error al recargar datos'; console.error('Error reloading equivalencies:', err); + status = err.status || 500; } finally { loading = false; } @@ -169,23 +179,33 @@ } function handleRowDoubleClick(row: Equivalency) { - if (!canEdit) return; selectedEquivalency = row; dataMode = 'edit'; dataDialogOpen = true; } function handleEditSelected() { - if (!singleSelected || !canEdit) return; + if (!singleSelected) return; selectedEquivalency = singleSelected; dataMode = 'edit'; dataDialogOpen = true; } +<<<<<<< HEAD + const columns = $derived( + createCatalogColumns({ + onInsertItems: handleOpenInsertItems, + onEdit: handleOpenEditCatalog, + onSuccess: handleSuccess, + canEdit, + canDelete + }) + ); +======= async function handleDeleteSelected() { - if (!singleSelected || !companyStore.activeCompany || !canDelete) return; + if (!singleSelected || !companyStore.activeCompany) return; - if (!confirm(`¿Estás seguro de eliminar la equivalencia "${singleSelected.from_unit_code}"?`)) { + if (!confirm('¿Estás seguro de eliminar la equivalencia seleccionada?')) { return; } @@ -193,9 +213,7 @@ error = null; try { - const response = await deleteEquivalency(singleSelected.id, companyStore.activeCompany.id); - if (response.error) throw new Error(response.error); - + await deleteEquivalency(singleSelected.id, companyStore.activeCompany.id); selectedIds = []; await reloadData(); } catch (err) { @@ -205,43 +223,62 @@ loading = false; } } - - const columns = $derived(createCatalogColumns(handleSuccess, { canEdit, canDelete })); +>>>>>>> 88ab6b45df947b2091e7cd5fff7468284a0ceef6 -
+
+
+
+

Equivalencias

+

Gestión del catálogo de equivalencias de unidades de medida

+
+
+ + {#if !isError && canCreate} + + {/if} +
+<<<<<<< HEAD +======= +
+>>>>>>> 88ab6b45df947b2091e7cd5fff7468284a0ceef6 +
+ {#if isError} - window.location.reload()} /> {:else} -
-
-

Equivalencias

-

Catálogo de equivalencias del sistema

-
-
- - {#if canCreate} - - {/if} -
-
- {#if error} -
+
{error}
{/if} @@ -252,27 +289,17 @@ Listado de Equivalencias
-
- (selectedIds = ids)} - onRowClick={handleRowClick} - onRowDoubleClick={handleRowDoubleClick} - /> +
+
@@ -280,47 +307,53 @@
Mostrando {allItems.length} de {totalItems} registros
- - {/if} +<<<<<<< HEAD +======= + +
Listado de Equivalencias
+
(selectedIds = ids)} onRowClick={handleRowClick} onRowDoubleClick={handleRowDoubleClick} />
+
+ +
Mostrando {allItems.length} de {totalItems} registros
+ +
+ + + +>>>>>>> 88ab6b45df947b2091e7cd5fff7468284a0ceef6 handleSuccess()} - onEdit={(val) => handleSuccess()} + mode={dataMode} onSuccess={handleSuccess} />
diff --git a/frontend/src/routes/dashboard/general_catalogs/error_catalogs/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/error_catalogs/+page.svelte index 6f81bc5c..562e7539 100644 --- a/frontend/src/routes/dashboard/general_catalogs/error_catalogs/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/error_catalogs/+page.svelte @@ -1,22 +1,22 @@
@@ -163,85 +163,75 @@ const columns = $derived(createColumns(handleSuccess, {{ canEdit, canDelete }}))

Catálogo de Catálogos de Errores del sistema

- -{{#if !isError && canCreate}} - -{{/if}} +{/if}
-{{#if isError}} +{#if isError} -{{else}} +{:else}
Listado de Catálogos de Errores
- - + +
(selectedIds = ids)}} -onRowClick={{(row) => selectedIds = selectedIds.includes(row.id) ? [] : [row.id]}} -onRowDoubleClick={{(row) => {{ if(canEdit) {{ editingItem = row; createDialogOpen = true; }} }}}} +data={allItems} {columns} {loading} {hasMore} {loadMore} +{selectedIds} onSelectedIdsChange={(ids) => (selectedIds = ids)} +onRowClick={(row) => selectedIds = selectedIds.includes(row.id) ? [] : [row.id]} +onRowDoubleClick={(row) => { if(canEdit) { editingItem = row; createDialogOpen = true; } }} />
-Mostrando {{allItems.length}} de {{totalItems}} registros +Mostrando {allItems.length} de {totalItems} registros
-<-- 1. Primero borramos las relaciones de usuarios con tenants -DELETE FROM user_tenant; --- 2. Luego borramos las compañías (que dependen de los tenants) -DELETE FROM company; - --- 3. Finalmente borramos los tenants (inquilinos) -DELETE FROM tenant; - --- (Opcional) Si tienes una tabla de usuarios locales, también límpiala para evitar duplicados -DELETE FROM Footer Actions -->
-{{#if canEdit}} - -{{/if}} -{{#if canDelete}} - -{{/if}} +{/if}
-{{/if}} +{/if} - +
\ No newline at end of file diff --git a/frontend/src/routes/dashboard/general_catalogs/exchange-rate/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/exchange-rate/+page.svelte index e0e4ab1d..499baf93 100644 --- a/frontend/src/routes/dashboard/general_catalogs/exchange-rate/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/exchange-rate/+page.svelte @@ -2,64 +2,52 @@ import { page } from '$app/stores'; import { goto } from '$app/navigation'; import { browser } from '$app/environment'; - import { createColumns } from '$lib/components/dashboard/general_catalogs/exchange-rate/columns'; + import { createColumns } from '$lib/components/dashboard/exchange_rate/columns'; + import CreateEditDialog from '$lib/components/dashboard/exchange_rate/create-edit-dialog.svelte'; import InfiniteDataTable from '$lib/components/dashboard/common/infinite-data-table.svelte'; - import CreateEditDialog from '$lib/components/dashboard/general_catalogs/exchange-rate/create-edit-dialog.svelte'; import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; - import { Pencil, Plus, Trash2, RefreshCw } from 'lucide-svelte'; +<<<<<<< HEAD + import { Plus, RefreshCw, Search, Globe } from 'lucide-svelte'; +======= + import { Pencil, Plus, Trash2 } from 'lucide-svelte'; +>>>>>>> 88ab6b45df947b2091e7cd5fff7468284a0ceef6 import { useShortcuts } from '$lib/hooks/use-shortcuts'; - import { obtenerAtajosListaTC } from '$lib/config/shortcuts/dashboard/general_catalogs/exchange-rate/list'; - import { - getExchangeRates, - deleteExchangeRate, - type ExchangeRate - } from '$lib/api/dashboard/a76/general_catalogs/exchange-rates'; + import { obtenerAtajosListaTiposCambio } from '$lib/config/shortcuts/dashboard/general_catalogs/exchange_rate/list'; + import type { PageData } from './$types'; + import { getExchangeRates, deleteExchangeRate, type ExchangeRate } from '$lib/api/dashboard/a76/general_catalogs/exchange-rate'; import { companyStore } from '$lib/stores/company.svelte'; import { currentUser, userHasPermission } from '$lib/auth'; import ErrorState from '$lib/components/dashboard/common/error-state.svelte'; - import { toast } from 'svelte-sonner'; - let { data } = $props(); + let { data }: { data: PageData } = $props(); - // Estados básicos let createDialogOpen = $state(false); let editingItem = $state(null); let error = $state(data.error || null); +<<<<<<< HEAD let status = $state(data.status || 200); - let selectedIds = $state<(string | number)[]>([]); - // Estados de datos - let allItems = $state(data.exchange_rates?.items || []); - let currentPage = $state(data.exchange_rates?.page || 1); - let pageSize = $state(data.exchange_rates?.page_size || 50); - let totalItems = $state(data.exchange_rates?.total || 0); - let loading = $state(false); - - // Filtros - let searchCode = $state($page.url.searchParams.get('currency_code') || ''); - let timeout: ReturnType; - - // Derivados + // Permisos const canView = $derived(userHasPermission($currentUser, 'cat_exchange_rates.view')); const canCreate = $derived(userHasPermission($currentUser, 'cat_exchange_rates.create')); const canEdit = $derived(userHasPermission($currentUser, 'cat_exchange_rates.edit')); const canDelete = $derived(userHasPermission($currentUser, 'cat_exchange_rates.delete')); + const isError = $derived(!canView || status >= 400 || error); - const hasMore = $derived(allItems.length < totalItems); +======= + let selectedIds = $state<(string | number)[]>([]); const selectedItem = $derived( - selectedIds.length === 1 - ? allItems.find((item) => String(item.id) === String(selectedIds[0])) ?? null - : null + selectedIds.length === 1 ? allItems.find((item) => item.id === selectedIds[0]) ?? null : null ); +>>>>>>> 88ab6b45df947b2091e7cd5fff7468284a0ceef6 // Atajos useShortcuts( 'Lista Tipos de Cambio', - obtenerAtajosListaTC({ + obtenerAtajosListaTiposCambio({ manejarNuevo: () => { - if (!canCreate) return; editingItem = null; createDialogOpen = true; }, @@ -67,12 +55,24 @@ }) ); + // Filtros + let searchCurrency = $state($page.url.searchParams.get('local_currency') || ''); + let timeout: ReturnType; + + let allItems = $state(data.exchange_rates?.items || []); + let currentPage = $state(data.exchange_rates?.page || 1); + let pageSize = $state(data.exchange_rates?.page_size || 50); + let totalItems = $state(data.exchange_rates?.total || 0); + let loading = $state(false); + let hasMore = $derived(allItems.length < totalItems); + $effect(() => { if (data.exchange_rates) { - allItems = data.exchange_rates.items || []; - currentPage = data.exchange_rates.page || 1; - totalItems = data.exchange_rates.total || 0; - pageSize = data.exchange_rates.page_size || pageSize; + const payload = (data.exchange_rates as any).data || data.exchange_rates; + allItems = payload.items || []; + currentPage = payload.page || 1; + totalItems = payload.total || 0; + pageSize = payload.page_size || pageSize; } }); @@ -84,18 +84,16 @@ loading = true; error = null; try { - const response = await getExchangeRates( - companyStore.activeCompany.id, - 1, - pageSize, - searchCode || undefined - ); - if (response.error) throw new Error(response.error); - - if (response.data?.items) { - allItems = response.data.items; - currentPage = 1; - totalItems = response.data.total; + const response = await getExchangeRates(companyStore.activeCompany.id, { + local_currency: searchCurrency || undefined, + page: 1, + page_size: pageSize + }); + if (response.data) { + const payload = (response.data as any).data || response.data; + allItems = payload.items || []; + currentPage = payload.page || 1; + totalItems = payload.total || 0; } } catch (err) { error = 'Error aplicando filtros'; @@ -105,8 +103,8 @@ } const url = new URL($page.url); - if (searchCode) url.searchParams.set('currency_code', searchCode); - else url.searchParams.delete('currency_code'); + if (searchCurrency) url.searchParams.set('local_currency', searchCurrency); + else url.searchParams.delete('local_currency'); history.replaceState(history.state, '', url); }, 500); @@ -117,18 +115,15 @@ loading = true; error = null; try { - const response = await getExchangeRates( - companyStore.activeCompany.id, - currentPage + 1, - pageSize, - searchCode || undefined - ); - if (response.error) throw new Error(response.error); - + const response = await getExchangeRates(companyStore.activeCompany.id, { + local_currency: searchCurrency || undefined, + page: currentPage + 1, + page_size: pageSize + }); if (response.data?.items) { allItems = [...allItems, ...response.data.items]; currentPage += 1; - totalItems = response.data.total; + totalItems = response.data.total || 0; } } catch (err) { error = 'Error cargando mas datos'; @@ -143,18 +138,16 @@ loading = true; error = null; try { - const response = await getExchangeRates( - companyStore.activeCompany.id, - 1, - pageSize, - searchCode || undefined - ); - if (response.error) throw new Error(response.error); - - if (response.data?.items) { - allItems = response.data.items; - currentPage = 1; - totalItems = response.data.total; + const response = await getExchangeRates(companyStore.activeCompany.id, { + local_currency: searchCurrency || undefined, + page: 1, + page_size: pageSize + }); + if (response.data) { + const payload = (response.data as any).data || response.data; + allItems = payload.items; + currentPage = payload.page || 1; + totalItems = payload.total || 0; } } catch (err) { error = 'Error al recargar datos'; @@ -171,27 +164,33 @@ reloadData(); } +<<<<<<< HEAD + function fetchDOF() { + // Implementación pendiente + } + + const columns = $derived(createColumns(handleSuccess, { canEdit, canDelete })); +======= function handleRowClick(row: ExchangeRate) { const id = row.id; selectedIds = selectedIds.includes(id) ? [] : [id]; } function handleRowDoubleClick(row: ExchangeRate) { - if (!canEdit) return; editingItem = row; createDialogOpen = true; } function handleEditSelected() { - if (!selectedItem || !canEdit) return; + if (!selectedItem) return; editingItem = selectedItem; createDialogOpen = true; } async function handleDeleteSelected() { - if (!selectedItem || !companyStore.activeCompany || !canDelete) return; + if (!selectedItem || !companyStore.activeCompany) return; - if (!confirm(`¿Estás seguro de eliminar el tipo de cambio "${selectedItem.currency_code}"?`)) { + if (!confirm(`¿Estás seguro de eliminar este tipo de cambio?`)) { return; } @@ -199,8 +198,7 @@ error = null; try { - const response = await deleteExchangeRate(selectedItem.id, companyStore.activeCompany.id); - if (response.error) throw new Error(response.error); + await deleteExchangeRate(selectedItem.id, companyStore.activeCompany.id); selectedIds = []; await reloadData(); @@ -211,15 +209,48 @@ loading = false; } } - - async function handleConsultarDOF() { - toast.info('Consultando DOF - Próximamente'); - } - - const columns = $derived(createColumns(handleSuccess, { canEdit, canDelete })); +>>>>>>> 88ab6b45df947b2091e7cd5fff7468284a0ceef6
+
+
+

Tipos de Cambio

+

Gestión del catálogo de tipos de cambio oficiales y personalizados

+
+
+ + + {#if !isError && canCreate} + + {/if} +
+<<<<<<< HEAD +======= +
+ +
+>>>>>>> 88ab6b45df947b2091e7cd5fff7468284a0ceef6 +
+ {#if isError} {:else} -
-
-

Tipos de Cambio

-

Catálogo de tipos de cambio del sistema

-
-
- - - {#if canCreate} - - {/if} -
-
- {#if error}
{error}
{/if} - - -
- Listado de Tipos de Cambio -
- -
+ + +
+ Historial de Tipos de Cambio
- -
- +
+ (selectedIds = ids)} - onRowClick={handleRowClick} - onRowDoubleClick={handleRowDoubleClick} />
@@ -292,44 +288,67 @@
Mostrando {allItems.length} de {totalItems} registros
+ {/if} - -{{#if isError}} +{#if isError} -{{else}} +{:else}
Listado de Identificadores
- - + +
(selectedIds = ids)}} -onRowClick={{(row) => selectedIds = selectedIds.includes(row.id) ? [] : [row.id]}} -onRowDoubleClick={{(row) => {{ if(canEdit) {{ editingItem = row; createDialogOpen = true; }} }}}} +data={allItems} {columns} {loading} {hasMore} {loadMore} +{selectedIds} onSelectedIdsChange={(ids) => (selectedIds = ids)} +onRowClick={(row) => selectedIds = selectedIds.includes(row.id) ? [] : [row.id]} +onRowDoubleClick={(row) => { if(canEdit) { editingItem = row; createDialogOpen = true; } }} />
-Mostrando {{allItems.length}} de {{totalItems}} registros +Mostrando {allItems.length} de {totalItems} registros
-<-- 1. Primero borramos las relaciones de usuarios con tenants -DELETE FROM user_tenant; --- 2. Luego borramos las compañías (que dependen de los tenants) -DELETE FROM company; - --- 3. Finalmente borramos los tenants (inquilinos) -DELETE FROM tenant; - --- (Opcional) Si tienes una tabla de usuarios locales, también límpiala para evitar duplicados -DELETE FROM Footer Actions -->
-{{#if canEdit}} - -{{/if}} -{{#if canDelete}} - -{{/if}} +{/if}
-{{/if}} +{/if} - +
\ No newline at end of file diff --git a/frontend/src/routes/dashboard/general_catalogs/inpc/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/inpc/+page.svelte index 790a7511..876443a2 100644 --- a/frontend/src/routes/dashboard/general_catalogs/inpc/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/inpc/+page.svelte @@ -1,22 +1,22 @@
@@ -163,85 +163,75 @@ const columns = $derived(createColumns(handleSuccess, {{ canEdit, canDelete }}))

Catálogo de INPC del sistema

- -{{#if !isError && canCreate}} - -{{/if}} +{/if}
-{{#if isError}} +{#if isError} -{{else}} +{:else}
Listado de INPC
- - + +
(selectedIds = ids)}} -onRowClick={{(row) => selectedIds = selectedIds.includes(row.id) ? [] : [row.id]}} -onRowDoubleClick={{(row) => {{ if(canEdit) {{ editingItem = row; createDialogOpen = true; }} }}}} +data={allItems} {columns} {loading} {hasMore} {loadMore} +{selectedIds} onSelectedIdsChange={(ids) => (selectedIds = ids)} +onRowClick={(row) => selectedIds = selectedIds.includes(row.id) ? [] : [row.id]} +onRowDoubleClick={(row) => { if(canEdit) { editingItem = row; createDialogOpen = true; } }} />
-Mostrando {{allItems.length}} de {{totalItems}} registros +Mostrando {allItems.length} de {totalItems} registros
-<-- 1. Primero borramos las relaciones de usuarios con tenants -DELETE FROM user_tenant; --- 2. Luego borramos las compañías (que dependen de los tenants) -DELETE FROM company; - --- 3. Finalmente borramos los tenants (inquilinos) -DELETE FROM tenant; - --- (Opcional) Si tienes una tabla de usuarios locales, también límpiala para evitar duplicados -DELETE FROM Footer Actions -->
-{{#if canEdit}} - -{{/if}} -{{#if canDelete}} - -{{/if}} +{/if}
-{{/if}} +{/if} - +
\ No newline at end of file diff --git a/frontend/src/routes/dashboard/general_catalogs/legends/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/legends/+page.svelte index e0a4f2f4..80662218 100644 --- a/frontend/src/routes/dashboard/general_catalogs/legends/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/legends/+page.svelte @@ -1,22 +1,22 @@
@@ -163,85 +163,75 @@ const columns = $derived(createColumns(handleSuccess, {{ canEdit, canDelete }}))

Catálogo de Leyendas del sistema

- -{{#if !isError && canCreate}} - -{{/if}} +{/if}
-{{#if isError}} +{#if isError} -{{else}} +{:else}
Listado de Leyendas
- - + +
(selectedIds = ids)}} -onRowClick={{(row) => selectedIds = selectedIds.includes(row.id) ? [] : [row.id]}} -onRowDoubleClick={{(row) => {{ if(canEdit) {{ editingItem = row; createDialogOpen = true; }} }}}} +data={allItems} {columns} {loading} {hasMore} {loadMore} +{selectedIds} onSelectedIdsChange={(ids) => (selectedIds = ids)} +onRowClick={(row) => selectedIds = selectedIds.includes(row.id) ? [] : [row.id]} +onRowDoubleClick={(row) => { if(canEdit) { editingItem = row; createDialogOpen = true; } }} />
-Mostrando {{allItems.length}} de {{totalItems}} registros +Mostrando {allItems.length} de {totalItems} registros
-<-- 1. Primero borramos las relaciones de usuarios con tenants -DELETE FROM user_tenant; --- 2. Luego borramos las compañías (que dependen de los tenants) -DELETE FROM company; - --- 3. Finalmente borramos los tenants (inquilinos) -DELETE FROM tenant; - --- (Opcional) Si tienes una tabla de usuarios locales, también límpiala para evitar duplicados -DELETE FROM Footer Actions -->
-{{#if canEdit}} - -{{/if}} -{{#if canDelete}} - -{{/if}} +{/if}
-{{/if}} +{/if} - +
\ No newline at end of file diff --git a/frontend/src/routes/dashboard/general_catalogs/multi_currency_types/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/multi_currency_types/+page.svelte index a3a5cc27..b15d4c3b 100644 --- a/frontend/src/routes/dashboard/general_catalogs/multi_currency_types/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/multi_currency_types/+page.svelte @@ -6,7 +6,7 @@ import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; - import { Pencil, Plus, Trash2, RefreshCw } from 'lucide-svelte'; + import { Pencil, Plus, Trash2 } from 'lucide-svelte'; import { goto } from '$app/navigation'; import { browser } from '$app/environment'; import { useShortcuts } from '$lib/hooks/use-shortcuts'; @@ -21,52 +21,41 @@ import ErrorState from '$lib/components/dashboard/common/error-state.svelte'; let { data } = $props(); - - // Estados básicos let dialogOpen = $state(false); let editingItem = $state(null); let error = $state(data.error || null); let status = $state(data.status || 200); - let selectedIds = $state<(string | number)[]>([]); - // Estados de datos - let allItems = $state(data.types?.items || []); - let currentPage = $state(data.types?.page || 1); - let pageSize = $state(data.types?.page_size || 50); - let totalItems = $state(data.types?.total || 0); - let loading = $state(false); - - // Filtros - let searchCode = $state($page.url.searchParams.get('currency_type_code') || ''); - let searchCountry = $state($page.url.searchParams.get('country_key') || ''); - let timeout: ReturnType; - - // Derivados + // Permisos const canView = $derived(userHasPermission($currentUser, 'cat_multi_currency_types.view')); const canCreate = $derived(userHasPermission($currentUser, 'cat_multi_currency_types.create')); const canEdit = $derived(userHasPermission($currentUser, 'cat_multi_currency_types.edit')); const canDelete = $derived(userHasPermission($currentUser, 'cat_multi_currency_types.delete')); + const isError = $derived(!canView || status >= 400 || error); - const hasMore = $derived(allItems.length < totalItems); - const selectedItem = $derived( - selectedIds.length === 1 - ? allItems.find((item) => String(item.id) === String(selectedIds[0])) ?? null - : null - ); // Atajos useShortcuts( 'Lista Monedas', obtenerAtajosListaMonedas({ +<<<<<<< HEAD + manejarNuevo: () => (dialogOpen = true), + manejarActualizar: () => reloadData() +======= manejarNuevo: () => { - if (!canCreate) return; editingItem = null; dialogOpen = true; }, manejarActualizar: handleSuccess +>>>>>>> 88ab6b45df947b2091e7cd5fff7468284a0ceef6 }) ); + // Filtros + let searchCode = $state($page.url.searchParams.get('currency_type_code') || ''); + let searchCountry = $state($page.url.searchParams.get('country_key') || ''); + let timeout: ReturnType; + const columns: ColumnDef[] = [ { accessorKey: 'currency_type_code', @@ -90,6 +79,19 @@ } ]; + let allItems = $state(data.types?.items || []); + let currentPage = $state(data.types?.page || 1); + let pageSize = $state(data.types?.page_size || 50); + let totalItems = $state(data.types?.total || 0); + let loading = $state(false); + let hasMore = $derived(allItems.length < totalItems); + let selectedIds = $state<(string | number)[]>([]); + const selectedItem = $derived( + selectedIds.length === 1 + ? allItems.find((item) => String(item.id) === String(selectedIds[0])) ?? null + : null + ); + $effect(() => { if (data.types) { allItems = data.types.items || []; @@ -107,6 +109,12 @@ loading = true; error = null; try { +<<<<<<< HEAD + const response = await getMultiCurrencyTypes(companyStore.activeCompany.id, 1, pageSize, { + currency_type_code: searchCode || undefined, + country_key: searchCountry || undefined + }); +======= const response = await getMultiCurrencyTypes( companyStore.activeCompany.id, 1, @@ -116,14 +124,16 @@ country_key: searchCountry || undefined } ); - if (response.error) throw new Error(response.error); - + if (response.error) { + throw new Error(response.error); + } +>>>>>>> 88ab6b45df947b2091e7cd5fff7468284a0ceef6 if (response.data?.items) { allItems = response.data.items; currentPage = response.data.page || 1; totalItems = response.data.total || 0; } - } catch (err: any) { + } catch (err) { error = 'Error aplicando filtros'; console.error('Error applying filters:', err); } finally { @@ -155,8 +165,9 @@ country_key: searchCountry || undefined } ); - if (response.error) throw new Error(response.error); - + if (response.error) { + throw new Error(response.error); + } if (response.data?.items) { allItems = [...allItems, ...response.data.items]; currentPage += 1; @@ -175,6 +186,12 @@ loading = true; error = null; try { +<<<<<<< HEAD + const response = await getMultiCurrencyTypes(companyStore.activeCompany.id, 1, pageSize, { + currency_type_code: searchCode || undefined, + country_key: searchCountry || undefined + }); +======= const response = await getMultiCurrencyTypes( companyStore.activeCompany.id, 1, @@ -184,8 +201,10 @@ country_key: searchCountry || undefined } ); - if (response.error) throw new Error(response.error); - + if (response.error) { + throw new Error(response.error); + } +>>>>>>> 88ab6b45df947b2091e7cd5fff7468284a0ceef6 if (response.data?.items) { allItems = response.data.items; currentPage = response.data.page || 1; @@ -212,19 +231,18 @@ } function handleRowDoubleClick(row: MultiCurrencyType) { - if (!canEdit) return; editingItem = row; dialogOpen = true; } function handleEditSelected() { - if (!selectedItem || !canEdit) return; + if (!selectedItem) return; editingItem = selectedItem; dialogOpen = true; } async function handleDeleteSelected() { - if (!selectedItem || !companyStore.activeCompany || !canDelete) return; + if (!selectedItem || !companyStore.activeCompany) return; if (!confirm(`¿Estás seguro de eliminar el tipo de moneda "${selectedItem.currency_type_code}"?`)) { return; @@ -235,7 +253,9 @@ try { const response = await deleteMultiCurrencyType(selectedItem.id, companyStore.activeCompany.id); - if (response.error) throw new Error(response.error); + if (response.error) { + throw new Error(response.error); + } selectedIds = []; await reloadData(); @@ -249,6 +269,38 @@
+
+
+

Tipos de Moneda Múltiple

+

Gestión del catálogo de tipos de moneda múltiple

+
+
+<<<<<<< HEAD + + {#if !isError && canCreate} + + {/if} +======= + +>>>>>>> 88ab6b45df947b2091e7cd5fff7468284a0ceef6 +
+
+ {#if isError} {:else} -
-
-

Tipos de Moneda Múltiple

-

Gestión del catálogo de tipos de moneda múltiple

-
-
- - {#if canCreate} - - {#/if} -
-
- {#if error}
{error} @@ -306,17 +336,7 @@
- (selectedIds = ids)} - onRowClick={handleRowClick} - onRowDoubleClick={handleRowDoubleClick} - /> +
@@ -324,44 +344,67 @@
Mostrando {allItems.length} de {totalItems} registros
+ {/if} - -{{#if isError}} +{#if isError} -{{else}} +{:else}
Listado de Embalajes
- - + +
(selectedIds = ids)}} -onRowClick={{(row) => selectedIds = selectedIds.includes(row.id) ? [] : [row.id]}} -onRowDoubleClick={{(row) => {{ if(canEdit) {{ editingItem = row; createDialogOpen = true; }} }}}} +data={allItems} {columns} {loading} {hasMore} {loadMore} +{selectedIds} onSelectedIdsChange={(ids) => (selectedIds = ids)} +onRowClick={(row) => selectedIds = selectedIds.includes(row.id) ? [] : [row.id]} +onRowDoubleClick={(row) => { if(canEdit) { editingItem = row; createDialogOpen = true; } }} />
-Mostrando {{allItems.length}} de {{totalItems}} registros +Mostrando {allItems.length} de {totalItems} registros
-<-- 1. Primero borramos las relaciones de usuarios con tenants -DELETE FROM user_tenant; --- 2. Luego borramos las compañías (que dependen de los tenants) -DELETE FROM company; - --- 3. Finalmente borramos los tenants (inquilinos) -DELETE FROM tenant; - --- (Opcional) Si tienes una tabla de usuarios locales, también límpiala para evitar duplicados -DELETE FROM Footer Actions -->
-{{#if canEdit}} - -{{/if}} -{{#if canDelete}} - -{{/if}} +{/if}
-{{/if}} +{/if} - +
\ No newline at end of file diff --git a/frontend/src/routes/dashboard/general_catalogs/ports/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/ports/+page.svelte index 46d8d00c..a5a85ed8 100644 --- a/frontend/src/routes/dashboard/general_catalogs/ports/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/ports/+page.svelte @@ -1,22 +1,22 @@
@@ -163,85 +163,75 @@ const columns = $derived(createColumns(handleSuccess, {{ canEdit, canDelete }}))

Catálogo de Puertos del sistema

- -{{#if !isError && canCreate}} - -{{/if}} +{/if}
-{{#if isError}} +{#if isError} -{{else}} +{:else}
Listado de Puertos
- - + +
(selectedIds = ids)}} -onRowClick={{(row) => selectedIds = selectedIds.includes(row.id) ? [] : [row.id]}} -onRowDoubleClick={{(row) => {{ if(canEdit) {{ editingItem = row; createDialogOpen = true; }} }}}} +data={allItems} {columns} {loading} {hasMore} {loadMore} +{selectedIds} onSelectedIdsChange={(ids) => (selectedIds = ids)} +onRowClick={(row) => selectedIds = selectedIds.includes(row.id) ? [] : [row.id]} +onRowDoubleClick={(row) => { if(canEdit) { editingItem = row; createDialogOpen = true; } }} />
-Mostrando {{allItems.length}} de {{totalItems}} registros +Mostrando {allItems.length} de {totalItems} registros
-<-- 1. Primero borramos las relaciones de usuarios con tenants -DELETE FROM user_tenant; --- 2. Luego borramos las compañías (que dependen de los tenants) -DELETE FROM company; - --- 3. Finalmente borramos los tenants (inquilinos) -DELETE FROM tenant; - --- (Opcional) Si tienes una tabla de usuarios locales, también límpiala para evitar duplicados -DELETE FROM Footer Actions -->
-{{#if canEdit}} - -{{/if}} -{{#if canDelete}} - -{{/if}} +{/if}
-{{/if}} +{/if} - +
\ No newline at end of file diff --git a/frontend/src/routes/dashboard/general_catalogs/prevalidators/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/prevalidators/+page.svelte index d5dfcf52..1696746b 100644 --- a/frontend/src/routes/dashboard/general_catalogs/prevalidators/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/prevalidators/+page.svelte @@ -1,72 +1,70 @@ @@ -238,24 +183,109 @@

Prevalidadores

-

Catálogo de prevalidadores autorizados

+

Catálogo de prevalidadores

- + {#if canCreate} - + {/if}
+======= + function handleRowClick(row: Prevalidator) { + const id = row.id; + selectedIds = selectedIds.includes(id) ? [] : [id]; + } + + function handleRowDoubleClick(row: Prevalidator) { + editingItem = row; + dialogOpen = true; + } + + function handleEditSelected() { + if (!selectedItem) return; + editingItem = selectedItem; + dialogOpen = true; + } + + async function handleDeleteSelected() { + if (!selectedItem || !companyStore.activeCompany) return; + + if (!confirm(`¿Estás seguro de eliminar el prevalidador "${selectedItem.code}"?`)) { + return; + } + + loading = true; + error = null; + + try { + await deletePrevalidator(selectedItem.id, companyStore.activeCompany.id); + + selectedIds = []; + await reloadData(); + } catch (err) { + error = 'Error al eliminar el prevalidador'; + console.error('Error deleting prevalidator:', err); + } finally { + loading = false; + } + } + + +
+
+
+

Prevalidadores

+

Catálogo de prevalidadores

+
+
+ +
+
+ + {#if error} +
+ {error} +
+ {/if} + + + +
+ Listado de Prevalidadores +
+ + +
+
+
+ +
+ (selectedIds = ids)} + onRowClick={handleRowClick} + onRowDoubleClick={handleRowDoubleClick} + /> +
+
+
+>>>>>>> 88ab6b45df947b2091e7cd5fff7468284a0ceef6 {#if error}
@@ -263,83 +293,59 @@
{/if} - +
Listado de Prevalidadores
- - + +
- -
- (selectedIds = ids)} - onRowClick={handleRowClick} - onRowDoubleClick={handleRowDoubleClick} - /> + +
+
-
- Mostrando {allItems.length} de {totalItems} registros -
- - +
Mostrando {allItems.length} de {totalItems} registros
{/if} +
+ + + diff --git a/frontend/src/routes/dashboard/general_catalogs/seal/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/seal/+page.svelte index 0c0a92a1..9009340e 100644 --- a/frontend/src/routes/dashboard/general_catalogs/seal/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/seal/+page.svelte @@ -1,22 +1,22 @@
@@ -163,85 +163,75 @@ const columns = $derived(createColumns(handleSuccess, {{ canEdit, canDelete }}))

Catálogo de Sellos del sistema

- -{{#if !isError && canCreate}} - -{{/if}} +{/if}
-{{#if isError}} +{#if isError} -{{else}} +{:else}
Listado de Sellos
- - + +
(selectedIds = ids)}} -onRowClick={{(row) => selectedIds = selectedIds.includes(row.id) ? [] : [row.id]}} -onRowDoubleClick={{(row) => {{ if(canEdit) {{ editingItem = row; createDialogOpen = true; }} }}}} +data={allItems} {columns} {loading} {hasMore} {loadMore} +{selectedIds} onSelectedIdsChange={(ids) => (selectedIds = ids)} +onRowClick={(row) => selectedIds = selectedIds.includes(row.id) ? [] : [row.id]} +onRowDoubleClick={(row) => { if(canEdit) { editingItem = row; createDialogOpen = true; } }} />
-Mostrando {{allItems.length}} de {{totalItems}} registros +Mostrando {allItems.length} de {totalItems} registros
-<-- 1. Primero borramos las relaciones de usuarios con tenants -DELETE FROM user_tenant; --- 2. Luego borramos las compañías (que dependen de los tenants) -DELETE FROM company; - --- 3. Finalmente borramos los tenants (inquilinos) -DELETE FROM tenant; - --- (Opcional) Si tienes una tabla de usuarios locales, también límpiala para evitar duplicados -DELETE FROM Footer Actions -->
-{{#if canEdit}} - -{{/if}} -{{#if canDelete}} - -{{/if}} +{/if}
-{{/if}} +{/if} - +
\ No newline at end of file diff --git a/frontend/src/routes/dashboard/general_catalogs/signatures/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/signatures/+page.svelte index 46615fdd..0b62f0a3 100644 --- a/frontend/src/routes/dashboard/general_catalogs/signatures/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/signatures/+page.svelte @@ -1,22 +1,22 @@
@@ -163,85 +163,75 @@ const columns = $derived(createColumns(handleSuccess, {{ canEdit, canDelete }}))

Catálogo de Firmas del sistema

- -{{#if !isError && canCreate}} - -{{/if}} +{/if}
-{{#if isError}} +{#if isError} -{{else}} +{:else}
Listado de Firmas
- - + +
(selectedIds = ids)}} -onRowClick={{(row) => selectedIds = selectedIds.includes(row.id) ? [] : [row.id]}} -onRowDoubleClick={{(row) => {{ if(canEdit) {{ editingItem = row; createDialogOpen = true; }} }}}} +data={allItems} {columns} {loading} {hasMore} {loadMore} +{selectedIds} onSelectedIdsChange={(ids) => (selectedIds = ids)} +onRowClick={(row) => selectedIds = selectedIds.includes(row.id) ? [] : [row.id]} +onRowDoubleClick={(row) => { if(canEdit) { editingItem = row; createDialogOpen = true; } }} />
-Mostrando {{allItems.length}} de {{totalItems}} registros +Mostrando {allItems.length} de {totalItems} registros
-<-- 1. Primero borramos las relaciones de usuarios con tenants -DELETE FROM user_tenant; --- 2. Luego borramos las compañías (que dependen de los tenants) -DELETE FROM company; - --- 3. Finalmente borramos los tenants (inquilinos) -DELETE FROM tenant; - --- (Opcional) Si tienes una tabla de usuarios locales, también límpiala para evitar duplicados -DELETE FROM Footer Actions -->
-{{#if canEdit}} - -{{/if}} -{{#if canDelete}} - -{{/if}} +{/if}
-{{/if}} +{/if} - +
\ No newline at end of file diff --git a/frontend/src/routes/dashboard/general_catalogs/trailers/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/trailers/+page.svelte index 7728ef63..4f21be79 100644 --- a/frontend/src/routes/dashboard/general_catalogs/trailers/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/trailers/+page.svelte @@ -154,7 +154,7 @@ status={!canView ? 403 : status} error={!canView ? 'Permission denied: cat_trailers.view' : error || ''} onRetry={loadData} /> -{else} +{:else}
@@ -182,17 +182,7 @@ getRowId={(row) => row.trailer_key} Mostrando {data.length} de {totalItems} registros
-<-- 1. Primero borramos las relaciones de usuarios con tenants -DELETE FROM user_tenant; --- 2. Luego borramos las compañías (que dependen de los tenants) -DELETE FROM company; - --- 3. Finalmente borramos los tenants (inquilinos) -DELETE FROM tenant; - --- (Opcional) Si tienes una tabla de usuarios locales, también límpiala para evitar duplicados -DELETE FROM Footer Actions -->
diff --git a/frontend/src/routes/dashboard/general_catalogs/unit_conversions/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/unit_conversions/+page.svelte index 944ac58a..364e15a4 100644 --- a/frontend/src/routes/dashboard/general_catalogs/unit_conversions/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/unit_conversions/+page.svelte @@ -172,7 +172,7 @@ const columns = $derived(createColumns(handleSuccess, { canEdit, canDelete })); Actualizar {#if !isError && canCreate} - {/if} @@ -202,7 +202,7 @@ onRetry={reloadData} data={allItems} {columns} {loading} {hasMore} {loadMore} {selectedIds} onSelectedIdsChange={(ids) => (selectedIds = ids)} onRowClick={(row) => selectedIds = selectedIds.includes(row.id) ? [] : [row.id]} -onRowDoubleClick={(row) => { if(canEdit) { editingItem = row; createDialogOpen = true; } }} +onRowDoubleClick={(row) => { if(canEdit) { editingItem = row; createDialogOpen = true; } } />
@@ -212,22 +212,12 @@ onRowDoubleClick={(row) => { if(canEdit) { editingItem = row; createDialogOpen = Mostrando {allItems.length} de {totalItems} registros
-<-- 1. Primero borramos las relaciones de usuarios con tenants -DELETE FROM user_tenant; --- 2. Luego borramos las compañías (que dependen de los tenants) -DELETE FROM company; - --- 3. Finalmente borramos los tenants (inquilinos) -DELETE FROM tenant; - --- (Opcional) Si tienes una tabla de usuarios locales, también límpiala para evitar duplicados -DELETE FROM Footer Actions -->
{#if canEdit} - {/if} @@ -239,7 +229,7 @@ await deleteUnitConversion(selectedItem.id, companyStore.activeCompany.id); selectedIds = []; reloadData(); } -}} disabled={!selectedItem} class="text-destructive hover:bg-destructive/10"> +} disabled={!selectedItem} class="text-destructive hover:bg-destructive/10"> Eliminar {/if} diff --git a/frontend/src/routes/dashboard/general_catalogs/units_of_measure/ace/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/units_of_measure/ace/+page.svelte index 3af5e2b6..e588e4f4 100644 --- a/frontend/src/routes/dashboard/general_catalogs/units_of_measure/ace/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/units_of_measure/ace/+page.svelte @@ -163,7 +163,7 @@ const columns = $derived(createColumns(handleSuccess, { canEdit, canDelete })); Actualizar {#if !isError && canCreate} - {/if} @@ -176,7 +176,7 @@ status={!canView ? 403 : status} error={!canView ? 'Permission denied: cat_um_ace.view' : error || ''} onRetry={reloadData} /> -{else} +{:else}
@@ -193,7 +193,7 @@ onRetry={reloadData} data={allItems} {columns} {loading} {hasMore} {loadMore} {selectedIds} onSelectedIdsChange={(ids) => (selectedIds = ids)} onRowClick={(row) => selectedIds = selectedIds.includes(row.id) ? [] : [row.id]} -onRowDoubleClick={(row) => { if(canEdit) { editingItem = row; createDialogOpen = true; } }} +onRowDoubleClick={(row) => { if(canEdit) { editingItem = row; createDialogOpen = true; } } />
@@ -203,22 +203,12 @@ onRowDoubleClick={(row) => { if(canEdit) { editingItem = row; createDialogOpen = Mostrando {allItems.length} de {totalItems} registros
-<-- 1. Primero borramos las relaciones de usuarios con tenants -DELETE FROM user_tenant; --- 2. Luego borramos las compañías (que dependen de los tenants) -DELETE FROM company; - --- 3. Finalmente borramos los tenants (inquilinos) -DELETE FROM tenant; - --- (Opcional) Si tienes una tabla de usuarios locales, también límpiala para evitar duplicados -DELETE FROM Footer Actions -->
{#if canEdit} - {/if} @@ -230,7 +220,7 @@ await deleteUnitOfMeasureACE(selectedItem.id, companyStore.activeCompany.id); selectedIds = []; reloadData(); } -}} disabled={!selectedItem} class="text-destructive hover:bg-destructive/10"> +} disabled={!selectedItem} class="text-destructive hover:bg-destructive/10"> Eliminar {/if} diff --git a/frontend/src/routes/dashboard/general_catalogs/units_of_measure/american/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/units_of_measure/american/+page.svelte index a2bc8117..21b4ec54 100644 --- a/frontend/src/routes/dashboard/general_catalogs/units_of_measure/american/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/units_of_measure/american/+page.svelte @@ -163,7 +163,7 @@ const columns = $derived(createColumns(handleSuccess, { canEdit, canDelete })); Actualizar {#if !isError && canCreate} - {/if} @@ -176,7 +176,7 @@ status={!canView ? 403 : status} error={!canView ? 'Permission denied: cat_um_american.view' : error || ''} onRetry={reloadData} /> -{else} +{:else}
@@ -193,7 +193,7 @@ onRetry={reloadData} data={allItems} {columns} {loading} {hasMore} {loadMore} {selectedIds} onSelectedIdsChange={(ids) => (selectedIds = ids)} onRowClick={(row) => selectedIds = selectedIds.includes(row.id) ? [] : [row.id]} -onRowDoubleClick={(row) => { if(canEdit) { editingItem = row; createDialogOpen = true; } }} +onRowDoubleClick={(row) => { if(canEdit) { editingItem = row; createDialogOpen = true; } } />
@@ -203,22 +203,12 @@ onRowDoubleClick={(row) => { if(canEdit) { editingItem = row; createDialogOpen = Mostrando {allItems.length} de {totalItems} registros
-<-- 1. Primero borramos las relaciones de usuarios con tenants -DELETE FROM user_tenant; --- 2. Luego borramos las compañías (que dependen de los tenants) -DELETE FROM company; - --- 3. Finalmente borramos los tenants (inquilinos) -DELETE FROM tenant; - --- (Opcional) Si tienes una tabla de usuarios locales, también límpiala para evitar duplicados -DELETE FROM Footer Actions -->
{#if canEdit} - {/if} @@ -230,7 +220,7 @@ await deleteUnitOfMeasureAmerican(selectedItem.id, companyStore.activeCompany.id selectedIds = []; reloadData(); } -}} disabled={!selectedItem} class="text-destructive hover:bg-destructive/10"> +} disabled={!selectedItem} class="text-destructive hover:bg-destructive/10"> Eliminar {/if} diff --git a/frontend/src/routes/dashboard/general_catalogs/units_of_measure/customs/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/units_of_measure/customs/+page.svelte index b217a186..84f6a59c 100644 --- a/frontend/src/routes/dashboard/general_catalogs/units_of_measure/customs/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/units_of_measure/customs/+page.svelte @@ -163,7 +163,7 @@ const columns = $derived(createColumns(handleSuccess, { canEdit, canDelete })); Actualizar {#if !isError && canCreate} - {/if} @@ -176,7 +176,7 @@ status={!canView ? 403 : status} error={!canView ? 'Permission denied: cat_um_customs.view' : error || ''} onRetry={reloadData} /> -{else} +{:else}
@@ -193,7 +193,7 @@ onRetry={reloadData} data={allItems} {columns} {loading} {hasMore} {loadMore} {selectedIds} onSelectedIdsChange={(ids) => (selectedIds = ids)} onRowClick={(row) => selectedIds = selectedIds.includes(row.id) ? [] : [row.id]} -onRowDoubleClick={(row) => { if(canEdit) { editingItem = row; createDialogOpen = true; } }} +onRowDoubleClick={(row) => { if(canEdit) { editingItem = row; createDialogOpen = true; } } />
@@ -203,22 +203,12 @@ onRowDoubleClick={(row) => { if(canEdit) { editingItem = row; createDialogOpen = Mostrando {allItems.length} de {totalItems} registros
-<-- 1. Primero borramos las relaciones de usuarios con tenants -DELETE FROM user_tenant; --- 2. Luego borramos las compañías (que dependen de los tenants) -DELETE FROM company; - --- 3. Finalmente borramos los tenants (inquilinos) -DELETE FROM tenant; - --- (Opcional) Si tienes una tabla de usuarios locales, también límpiala para evitar duplicados -DELETE FROM Footer Actions -->
{#if canEdit} - {/if} @@ -230,7 +220,7 @@ await deleteUnitOfMeasureCustoms(selectedItem.id, companyStore.activeCompany.id) selectedIds = []; reloadData(); } -}} disabled={!selectedItem} class="text-destructive hover:bg-destructive/10"> +} disabled={!selectedItem} class="text-destructive hover:bg-destructive/10"> Eliminar {/if} diff --git a/frontend/src/routes/dashboard/general_catalogs/units_of_measure/general/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/units_of_measure/general/+page.svelte index c77126c3..0ed5640b 100644 --- a/frontend/src/routes/dashboard/general_catalogs/units_of_measure/general/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/units_of_measure/general/+page.svelte @@ -8,54 +8,38 @@ import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; - import { Pencil, Plus, Trash2, RefreshCw } from 'lucide-svelte'; + import { Pencil, Plus, Trash2 } from 'lucide-svelte'; import { useShortcuts } from '$lib/hooks/use-shortcuts'; import { obtenerAtajosListaUnidadesGeneral } from '$lib/config/shortcuts/dashboard/general_catalogs/units_of_measure/general/list'; import { getUnitsOfMeasureGeneral, deleteUnitOfMeasureGeneral, type UnitOfMeasureGeneral } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure'; import { companyStore } from '$lib/stores/company.svelte'; - import { currentUser, userHasPermission } from '$lib/auth'; +<<<<<<< HEAD import ErrorState from '$lib/components/dashboard/common/error-state.svelte'; + import { currentUser, userHasPermission } from '$lib/auth'; +======= + import type { PageData } from './$types'; +>>>>>>> 88ab6b45df947b2091e7cd5fff7468284a0ceef6 - let { data } = $props(); + let { data }: { data: PageData } = $props(); - // Estados básicos let createDialogOpen = $state(false); let editingItem = $state(null); let error = $state(data.error || null); let status = $state(data.status || 200); - let selectedIds = $state<(string | number)[]>([]); - // Estados de datos - let allItems = $state(data.general_units?.items || []); - let currentPage = $state(data.general_units?.page || 1); - let pageSize = $state(data.general_units?.page_size || 50); - let totalItems = $state(data.general_units?.total || 0); - let loading = $state(false); - - // Filtros - let searchCode = $state($page.url.searchParams.get('code') || ''); - let searchDesc = $state($page.url.searchParams.get('description') || ''); - let timeout: ReturnType; - - // Derivados + // Permisos const canView = $derived(userHasPermission($currentUser, 'cat_um_general.view')); const canCreate = $derived(userHasPermission($currentUser, 'cat_um_general.create')); const canEdit = $derived(userHasPermission($currentUser, 'cat_um_general.edit')); const canDelete = $derived(userHasPermission($currentUser, 'cat_um_general.delete')); + const isError = $derived(!canView || status >= 400 || error); - const hasMore = $derived(allItems.length < totalItems); - const selectedItem = $derived( - selectedIds.length === 1 - ? allItems.find((item) => String(item.id) === String(selectedIds[0])) ?? null - : null - ); // Atajos useShortcuts( 'Lista Unidades de Medida', obtenerAtajosListaUnidadesGeneral({ manejarNuevo: () => { - if (!canCreate) return; editingItem = null; createDialogOpen = true; }, @@ -63,6 +47,24 @@ }) ); + // Filtros + let searchCode = $state($page.url.searchParams.get('code') || ''); + let searchDesc = $state($page.url.searchParams.get('description') || ''); + let timeout: ReturnType; + + let allItems = $state(data.general_units?.items || []); + let currentPage = $state(data.general_units?.page || 1); + let pageSize = $state(data.general_units?.page_size || 50); + let totalItems = $state(data.general_units?.total || 0); + let loading = $state(false); + let hasMore = $derived(allItems.length < totalItems); + let selectedIds = $state<(string | number)[]>([]); + const selectedItem = $derived( + selectedIds.length === 1 + ? allItems.find((item) => String(item.id) === String(selectedIds[0])) ?? null + : null + ); + $effect(() => { if (data.general_units) { allItems = data.general_units.items || []; @@ -90,8 +92,9 @@ companyStore.activeCompany.id, filters ); - if (response.error) throw new Error(response.error); - + if (response.error) { + throw new Error(response.error); + } if (response.data) { allItems = response.data.items; currentPage = 1; @@ -130,8 +133,9 @@ companyStore.activeCompany.id, filters ); - if (response.error) throw new Error(response.error); - + if (response.error) { + throw new Error(response.error); + } if (response.data?.items) { allItems = [...allItems, ...response.data.items]; currentPage += 1; @@ -160,8 +164,9 @@ companyStore.activeCompany.id, filters ); - if (response.error) throw new Error(response.error); - + if (response.error) { + throw new Error(response.error); + } if (response.data) { allItems = response.data.items; currentPage = 1; @@ -182,25 +187,27 @@ reloadData(); } +<<<<<<< HEAD + const columns = $derived(createColumns(handleSuccess, { canEdit, canDelete })); +======= function handleRowClick(row: UnitOfMeasureGeneral) { const id = row.id; selectedIds = selectedIds.includes(id) ? [] : [id]; } function handleRowDoubleClick(row: UnitOfMeasureGeneral) { - if (!canEdit) return; editingItem = row; createDialogOpen = true; } function handleEditSelected() { - if (!selectedItem || !canEdit) return; + if (!selectedItem) return; editingItem = selectedItem; createDialogOpen = true; } async function handleDeleteSelected() { - if (!selectedItem || !companyStore.activeCompany || !canDelete) return; + if (!selectedItem || !companyStore.activeCompany) return; if (!confirm(`¿Estás seguro de eliminar la unidad "${selectedItem.code}"?`)) { return; @@ -211,7 +218,9 @@ try { const response = await deleteUnitOfMeasureGeneral(selectedItem.id, companyStore.activeCompany.id); - if (response.error) throw new Error(response.error); + if (response.error) { + throw new Error(response.error); + } selectedIds = []; await reloadData(); @@ -222,8 +231,7 @@ loading = false; } } - - const columns = $derived(createColumns(handleSuccess, { canEdit, canDelete })); +>>>>>>> 88ab6b45df947b2091e7cd5fff7468284a0ceef6
@@ -245,16 +253,29 @@ Actualizar {#if canCreate} - {/if}
+<<<<<<< HEAD +======= +
+ +
+
+>>>>>>> 88ab6b45df947b2091e7cd5fff7468284a0ceef6 {#if error}
@@ -262,84 +283,77 @@
{/if} - - -
- Listado de Unidades -
- - -
-
-
- -
- (selectedIds = ids)} - onRowClick={handleRowClick} - onRowDoubleClick={handleRowDoubleClick} - /> + +
Listado de Unidades
+ +
+
-
- Mostrando {allItems.length} de {totalItems} registros -
+
Mostrando {allItems.length} de {totalItems} registros
+ {/if} - -<-- 1. Primero borramos las relaciones de usuarios con tenants -DELETE FROM user_tenant; --- 2. Luego borramos las compañías (que dependen de los tenants) -DELETE FROM company; - --- 3. Finalmente borramos los tenants (inquilinos) -DELETE FROM tenant; - --- (Opcional) Si tienes una tabla de usuarios locales, también límpiala para evitar duplicados -DELETE FROM Footer Actions -->
{#if canEdit} - {/if} @@ -230,7 +220,7 @@ await deleteUnitOfMeasureOMA(selectedItem.id, companyStore.activeCompany.id); selectedIds = []; reloadData(); } -}} disabled={!selectedItem} class="text-destructive hover:bg-destructive/10"> +} disabled={!selectedItem} class="text-destructive hover:bg-destructive/10"> Eliminar {/if} diff --git a/frontend/src/routes/dashboard/general_catalogs/vehicles/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/vehicles/+page.svelte index 4aa0c25b..29a3f8bd 100644 --- a/frontend/src/routes/dashboard/general_catalogs/vehicles/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/vehicles/+page.svelte @@ -5,58 +5,58 @@ import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; import { Plus, RefreshCw, Pencil, Trash2 } from 'lucide-svelte'; - import { browser } from '$app/environment'; - import { useShortcuts } from '$lib/hooks/use-shortcuts'; - import { obtenerAtajosCatalogoSimple } from '$lib/config/shortcuts/dashboard/general_catalogs/common/factory'; - import { companyStore } from '$lib/stores/company.svelte'; - import { currentUser, userHasPermission } from '$lib/auth'; - import ErrorState from '$lib/components/dashboard/common/error-state.svelte'; + + // Importar componentes de la librería import InfiniteDataTable from '$lib/components/dashboard/common/infinite-data-table.svelte'; import CreateEditDialog from '$lib/components/dashboard/transportation/vehicles/create-edit-dialog.svelte'; import { createColumns } from '$lib/components/dashboard/transportation/vehicles/columns'; - import { vehiclesApi, type Vehicle } from '$lib/api/dashboard/a76/vehicles'; - // ESTADO + import { vehiclesApi, type Vehicle } from '$lib/api/dashboard/a76/vehicles'; + import { companyStore } from '$lib/stores/company.svelte'; + import { browser } from '$app/environment'; + import { useShortcuts } from '$lib/hooks/use-shortcuts'; + import { obtenerAtajosCatalogoSimple } from '$lib/config/shortcuts/dashboard/general_catalogs/common/factory'; + import { currentUser, userHasPermission } from '$lib/auth'; + import ErrorState from '$lib/components/dashboard/common/error-state.svelte'; + + // --- ESTADO --- let data = $state([]); let totalItems = $state(0); let loading = $state(false); let currentPage = $state(1); let pageSize = $state(50); + let hasMore = $derived(data.length < totalItems); + let createDialogOpen = $state(false); +<<<<<<< HEAD let error = $state(null); let status = $state(200); - let createDialogOpen = $state(false); - let editingItem = $state(null); - let selectedIds = $state<(string | number)[]>([]); - - // Filtros - let searchKey = $state($page.url.searchParams.get('vehicle_key') || ''); - let searchPlate = $state($page.url.searchParams.get('plate_number') || ''); - let searchTimeout: ReturnType; - - // PERMISOS + // Permisos const canView = $derived(userHasPermission($currentUser, 'vehicles.view')); const canCreate = $derived(userHasPermission($currentUser, 'vehicles.create')); const canEdit = $derived(userHasPermission($currentUser, 'vehicles.edit')); const canDelete = $derived(userHasPermission($currentUser, 'vehicles.delete')); const isError = $derived(!canView || status >= 400 || error); - const hasMore = $derived(data.length < totalItems); +======= + let editingItem = $state(null); + let selectedIds = $state<(string | number)[]>([]); const selectedVehicle = $derived( - selectedIds.length === 1 ? data.find((v) => String(v.vehicle_key) === String(selectedIds[0])) : null + selectedIds.length === 1 ? data.find((v) => v.vehicle_key === selectedIds[0]) : null ); + const hasSelection = $derived(selectedIds.length > 0); +>>>>>>> 88ab6b45df947b2091e7cd5fff7468284a0ceef6 - // COLUMNAS - const columns = $derived(createColumns(loadData, { canEdit, canDelete })); - - $effect(() => { - if (companyStore.activeCompany?.id) { - loadData(); - } - }); + // Filtros + let searchKey = $state($page.url.searchParams.get('vehicle_key') || ''); + let searchPlate = $state($page.url.searchParams.get('plate_number') || ''); + let searchTimeout: ReturnType; + // --- LOGICA --- async function loadData() { - if (!companyStore.activeCompany || !canView) return; + if (!companyStore.activeCompany) return; + if (!canView) return; + loading = true; error = null; try { @@ -70,14 +70,18 @@ if (response.error) { error = response.error; status = response.status || 500; - } else if (response.data) { + return; + } + + if (response.data) { + status = 200; data = response.data.items; currentPage = 1; totalItems = response.data.total; - status = 200; } } catch (e: any) { - error = e.message || 'Error al cargar datos'; + console.error('Error loading vehicles:', e); + error = e.message || 'Error al cargar los datos'; status = e.status || 500; } finally { loading = false; @@ -94,11 +98,14 @@ vehicle_key: searchKey, plate_number: searchPlate }); + if (response.data?.items) { data = [...data, ...response.data.items]; currentPage += 1; totalItems = response.data.total; } + } catch (e) { + console.error('Error loading more vehicles:', e); } finally { loading = false; } @@ -110,39 +117,129 @@ loadData(); } + function handleRowDoubleClick(row: Vehicle) { + editingItem = row; + createDialogOpen = true; + } + + function handleRowClick(row: Vehicle) { + const id = row.vehicle_key; + if (selectedIds.includes(id)) { + selectedIds = selectedIds.filter((i) => i !== id); + } else { + selectedIds = [id]; + } + } + + function handleEditSelected() { + if (selectedVehicle) { + editingItem = selectedVehicle; + createDialogOpen = true; + } + } + + async function handleDeleteSelected() { + if (!selectedVehicle) return; + if ( + !confirm( + `¿Estás seguro de eliminar el vehículo "${selectedVehicle.vehicle_key}"?\n\nNota: No se puede eliminar si tiene registros relacionados.` + ) + ) { + return; + } + + if (!companyStore.activeCompany) return; + + try { + const response = await vehiclesApi.delete( + selectedVehicle.vehicle_key, + companyStore.activeCompany.id + ); + if (response.error) { + alert(`❌ Error al eliminar:\n\n${response.error}`); + } else { + alert(`✅ Vehículo eliminado correctamente`); + selectedIds = []; + loadData(); + } + } catch (e) { + console.error('Error deleting:', e); + } + } + function handleSearch() { if (!browser) return; clearTimeout(searchTimeout); searchTimeout = setTimeout(() => { const url = new URL($page.url); + if (searchKey) url.searchParams.set('vehicle_key', searchKey); else url.searchParams.delete('vehicle_key'); + if (searchPlate) url.searchParams.set('plate_number', searchPlate); else url.searchParams.delete('plate_number'); + history.replaceState(history.state, '', url); loadData(); }, 500); } - useShortcuts('Vehículos', obtenerAtajosCatalogoSimple({ - manejarNuevo: () => { if(canCreate) { editingItem = null; createDialogOpen = true; } }, - manejarActualizar: loadData - })); + // Recargar datos cuando cambia el contexto de compañía o la página/filtros + $effect(() => { +<<<<<<< HEAD + const id = companyStore.activeCompany?.id; + if (id) { + loadData(); +======= + const _ = { p: $page.url.href, c: companyStore.activeCompany?.id }; + loadData(); + + if (!createDialogOpen) { + editingItem = null; +>>>>>>> 88ab6b45df947b2091e7cd5fff7468284a0ceef6 + } + }); + + useShortcuts('Vehículos (transporte)', [ + ...obtenerAtajosCatalogoSimple({ + manejarNuevo: () => { + if (canCreate) createDialogOpen = true; + }, + manejarActualizar: loadData + }), + { + key: 'Ctrl+S', + description: 'Guardar', + action: () => { + if (!createDialogOpen) return; + document.dispatchEvent(new CustomEvent('save-form')); + } + } + ]); + + const columns = $derived(createColumns(loadData, { canEdit, canDelete })); -
-
+
+

Vehículos (Transporte)

-

Gestión del catálogo de camiones de transporte

+

+ Gestión del catálogo de camiones y vehículos de transporte +

+<<<<<<< HEAD
- {#if canCreate && !isError} - {/if}
@@ -155,58 +252,89 @@ onRetry={loadData} /> {:else} + {#if error} +
+ {error} +
+ {/if} +======= +
+
+ + +
Listado de Vehículos
+ {#if loading && data.length === 0}
Cargando vehículos...
{:else}
(selectedIds = ids)} onRowClick={handleRowClick} onRowDoubleClick={handleRowDoubleClick} getRowId={(row) => row.vehicle_key} />
{/if}
+
+>>>>>>> 88ab6b45df947b2091e7cd5fff7468284a0ceef6 + -
- Listado de Vehículos +
- - + +
-
- (selectedIds = ids)} - onRowClick={(row) => selectedIds = selectedIds.includes(row.vehicle_key) ? [] : [row.vehicle_key]} - onRowDoubleClick={(row) => { if(canEdit) { editingItem = row; createDialogOpen = true; } }} - getRowId={(row) => row.vehicle_key} - /> +
+
+<<<<<<< HEAD
Mostrando {data.length} de {totalItems} registros
- -
-
-
- {#if canEdit} - - {/if} - {#if canDelete} - - {/if} -
+ + {/if} +======= +
+ + + +>>>>>>> 88ab6b45df947b2091e7cd5fff7468284a0ceef6
diff --git a/frontend/src/routes/dashboard/goods/fixed-asset-classes/+page.svelte b/frontend/src/routes/dashboard/goods/fixed-asset-classes/+page.svelte index daba20da..eaa2a1c8 100644 --- a/frontend/src/routes/dashboard/goods/fixed-asset-classes/+page.svelte +++ b/frontend/src/routes/dashboard/goods/fixed-asset-classes/+page.svelte @@ -4,22 +4,26 @@ import { Label } from '$lib/components/ui/label'; import * as Dialog from '$lib/components/ui/dialog'; import FixedAssetClassForm from '$lib/components/dashboard/goods/classes/forms/FixedAssetClassForm.svelte'; - import { Folder, Save, Plus, RefreshCw, Pencil, Trash2 } from 'lucide-svelte'; + import { Folder, Save, Plus, RefreshCw } from 'lucide-svelte'; import { toast } from 'svelte-sonner'; import { classesApi, type A76Class } from '$lib/api/dashboard/a76/classes'; - import { faClassesApi } from '$lib/api/dashboard/a24/fa_classes'; + import { faClassesApi, type FAClass } from '$lib/api/dashboard/a24/fa_classes'; import { companyStore } from '$lib/stores/company.svelte'; + import { onMount } from 'svelte'; import DataTable from '$lib/components/dashboard/goods/classes/data-table.svelte'; import { columns } from '$lib/components/dashboard/goods/classes/columns'; +<<<<<<< HEAD import { currentUser, userHasPermission } from '$lib/auth'; import ErrorState from '$lib/components/dashboard/common/error-state.svelte'; - import { useShortcuts } from '$lib/hooks/use-shortcuts'; + +======= import { formatMexTariffDigitsForDisplay, normalizeMexTariffDigitsStored } from '$lib/utils/mexican-tariff-fraction'; +>>>>>>> 88ab6b45df947b2091e7cd5fff7468284a0ceef6 - // Tipo extendido + // Tipo extendido que combina A76Class y FAClass interface FixedAssetClassExtended extends A76Class { fa_class_id?: number; import_tariff_code?: string | null; @@ -30,32 +34,26 @@ fda_code?: string | null; eccn_code?: string | null; class_enabled?: boolean | null; + // Virtual fields for form compatibility annual_depreciation_rate?: number | string | null; fda_key?: string | null; } - let { data } = $props(); - - // Estado + // Estado de la lista de clases let classes = $state([]); let selectedClassIds = $state([]); let selectedClass = $state(null); let isLoading = $state(false); - let error = $state(null); - let status = $state(200); - - // Filtros let searchTerm = $state(''); let searchDescription = $state(''); let searchType = $state(''); let searchFraction = $state(''); - let sorting = $state([]); - - // Diálogos let showInsertDialog = $state(false); let showDeleteDialog = $state(false); let validationError = $state(''); let isSaving = $state(false); + let sorting = $state([]); + let status = $state(200); // Permisos const canView = $derived(userHasPermission($currentUser, 'goods_classes.view')); @@ -63,9 +61,9 @@ const canEdit = $derived(userHasPermission($currentUser, 'goods_classes.edit')); const canDelete = $derived(userHasPermission($currentUser, 'goods_classes.delete')); - const isError = $derived(!canView || status >= 400 || error); + const isError = $derived(!canView || status >= 400); - // Estado del formulario (detalle) + // Estado del formulario let formData = $state({ class_code: '', description_es: '', @@ -81,7 +79,7 @@ bom: '' }); - // Filtrado local + // Clases filtradas según búsqueda (mantenemos filtrado local para compatibilidad inmediata) const filteredClasses = $derived( classes.filter((c) => { const matchesCode = !searchTerm || c.class_code.toLowerCase().includes(searchTerm.toLowerCase()); @@ -94,9 +92,17 @@ }) ); + // Efecto para reaccionar al cambio de ordenamiento + $effect(() => { + if (sorting.length >= 0) { + loadClasses(); + } + }); + + // Reactively load classes when company changes $effect(() => { const companyId = companyStore.activeCompany?.id; - if (companyId && sorting) { + if (companyId) { loadClasses(); } }); @@ -104,10 +110,13 @@ async function loadClasses() { if (!canView) return; const companyId = companyStore.activeCompany?.id; - if (!companyId) return; + if (!companyId) { + return; + } isLoading = true; try { + // Use the new consolidated endpoint that fetches classes + FA data in ONE query const response = await classesApi.getWithFAData({ company_id: companyId, page: 1, @@ -117,25 +126,71 @@ }); if (response.data) { + // Data already comes with FA fields embedded classes = response.data as FixedAssetClassExtended[]; + handleSelectedIdsChange( + selectedClassIds.filter((id) => classes.some((item) => item.id === id)) + ); } - } catch (err: any) { - console.error('Error cargando clases:', err); - status = err?.status || 500; - toast.error('Error al cargar las clases'); + status = 200; + } catch (error: any) { + console.error('Error cargando clases:', error); + if (error?.response?.status) { + status = error.response.status; + } else if (error?.status) { + status = error.status; + } + toast.error('Error al cargar las clases de activo fijo'); } finally { isLoading = false; } } +<<<<<<< HEAD + function selectClass(cls: A76Class) { + if (!canView) return; + selectedClass = cls; + formData = { + class_code: cls.class_code, + description_es: cls.description_es || '', + description_en: cls.description_en || '', + material_key: cls.material_key || '', + unit_of_measure: cls.unit_of_measure || '', + fraction: cls.fraction || '', + us_fraction: cls.us_fraction || '', + unit_measure_trade: '', + depreciation_rate: (cls as FixedAssetClassExtended).depreciation_rate || null, + fda_code: (cls as FixedAssetClassExtended).fda_code || '', + eccn_code: (cls as FixedAssetClassExtended).eccn_code || '', + bom: '' + }; + } + + function handleNew() { + if (!canCreate) return; +======= + function clearSelectedClass() { +>>>>>>> 88ab6b45df947b2091e7cd5fff7468284a0ceef6 + selectedClass = null; + formData = { + class_code: '', + description_es: '', + description_en: '', + material_key: '', + unit_of_measure: '', + fraction: '', + us_fraction: '', + unit_measure_trade: '', + depreciation_rate: null as number | null, + fda_code: '', + eccn_code: '', + bom: '' + }; + } + function setActiveClass(cls: FixedAssetClassExtended | null) { if (!cls) { - selectedClass = null; - formData = { - class_code: '', description_es: '', description_en: '', material_key: '', - unit_of_measure: '', fraction: '', us_fraction: '', unit_measure_trade: '', - depreciation_rate: null, fda_code: '', eccn_code: '', bom: '' - }; + clearSelectedClass(); return; } @@ -146,12 +201,14 @@ description_en: cls.description_en || '', material_key: cls.material_key || '', unit_of_measure: cls.unit_of_measure || '', - fraction: normalizeMexTariffDigitsStored(cls.import_tariff_code || cls.fraction || ''), + fraction: normalizeMexTariffDigitsStored( + (cls as FixedAssetClassExtended).import_tariff_code || cls.fraction || '' + ), us_fraction: cls.us_fraction || '', unit_measure_trade: '', - depreciation_rate: cls.depreciation_rate || null, - fda_code: cls.fda_code || '', - eccn_code: cls.eccn_code || '', + depreciation_rate: (cls as FixedAssetClassExtended).depreciation_rate || null, + fda_code: (cls as FixedAssetClassExtended).fda_code || '', + eccn_code: (cls as FixedAssetClassExtended).eccn_code || '', bom: '' }; } @@ -159,38 +216,63 @@ function handleSelectedIdsChange(ids: number[]) { selectedClassIds = ids; const primaryId = ids[0]; - const primaryClass = primaryId ? classes.find((item) => item.id === primaryId) ?? null : null; + const primaryClass = primaryId + ? classes.find((item) => item.id === primaryId) ?? null + : null; + setActiveClass(primaryClass); } function handleRowDoubleClick(cls: A76Class) { - if (!canEdit) return; const fixedClass = cls as FixedAssetClassExtended; - selectedClassIds = [fixedClass.id]; + selectedClassIds = [fixedClass.id, ...selectedClassIds.filter((id) => id !== fixedClass.id)]; setActiveClass(fixedClass); validationError = ''; showInsertDialog = true; } + function handleNew() { + selectedClassIds = []; + clearSelectedClass(); + } + async function handleRefresh() { + if (!canView) return; await loadClasses(); toast.success('Clases actualizadas'); } function handleDelete() { - if (!canDelete || !selectedClass) return; + if (!canDelete) return; + if (!selectedClass) { + toast.error('Selecciona una clase para borrar'); + return; + } showDeleteDialog = true; } async function confirmDelete() { - if (!selectedClass || !companyStore.activeCompany?.id) return; + if (!selectedClass) return; + + const companyId = companyStore.activeCompany?.id; + if (!companyId) { + toast.error('No hay empresa seleccionada'); + return; + } + + const classToDelete = selectedClass; try { - await classesApi.delete(selectedClass.id, companyStore.activeCompany.id); - toast.success(`Clase ${selectedClass.class_code} eliminada`); + // El backend ahora elimina automáticamente la extensión FA si existe + await classesApi.delete(classToDelete.id, companyId); + + toast.success(`Clase ${classToDelete.class_code} eliminada correctamente`); + + // Recargar lista await loadClasses(); + selectedClassIds = []; - setActiveClass(null); + clearSelectedClass(); showDeleteDialog = false; } catch (error) { console.error('Error deleting class:', error); @@ -198,40 +280,60 @@ } } + // Keyboard Shortcuts + import { useShortcuts } from '$lib/hooks/use-shortcuts'; + useShortcuts('Goods / Classes', [ { key: 'Alt+Shift+N', description: 'New Class', action: () => { if (!canCreate) return; - selectedClassIds = []; - setActiveClass(null); + handleNew(); + validationError = ''; showInsertDialog = true; } }, - { key: 'Alt+Shift+R', description: 'Refresh', action: handleRefresh }, + { + key: 'Alt+Shift+R', + description: 'Refresh', + action: handleRefresh + }, { key: 'Alt+Shift+D', description: 'Delete', - action: () => { if (canDelete) handleDelete(); } + action: () => { + if (!canDelete) return; + handleDelete(); + } + }, + { + key: 'Ctrl+S', + description: 'Guardar clase', + action: () => { + if (!showInsertDialog || isSaving) return; + document.dispatchEvent(new CustomEvent('save-form')); + } } ]); -
-
+
+

Clases de Activo Fijo

-

Gestiona las categorías de activo fijo

+

Gestiona y consulta las clases de activo fijo

+ {#if !isError && canCreate} + - {#if canCreate && !isError} - {/if}
@@ -239,36 +341,65 @@ {#if isError} {:else} -
-
- -
+ +
+ +
+ +
+
+
+

Filtros

+ + Filtra las clases por diferentes criterios (los filtros se aplican automáticamente) + +
- +
- - + +
- +
- - + +
+
- -
+ +
+
+

Listado de Clases

+
+ + Mostrando de {filteredClasses.length} registros + + +
+
+ + +
(sorting = val)} + onSortingChange={(newSorting) => (sorting = newSorting)} />
+
- -
-
-

Código de Clase

-

{formData.class_code || '---'}

+ +
+
+

+ Código de Clase +

+

+ {formData.class_code || '---'} +

+
+ +
+
+
+ +

+ {formData.description_es || 'Sin descripción'} +

+
+
+ +

+ {formData.description_en || 'No translation available'} +

+
-
- {#if selectedClass} -
-
- -

{formData.description_es || '-'}

-
-
- -

{formData.description_en || '-'}

-
- -
-
- -

{formData.material_key || '-'}

-
-
- -

{formData.unit_of_measure || '-'}

-
-
- -
- -

- {formatMexTariffDigitsForDisplay(formData.fraction) || '—'} -

-
- -
- -

{formData.eccn_code || '---'}

-
+
+
+ +
+ + {formData.material_key || '-'}
- {:else} -
- -

Selecciona una clase

-
- {/if} -
-
-
- - -
-
-
- {#if canEdit} - - {/if} - {#if canDelete} - - {/if} +
+
+ + {formData.unit_of_measure || '-'} +
+
+ +
+ +

+ {formatMexTariffDigitsForDisplay(formData.fraction) || '—'} +

+
+ + +
+ +

+ {formData.eccn_code || '---'} +

+
{/if}
+
+ + +{#if !isError} +
+
+ +
+ {#if canCreate} + + {/if} + {#if canEdit} + + {/if} + {#if canDelete} + + {/if} +
+
+
+{/if} + + {selectedClass ? 'Editar' : 'Nueva'} Clase de Activo Fijo + {#if validationError} -
-

Error de Validación

-

{validationError}

+
+
+
+ ! +
+
+

Error de Validación

+

{validationError}

+
+ +
{/if}
{ - if (isSaving) return; + externalError={validationError} + onClearError={() => (validationError = '')} + onSave={async (data: Partial) => { + // Evitar múltiples clics + if (isSaving) { + return; + } isSaving = true; validationError = ''; + try { + const cleanData = $state.snapshot(data); + const mxFraccionNorm = normalizeMexTariffDigitsStored( + String(cleanData.import_tariff_code || cleanData.fraction || '').trim() + ); const companyId = companyStore.activeCompany?.id; - if (!companyId) throw new Error('No company active'); - - const mxFraccionNorm = normalizeMexTariffDigitsStored(String(data.import_tariff_code || data.fraction || '').trim()); - if (selectedClass?.id) { - await classesApi.update(selectedClass.id, { - class_code: data.class_code, - description_es: data.description_es, - description_en: data.description_en, - material_key: data.material_key, - unit_of_measure: data.unit_of_measure, - fraction: mxFraccionNorm, - us_fraction: data.us_fraction, - physical_review: data.physical_review ? 1 : 0 - }, companyId); - - if (selectedClass.fa_class_id) { - await faClassesApi.update(selectedClass.fa_class_id, { - depreciation_rate: Number(data.annual_depreciation_rate || data.depreciation_rate || 0), - import_tariff_code: mxFraccionNorm, - fda_code: data.fda_key || data.fda_code, - eccn_code: data.eccn_code - }, companyId); - } - } else { - await classesApi.createFA({ - ...data, - fraction: mxFraccionNorm, - depreciation_rate: Number(data.annual_depreciation_rate || data.depreciation_rate || 0) - }, companyId); + if (!companyId) { + throw new Error('No hay empresa seleccionada'); } - toast.success('Clase guardada correctamente'); + let response; + + if (selectedClass?.id) { + // === ACTUALIZACIÓN === + + response = await classesApi.update( + selectedClass.id, + { + class_code: cleanData.class_code?.trim() || '', + description_es: cleanData.description_es?.trim() || '', + description_en: cleanData.description_en?.trim() || '', + material_key: cleanData.material_key?.trim() || '', + unit_of_measure: cleanData.unit_of_measure?.trim() || '', + fraction: mxFraccionNorm, + us_fraction: cleanData.us_fraction || '', + physical_review: cleanData.physical_review ? 1 : 0, + iva_exempt_fraction: cleanData.iva_exempt_fraction || '' + }, + companyId + ); + + // También actualizar la extensión FA + if (selectedClass.fa_class_id) { + await faClassesApi.update( + selectedClass.fa_class_id, + { + depreciation_rate: + cleanData.annual_depreciation_rate !== undefined && + cleanData.annual_depreciation_rate !== null && + cleanData.annual_depreciation_rate !== '' + ? Number(cleanData.annual_depreciation_rate) + : cleanData.depreciation_rate !== undefined && + cleanData.depreciation_rate !== null && + cleanData.depreciation_rate != null + ? Number(cleanData.depreciation_rate) + : null, + import_tariff_code: mxFraccionNorm || null, + import_tariff_type: cleanData.import_tariff_type || null, + export_tariff_code: cleanData.export_tariff_code || null, + export_tariff_type: cleanData.export_tariff_type || null, + fda_code: (cleanData.fda_key ?? cleanData.fda_code) || null, + eccn_code: cleanData.eccn_code || null + }, + companyId + ); + } else { + await faClassesApi.create( + { + class_id: selectedClass.id, + depreciation_rate: + cleanData.annual_depreciation_rate !== undefined && + cleanData.annual_depreciation_rate !== null && + cleanData.annual_depreciation_rate !== '' + ? Number(cleanData.annual_depreciation_rate) + : cleanData.depreciation_rate !== undefined && + cleanData.depreciation_rate !== null && + cleanData.depreciation_rate != null + ? Number(cleanData.depreciation_rate) + : null, + fda_code: (cleanData.fda_key ?? cleanData.fda_code) || null, + eccn_code: cleanData.eccn_code || null, + class_enabled: true + }, + companyId + ); + } + + // ¡IMPORTANTE! fetchApi NO lanza excepciones, retorna { error, status } + if (response.error) { + console.error('❌ Error en respuesta de actualización:', response); + throw new Error(response.error); + } + } else { + // === CREACIÓN === + const payload = { + class_code: cleanData.class_code?.trim() || '', + description_es: cleanData.description_es?.trim() || '', + description_en: cleanData.description_en?.trim() || '', + material_key: cleanData.material_key?.trim() || '', + unit_of_measure: cleanData.unit_of_measure?.trim() || '', + fraction: mxFraccionNorm, + us_fraction: cleanData.us_fraction?.trim() || '', + sub_key: cleanData.sub_key || '', + physical_review: cleanData.physical_review ? 1 : 0, + iva_exempt_fraction: cleanData.iva_exempt_fraction || '', + depreciation_rate: + cleanData.annual_depreciation_rate !== undefined && + cleanData.annual_depreciation_rate !== null && + cleanData.annual_depreciation_rate !== '' + ? Number(cleanData.annual_depreciation_rate) + : cleanData.depreciation_rate !== undefined && + cleanData.depreciation_rate !== null && + cleanData.depreciation_rate != null + ? Number(cleanData.depreciation_rate) + : null, + fda_code: (cleanData.fda_key ?? cleanData.fda_code) || null, + eccn_code: cleanData.eccn_code || null, + class_enabled: true + }; + + response = await classesApi.createFA(payload, companyId); + + if (response.error) { + console.error('❌ Error del servidor:', response.error); + throw new Error(response.error); + } + } + + // === ÉXITO TOTAL === + const wasUpdate = !!selectedClass?.id; await loadClasses(); showInsertDialog = false; - } catch (e: any) { - validationError = e.message || 'Error al guardar'; + selectedClassIds = []; + clearSelectedClass(); + validationError = ''; + toast.success( + wasUpdate ? 'Clase actualizada correctamente' : 'Clase creada correctamente' + ); + } catch (error: any) { + // === ERROR === + console.error('========================================'); + console.error('❌ ERROR CAPTURADO'); + console.error('Error:', error); + console.error('Error.response:', error?.response); + console.error('Error.response.data:', error?.response?.data); + console.error('Error.detail:', error?.detail); + console.error('========================================'); + + let errorMsg = 'Error al guardar'; + + // Primero intentar con error.detail (fetch directo) + if (error?.detail) { + if (typeof error.detail === 'string') { + errorMsg = error.detail; + } else if (Array.isArray(error.detail)) { + errorMsg = error.detail.map((e: any) => e.msg || e).join(', '); + } + } + // Luego con error.response.data.detail (axios) + else if (error?.response?.data?.detail) { + if (typeof error.response.data.detail === 'string') { + errorMsg = error.response.data.detail; + } else if (Array.isArray(error.response.data.detail)) { + errorMsg = error.response.data.detail.map((e: any) => e.msg || e).join(', '); + } + } + // Por último el mensaje genérico + else if (error?.message) { + errorMsg = error.message; + } + + console.error('📝 Mensaje de error extraído:', errorMsg); + + validationError = errorMsg; + console.error('🔴 validationError asignado:', validationError); + console.error('🔴 showInsertDialog permanece:', showInsertDialog); + console.error('========================================'); + + // NO cerramos el diálogo, permanece abierto } finally { isSaving = false; } }} - onCancel={() => (showInsertDialog = false)} + onCancel={() => { + showInsertDialog = false; + selectedClass = null; + }} />
+ + + + -
+ + + + + ¿Confirmar eliminación? + +
+

+ ¿Estás seguro que deseas eliminar la clase {selectedClass?.class_code}? +

+

+ {selectedClass?.description_es} +

+

Esta acción no se puede deshacer.

+
+ + + + +
+
diff --git a/frontend/src/routes/dashboard/goods/parts/+page.svelte b/frontend/src/routes/dashboard/goods/parts/+page.svelte index d7576a22..0af3d21c 100644 --- a/frontend/src/routes/dashboard/goods/parts/+page.svelte +++ b/frontend/src/routes/dashboard/goods/parts/+page.svelte @@ -2,7 +2,7 @@ import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; import { Label } from '$lib/components/ui/label'; - import { Plus, RefreshCw, Package, Pencil, Trash2 } from 'lucide-svelte'; + import { Plus, RefreshCw, Package } from 'lucide-svelte'; import { toast } from 'svelte-sonner'; import { partsApi, type Part } from '$lib/api/dashboard/a76/parts'; import { clientsProvidersApi } from '$lib/api/dashboard/a76/clients-providers'; @@ -12,19 +12,18 @@ import DataTable from '$lib/components/dashboard/goods/parts/data-table.svelte'; import { createColumns } from '$lib/components/dashboard/goods/parts/columns'; import { obtenerAtajosListaMercancias } from '$lib/config/shortcuts/dashboard/goods/list'; +<<<<<<< HEAD import { currentUser, userHasPermission } from '$lib/auth'; import ErrorState from '$lib/components/dashboard/common/error-state.svelte'; +======= import { formatMexTariffDigitsForDisplay } from '$lib/utils/mexican-tariff-fraction'; +>>>>>>> 88ab6b45df947b2091e7cd5fff7468284a0ceef6 - // Estado + // Estado de la lista de partes let parts = $state([]); - let clientsMap = $state>({}); + let clientsMap = $state>({}); // Mapa ID -> Nombre let selectedPart = $state(null); let isLoading = $state(false); - let error = $state(null); - let status = $state(200); - - // Filtros let searchPartNumber = $state(''); let searchDescription = $state(''); let searchClient = $state(''); @@ -34,6 +33,11 @@ { id: 'updated_at', desc: true } ]); + // Pasamos los permisos a createColumns para bloquear las acciones de fila internamente + const columns = createColumns(() => loadParts()); + let error = $state(null); + let status = $state(200); + // Permisos const canView = $derived(userHasPermission($currentUser, 'goods_parts.view')); const canCreate = $derived(userHasPermission($currentUser, 'goods_parts.create')); @@ -43,9 +47,6 @@ const isError = $derived(!canView || status >= 400 || error); - // Columnas (pasamos permisos para lógica interna si es necesario) - const columns = createColumns(() => loadParts()); - // Filtros locales const filteredParts = $derived( parts.filter((p) => { @@ -71,6 +72,7 @@ }) ); + // Efecto unificado para evitar doble llamada a la API $effect(() => { const companyId = companyStore.activeCompany?.id; if (companyId && sorting) { @@ -78,6 +80,7 @@ } }); + // Shortcuts con validación de permisos useShortcuts( 'Goods List', obtenerAtajosListaMercancias({ @@ -141,8 +144,8 @@ if (response.data) { parts = response.data.items || []; } - } catch (err) { - console.error('Error cargando partes:', err); + } catch (error) { + console.error('Error cargando partes:', error); toast.error('Error al cargar las partes'); } finally { isLoading = false; @@ -150,18 +153,27 @@ } function selectPart(part: Part) { + if (!canView) return; selectedPart = part; } async function handleRefresh() { + if (!canView) return; await loadData(); toast.success('Partes actualizadas'); } async function handleDelete() { - if (!canDelete || !selectedPart) return; + if (!canDelete) return; + if (!selectedPart) { + toast.error('Selecciona una parte para borrar'); + return; + } - if (!confirm(`¿Estás seguro de eliminar la parte ${selectedPart.part_number}?`)) return; + const confirmed = window.confirm( + `¿Estás seguro de que deseas eliminar la parte ${selectedPart.part_number}? Esta acción no se puede deshacer.` + ); + if (!confirmed) return; isLoading = true; try { @@ -181,18 +193,22 @@ } -
+

Catálogo de Partes

-

Gestiona las partes de inventario y activo fijo

+

+ Gestiona y consulta las partes de inventario y activo fijo +

- {#if canCreate && !isError} + {#if !isError && canCreate} - {/each} + + +
-
+
+

Listado de Partes

+
+ + Mostrando {filteredParts.length} registros + + +
+
+ +
+ {}} + {sorting} + onSortingChange={(newSorting) => (sorting = newSorting)} + onRowClick={selectPart} + /> +
+
+
+ +
+
+

+ Número de Parte +

+
+

+ {selectedPart?.part_number || '---'} +

+ {#if selectedPart?.inv_data && selectedPart?.fa_data} + AMBOS + {:else if selectedPart?.inv_data} + SCAI + {:else if selectedPart?.fa_data} + SCAF + {/if} +
+
+ +<<<<<<< HEAD +
+ {#if selectedPart} +
+
+ +

+ {selectedPart.description_spanish || 'Sin descripción'} +

+
+
+ +

+ {selectedPart.description_english || 'No translation available'} +

+
+
+ +
+
+ +
+ + {clientsMap[selectedPart.client_id] || 'Sin cliente'} +
+
+
+ + {selectedPart.part_class || '-'} +
+
+ +
+
+ + {selectedPart.unit_of_measure || '-'} +
+
+ + {selectedPart.unit_weight || '-'} +
+
+ +
+ +

+ {selectedPart.fraction || '0000.00.00'} +

+
+ + {#if selectedPart.unit_cost} +
+ +

+ ${selectedPart.unit_cost} + {selectedPart.currency_key || 'USD'} +

+
+ {/if} + {:else} +
+ +

Selecciona una parte para ver sus detalles

+
+ {/if} +======= + +
{}} {sorting} - onSortingChange={(val) => (sorting = val)} + onSortingChange={(newSorting) => (sorting = newSorting)} onRowClick={selectPart} - onRowDoubleClick={(row) => canEdit && goto(`/dashboard/goods/parts/edit/${row.id}`)} + onRowDoubleClick={(part) => goto(`/dashboard/goods/parts/edit/${part.id}`)} /> -
-
- - -
-
-

Número de Parte

-
-

- {selectedPart?.part_number || '---'} -

- {#if selectedPart} -
- {#if selectedPart.inv_data}SCAI{/if} - {#if selectedPart.fa_data}SCAF{/if} -
- {/if} -
-
- -
- {#if selectedPart} -
-
- -

{selectedPart.description_spanish || '-'}

-
-
- -

{selectedPart.description_english || '-'}

-
- -
-
- -

{clientsMap[selectedPart.client_id] || '-'}

-
-
- -

{selectedPart.part_class || '-'}

-
-
- -
-
- -

{selectedPart.unit_of_measure || '-'}

-
-
- -

{selectedPart.unit_weight || '-'}

-
-
- -
- -

- {formatMexTariffDigitsForDisplay(selectedPart.fraction || '') || '—'} -

-
- - {#if selectedPart.unit_cost} -
- -

${selectedPart.unit_cost} {selectedPart.currency_key}

-
- {/if} -
- {:else} -
- -

Selecciona una parte

-
- {/if} -
-
-
- - -
-
-
- {#if canEdit} - - {/if} - {#if canDelete} - - {/if} +>>>>>>> 88ab6b45df947b2091e7cd5fff7468284a0ceef6
@@ -359,3 +464,47 @@
+ +{#if !isError} +
+
+
+ {#if canCreate} + + {/if} + + {#if canEdit} + + {/if} +======= + +

+ {formatMexTariffDigitsForDisplay(selectedPart.fraction || '') || '—'} +

+
+>>>>>>> 88ab6b45df947b2091e7cd5fff7468284a0ceef6 + + {#if canDelete} + + {/if} +
+
+
+{/if} diff --git a/frontend/src/routes/dashboard/invoices/+page.server.ts b/frontend/src/routes/dashboard/invoices/+page.server.ts index a4f94275..c4180395 100644 --- a/frontend/src/routes/dashboard/invoices/+page.server.ts +++ b/frontend/src/routes/dashboard/invoices/+page.server.ts @@ -102,7 +102,10 @@ export const load: PageServerLoad = async ({ fetch, cookies, url, parent }) => { currentCompanyId: companyId, filters: { invoice_type: invoiceType, +<<<<<<< HEAD +======= operation_type: operationType +>>>>>>> 88ab6b45df947b2091e7cd5fff7468284a0ceef6 } }; } diff --git a/frontend/src/routes/dashboard/reports/invoices/+page.svelte b/frontend/src/routes/dashboard/reports/invoices/+page.svelte index c7f94dd4..91fc58f3 100644 --- a/frontend/src/routes/dashboard/reports/invoices/+page.svelte +++ b/frontend/src/routes/dashboard/reports/invoices/+page.svelte @@ -1580,7 +1580,6 @@ -<<<<<<< HEAD {#if isError} {:else} -
- {#each menuOptions as item} - {#if item.items && item.items.length > 0} - - - {#snippet child({ props })} - + {/snippet}