diff --git a/backend/api/v1/modules/a76/invoices/routes.py b/backend/api/v1/modules/a76/invoices/routes.py index 858d53f3..f5428197 100644 --- a/backend/api/v1/modules/a76/invoices/routes.py +++ b/backend/api/v1/modules/a76/invoices/routes.py @@ -67,6 +67,8 @@ def get_creation_data( required_permissions=["invoice.exp.create"], ) return InvoiceCatalogService.get_creation_data(db, tenant_id, company_id) + except HTTPException: + raise except Exception as e: logger.exception("get_creation_data failed: %s", e) raise HTTPException(status_code=500, detail=f"Error al cargar datos de creación: {str(e)}") @@ -187,6 +189,8 @@ def create_invoice( validate_access_to_resource(db, company_id, current_user, required_permissions=[f"{perm_base}.create"]) return services.InvoiceService.create(db, data, tenant_id, company_id) + except HTTPException: + raise except Exception as e: logger.exception("create_invoice failed: %s", e) raise HTTPException(status_code=500, detail=f"Error al guardar factura: {str(e)}") diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimentos.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimentos.py index c1cfe07f..f2c501a6 100644 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimentos.py +++ b/backend/api/v1/modules/a76/pedmientos/routes/pedimentos.py @@ -30,13 +30,15 @@ async def get_creation_data( Consolidates multiple catalog calls into a single endpoint. """ from core.security import validate_access_to_resource - # Alta de pedimento: misma capacidad que editar en negocio / UI (pedimento-permissions). - validate_access_to_resource(db, company_id, current_user, ["pedimentos_mgmt.edit"]) + # Alta de pedimento: requiere permiso create. + validate_access_to_resource(db, company_id, current_user, ["pedimentos_mgmt.create"]) tenant_id = current_user["tenant_id"] try: return PedimentoCatalogService.get_creation_data(db, tenant_id, company_id) + except HTTPException: + raise except Exception as e: print(f"Error fetching creation data: {e}") import traceback @@ -93,7 +95,7 @@ crud_router = TenantCRUDRoutes( max_page_size=1000, list_permissions=["pedimentos_mgmt.view"], get_permissions=["pedimentos_mgmt.view"], - create_permissions=["pedimentos_mgmt.edit"], + create_permissions=["pedimentos_mgmt.create"], update_permissions=["pedimentos_mgmt.edit"], delete_permissions=["pedimentos_mgmt.delete"], ).router diff --git a/frontend/src/lib/components/dashboard/ports/data-table-actions.svelte b/frontend/src/lib/components/dashboard/ports/data-table-actions.svelte index 002b6c3b..0a2835ee 100644 --- a/frontend/src/lib/components/dashboard/ports/data-table-actions.svelte +++ b/frontend/src/lib/components/dashboard/ports/data-table-actions.svelte @@ -22,31 +22,33 @@ let deleteDialogOpen = $state(false); - - - {#snippet child({ props })} - - {/snippet} - - - Acciones - {#if canEdit} - editDialogOpen = true}> - - Editar - - {/if} - {#if canDelete} - deleteDialogOpen = true} class="text-destructive"> - - Eliminar - - {/if} - - +{#if canEdit || canDelete} + + + {#snippet child({ props })} + + {/snippet} + + + Acciones + {#if canEdit} + editDialogOpen = true}> + + Editar + + {/if} + {#if canDelete} + deleteDialogOpen = true} class="text-destructive"> + + Eliminar + + {/if} + + +{/if} {#if canEdit} ([]); @@ -116,13 +128,25 @@ selectedItem = item; } function handleRowDoubleClick(item: CustomsBroker) { + if (!canEditBroker) { + toast.error('No tienes permiso para editar agentes aduanales'); + return; + } selectedItem = item; goto(`/dashboard/customs_brokers/edit/${item.broker_key}`); } function handleEdit() { + if (!canEditBroker) { + toast.error('No tienes permiso para editar agentes aduanales'); + return; + } if (selectedItem) goto(`/dashboard/customs_brokers/edit/${selectedItem.broker_key}`); } async function handleDelete() { + if (!canDeleteBroker) { + toast.error('No tienes permiso para borrar agentes aduanales'); + return; + } if (!selectedItem || !companyStore.activeCompany?.id) return; showDeleteDialog = true; } @@ -166,6 +190,10 @@ irAduanas: () => (activeTab = 'customs'), crear: () => { if (activeTab === 'brokers') { + if (!canCreateBroker) { + toast.error('No tienes permiso para crear agentes aduanales'); + return; + } goto('/dashboard/customs_brokers/edit/new'); } else { // For Customs Sections, we might need a dialog. @@ -191,6 +219,13 @@ const brokerColumns = createBrokerColumns(handleActionSuccess); +{#if !canViewBrokerList} +
+ +
+{:else}
@@ -419,22 +454,33 @@
{#if activeTab === 'brokers'} - - - + {#if canCreateBroker} + + {/if} + {#if canEditBroker} + + {/if} + {#if canDeleteBroker} + + {/if} {:else}
+{/if} diff --git a/frontend/src/routes/dashboard/customs_brokers/edit/[[id]]/+page.svelte b/frontend/src/routes/dashboard/customs_brokers/edit/[[id]]/+page.svelte index 3cfe017c..021b34e2 100644 --- a/frontend/src/routes/dashboard/customs_brokers/edit/[[id]]/+page.svelte +++ b/frontend/src/routes/dashboard/customs_brokers/edit/[[id]]/+page.svelte @@ -2,11 +2,16 @@ import { page } from '$app/stores'; import { goto } from '$app/navigation'; import { companyStore } from '$lib/stores/company.svelte'; + import { currentUser } from '$lib/auth'; import { customsBrokersApi, uploadCustomsBrokerVuFile, type CreateCustomsBrokerData } from '$lib/api/dashboard/a76/customs-brokers'; + import { + canCreateCustomsBrokers, + canEditCustomsBrokers + } from '$lib/permissions/customs-broker-permissions'; // UI Components import CountryDialog from '$lib/components/dashboard/invoices/edit/items/fa/country-dialog.svelte'; @@ -44,11 +49,15 @@ import { getFileDisplayName } from '$lib/utils'; import { useShortcuts } from '$lib/hooks/use-shortcuts'; import { obtenerAtajosEdicionAgente } from '$lib/config/shortcuts/dashboard/customs_brokers/edit'; + import ErrorState from '$lib/components/dashboard/common/error-state.svelte'; // --- 1. LÓGICA DE IDENTIFICACIÓN --- let routeId = $derived($page.params.id); let isEdit = $derived(!!routeId && routeId !== 'new'); let title = $derived(isEdit ? 'Editar Agente Aduanal' : 'Nuevo Agente Aduanal'); + const canAccessBrokerEditor = $derived( + isEdit ? canEditCustomsBrokers($currentUser) : canCreateCustomsBrokers($currentUser) + ); // --- 2. ESTADO --- let loading = $state(false); @@ -279,6 +288,10 @@ // --- 4. GUARDADO --- async function handleSave() { + if (!canAccessBrokerEditor) { + toast.error('No tienes permiso para guardar agentes aduanales'); + return; + } if (!companyStore.activeCompany) { toast.error('Selecciona una compañía'); return; @@ -406,6 +419,13 @@ ); +{#if !canAccessBrokerEditor} +
+ +
+{:else}
@@ -1144,3 +1164,4 @@
+{/if} diff --git a/frontend/src/routes/dashboard/invoices/+page.svelte b/frontend/src/routes/dashboard/invoices/+page.svelte index d31cd57f..60807102 100644 --- a/frontend/src/routes/dashboard/invoices/+page.svelte +++ b/frontend/src/routes/dashboard/invoices/+page.svelte @@ -1017,6 +1017,11 @@ } function handleCreateClick() { + if (!canCreateInvoice) { + toast.error('No tienes permiso para crear este tipo de factura.'); + return; + } + const params = new URLSearchParams(window.location.search); const queryString = params.toString(); @@ -1533,10 +1538,12 @@ {m.invoice_list_actions_parameters()} - + {#if canCreateInvoice} + + {/if}
diff --git a/frontend/src/routes/dashboard/invoices/edit/[id]/+page.server.ts b/frontend/src/routes/dashboard/invoices/edit/[id]/+page.server.ts index f4857420..2d1eee3b 100644 --- a/frontend/src/routes/dashboard/invoices/edit/[id]/+page.server.ts +++ b/frontend/src/routes/dashboard/invoices/edit/[id]/+page.server.ts @@ -74,10 +74,13 @@ export const load: PageServerLoad = async ({ params, cookies, fetch, url }) => { catalogsData = await creationResponse.json(); } else if (creationResponse.status === 403) { throw error(403, 'No tienes permiso para crear este tipo de factura.'); + } else if (creationResponse.status === 401) { + throw error(401, 'Tu sesión expiró. Vuelve a iniciar sesión.'); + } else if (creationResponse.status === 422) { + throw error(422, 'Parámetros inválidos para crear factura.'); } else { console.error('Error fetching creation data:', creationResponse.status); - // We continuing with empty catalogs might be better than crashing? - // But UI will likely be broken. Let's rely on empty arrays initialization below. + throw error(creationResponse.status, 'No fue posible preparar el formulario de creación.'); } if (settingsResult && settingsResult.settings) { @@ -160,7 +163,9 @@ export const load: PageServerLoad = async ({ params, cookies, fetch, url }) => { } catch (err: any) { console.error('Error in load function:', err); - if (err && err.status === 404) throw err; // Propagate 404 + if (err && typeof err.status === 'number' && [401, 403, 404, 422].includes(err.status)) { + throw err; + } throw error(500, 'Error interno al cargar la página'); } }; diff --git a/frontend/src/routes/dashboard/pedimentos/+page.svelte b/frontend/src/routes/dashboard/pedimentos/+page.svelte index 6f70ee4a..ec31df6b 100644 --- a/frontend/src/routes/dashboard/pedimentos/+page.svelte +++ b/frontend/src/routes/dashboard/pedimentos/+page.svelte @@ -36,6 +36,8 @@ const canViewList = $derived(canViewPedimentosList($currentUser)); const canNuevoPedimentoAction = $derived(canOpenNuevoPedimentoForm($currentUser)); + const canEditPedimentoAction = $derived(canEditPedimento($currentUser)); + const canDeletePedimentoAction = $derived(canDeletePedimento($currentUser)); // Estado para filtros let filters = $state({ @@ -74,21 +76,13 @@ let filters = $state({ } // Escuchar cambios de compañía - const handleCompanyChange = (event: CustomEvent) => { + const handleCompanyChange = () => { // Recargar los datos sin recargar la página completa reloadData(); }; window.addEventListener('companyChanged', handleCompanyChange as EventListener); - void (async () => { - const cid = companyStore.activeCompany?.id; - if (cid) { - const { syncCompanyPermissions } = await import('$lib/auth'); - await syncCompanyPermissions(cid); - } - })(); - // Cleanup return () => { window.removeEventListener('companyChanged', handleCompanyChange as EventListener); @@ -468,7 +462,7 @@ let filters = $state({ function handleCreateClick() { if (!canOpenNuevoPedimentoForm(get(currentUser))) { - toast.error('No tiene permiso para editar pedimentos'); + toast.error('No tiene permiso para crear pedimentos'); return; } window.location.href = '/dashboard/pedimentos/edit/new'; @@ -543,10 +537,12 @@ let filters = $state({ {/each} - + {#if canNuevoPedimentoAction} + + {/if} @@ -624,24 +620,28 @@ let filters = $state({
- - + {#if canEditPedimentoAction} + + {/if} + {#if canDeletePedimentoAction} + + {/if}