From ddd0635af689b4ac7dd48dfaabd7467f5d390559 Mon Sep 17 00:00:00 2001 From: Kevin_Ramirez Date: Tue, 2 Jun 2026 08:33:24 -0500 Subject: [PATCH 1/7] =?UTF-8?q?fix(xml-rb-systems):=20ocultar=20puertos=20?= =?UTF-8?q?en=20exportaci=C3=B3n=20y=20mover=20validaci=C3=B3n=20a=20impor?= =?UTF-8?q?taci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Para exportación los puertos vienen del manifiesto en BD (entry_port/foreign_exit_port), no del request. El selector de puertos ahora solo aparece cuando movementType === 'Importacion'. Co-Authored-By: Claude Opus 4.7 --- .../transferencia-electronica-modal.svelte | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/frontend/src/lib/components/dashboard/invoices/transferencia-electronica-modal.svelte b/frontend/src/lib/components/dashboard/invoices/transferencia-electronica-modal.svelte index 1f428a92..6d15ab6b 100644 --- a/frontend/src/lib/components/dashboard/invoices/transferencia-electronica-modal.svelte +++ b/frontend/src/lib/components/dashboard/invoices/transferencia-electronica-modal.svelte @@ -129,13 +129,13 @@ // --- XML_RB_SYSTEMS --- if (interfaceType === 'XML_RB_SYSTEMS') { - if (!entryPort || !exitPort) { - toast.error('Debe seleccionar tanto el puerto de entrada como el de salida'); - return; - } try { let res; if (movementType === 'Importacion') { + if (!entryPort || !exitPort) { + toast.error('Debe seleccionar tanto el puerto de entrada como el de salida'); + return; + } const validInvoices = manualInvoices.filter((i) => i && i.trim() !== ''); if (validInvoices.length === 0) { toast.error('Debe seleccionar al menos una factura'); @@ -156,8 +156,6 @@ } res = await reportsTransmissionApi.triggerXmlRbSystemsExpo({ manifest_numbers: validManifests, - entry_port: entryPort, - exit_port: exitPort, include_emanifest: !checks.no_enviar_emanifest }); } @@ -494,8 +492,8 @@ {/if} - - {#if movementType === 'Importacion' || interfaceType === 'XML_RB_SYSTEMS'} + + {#if movementType === 'Importacion'}
From 7a8028ac81791994485306574107cfcfbe42b25b Mon Sep 17 00:00:00 2001 From: Kevin_Ramirez Date: Tue, 2 Jun 2026 08:35:04 -0500 Subject: [PATCH 2/7] =?UTF-8?q?fix(xml-rb-systems):=20mostrar=20toast=20de?= =?UTF-8?q?=20sesi=C3=B3n=20expirada=20en=20error=20401?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Detecta respuestas 401 en los 4 métodos XML RB Systems (impo-temp, impo-def, impo, expo) y lanza un mensaje claro en lugar del error genérico de generación. Además elimina entry_port/exit_port del tipo de request de exportación. Co-Authored-By: Claude Opus 4.7 --- .../lib/api/dashboard/a76/reports/reports-transmission.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/src/lib/api/dashboard/a76/reports/reports-transmission.ts b/frontend/src/lib/api/dashboard/a76/reports/reports-transmission.ts index a845ed10..422205b5 100644 --- a/frontend/src/lib/api/dashboard/a76/reports/reports-transmission.ts +++ b/frontend/src/lib/api/dashboard/a76/reports/reports-transmission.ts @@ -174,6 +174,7 @@ export const reportsTransmissionApi = { body: JSON.stringify(request) }); if (!response.ok) { + if (response.status === 401) throw new Error('Sesión expirada. Por favor recarga la página e inicia sesión nuevamente.'); let errorMessage = 'Error al generar XML RB Systems (importación temporal)'; try { errorMessage = (await response.json()).detail || errorMessage; } catch (e) {} throw new Error(errorMessage); @@ -190,6 +191,7 @@ export const reportsTransmissionApi = { body: JSON.stringify(request) }); if (!response.ok) { + if (response.status === 401) throw new Error('Sesión expirada. Por favor recarga la página e inicia sesión nuevamente.'); let errorMessage = 'Error al generar XML RB Systems (importación definitiva)'; try { errorMessage = (await response.json()).detail || errorMessage; } catch (e) {} throw new Error(errorMessage); @@ -206,6 +208,7 @@ export const reportsTransmissionApi = { body: JSON.stringify(request) }); if (!response.ok) { + if (response.status === 401) throw new Error('Sesión expirada. Por favor recarga la página e inicia sesión nuevamente.'); let errorMessage = 'Error al generar XML RB Systems (importación)'; try { errorMessage = (await response.json()).detail || errorMessage; } catch (e) {} throw new Error(errorMessage); @@ -213,7 +216,7 @@ export const reportsTransmissionApi = { return await response.json(); }, - triggerXmlRbSystemsExpo: async (request: { manifest_numbers: string[]; entry_port?: string; exit_port?: string; include_emanifest?: boolean }) => { + triggerXmlRbSystemsExpo: async (request: { manifest_numbers: string[]; include_emanifest?: boolean }) => { const endpoint = `${BASE_URL}/v1/a76/reports/xml-rb-systems/exportacion/generate`; const token = localStorage.getItem('access_token'); const response = await fetch(endpoint, { @@ -222,6 +225,7 @@ export const reportsTransmissionApi = { body: JSON.stringify(request) }); if (!response.ok) { + if (response.status === 401) throw new Error('Sesión expirada. Por favor recarga la página e inicia sesión nuevamente.'); let errorMessage = 'Error al generar XML RB Systems (exportación)'; try { errorMessage = (await response.json()).detail || errorMessage; } catch (e) {} throw new Error(errorMessage); From 237ed17bddf3c325a80f049db6610f3587956504 Mon Sep 17 00:00:00 2001 From: Kevin_Ramirez Date: Tue, 2 Jun 2026 08:38:51 -0500 Subject: [PATCH 3/7] =?UTF-8?q?fix(xml-rb-systems):=20migrar=20fetch=20dir?= =?UTF-8?q?ecto=20a=20api.post=20para=20silent=20refresh=20autom=C3=A1tico?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Los 4 métodos XML RB Systems usaban fetch() con localStorage, bypaseando el fetchApi() de $lib/api que tiene 401 → silent refresh → retry automático. Ahora el token expirado se renueva de forma transparente sin interrumpir al usuario. Co-Authored-By: Claude Opus 4.7 --- .../a76/reports/reports-transmission.ts | 69 ++++--------------- 1 file changed, 13 insertions(+), 56 deletions(-) diff --git a/frontend/src/lib/api/dashboard/a76/reports/reports-transmission.ts b/frontend/src/lib/api/dashboard/a76/reports/reports-transmission.ts index 422205b5..cdf27443 100644 --- a/frontend/src/lib/api/dashboard/a76/reports/reports-transmission.ts +++ b/frontend/src/lib/api/dashboard/a76/reports/reports-transmission.ts @@ -1,3 +1,4 @@ +import { api } from '$lib/api'; const BASE_URL = import.meta.env.VITE_API_URL || ''; @@ -166,71 +167,27 @@ export const reportsTransmissionApi = { }, triggerXmlRbSystemsImpoTemp: async (request: { invoice_numbers: string[]; entry_port?: string; exit_port?: string }) => { - const endpoint = `${BASE_URL}/v1/a76/reports/xml-rb-systems/importacion-temporal/generate`; - const token = localStorage.getItem('access_token'); - const response = await fetch(endpoint, { - method: 'POST', - headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, - body: JSON.stringify(request) - }); - if (!response.ok) { - if (response.status === 401) throw new Error('Sesión expirada. Por favor recarga la página e inicia sesión nuevamente.'); - let errorMessage = 'Error al generar XML RB Systems (importación temporal)'; - try { errorMessage = (await response.json()).detail || errorMessage; } catch (e) {} - throw new Error(errorMessage); - } - return await response.json(); + const result = await api.post('/v1/a76/reports/xml-rb-systems/importacion-temporal/generate', request); + if (result.error || !result.data) throw new Error(result.error || 'Error al generar XML RB Systems (importación temporal)'); + return result.data; }, triggerXmlRbSystemsImpoDef: async (request: { invoice_numbers: string[]; entry_port?: string; exit_port?: string }) => { - const endpoint = `${BASE_URL}/v1/a76/reports/xml-rb-systems/importacion-definitiva/generate`; - const token = localStorage.getItem('access_token'); - const response = await fetch(endpoint, { - method: 'POST', - headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, - body: JSON.stringify(request) - }); - if (!response.ok) { - if (response.status === 401) throw new Error('Sesión expirada. Por favor recarga la página e inicia sesión nuevamente.'); - let errorMessage = 'Error al generar XML RB Systems (importación definitiva)'; - try { errorMessage = (await response.json()).detail || errorMessage; } catch (e) {} - throw new Error(errorMessage); - } - return await response.json(); + const result = await api.post('/v1/a76/reports/xml-rb-systems/importacion-definitiva/generate', request); + if (result.error || !result.data) throw new Error(result.error || 'Error al generar XML RB Systems (importación definitiva)'); + return result.data; }, triggerXmlRbSystemsImpo: async (request: { invoice_numbers: string[]; entry_port?: string; exit_port?: string }) => { - const endpoint = `${BASE_URL}/v1/a76/reports/xml-rb-systems/importacion/generate`; - const token = localStorage.getItem('access_token'); - const response = await fetch(endpoint, { - method: 'POST', - headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, - body: JSON.stringify(request) - }); - if (!response.ok) { - if (response.status === 401) throw new Error('Sesión expirada. Por favor recarga la página e inicia sesión nuevamente.'); - let errorMessage = 'Error al generar XML RB Systems (importación)'; - try { errorMessage = (await response.json()).detail || errorMessage; } catch (e) {} - throw new Error(errorMessage); - } - return await response.json(); + const result = await api.post('/v1/a76/reports/xml-rb-systems/importacion/generate', request); + if (result.error || !result.data) throw new Error(result.error || 'Error al generar XML RB Systems (importación)'); + return result.data; }, triggerXmlRbSystemsExpo: async (request: { manifest_numbers: string[]; include_emanifest?: boolean }) => { - const endpoint = `${BASE_URL}/v1/a76/reports/xml-rb-systems/exportacion/generate`; - const token = localStorage.getItem('access_token'); - const response = await fetch(endpoint, { - method: 'POST', - headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, - body: JSON.stringify(request) - }); - if (!response.ok) { - if (response.status === 401) throw new Error('Sesión expirada. Por favor recarga la página e inicia sesión nuevamente.'); - let errorMessage = 'Error al generar XML RB Systems (exportación)'; - try { errorMessage = (await response.json()).detail || errorMessage; } catch (e) {} - throw new Error(errorMessage); - } - return await response.json(); + const result = await api.post('/v1/a76/reports/xml-rb-systems/exportacion/generate', request); + if (result.error || !result.data) throw new Error(result.error || 'Error al generar XML RB Systems (exportación)'); + return result.data; }, triggerXmlOptimaExpo: async (request: { manifest_numbers: string[]; consolidar_partidas?: boolean }) => { From 4bbc93534c355a60e1909fb51b7098d72e7e02b4 Mon Sep 17 00:00:00 2001 From: Kevin_Ramirez Date: Tue, 2 Jun 2026 08:44:20 -0500 Subject: [PATCH 4/7] =?UTF-8?q?fix(reports-transmission):=20migrar=20todos?= =?UTF-8?q?=20los=20m=C3=A9todos=20a=20api.post/get=20con=20silent=20refre?= =?UTF-8?q?sh?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Todos los métodos del archivo usaban fetch() directo con localStorage, bypaseando el silent refresh automático. Ahora todos usan api.post()/api.get() de $lib/api para manejo uniforme de 401 → refresh → retry en toda la interfaz. Co-Authored-By: Claude Opus 4.7 --- .../a76/reports/reports-transmission.ts | 180 ++---------------- 1 file changed, 21 insertions(+), 159 deletions(-) diff --git a/frontend/src/lib/api/dashboard/a76/reports/reports-transmission.ts b/frontend/src/lib/api/dashboard/a76/reports/reports-transmission.ts index cdf27443..5b3283a4 100644 --- a/frontend/src/lib/api/dashboard/a76/reports/reports-transmission.ts +++ b/frontend/src/lib/api/dashboard/a76/reports/reports-transmission.ts @@ -1,169 +1,41 @@ import { api } from '$lib/api'; -const BASE_URL = import.meta.env.VITE_API_URL || ''; - export const reportsTransmissionApi = { triggerGeneration: async (request: any) => { - const endpoint = `${BASE_URL}/v1/a76/reports/exportacion/transmission/generate`; - - const token = localStorage.getItem('access_token'); - const response = await fetch(endpoint, { - method: 'POST', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify(request) - }); - - if (!response.ok) { - let errorMessage = 'Error al iniciar la generación del archivo de transmisión'; - try { - const errorData = await response.json(); - errorMessage = errorData.detail || errorData.message || errorMessage; - } catch (e) { - try { - const text = await response.text(); - if (text) errorMessage = text; - } catch (e2) {} - } - throw new Error(errorMessage); - } - return await response.json(); + const result = await api.post('/v1/a76/reports/exportacion/transmission/generate', request); + if (result.error || !result.data) throw new Error(result.error || 'Error al iniciar la generación del archivo de transmisión'); + return result.data; }, getTaskStatus: async (taskId: string) => { - const endpoint = `${BASE_URL}/v1/a76/reports/exportacion/transmission/tasks/${taskId}`; - - const token = localStorage.getItem('access_token'); - const response = await fetch(endpoint, { - method: 'GET', - headers: { 'Authorization': `Bearer ${token}` } - }); - - if (!response.ok) { - let errorMessage = 'Error al consultar estado de la transmisión'; - try { - const errorData = await response.json(); - errorMessage = errorData.detail || errorData.message || errorMessage; - } catch (e) { - try { - const text = await response.text(); - if (text) errorMessage = text; - } catch (e2) {} - } - throw new Error(errorMessage); - } - return await response.json(); + const result = await api.get(`/v1/a76/reports/exportacion/transmission/tasks/${taskId}`); + if (result.error || !result.data) throw new Error(result.error || 'Error al consultar estado de la transmisión'); + return result.data; }, triggerTemporalGeneration: async (request: any) => { - const endpoint = `${BASE_URL}/v1/a76/reports/importacion/transmission/temporal/generate`; - - const token = localStorage.getItem('access_token'); - const response = await fetch(endpoint, { - method: 'POST', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify(request) - }); - - if (!response.ok) { - let errorMessage = 'Error al iniciar la generación temporal'; - try { - const errorData = await response.json(); - errorMessage = errorData.detail || errorData.message || errorMessage; - } catch (e) { - try { - const text = await response.text(); - if (text) errorMessage = text; - } catch (e2) {} - } - throw new Error(errorMessage); - } - return await response.json(); + const result = await api.post('/v1/a76/reports/importacion/transmission/temporal/generate', request); + if (result.error || !result.data) throw new Error(result.error || 'Error al iniciar la generación temporal'); + return result.data; }, getTemporalTaskStatus: async (taskId: string) => { - const endpoint = `${BASE_URL}/v1/a76/reports/importacion/transmission/temporal/tasks/${taskId}`; - - const token = localStorage.getItem('access_token'); - const response = await fetch(endpoint, { - method: 'GET', - headers: { 'Authorization': `Bearer ${token}` } - }); - - if (!response.ok) { - let errorMessage = 'Error al consultar estado de la transmisión temporal'; - try { - const errorData = await response.json(); - errorMessage = errorData.detail || errorData.message || errorMessage; - } catch (e) { - try { - const text = await response.text(); - if (text) errorMessage = text; - } catch (e2) {} - } - throw new Error(errorMessage); - } - return await response.json(); + const result = await api.get(`/v1/a76/reports/importacion/transmission/temporal/tasks/${taskId}`); + if (result.error || !result.data) throw new Error(result.error || 'Error al consultar estado de la transmisión temporal'); + return result.data; }, triggerDefinitiveGeneration: async (request: any) => { - const endpoint = `${BASE_URL}/v1/a76/reports/importacion/transmission/definitive/generate`; - - const token = localStorage.getItem('access_token'); - const response = await fetch(endpoint, { - method: 'POST', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify(request) - }); - - if (!response.ok) { - let errorMessage = 'Error al iniciar la generación definitiva'; - try { - const errorData = await response.json(); - errorMessage = errorData.detail || errorData.message || errorMessage; - } catch (e) { - try { - const text = await response.text(); - if (text) errorMessage = text; - } catch (e2) {} - } - throw new Error(errorMessage); - } - return await response.json(); + const result = await api.post('/v1/a76/reports/importacion/transmission/definitive/generate', request); + if (result.error || !result.data) throw new Error(result.error || 'Error al iniciar la generación definitiva'); + return result.data; }, getDefinitiveTaskStatus: async (taskId: string) => { - const endpoint = `${BASE_URL}/v1/a76/reports/importacion/transmission/definitive/tasks/${taskId}`; - - const token = localStorage.getItem('access_token'); - const response = await fetch(endpoint, { - method: 'GET', - headers: { 'Authorization': `Bearer ${token}` } - }); - - if (!response.ok) { - let errorMessage = 'Error al consultar estado de la transmisión definitiva'; - try { - const errorData = await response.json(); - errorMessage = errorData.detail || errorData.message || errorMessage; - } catch (e) { - try { - const text = await response.text(); - if (text) errorMessage = text; - } catch (e2) {} - } - throw new Error(errorMessage); - } - return await response.json(); + const result = await api.get(`/v1/a76/reports/importacion/transmission/definitive/tasks/${taskId}`); + if (result.error || !result.data) throw new Error(result.error || 'Error al consultar estado de la transmisión definitiva'); + return result.data; }, triggerXmlRbSystemsImpoTemp: async (request: { invoice_numbers: string[]; entry_port?: string; exit_port?: string }) => { @@ -191,18 +63,8 @@ export const reportsTransmissionApi = { }, triggerXmlOptimaExpo: async (request: { manifest_numbers: string[]; consolidar_partidas?: boolean }) => { - const endpoint = `${BASE_URL}/v1/a76/reports/xml-optima/exportacion/generate`; - const token = localStorage.getItem('access_token'); - const response = await fetch(endpoint, { - method: 'POST', - headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, - body: JSON.stringify(request) - }); - if (!response.ok) { - let errorMessage = 'Error al generar XML Optima (exportación)'; - try { errorMessage = (await response.json()).detail || errorMessage; } catch (e) {} - throw new Error(errorMessage); - } - return await response.json(); + const result = await api.post('/v1/a76/reports/xml-optima/exportacion/generate', request); + if (result.error || !result.data) throw new Error(result.error || 'Error al generar XML Optima (exportación)'); + return result.data; } }; From 7ee5200cfa96b2ebd99ba8997cd4883555f6ba6f Mon Sep 17 00:00:00 2001 From: Kevin_Ramirez Date: Tue, 2 Jun 2026 08:48:25 -0500 Subject: [PATCH 5/7] chore(reports-transmission): eliminar triggerXmlRbSystemsImpo sin usar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No hay opción en el modal que lo llame. Solo existen las 8 interfaces del select y los 4 regímenes de importación XML_RB_SYSTEMS. Co-Authored-By: Claude Opus 4.7 --- .../lib/api/dashboard/a76/reports/reports-transmission.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/frontend/src/lib/api/dashboard/a76/reports/reports-transmission.ts b/frontend/src/lib/api/dashboard/a76/reports/reports-transmission.ts index 5b3283a4..d9bccf67 100644 --- a/frontend/src/lib/api/dashboard/a76/reports/reports-transmission.ts +++ b/frontend/src/lib/api/dashboard/a76/reports/reports-transmission.ts @@ -50,12 +50,6 @@ export const reportsTransmissionApi = { return result.data; }, - triggerXmlRbSystemsImpo: async (request: { invoice_numbers: string[]; entry_port?: string; exit_port?: string }) => { - const result = await api.post('/v1/a76/reports/xml-rb-systems/importacion/generate', request); - if (result.error || !result.data) throw new Error(result.error || 'Error al generar XML RB Systems (importación)'); - return result.data; - }, - triggerXmlRbSystemsExpo: async (request: { manifest_numbers: string[]; include_emanifest?: boolean }) => { const result = await api.post('/v1/a76/reports/xml-rb-systems/exportacion/generate', request); if (result.error || !result.data) throw new Error(result.error || 'Error al generar XML RB Systems (exportación)'); From 6cf74c3bbdde312eb6f457e4fc1f62c6f9e60fbb Mon Sep 17 00:00:00 2001 From: Kevin_Ramirez Date: Tue, 2 Jun 2026 08:56:01 -0500 Subject: [PATCH 6/7] fix(exchange-rate-guard): evitar SSR de CreateEditDialog con {#if browser} MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dialog.Portal intenta instanciar clases del DOM durante SSR causando 'Class constructors cannot be invoked without new' en GET /dashboard. El diálogo es puramente client-side, no necesita renderizado en servidor. Co-Authored-By: Claude Opus 4.7 --- .../dashboard/exchange_rate/exchange-rate-guard.svelte | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/lib/components/dashboard/exchange_rate/exchange-rate-guard.svelte b/frontend/src/lib/components/dashboard/exchange_rate/exchange-rate-guard.svelte index 47e19ee7..33164161 100644 --- a/frontend/src/lib/components/dashboard/exchange_rate/exchange-rate-guard.svelte +++ b/frontend/src/lib/components/dashboard/exchange_rate/exchange-rate-guard.svelte @@ -1,5 +1,6 @@ - +{#if browser} + +{/if} From 64bf1fee0bb219b0363f0b6b5a80eeb70daba2e5 Mon Sep 17 00:00:00 2001 From: Kevin_Ramirez Date: Tue, 2 Jun 2026 09:11:00 -0500 Subject: [PATCH 7/7] chore: ignorar carpeta de archivos generados en runtime Co-Authored-By: Claude Opus 4.7 --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a7c93c6b..3126b165 100644 --- a/.gitignore +++ b/.gitignore @@ -82,3 +82,4 @@ backend/celerybeat-schedule celerybeat-schedule celerybeat-schedule.* celerybeat.pid +backend/api/v1/modules/reports/generated/