-
-
- {
- formData.operation_type = v;
- }}
- >
-
-
- {formData.operation_type
- ? (formData.operation_type === 'exp' ? 'Exp' : 'Imp')
- : '...'}
-
-
-
- Exportación
- Importación
-
-
-
-
-
- {
- formData.invoice_type = v ?? '';
- }}
- >
-
-
- {formData.invoice_type
- ? `${formData.invoice_type}`
- : '...'}
-
-
-
- {#each invoiceTypes as type}
-
- {type.key} - {type.description}
-
- {/each}
-
-
-
+
+
+
+ {
+ formData.operation_type = v;
+ }}
+ >
+
+
+ {formData.operation_type ? (formData.operation_type === 'exp' ? 'Exp' : 'Imp') : '...'}
+
+
+
+ Exportación
+ Importación
+
+
+
+
+
+ {
+ formData.invoice_type = v ?? '';
+ }}
+ >
+
+
+ {formData.invoice_type ? `${formData.invoice_type}` : '...'}
+
+
+
+ {#each invoiceTypes as type}
+
+ {type.key} - {type.description}
+
+ {/each}
+
+
+
-
-
- {
- formData.is_pedimento_pending = checked;
- }}
- />
-
-
-
- {
- formData.pedimento_id = v ? parseInt(v) : null;
- if (v) {
- handlePedimentoChange(v);
- }
- }}
- >
-
-
- {formData.pedimento || 'Selecciona pedimento...'}
-
-
-
- {#each pedimentos as pedimento}
-
- {pedimento.customs_office?.slice(0, 2)}-{pedimento.license}-{pedimento.pedimento_number}
-
- {/each}
-
-
-
+
+
+ {
+ formData.is_pedimento_pending = checked;
+ }}
+ />
+
+
+
+ {
+ formData.pedimento_id = v ? parseInt(v) : null;
+ if (v) {
+ handlePedimentoChange(v);
+ }
+ }}
+ >
+
+
+ {formData.pedimento || 'Selecciona pedimento...'}
+
+
+
+ {#each pedimentos as pedimento}
+
+ {pedimento.customs_office?.slice(0, 2)}-{pedimento.license}-{pedimento.pedimento_number}
+
+ {/each}
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
diff --git a/frontend/src/lib/stores/ui.svelte.ts b/frontend/src/lib/stores/ui.svelte.ts
new file mode 100644
index 00000000..36bd0e08
--- /dev/null
+++ b/frontend/src/lib/stores/ui.svelte.ts
@@ -0,0 +1,17 @@
+
+/**
+ * UI Store for managing global UI states
+ */
+class UIStore {
+ private _isExchangeRateDialogOpen = $state(false);
+
+ get isExchangeRateDialogOpen() {
+ return this._isExchangeRateDialogOpen;
+ }
+
+ set isExchangeRateDialogOpen(value: boolean) {
+ this._isExchangeRateDialogOpen = value;
+ }
+}
+
+export const uiStore = new UIStore();
diff --git a/frontend/src/routes/dashboard/+layout.svelte b/frontend/src/routes/dashboard/+layout.svelte
index 9891e638..95c9163b 100644
--- a/frontend/src/routes/dashboard/+layout.svelte
+++ b/frontend/src/routes/dashboard/+layout.svelte
@@ -2,18 +2,18 @@
import { setContext, onMount } from 'svelte';
import { invalidateAll } from '$app/navigation';
import type { LayoutData } from './$types';
- import AppSidebar from "$lib/components/sidebar/app-sidebar.svelte";
- import * as Breadcrumb from "$lib/components/ui/breadcrumb/index.js";
- import { Separator } from "$lib/components/ui/separator/index.js";
- import * as Sidebar from "$lib/components/ui/sidebar/index.js";
- import { companyStore } from "$lib/stores/company.svelte";
- import ExchangeRateGuard from "$lib/components/dashboard/exchange_rate/exchange-rate-guard.svelte";
+ import AppSidebar from '$lib/components/sidebar/app-sidebar.svelte';
+ import * as Breadcrumb from '$lib/components/ui/breadcrumb/index.js';
+ import { Separator } from '$lib/components/ui/separator/index.js';
+ import * as Sidebar from '$lib/components/ui/sidebar/index.js';
+ import { companyStore } from '$lib/stores/company.svelte';
+ import ExchangeRateGuard from '$lib/components/dashboard/exchange_rate/exchange-rate-guard.svelte';
let { data, children }: { data: LayoutData; children: any } = $props();
// Hacer disponible el usuario en el contexto para los componentes hijos
setContext('user', data.user);
-
+
// Inicializar el store con las compañías pre-cargadas desde el servidor
onMount(() => {
if (data.companies) {
@@ -36,7 +36,7 @@
-
+
{@render children()}
-
+
diff --git a/frontend/src/routes/dashboard/invoices/edit/[id]/+page.svelte b/frontend/src/routes/dashboard/invoices/edit/[id]/+page.svelte
index 290aa6ad..52b6ff81 100644
--- a/frontend/src/routes/dashboard/invoices/edit/[id]/+page.svelte
+++ b/frontend/src/routes/dashboard/invoices/edit/[id]/+page.svelte
@@ -8,6 +8,7 @@
import { Badge } from '$lib/components/ui/badge';
import { Separator } from '$lib/components/ui/separator';
import { toast } from 'svelte-sonner';
+ import { uiStore } from '$lib/stores/ui.svelte';
import {
ArrowLeft,
FileText,
@@ -110,7 +111,21 @@
);
let showExchangeRateDialog = $state(false);
+
+ $effect(() => {
+ if (showExchangeRateDialog) {
+ uiStore.isExchangeRateDialogOpen = true;
+ } else {
+ // Solo limpiar si este componente tenía el modal abierto
+ if (uiStore.isExchangeRateDialogOpen && !showExchangeRateDialog) {
+ uiStore.isExchangeRateDialogOpen = false;
+ }
+ }
+ });
+
let missingExchangeRateDate = $state('');
+ let lastFetchedDate = $state('');
+ let originalInvoiceDate = $state(data.invoice?.invoice_date || '');
async function checkExchangeRate(date: string): Promise
{
if (!date || !companyStore?.activeCompany?.id) return true;
@@ -130,8 +145,13 @@
if (items.length === 0) {
console.log('No exchange rate found for', date);
- missingExchangeRateDate = date;
- showExchangeRateDialog = true;
+ if (!uiStore.isExchangeRateDialogOpen) {
+ missingExchangeRateDate = date;
+ showExchangeRateDialog = true;
+ uiStore.isExchangeRateDialogOpen = true;
+ return false;
+ }
+ // Si ya hay un modal abierto, no abrir este pero retornar false para indicar que falta
return false;
}
return true;
@@ -149,13 +169,41 @@
// Efecto para actualizar el tipo de cambio cuando cambia la fecha de factura
$effect(() => {
- if (mounted && companyStore?.activeCompany?.id && InvoiceTopFieldsFormData?.invoice_date) {
- getExchangeRateByDate(InvoiceTopFieldsFormData.invoice_date, companyStore.activeCompany.id)
+ const currentDate = InvoiceTopFieldsFormData?.invoice_date;
+
+ if (mounted && companyStore?.activeCompany?.id && currentDate) {
+ // No buscar si:
+ // 1. Ya tenemos un valor y la fecha es la original (evita sobreescribir al cargar)
+ const currentRate = calculatedExchangeRate;
+ if (
+ !data.isCreate &&
+ currentDate === originalInvoiceDate &&
+ typeof currentRate === 'number' &&
+ currentRate > 0
+ ) {
+ return;
+ }
+
+ // 2. Ya buscamos para esta fecha recientemente
+ if (currentDate === lastFetchedDate) {
+ return;
+ }
+
+ getExchangeRateByDate(currentDate, companyStore.activeCompany.id)
.then((rate) => {
+ lastFetchedDate = currentDate;
if (rate) {
calculatedExchangeRate = rate.value;
} else {
calculatedExchangeRate = 0;
+ // Solo abrir modal si NO es la fecha de hoy (el guard global la maneja)
+ // Y si no hay otro modal de TC abierto ya
+ const today = new Date().toISOString().split('T')[0];
+ if (currentDate !== today && !uiStore.isExchangeRateDialogOpen) {
+ missingExchangeRateDate = currentDate;
+ showExchangeRateDialog = true;
+ uiStore.isExchangeRateDialogOpen = true;
+ }
}
})
.catch((err) => console.error('Error auto-updating exchange rate:', err));
@@ -293,7 +341,7 @@
: null}
Nueva Factura
{#if invoiceTypeInfo}
-
+
- {invoiceTypeInfo.description}
{/if}
@@ -407,7 +455,14 @@
{
+ uiStore.isExchangeRateDialogOpen = false;
+ // Si es factura nueva y no tiene fecha, poner la de hoy
+ if (data.isCreate && !InvoiceTopFieldsFormData.invoice_date) {
+ InvoiceTopFieldsFormData.invoice_date = new Date().toISOString().split('T')[0];
+ }
+
// Actualizar el tipo de cambio mostrado
if (InvoiceTopFieldsFormData.invoice_date && companyStore?.activeCompany?.id) {
getExchangeRateByDate(
@@ -420,9 +475,9 @@
}}
/>