From cc3942bbd785b5faf8b602c1da84a03d174127d4 Mon Sep 17 00:00:00 2001 From: hreyes Date: Mon, 6 Apr 2026 15:53:25 -0600 Subject: [PATCH 01/13] feature/recive-respuesta-de-vucem --- .../v1/modules/a76/factura_cove/service.py | 52 ++++++++++++++++--- .../a76/general_catalogs/company/routes.py | 17 ++++-- .../edit/[[id]]/+page.svelte | 9 ++-- 3 files changed, 62 insertions(+), 16 deletions(-) diff --git a/backend/api/v1/modules/a76/factura_cove/service.py b/backend/api/v1/modules/a76/factura_cove/service.py index d79ab6a8..21e4199b 100644 --- a/backend/api/v1/modules/a76/factura_cove/service.py +++ b/backend/api/v1/modules/a76/factura_cove/service.py @@ -88,12 +88,28 @@ class FacturaCoveDomainService: """ vu = ctx.vu - if not (vu.web_service_user and vu.web_service_access_key and vu.fiel_access_key): + # Determinar usuario efectivo de WebService: + # - Preferimos el usuario configurado en VU (web_service_user) + # - Si no existe, usamos el de DODA-PITA (doda_web_service_user) + effective_ws_user = ( + (vu.web_service_user or vu.doda_web_service_user or "").strip() if vu else "" + ) + + # Determinar clave FIEL efectiva. + # Mientras se define el flujo real de administración de FIEL para VU, + # usamos la misma clave de ejemplo que se usó en el JSON que funciona. + hardcoded_fiel_key = "amH8Ax3EJoUBMuQSu4TAzQ==" + clave_fiel_value = hardcoded_fiel_key + + # Validación básica de credenciales VU: para COVE necesitamos al menos + # un usuario de web service (VU o DODA); la clave FIEL se inyecta desde + # la configuración de ejemplo anterior. + if not effective_ws_user: errors.add_error( field="vu", message="Faltan credenciales de web service o clave FIEL en VU", solution=[ - "Captura usuario y clave de web service (web_service_user, web_service_access_key) " + "Captura usuario y clave de web service en la pestaña VU o DODA del agente " "y la clave FIEL en la configuración VU del agente aduanal." ], code="MISSING_VU_CREDENTIALS", @@ -149,12 +165,18 @@ class FacturaCoveDomainService: rfc_usuario_vu = vu.query_tax_id or "" + # Clave/token del webservice: usar el valor de VU si existe, o una + # clave fija de pruebas mientras se termina la configuración real. + hardcoded_ws_key = ( + "RZGd+CB4R6PfSrstOyN8Is9FXL9AK9NPFisyGGaEWa0vVHoVOl8v2SBcHBoGbt3T/4uHTGcsFQO3b7EonWVfugQjBooywbz74K+jM68j8/Y=" + ) + return ConfiguracionVU( rfc_usuario_vu=rfc_usuario_vu, - clave_webservice=vu.web_service_access_key or "", + clave_webservice=(vu.web_service_access_key or hardcoded_ws_key), archivo_cer_base64=cer_b64 or "", archivo_key_base64=key_b64 or "", - clave_fiel=vu.fiel_access_key or "", + clave_fiel=clave_fiel_value, ) def _clientprovider_to_persona(self, cp: ClientProvider) -> PersonaCove: @@ -556,14 +578,30 @@ class FacturaCoveDomainService: raw_patente = (ctx.broker.license if ctx.broker else "") or "" patente_aduanal = raw_patente.strip().upper()[:10] + # Normalizar tipo_figura desde VU: el API externo espera un código corto + # (en el ejemplo: "5" para agente aduanal). Hacemos un mapeo simple + # desde el texto configurado en la UI. + raw_figura = ( + (ctx.vu.vu_figure_type or "").strip().upper() + if ctx.vu and ctx.vu.vu_figure_type + else "" + ) + if "AGENTE" in raw_figura: + tipo_figura = "5" + elif "APODERADO" in raw_figura: + tipo_figura = "6" + elif "MANDATARIO" in raw_figura: + tipo_figura = "7" + else: + # Fallback: recortar a máximo 10 caracteres para cumplir el esquema + tipo_figura = raw_figura[:10] + return FacturaCoveRequest( configuracion_vu=configuracion_vu, rfc_consulta=( (ctx.vu.query_tax_id or "").strip().upper() if ctx.vu and ctx.vu.query_tax_id else "" ), - tipo_figura=( - (ctx.vu.vu_figure_type or "").strip().upper() if ctx.vu and ctx.vu.vu_figure_type else "" - ), + tipo_figura=tipo_figura, numero_factura=numero_factura, tipo_operacion=tipo_operacion, patente_aduanal=patente_aduanal, diff --git a/backend/api/v1/modules/a76/general_catalogs/company/routes.py b/backend/api/v1/modules/a76/general_catalogs/company/routes.py index d6871d64..31eb0912 100644 --- a/backend/api/v1/modules/a76/general_catalogs/company/routes.py +++ b/backend/api/v1/modules/a76/general_catalogs/company/routes.py @@ -432,11 +432,18 @@ async def upload_company_certificate( detail=f"File type not allowed. Allowed: {', '.join(allowed_exts)}", ) - # Validar correspondencia extensión vs tipo (simple check) - if "cer" in certificate_type and file_ext != ".cer": - raise HTTPException(status_code=400, detail="For this certificate type, file must be .cer") - if "key" in certificate_type and file_ext != ".key": - raise HTTPException(status_code=400, detail="For this certificate type, file must be .key") + # Validar correspondencia extensión vs tipo (simple check, respetando sufijo) + ctype = (certificate_type or "").lower() + if ctype.endswith("_cer") and file_ext != ".cer": + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="For this certificate type, file must be .cer", + ) + if ctype.endswith("_key") and file_ext != ".key": + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="For this certificate type, file must be .key", + ) # Validar tamaño content = await file.read() diff --git a/frontend/src/routes/dashboard/general_catalogs/company_information/edit/[[id]]/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/company_information/edit/[[id]]/+page.svelte index 1401d421..d4c5d42c 100644 --- a/frontend/src/routes/dashboard/general_catalogs/company_information/edit/[[id]]/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/company_information/edit/[[id]]/+page.svelte @@ -466,15 +466,16 @@ if (!file) return; - // Validar extensión + // Validar extensión de forma estricta según el tipo de campo: + // - *_cer => solo .cer + // - *_key => solo .key const ext = file.name.split('.').pop()?.toLowerCase(); - let allowed = ['cer', 'key']; - if (type.includes('cer') && ext !== 'cer') { + if (type.toLowerCase().endsWith('_cer') && ext !== 'cer') { error = 'El archivo debe ser .cer'; target.value = ''; // Reset return; } - if (type.includes('key') && ext !== 'key') { + if (type.toLowerCase().endsWith('_key') && ext !== 'key') { error = 'El archivo debe ser .key'; target.value = ''; // Reset return; From df4024436be8711d8f24d1ed5c66140923307eae Mon Sep 17 00:00:00 2001 From: hreyes Date: Tue, 7 Apr 2026 08:15:02 -0600 Subject: [PATCH 02/13] checkpoint --- .../v1/modules/a76/factura_cove/service.py | 90 +++++++++++++++---- backend/core/config.py | 2 + .../invoices/pdf-progress-dialog.svelte | 8 +- 3 files changed, 82 insertions(+), 18 deletions(-) diff --git a/backend/api/v1/modules/a76/factura_cove/service.py b/backend/api/v1/modules/a76/factura_cove/service.py index 21e4199b..19df3840 100644 --- a/backend/api/v1/modules/a76/factura_cove/service.py +++ b/backend/api/v1/modules/a76/factura_cove/service.py @@ -1,12 +1,14 @@ from __future__ import annotations import base64 +import hashlib from dataclasses import dataclass from decimal import Decimal from typing import List, Tuple from sqlalchemy.orm import Session +from core.config import settings from core.database import CoreSessionLocal from core.exceptions import ValidationException, ErrorCollector from core.storage_s3 import get_object_bytes, object_exists @@ -27,6 +29,15 @@ from .schemas import ( ) +# Clave de encriptado/sal para hash de FIEL. +# TODO: mover a configuración (p. ej. variable de entorno) cuando se habilite el flujo real. +FIEL_ENCRYPTION_KEY = "6a7f92d3c8d1e5b3b0ac23ff1926a7c9" + +# Flag para, en el futuro, activar el uso real de la FIEL hasheada. +# Mientras sea False, se seguirá usando la clave FIEL hardcodeada de pruebas. +USE_HASHED_FIEL_FOR_COVE = True + + @dataclass class InvoiceContext: invoice: InvoiceHeader @@ -78,6 +89,19 @@ class FacturaCoveDomainService: return InvoiceContext(invoice=invoice, broker=broker, vu=vu) + def _hash_fiel(self, raw_fiel: str) -> str: + """ + Calcula un hash determinista (base64) de la clave FIEL usando una + clave de encriptado fija. Este valor es el que se enviará como + `clave_fiel` al API externo cuando se active USE_HASHED_FIEL_FOR_COVE. + """ + if not raw_fiel: + return "" + + data = f"{FIEL_ENCRYPTION_KEY}:{raw_fiel}".encode("utf-8") + digest = hashlib.sha256(data).digest() + return base64.b64encode(digest).decode("ascii") + def _build_configuracion_vu(self, ctx: InvoiceContext, errors: ErrorCollector) -> ConfiguracionVU | None: """ Construye la sección configuracion_vu usando CustomsBrokerVU + S3. @@ -96,14 +120,21 @@ class FacturaCoveDomainService: ) # Determinar clave FIEL efectiva. - # Mientras se define el flujo real de administración de FIEL para VU, - # usamos la misma clave de ejemplo que se usó en el JSON que funciona. - hardcoded_fiel_key = "amH8Ax3EJoUBMuQSu4TAzQ==" - clave_fiel_value = hardcoded_fiel_key + # Orden de prioridad: + # 1) Si USE_HASHED_FIEL_FOR_COVE=True y existe vu.fiel_access_key, se hashea. + # 2) En caso contrario, se usa la variable de entorno COVE_FIEL_PASSWORD. + clave_fiel_value = "" + + if USE_HASHED_FIEL_FOR_COVE and vu and getattr(vu, "fiel_access_key", None): + # Usar la clave capturada en VU, hasheada con hashlib + FIEL_ENCRYPTION_KEY + clave_fiel_value = self._hash_fiel(vu.fiel_access_key or "") + + if not clave_fiel_value: + # Fallback: usar la contraseña FIEL configurada vía entorno + clave_fiel_value = (settings.COVE_FIEL_PASSWORD or "").strip() # Validación básica de credenciales VU: para COVE necesitamos al menos - # un usuario de web service (VU o DODA); la clave FIEL se inyecta desde - # la configuración de ejemplo anterior. + # un usuario de web service (VU o DODA) y una clave FIEL no vacía. if not effective_ws_user: errors.add_error( field="vu", @@ -115,6 +146,17 @@ class FacturaCoveDomainService: code="MISSING_VU_CREDENTIALS", ) + if not clave_fiel_value: + errors.add_error( + field="vu.clave_fiel", + message="La clave FIEL para COVE no está configurada. Define COVE_FIEL_PASSWORD o captura la FIEL en VU.", + solution=[ + "Configura la variable de entorno COVE_FIEL_PASSWORD con la contraseña FIEL de VUCEM, " + "o captura la clave FIEL en la configuración VU del agente aduanal." + ], + code="MISSING_FIEL_PASSWORD", + ) + if not (vu.certificate_path and vu.key_path): errors.add_error( field="vu", @@ -412,7 +454,8 @@ class FacturaCoveDomainService: # Saltar partidas sin cantidad válida continue - cantidad = Decimal(str(qty_model.quantity)) + # Cantidad: normalizar a EXACTAMENTE 2 decimales (ej. 12.23) + cantidad = Decimal(str(qty_model.quantity)).quantize(Decimal("0.01")) # Descripción genérica: priorizar descripción de parte / inglés / español descripcion = "" @@ -475,21 +518,24 @@ class FacturaCoveDomainService: or Decimal("0") ) - valor_total = Decimal(str(base_total or 0)) + # Valor total: normalizar a EXACTAMENTE 2 decimales + valor_total = Decimal(str(base_total or 0)).quantize(Decimal("0.01")) if cantidad > 0: - valor_unitario = (valor_total / cantidad).quantize( - Decimal("0.000001") - ) + # Valor unitario también a EXACTAMENTE 2 decimales + valor_unitario = (valor_total / cantidad).quantize(Decimal("0.01")) else: valor_unitario = Decimal("0") - # Valor en dólares: si ya existe, lo usamos; si no, convertimos suponiendo que los totales ya están en USD + # Valor en dólares: si ya existe, lo usamos; si no, asumimos que los totales ya están en USD. if fin_model.value_total_usd: valor_dolares = Decimal(str(fin_model.value_total_usd)) elif tipo_moneda == "USD": valor_dolares = valor_total else: valor_dolares = Decimal("0") + + # Normalizar valor en dólares a EXACTAMENTE 2 decimales + valor_dolares = valor_dolares.quantize(Decimal("0.01")) else: errors.add_error( field="mercancias", @@ -556,9 +602,18 @@ class FacturaCoveDomainService: raise ValidationException("No se puede generar COVE desde la factura", errors=errors.get_errors()) # Campos genéricos que se pueden poblar de forma segura - raw_tipo_operacion = (ctx.invoice.operation_type or "").strip() - # Normalizar tipo_operacion a MAYÚSCULAS (ej. IMP, EXP) con longitud acotada - tipo_operacion = raw_tipo_operacion.upper()[:10] + raw_tipo_operacion = (ctx.invoice.operation_type or "").strip().lower() + # Mapear tipo_operacion al código esperado por el API de COVE + # Ejemplos: + # - IMP / importación -> "TOCE.IMP" + # - EXP / exportación -> "TOCE.EXP" + if raw_tipo_operacion in {"imp", "import", "importacion", "importación"}: + tipo_operacion = "TOCE.IMP" + elif raw_tipo_operacion in {"exp", "export", "exportacion", "exportación"}: + tipo_operacion = "TOCE.EXP" + else: + # Fallback seguro: usar valor por defecto de importación + tipo_operacion = "TOCE.IMP" numero_factura = (ctx.invoice.invoice_number or "").strip()[:50] fecha_expedicion = ctx.invoice.invoice_date or ctx.invoice.emission_date or ctx.invoice.capture_date @@ -598,8 +653,11 @@ class FacturaCoveDomainService: return FacturaCoveRequest( configuracion_vu=configuracion_vu, + # El RFC de consulta NO debe ser igual al RFC del que registra el comprobante. + # Usamos como RFC de consulta el RFC del agente aduanal (customs broker), + # y dejamos que configuracion_vu.rfc_usuario_vu represente al contribuyente. rfc_consulta=( - (ctx.vu.query_tax_id or "").strip().upper() if ctx.vu and ctx.vu.query_tax_id else "" + (ctx.broker.tax_id or "").strip().upper() if ctx.broker and ctx.broker.tax_id else "" ), tipo_figura=tipo_figura, numero_factura=numero_factura, diff --git a/backend/core/config.py b/backend/core/config.py index 84a450c4..e52af02d 100644 --- a/backend/core/config.py +++ b/backend/core/config.py @@ -56,6 +56,8 @@ class Settings(BaseSettings): SITAR_API_URL: str = "api.sitar.aduanasoft.com:880" # Endpoint base para el servicio externo de Factura COVE COVE_API_URL: str = "" + # Clave FIEL (contraseña) para COVE/VUCEM. Debe configurarse vía entorno en entornos reales. + COVE_FIEL_PASSWORD: str = "" SITAR_API_USER: str = "" SITAR_API_PASSWORD: str = "" diff --git a/frontend/src/lib/components/dashboard/invoices/pdf-progress-dialog.svelte b/frontend/src/lib/components/dashboard/invoices/pdf-progress-dialog.svelte index 6618906f..76485457 100644 --- a/frontend/src/lib/components/dashboard/invoices/pdf-progress-dialog.svelte +++ b/frontend/src/lib/components/dashboard/invoices/pdf-progress-dialog.svelte @@ -195,9 +195,13 @@ COVE: {lastResult.cove_number} {/if} - {#if taskId} + {#if lastResult?.external_task_id} - Task ID: {taskId} + Task ID COVE: {lastResult.external_task_id} + + {:else if taskId} + + Task interno: {taskId} {/if} From 2e529a0026d741a4a27759365fbdc85f7c3a2613 Mon Sep 17 00:00:00 2001 From: hreyes Date: Tue, 7 Apr 2026 12:30:32 -0600 Subject: [PATCH 03/13] feature/botones-vu-more-actions --- .../api/v1/modules/a76/factura_cove/routes.py | 7 +- .../api/v1/modules/a76/factura_cove/tasks.py | 52 ++++++++++ .../routes/dashboard/invoices/+page.svelte | 97 ++++++++++++++++++- .../dashboard/invoices/edit/[id]/+page.svelte | 4 +- 4 files changed, 150 insertions(+), 10 deletions(-) diff --git a/backend/api/v1/modules/a76/factura_cove/routes.py b/backend/api/v1/modules/a76/factura_cove/routes.py index 341e90b6..9c20bb3a 100644 --- a/backend/api/v1/modules/a76/factura_cove/routes.py +++ b/backend/api/v1/modules/a76/factura_cove/routes.py @@ -59,11 +59,8 @@ def trigger_cove_for_invoice( task=factura_cove_generate, tenant_id=tenant_id, company_id=body.company_id, - requested_by_user=( - current_user.get("preferred_username") - or current_user.get("email") - or current_user.get("sub") - ), + # Hardcodeado a petición: siempre registrar esta tarea con el correo de Hugo Reyes. + requested_by_user="hreyes@aduanasoft.com.mx", task_name="factura_cove_generate", task_group="factura_cove", task_origin="a76/factura_cove/invoices/cove", diff --git a/backend/api/v1/modules/a76/factura_cove/tasks.py b/backend/api/v1/modules/a76/factura_cove/tasks.py index c4b95130..c0f2b4c5 100644 --- a/backend/api/v1/modules/a76/factura_cove/tasks.py +++ b/backend/api/v1/modules/a76/factura_cove/tasks.py @@ -9,6 +9,7 @@ from celery import Task from core.celery_app import celery_app from core.database import CoreSessionLocal from core.exceptions import ValidationException +from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceComplianceMx from .service import FacturaCoveDomainService from .schemas import GenerateCoveResult @@ -21,6 +22,50 @@ def _progress(task: Task, current: int, status: str) -> None: task.update_state(state="PROGRESS", meta={"current": current, "status": status}) +def _save_cove_result( + db: "Session", invoice_id: int, final_external: CoveExternalResult +) -> None: + """ + Persiste en la factura el número de COVE y el número de operación VUCEM + cuando el servicio externo reporta SUCCESS. + """ + if final_external.status != "success": + return + + if not final_external.cove_number and not final_external.vucem_operation_num: + return + + invoice = db.get(InvoiceHeader, invoice_id) + if not invoice: + logger.error("No se encontró la factura %s para guardar COVE", invoice_id) + return + + compliance = invoice.compliance_mx + if not compliance: + # Creamos un registro mínimo de compliance ligado a la factura. + compliance = InvoiceComplianceMx( + invoice_id=invoice.id, + tenant_id=invoice.tenant_id, + company_id=invoice.company_id, + ) + db.add(compliance) + + # Idempotencia básica: solo sobrescribir si está vacío o coincide. + if final_external.cove_number: + current_cove = compliance.edocument or "" + new_cove = final_external.cove_number or "" + if not current_cove or current_cove == new_cove: + compliance.edocument = new_cove + + if final_external.vucem_operation_num: + current_op = compliance.vucem_operation_num or "" + new_op = final_external.vucem_operation_num or "" + if not current_op or current_op == new_op: + compliance.vucem_operation_num = new_op + + db.commit() + + def _poll_external_status( task: Task, external: CoveExternalService, external_task_id: str, timeout_seconds: int = 300 ) -> CoveExternalResult: @@ -171,6 +216,13 @@ def factura_cove_generate(self: Task, invoice_id: int, tenant_id: int, company_i # Fallback: usamos el resultado tal cual devolvió el endpoint de generación final_external = external_result + # Intentar persistir COVE / número de operación en la factura cuando sea éxito. + try: + _save_cove_result(db, invoice_id, final_external) + except Exception: + # No fallamos la tarea por errores de persistencia; solo los registramos. + logger.exception("Error guardando COVE en la factura %s", invoice_id) + _progress(self, 100, "Proceso de COVE finalizado.") result = GenerateCoveResult( diff --git a/frontend/src/routes/dashboard/invoices/+page.svelte b/frontend/src/routes/dashboard/invoices/+page.svelte index 85d94657..389a2874 100644 --- a/frontend/src/routes/dashboard/invoices/+page.svelte +++ b/frontend/src/routes/dashboard/invoices/+page.svelte @@ -217,6 +217,17 @@ let hasMore = $derived(allItems.length < totalItems); let error = $state(data.error || null); + // Submenú contextual para Interface VU (click derecho) + let showVuSubmenu = $state(false); + let vuSubmenuPosition = $state({ x: 0, y: 0 }); + + // Si se pierde la selección, cerramos el submenú contextual VU + $effect(() => { + if (!hasSelection) { + showVuSubmenu = false; + } + }); + // Estado para selección de filas (múltiple) let selectedInvoiceIds = $state([]); // Estado para los diálogos de acciones @@ -1129,6 +1140,65 @@ + {#if showVuSubmenu} + +
(showVuSubmenu = false)} + on:contextmenu|preventDefault={() => (showVuSubmenu = false)} + /> + +
+ + + + +
+ {/if} +
- + { + if (!open) { + showVuSubmenu = false; + } + }} + > {#snippet child({ props })}
From 9d8d27a9d05ac2686dfde4660935eef24d94ccf9 Mon Sep 17 00:00:00 2001 From: hreyes Date: Tue, 7 Apr 2026 14:55:55 -0600 Subject: [PATCH 05/13] fix/variable-encriptado --- .env.example | 3 +++ backend/.env.example | 3 +++ .../v1/modules/a76/factura_cove/service.py | 27 ++++++++----------- backend/core/config.py | 3 +-- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.env.example b/.env.example index 1ef60c19..c0578339 100644 --- a/.env.example +++ b/.env.example @@ -61,6 +61,9 @@ S3_USE_SSL=false S3_FILE_STORAGE=true S3_PRESIGNED_EXPIRES_SECONDS=3600 +COVE_FIEL_PASSWORD= +COVE_FIEL_HASH_KEY= + # ----- Sitar API ----- SITAR_API_URL=http://api.sitar.aduanasoft.com SITAR_API_USER=user_sitar_api diff --git a/backend/.env.example b/backend/.env.example index 77ae339c..b3614acf 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -28,6 +28,9 @@ CORS_ORIGINS=http://localhost:5173,http://localhost:3000 # License Service LICENSE_CHECK_ENABLED=True +COVE_FIEL_PASSWORD= +COVE_FIEL_HASH_KEY= + # Synchronization (Hub & Spoke) SYNC_SECRET_TOKEN=change-this-sync-token-in-production # Only for spokes/clients. Leave empty if this is the Hub. diff --git a/backend/api/v1/modules/a76/factura_cove/service.py b/backend/api/v1/modules/a76/factura_cove/service.py index 19df3840..7f51c469 100644 --- a/backend/api/v1/modules/a76/factura_cove/service.py +++ b/backend/api/v1/modules/a76/factura_cove/service.py @@ -28,16 +28,6 @@ from .schemas import ( PersonaCove, ) - -# Clave de encriptado/sal para hash de FIEL. -# TODO: mover a configuración (p. ej. variable de entorno) cuando se habilite el flujo real. -FIEL_ENCRYPTION_KEY = "6a7f92d3c8d1e5b3b0ac23ff1926a7c9" - -# Flag para, en el futuro, activar el uso real de la FIEL hasheada. -# Mientras sea False, se seguirá usando la clave FIEL hardcodeada de pruebas. -USE_HASHED_FIEL_FOR_COVE = True - - @dataclass class InvoiceContext: invoice: InvoiceHeader @@ -92,13 +82,18 @@ class FacturaCoveDomainService: def _hash_fiel(self, raw_fiel: str) -> str: """ Calcula un hash determinista (base64) de la clave FIEL usando una - clave de encriptado fija. Este valor es el que se enviará como - `clave_fiel` al API externo cuando se active USE_HASHED_FIEL_FOR_COVE. + clave de encriptado fija obtenida desde configuración. Este valor es + el que se enviará como `clave_fiel` al API externo cuando exista una + clave FIEL capturada en VU. """ if not raw_fiel: return "" - data = f"{FIEL_ENCRYPTION_KEY}:{raw_fiel}".encode("utf-8") + hash_key = (settings.COVE_FIEL_HASH_KEY or "").strip() + if not hash_key: + return "" + + data = f"{hash_key}:{raw_fiel}".encode("utf-8") digest = hashlib.sha256(data).digest() return base64.b64encode(digest).decode("ascii") @@ -121,12 +116,12 @@ class FacturaCoveDomainService: # Determinar clave FIEL efectiva. # Orden de prioridad: - # 1) Si USE_HASHED_FIEL_FOR_COVE=True y existe vu.fiel_access_key, se hashea. + # 1) Si existe vu.fiel_access_key, se hashea. # 2) En caso contrario, se usa la variable de entorno COVE_FIEL_PASSWORD. clave_fiel_value = "" - if USE_HASHED_FIEL_FOR_COVE and vu and getattr(vu, "fiel_access_key", None): - # Usar la clave capturada en VU, hasheada con hashlib + FIEL_ENCRYPTION_KEY + if vu and getattr(vu, "fiel_access_key", None): + # Usar la clave capturada en VU, hasheada con hashlib + COVE_FIEL_HASH_KEY clave_fiel_value = self._hash_fiel(vu.fiel_access_key or "") if not clave_fiel_value: diff --git a/backend/core/config.py b/backend/core/config.py index e52af02d..5f8bea29 100644 --- a/backend/core/config.py +++ b/backend/core/config.py @@ -54,10 +54,9 @@ class Settings(BaseSettings): # External APIs SITAR_API_URL: str = "api.sitar.aduanasoft.com:880" - # Endpoint base para el servicio externo de Factura COVE COVE_API_URL: str = "" - # Clave FIEL (contraseña) para COVE/VUCEM. Debe configurarse vía entorno en entornos reales. COVE_FIEL_PASSWORD: str = "" + COVE_FIEL_HASH_KEY: str = "" SITAR_API_USER: str = "" SITAR_API_PASSWORD: str = "" From f7af5cb184d86fd3ae0dbd4a1b89c4990f709f82 Mon Sep 17 00:00:00 2001 From: hreyes Date: Tue, 7 Apr 2026 15:16:22 -0600 Subject: [PATCH 06/13] feature/filtros-cambio-posicion --- .../routes/dashboard/invoices/+page.svelte | 64 ++++++++----------- 1 file changed, 25 insertions(+), 39 deletions(-) diff --git a/frontend/src/routes/dashboard/invoices/+page.svelte b/frontend/src/routes/dashboard/invoices/+page.svelte index cff0d5df..5bc72e91 100644 --- a/frontend/src/routes/dashboard/invoices/+page.svelte +++ b/frontend/src/routes/dashboard/invoices/+page.svelte @@ -869,55 +869,41 @@

Facturas

Gestiona las facturas del sistema

-
- -
- - - Filtros - Filtra las facturas por diferentes criterios (los filtros se aplican automáticamente) - - -
-
- - -
-
- - -
-
-
-
{#if error} From 55821e69e58ae67629a253e55b942fe5bf72af1f Mon Sep 17 00:00:00 2001 From: hreyes Date: Wed, 8 Apr 2026 07:16:59 -0600 Subject: [PATCH 07/13] feature/upgrade-styles-pediment --- .../a76/pedmientos/services/pedimentos.py | 18 ++- .../src/lib/api/dashboard/a76/pedimentos.ts | 1 + .../clients_and_providers/data-table.svelte | 5 +- .../export/manifest/data-table.svelte | 5 +- .../general_catalogs/doda/data-table.svelte | 5 +- .../fractions/HistoricalFractionList.svelte | 78 +++++++---- .../dashboard/goods/parts/data-table.svelte | 5 +- .../dashboard/pedimentos/data-table.svelte | 5 +- .../code_pedimento_regimens/data-table.svelte | 5 +- .../containers/data-table.svelte | 5 +- .../countries/data-table.svelte | 5 +- .../currency_types/data-table.svelte | 5 +- .../customs_sections/data-table.svelte | 5 +- .../customs_warehouses/data-table.svelte | 5 +- .../invoice_types/data-table.svelte | 5 +- .../material_types/data-table.svelte | 5 +- .../payment_methods/data-table.svelte | 5 +- .../pedimento_codes/data-table.svelte | 5 +- .../pedimento_regimens/data-table.svelte | 5 +- .../reference_data/sectors/data-table.svelte | 5 +- .../reference_data/states/data-table.svelte | 5 +- .../transport_modes/data-table.svelte | 5 +- .../transport_types/data-table.svelte | 5 +- .../valuation_methods/data-table.svelte | 5 +- .../dashboard/seal/data-table.svelte | 5 +- .../routes/dashboard/pedimentos/+page.svelte | 125 ++++++++---------- 26 files changed, 210 insertions(+), 122 deletions(-) diff --git a/backend/api/v1/modules/a76/pedmientos/services/pedimentos.py b/backend/api/v1/modules/a76/pedmientos/services/pedimentos.py index 09a607df..a58756f6 100644 --- a/backend/api/v1/modules/a76/pedmientos/services/pedimentos.py +++ b/backend/api/v1/modules/a76/pedmientos/services/pedimentos.py @@ -3,9 +3,10 @@ Service layer for Pedimentos CRUD operations """ import logging +import re from typing import Any, Dict, List, Optional -from sqlalchemy import desc +from sqlalchemy import desc, func from sqlalchemy.orm import Session, joinedload from sqlalchemy.orm import selectinload from sqlalchemy.exc import IntegrityError @@ -95,10 +96,21 @@ class PedimentosService: if filters.get("status"): query = query.filter(Pedimentos.status == filters["status"]) if filters.get("client_id"): - query = query.filter( - Pedimentos.client_id == filters["client_id"]) + query = query.filter(Pedimentos.client_id == filters["client_id"]) if filters.get("year"): query = query.filter(Pedimentos.year == filters["year"]) + if filters.get("pedimento"): + raw_value = str(filters["pedimento"]) + # Normalizar: dejar solo dígitos (ignorar guiones, espacios, etc.) + normalized = re.sub(r"\D", "", raw_value) + if normalized: + ped_key_expr = func.concat( + func.coalesce(Pedimentos.year, ""), + func.coalesce(func.substr(Pedimentos.customs_office, 1, 2), ""), + func.coalesce(Pedimentos.license, ""), + func.coalesce(Pedimentos.pedimento_number, ""), + ) + query = query.filter(ped_key_expr.ilike(f"%{normalized}%")) total = query.count() diff --git a/frontend/src/lib/api/dashboard/a76/pedimentos.ts b/frontend/src/lib/api/dashboard/a76/pedimentos.ts index dd876a08..9c5e93ec 100644 --- a/frontend/src/lib/api/dashboard/a76/pedimentos.ts +++ b/frontend/src/lib/api/dashboard/a76/pedimentos.ts @@ -273,6 +273,7 @@ export interface UpdatePedimentoData { export interface PedimentoFilters { status?: string; client_id?: number; + pedimento?: string; year?: string; sort_by?: string; sort_order?: 'asc' | 'desc' | string; diff --git a/frontend/src/lib/components/dashboard/clients_and_providers/data-table.svelte b/frontend/src/lib/components/dashboard/clients_and_providers/data-table.svelte index 4c448e3d..2f4a88e7 100644 --- a/frontend/src/lib/components/dashboard/clients_and_providers/data-table.svelte +++ b/frontend/src/lib/components/dashboard/clients_and_providers/data-table.svelte @@ -60,7 +60,10 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/export/manifest/data-table.svelte b/frontend/src/lib/components/dashboard/export/manifest/data-table.svelte index 0cdb9644..ec6893aa 100644 --- a/frontend/src/lib/components/dashboard/export/manifest/data-table.svelte +++ b/frontend/src/lib/components/dashboard/export/manifest/data-table.svelte @@ -71,7 +71,10 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/general_catalogs/doda/data-table.svelte b/frontend/src/lib/components/dashboard/general_catalogs/doda/data-table.svelte index b96d47d8..7b79bea9 100644 --- a/frontend/src/lib/components/dashboard/general_catalogs/doda/data-table.svelte +++ b/frontend/src/lib/components/dashboard/general_catalogs/doda/data-table.svelte @@ -68,7 +68,10 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/goods/fractions/HistoricalFractionList.svelte b/frontend/src/lib/components/dashboard/goods/fractions/HistoricalFractionList.svelte index f16442a6..d4ed14a3 100644 --- a/frontend/src/lib/components/dashboard/goods/fractions/HistoricalFractionList.svelte +++ b/frontend/src/lib/components/dashboard/goods/fractions/HistoricalFractionList.svelte @@ -12,19 +12,22 @@ import { toast } from 'svelte-sonner'; import HistoricalFractionDialog from './HistoricalFractionDialog.svelte'; - import { companyStore } from '$lib/stores/company.svelte'; +import { companyStore } from '$lib/stores/company.svelte'; - let fractions = $state([]); - let loading = $state(false); - let historicalFraction = $state(''); - let page = $state(1); - let totalItems = $state(0); - let totalPages = $state(0); - let pageSize = 50; +let { title = 'Fracciones históricas' }: { title?: string } = $props(); - let searchTimeout: ReturnType; - let observer: IntersectionObserver; - let sentinel: HTMLDivElement; +let fractions = $state([]); +let loading = $state(false); +let historicalFraction = $state(''); +let page = $state(1); +let totalItems = $state(0); +let totalPages = $state(0); +let pageSize = 50; + +let searchTimeout: ReturnType; +let observer: IntersectionObserver; +let sentinel: HTMLDivElement; +let scrollContainer: HTMLDivElement; // Infinite scroll state let hasMore = $state(true); @@ -138,7 +141,10 @@ loadFractions(false); } }, - { rootMargin: '100px' } + { + root: scrollContainer || null, + rootMargin: '100px' + } ); if (sentinel) observer.observe(sentinel); @@ -163,20 +169,38 @@ }); -
+
+ +
+
+

{title}

+

+ Gestiona las fracciones históricas de la tarifa. +

+
+ +
+ +
- + Fracción Histórica +
-
-
- + +
+
+ Fracción @@ -269,11 +294,12 @@ {/if} - -
+ - -
+ +
+
+
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/pedimentos/data-table.svelte b/frontend/src/lib/components/dashboard/pedimentos/data-table.svelte index f2141ae7..3b32dada 100644 --- a/frontend/src/lib/components/dashboard/pedimentos/data-table.svelte +++ b/frontend/src/lib/components/dashboard/pedimentos/data-table.svelte @@ -97,7 +97,10 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/data-table.svelte index 4c448e3d..2f4a88e7 100644 --- a/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/data-table.svelte @@ -60,7 +60,10 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/reference_data/containers/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/containers/data-table.svelte index 4c448e3d..2f4a88e7 100644 --- a/frontend/src/lib/components/dashboard/reference_data/containers/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/containers/data-table.svelte @@ -60,7 +60,10 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/reference_data/countries/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/countries/data-table.svelte index 4c448e3d..2f4a88e7 100644 --- a/frontend/src/lib/components/dashboard/reference_data/countries/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/countries/data-table.svelte @@ -60,7 +60,10 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/reference_data/currency_types/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/currency_types/data-table.svelte index 4c448e3d..2f4a88e7 100644 --- a/frontend/src/lib/components/dashboard/reference_data/currency_types/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/currency_types/data-table.svelte @@ -60,7 +60,10 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/reference_data/customs_sections/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/customs_sections/data-table.svelte index 4c448e3d..2f4a88e7 100644 --- a/frontend/src/lib/components/dashboard/reference_data/customs_sections/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/customs_sections/data-table.svelte @@ -60,7 +60,10 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/reference_data/customs_warehouses/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/customs_warehouses/data-table.svelte index 4c448e3d..2f4a88e7 100644 --- a/frontend/src/lib/components/dashboard/reference_data/customs_warehouses/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/customs_warehouses/data-table.svelte @@ -60,7 +60,10 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/reference_data/invoice_types/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/invoice_types/data-table.svelte index 4c448e3d..2f4a88e7 100644 --- a/frontend/src/lib/components/dashboard/reference_data/invoice_types/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/invoice_types/data-table.svelte @@ -60,7 +60,10 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/reference_data/material_types/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/material_types/data-table.svelte index 4c448e3d..2f4a88e7 100644 --- a/frontend/src/lib/components/dashboard/reference_data/material_types/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/material_types/data-table.svelte @@ -60,7 +60,10 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/reference_data/payment_methods/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/payment_methods/data-table.svelte index 4c448e3d..2f4a88e7 100644 --- a/frontend/src/lib/components/dashboard/reference_data/payment_methods/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/payment_methods/data-table.svelte @@ -60,7 +60,10 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/reference_data/pedimento_codes/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/pedimento_codes/data-table.svelte index 4c448e3d..2f4a88e7 100644 --- a/frontend/src/lib/components/dashboard/reference_data/pedimento_codes/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/pedimento_codes/data-table.svelte @@ -60,7 +60,10 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/reference_data/pedimento_regimens/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/pedimento_regimens/data-table.svelte index 4c448e3d..2f4a88e7 100644 --- a/frontend/src/lib/components/dashboard/reference_data/pedimento_regimens/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/pedimento_regimens/data-table.svelte @@ -60,7 +60,10 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/reference_data/sectors/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/sectors/data-table.svelte index 4c448e3d..2f4a88e7 100644 --- a/frontend/src/lib/components/dashboard/reference_data/sectors/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/sectors/data-table.svelte @@ -60,7 +60,10 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/reference_data/states/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/states/data-table.svelte index 4c448e3d..2f4a88e7 100644 --- a/frontend/src/lib/components/dashboard/reference_data/states/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/states/data-table.svelte @@ -60,7 +60,10 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/reference_data/transport_modes/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/transport_modes/data-table.svelte index 4c448e3d..2f4a88e7 100644 --- a/frontend/src/lib/components/dashboard/reference_data/transport_modes/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/transport_modes/data-table.svelte @@ -60,7 +60,10 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/reference_data/transport_types/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/transport_types/data-table.svelte index 4c448e3d..2f4a88e7 100644 --- a/frontend/src/lib/components/dashboard/reference_data/transport_types/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/transport_types/data-table.svelte @@ -60,7 +60,10 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/reference_data/valuation_methods/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/valuation_methods/data-table.svelte index 4c84570d..36558b68 100644 --- a/frontend/src/lib/components/dashboard/reference_data/valuation_methods/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/valuation_methods/data-table.svelte @@ -61,7 +61,10 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/seal/data-table.svelte b/frontend/src/lib/components/dashboard/seal/data-table.svelte index 207266e0..6a70e449 100644 --- a/frontend/src/lib/components/dashboard/seal/data-table.svelte +++ b/frontend/src/lib/components/dashboard/seal/data-table.svelte @@ -61,7 +61,10 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/routes/dashboard/pedimentos/+page.svelte b/frontend/src/routes/dashboard/pedimentos/+page.svelte index 34f59e23..ce3c3e47 100644 --- a/frontend/src/routes/dashboard/pedimentos/+page.svelte +++ b/frontend/src/routes/dashboard/pedimentos/+page.svelte @@ -11,7 +11,7 @@ import type { PageData } from './$types'; import { browser } from '$app/environment'; import { companyStore } from '$lib/stores/company.svelte'; - import { Edit, Send, Plus, Filter, Trash2, RefreshCw } from 'lucide-svelte'; +import { Edit, Send, Plus, Trash2, RefreshCw } from 'lucide-svelte'; import { obtenerAtajosListaPedimento } from '$lib/config/shortcuts/dashboard/pedimentos/list'; import { useShortcuts } from '$lib/hooks/use-shortcuts'; import { goto } from '$app/navigation'; @@ -24,12 +24,12 @@ // Los datos iniciales vienen del servidor let { data }: { data: PageData } = $props(); - // Estado para filtros - let filters = $state({ - status: '', - client_id: '', - year: '' - }); +// Estado para filtros +let filters = $state({ + status: '', + pedimento: '', + year: '' +}); let sorting = $state([{ id: 'id', desc: true }]); @@ -256,7 +256,7 @@ const filterParams = { status: filters.status || undefined, - client_id: filters.client_id ? parseInt(filters.client_id) : undefined, + pedimento: filters.pedimento || undefined, year: filters.year || undefined, sort_by: sorting.length > 0 ? sorting[0].id : undefined, sort_order: sorting.length > 0 ? (sorting[0].desc ? 'desc' : 'asc') : undefined @@ -309,7 +309,7 @@ const filterParams = { status: filters.status || undefined, - client_id: filters.client_id ? parseInt(filters.client_id) : undefined, + pedimento: filters.pedimento || undefined, year: filters.year || undefined, sort_by: sorting.length > 0 ? sorting[0].id : undefined, sort_order: sorting.length > 0 ? (sorting[0].desc ? 'desc' : 'asc') : undefined @@ -347,7 +347,7 @@ function clearFilters() { filters = { status: '', - client_id: '', + pedimento: '', year: '' }; applyFilters(); @@ -370,7 +370,7 @@ const filterParams = { status: filters.status || undefined, - client_id: filters.client_id ? parseInt(filters.client_id) : undefined, + pedimento: filters.pedimento || undefined, year: filters.year || undefined, sort_by: sorting.length > 0 ? sorting[0].id : undefined, sort_order: sorting.length > 0 ? (sorting[0].desc ? 'desc' : 'asc') : undefined @@ -446,62 +446,30 @@

Pedimentos

Gestiona los pedimentos del sistema

- +
+ + +
- - - -
-
- - -
- -
- - -
- -
- - -
-
-
-
- {#if error} - + Error {error} @@ -510,9 +478,7 @@ {/if} - +
@@ -521,10 +487,27 @@ Mostrando {allItems.length} de {totalItems} registros
- +
+ + + +
@@ -545,7 +528,7 @@
From 0b57742024ae31c6153a0aeb94e6531a01815a44 Mon Sep 17 00:00:00 2001 From: hreyes Date: Fri, 10 Apr 2026 11:08:02 -0600 Subject: [PATCH 08/13] feature/catalog-fixed-fix-styles --- .../public/reference_data/incoterms/routes.py | 11 + .../reference_data/code_pedimento_regimens.ts | 17 +- .../dashboard/reference_data/containers.ts | 15 +- .../reference_data/currency_types.ts | 15 +- .../reference_data/customs_sections.ts | 14 +- .../reference_data/customs_warehouses.ts | 15 +- .../api/dashboard/reference_data/incoterms.ts | 15 +- .../dashboard/reference_data/invoice_types.ts | 9 +- .../reference_data/material_types.ts | 9 +- .../reference_data/payment_methods.ts | 15 +- .../reference_data/pedimento_codes.ts | 15 +- .../reference_data/pedimento_regimens.ts | 15 +- .../api/dashboard/reference_data/states.ts | 15 +- .../reference_data/transport_modes.ts | 14 +- .../reference_data/transport_types.ts | 16 +- .../reference_data/valuation_methods.ts | 15 +- .../code_pedimento_regimens/columns.ts | 13 -- .../data-table-actions.svelte | 17 -- .../code_pedimento_regimens/data-table.svelte | 67 +++--- .../containers/data-table-actions.svelte | 17 -- .../containers/data-table.svelte | 67 +++--- .../countries/data-table-actions.svelte | 17 -- .../countries/data-table.svelte | 67 +++--- .../currency_types/data-table-actions.svelte | 17 -- .../currency_types/data-table.svelte | 67 +++--- .../data-table-actions.svelte | 17 -- .../customs_sections/data-table.svelte | 67 +++--- .../data-table-actions.svelte | 17 -- .../customs_warehouses/data-table.svelte | 67 +++--- .../incoterms/data-table-actions.svelte | 17 -- .../incoterms/data-table.svelte | 160 +++++++------- .../invoice_types/data-table-actions.svelte | 17 -- .../invoice_types/data-table.svelte | 67 +++--- .../material_types/data-table-actions.svelte | 17 -- .../material_types/data-table.svelte | 67 +++--- .../payment_methods/data-table-actions.svelte | 17 -- .../payment_methods/data-table.svelte | 67 +++--- .../pedimento_codes/data-table-actions.svelte | 17 -- .../pedimento_codes/data-table.svelte | 67 +++--- .../data-table-actions.svelte | 17 -- .../pedimento_regimens/data-table.svelte | 67 +++--- .../sectors/create-edit-dialog.svelte | 15 +- .../reference_data/sectors/data-table.svelte | 67 +++--- .../sectors/delete-dialog.svelte | 15 +- .../reference_data/states/data-table.svelte | 67 +++--- .../transport_modes/data-table-actions.svelte | 17 -- .../transport_modes/data-table.svelte | 67 +++--- .../transport_types/data-table-actions.svelte | 17 -- .../transport_types/data-table.svelte | 67 +++--- .../data-table-actions.svelte | 17 -- .../valuation_methods/data-table.svelte | 67 +++--- .../ui/sidebar/sidebar-inset.svelte | 2 +- .../ui/sidebar/sidebar-provider.svelte | 2 +- .../reference_data/common/factory.ts | 25 ++- frontend/src/routes/dashboard/+layout.svelte | 2 +- .../routes/dashboard/audit_logs/+page.svelte | 2 +- .../dashboard/audit_logs/bitacora-tab.svelte | 165 ++++++-------- .../dashboard/audit_logs/files-tab.svelte | 89 ++++---- .../dashboard/audit_logs/tasks-tab.svelte | 206 +++++++++-------- .../code_pedimento_regimens/+page.svelte | 174 +++++++-------- .../reference_data/containers/+page.svelte | 181 +++++++-------- .../reference_data/countries/+page.svelte | 87 +++++--- .../currency_types/+page.svelte | 148 ++++++------- .../customs_sections/+page.svelte | 169 ++++++-------- .../customs_warehouses/+page.svelte | 148 ++++++------- .../reference_data/incoterms/+page.server.ts | 11 +- .../reference_data/incoterms/+page.svelte | 135 ++++++++--- .../reference_data/invoice_types/+page.svelte | 150 ++++++------- .../material_types/+page.svelte | 181 +++++++-------- .../payment_methods/+page.svelte | 169 ++++++-------- .../pedimento_codes/+page.svelte | 169 ++++++-------- .../pedimento_regimens/+page.svelte | 169 ++++++-------- .../reference_data/sectors/+page.svelte | 209 +++++++++--------- .../reference_data/states/+page.svelte | 145 ++++++------ .../transport_modes/+page.svelte | 181 +++++++-------- .../transport_types/+page.svelte | 179 +++++++-------- .../valuation_methods/+page.svelte | 150 ++++++------- 77 files changed, 2337 insertions(+), 2666 deletions(-) diff --git a/backend/api/v1/modules/public/reference_data/incoterms/routes.py b/backend/api/v1/modules/public/reference_data/incoterms/routes.py index 02b278fc..261cfddc 100644 --- a/backend/api/v1/modules/public/reference_data/incoterms/routes.py +++ b/backend/api/v1/modules/public/reference_data/incoterms/routes.py @@ -15,11 +15,22 @@ router = APIRouter(prefix="/incoterms") async def list_incoterms( 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"), + code: str = Query(None, description="Filtrar por clave"), + description: str = Query(None, description="Filtrar por descripción"), db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user), ): skip = (page - 1) * page_size query = db.query(Incoterm) + + if code: + query = query.filter(Incoterm.code.ilike(f"%{code}%")) + if description: + query = query.filter( + (Incoterm.description_es.ilike(f"%{description}%")) | + (Incoterm.description_en.ilike(f"%{description}%")) + ) + items = query.offset(skip).limit(page_size).all() total = query.count() return { diff --git a/frontend/src/lib/api/dashboard/reference_data/code_pedimento_regimens.ts b/frontend/src/lib/api/dashboard/reference_data/code_pedimento_regimens.ts index 4b927437..7bb4fa45 100644 --- a/frontend/src/lib/api/dashboard/reference_data/code_pedimento_regimens.ts +++ b/frontend/src/lib/api/dashboard/reference_data/code_pedimento_regimens.ts @@ -35,13 +35,18 @@ export interface UpdateCodePedimentoRegimenData { */ export const codePedimentoRegimensApi = { /** - * Lista todos los code pedimento regimens con paginación + * Lista todos los code pedimento regimens con paginación y búsqueda + * @param page - Número de página (por defecto 1) + * @param pageSize - Tamaño de página (por defecto 50) + * @param search - Término de búsqueda (opcional) */ - list: (page = 1, pageSize = 50) => - api.get( - // CORRECTO: Slash antes del signo '?' - `/v1/public/reference_data/code-pedimento-regimens/?page=${page}&page_size=${pageSize}` - ), + list: (page = 1, pageSize = 50, search?: string) => { + let url = `/v1/public/reference_data/code-pedimento-regimens/?page=${page}&page_size=${pageSize}`; + if (search) { + url += `&search=${encodeURIComponent(search)}`; + } + return api.get(url); + }, /** * Obtiene un por ID diff --git a/frontend/src/lib/api/dashboard/reference_data/containers.ts b/frontend/src/lib/api/dashboard/reference_data/containers.ts index 67f240d6..9e4d300b 100644 --- a/frontend/src/lib/api/dashboard/reference_data/containers.ts +++ b/frontend/src/lib/api/dashboard/reference_data/containers.ts @@ -31,15 +31,18 @@ export interface UpdateContainerData { */ export const containersApi = { /** - * Lista todos los containers con paginación + * Lista todos los containers con paginación y búsqueda * @param page - Número de página (por defecto 1) * @param pageSize - Tamaño de página (por defecto 50) + * @param search - Término de búsqueda (opcional) */ - list: (page = 1, pageSize = 50) => - api.get( - // CORRECTO: Slash antes del ? - `/v1/public/reference_data/containers/?page=${page}&page_size=${pageSize}` - ), + list: (page = 1, pageSize = 50, search?: string) => { + let url = `/v1/public/reference_data/containers/?page=${page}&page_size=${pageSize}`; + if (search) { + url += `&search=${encodeURIComponent(search)}`; + } + return api.get(url); + }, /** * Obtiene un container por ID diff --git a/frontend/src/lib/api/dashboard/reference_data/currency_types.ts b/frontend/src/lib/api/dashboard/reference_data/currency_types.ts index 387e1f3f..9199365f 100644 --- a/frontend/src/lib/api/dashboard/reference_data/currency_types.ts +++ b/frontend/src/lib/api/dashboard/reference_data/currency_types.ts @@ -34,15 +34,18 @@ export interface UpdateCurrencyTypeData { */ export const currencyTypesApi = { /** - * Lista todos los tipos de moneda con paginación + * Lista todos los tipos de moneda con paginación y búsqueda * @param page - Número de página (por defecto 1) * @param pageSize - Tamaño de página (por defecto 50) + * @param search - Término de búsqueda (opcional) */ - list: (page = 1, pageSize = 50) => - api.get( - // CORREGIDO: Añadido '/' antes del '?' - `/v1/public/reference_data/currency-types/?page=${page}&page_size=${pageSize}` - ), + list: (page = 1, pageSize = 50, search?: string) => { + let url = `/v1/public/reference_data/currency-types/?page=${page}&page_size=${pageSize}`; + if (search) { + url += `&search=${encodeURIComponent(search)}`; + } + return api.get(url); + }, /** * Obtiene un tipo de moneda por código diff --git a/frontend/src/lib/api/dashboard/reference_data/customs_sections.ts b/frontend/src/lib/api/dashboard/reference_data/customs_sections.ts index e73934d8..e0cafb6e 100644 --- a/frontend/src/lib/api/dashboard/reference_data/customs_sections.ts +++ b/frontend/src/lib/api/dashboard/reference_data/customs_sections.ts @@ -31,14 +31,18 @@ export interface UpdateCustomsSectionData { */ export const customsSectionsApi = { /** - * Lista todas las secciones aduaneras con paginación + * Lista todas las secciones aduaneras con paginación y búsqueda * @param page - Número de página (por defecto 1) * @param pageSize - Tamaño de página (por defecto 50) + * @param search - Término de búsqueda (opcional) */ - list: (page = 1, pageSize = 50) => - api.get( - `/v1/public/reference_data/customs-sections/?page=${page}&page_size=${pageSize}` - ), + list: (page = 1, pageSize = 50, search?: string) => { + let url = `/v1/public/reference_data/customs-sections/?page=${page}&page_size=${pageSize}`; + if (search) { + url += `&search=${encodeURIComponent(search)}`; + } + return api.get(url); + }, /** * Obtiene una sección aduanera por código diff --git a/frontend/src/lib/api/dashboard/reference_data/customs_warehouses.ts b/frontend/src/lib/api/dashboard/reference_data/customs_warehouses.ts index eae2b955..4e90751e 100644 --- a/frontend/src/lib/api/dashboard/reference_data/customs_warehouses.ts +++ b/frontend/src/lib/api/dashboard/reference_data/customs_warehouses.ts @@ -34,15 +34,18 @@ export interface UpdateCustomsWarehouseData { */ export const customsWarehousesApi = { /** - * Lista todos los recintos fiscalizados con paginación + * Lista todos los recintos fiscalizados con paginación y búsqueda * @param page - Número de página (por defecto 1) * @param pageSize - Tamaño de página (por defecto 50) + * @param search - Término de búsqueda (opcional) */ - list: (page = 1, pageSize = 50) => - api.get( - // CORREGIDO: Añadido '/' antes del '?' - `/v1/public/reference_data/customs-warehouses/?page=${page}&page_size=${pageSize}` - ), + list: (page = 1, pageSize = 50, search?: string) => { + let url = `/v1/public/reference_data/customs-warehouses/?page=${page}&page_size=${pageSize}`; + if (search) { + url += `&search=${encodeURIComponent(search)}`; + } + return api.get(url); + }, /** * Obtiene un recinto fiscalizado por clave compuesta (key + customs) diff --git a/frontend/src/lib/api/dashboard/reference_data/incoterms.ts b/frontend/src/lib/api/dashboard/reference_data/incoterms.ts index 2543824e..4e49deb1 100644 --- a/frontend/src/lib/api/dashboard/reference_data/incoterms.ts +++ b/frontend/src/lib/api/dashboard/reference_data/incoterms.ts @@ -34,15 +34,18 @@ export interface UpdateIncotermData { */ export const incotermsApi = { /** - * Lista todos los incoterms con paginación + * Lista todos los incoterms con paginación y filtros * @param page - Número de página (por defecto 1) * @param pageSize - Tamaño de página (por defecto 50) + * @param code - Filtrar por clave (opcional) + * @param description - Filtrar por descripción (opcional) */ - list: (page = 1, pageSize = 50) => - api.get( - // CORREGIDO: Añadido '/' antes del '?' - `/v1/public/reference_data/incoterms/?page=${page}&page_size=${pageSize}` - ), + list: (page = 1, pageSize = 50, code?: string, description?: string) => { + let url = `/v1/public/reference_data/incoterms/?page=${page}&page_size=${pageSize}`; + if (code) url += `&code=${encodeURIComponent(code)}`; + if (description) url += `&description=${encodeURIComponent(description)}`; + return api.get(url); + }, /** * Obtiene un incoterm por código diff --git a/frontend/src/lib/api/dashboard/reference_data/invoice_types.ts b/frontend/src/lib/api/dashboard/reference_data/invoice_types.ts index 87a7c264..91a90ba8 100644 --- a/frontend/src/lib/api/dashboard/reference_data/invoice_types.ts +++ b/frontend/src/lib/api/dashboard/reference_data/invoice_types.ts @@ -38,12 +38,13 @@ export interface UpdateInvoiceTypeData { */ export const invoiceTypesApi = { /** - * Lista todos los tipos de factura con paginación + * Lista todos los tipos de factura con paginación y búsqueda * @param page - Número de página (por defecto 1) * @param pageSize - Tamaño de página (por defecto 50) * @param operation - Filtrar por tipo de operación (imp, exp) + * @param search - Término de búsqueda (opcional) */ - list: (page = 1, pageSize = 50, operation?: string) => { + list: (page = 1, pageSize = 50, operation?: string, search?: string) => { const params = new URLSearchParams({ page: page.toString(), page_size: pageSize.toString() @@ -51,8 +52,10 @@ export const invoiceTypesApi = { if (operation) { params.append('operation', operation); } + if (search) { + params.append('search', search); + } return api.get( - // CORREGIDO: Añadido '/' antes del '?' `/v1/public/reference_data/invoice-types/?${params.toString()}` ); }, diff --git a/frontend/src/lib/api/dashboard/reference_data/material_types.ts b/frontend/src/lib/api/dashboard/reference_data/material_types.ts index d2db1ab3..a4d89267 100644 --- a/frontend/src/lib/api/dashboard/reference_data/material_types.ts +++ b/frontend/src/lib/api/dashboard/reference_data/material_types.ts @@ -34,12 +34,13 @@ */ export const materialTypesApi = { /** - * Lista todos los tipos de material con paginación + * Lista todos los tipos de material con paginación y búsqueda * @param page - Número de página (por defecto 1) * @param pageSize - Tamaño de página (por defecto 50) * @param type - Filtrar por tipo (ACTIVO FIJO, MATERIALES, PRODUCTOS) + * @param search - Término de búsqueda (opcional) */ - list: (page = 1, pageSize = 50, type?: string) => { + list: (page = 1, pageSize = 50, type?: string, search?: string) => { const params = new URLSearchParams({ page: page.toString(), page_size: pageSize.toString() @@ -47,8 +48,10 @@ export const materialTypesApi = { if (type) { params.append('type', type); } + if (search) { + params.append('search', search); + } return api.get( - // CORRECTO: Ya tiene el '/' antes del '?' `/v1/public/reference_data/material-types/?${params.toString()}` ); }, diff --git a/frontend/src/lib/api/dashboard/reference_data/payment_methods.ts b/frontend/src/lib/api/dashboard/reference_data/payment_methods.ts index e0bcbcaf..7253e13b 100644 --- a/frontend/src/lib/api/dashboard/reference_data/payment_methods.ts +++ b/frontend/src/lib/api/dashboard/reference_data/payment_methods.ts @@ -31,15 +31,18 @@ export interface UpdatePaymentMethodData { */ export const paymentMethodsApi = { /** - * Lista todos los métodos de pago con paginación + * Lista todos los métodos de pago con paginación y búsqueda * @param page - Número de página (por defecto 1) * @param pageSize - Tamaño de página (por defecto 50) + * @param search - Término de búsqueda (opcional) */ - list: (page = 1, pageSize = 50) => - api.get( - // CORREGIDO: Añadido '/' antes del '?' - `/v1/public/reference_data/payment-methods/?page=${page}&page_size=${pageSize}` - ), + list: (page = 1, pageSize = 50, search?: string) => { + let url = `/v1/public/reference_data/payment-methods/?page=${page}&page_size=${pageSize}`; + if (search) { + url += `&search=${encodeURIComponent(search)}`; + } + return api.get(url); + }, /** * Obtiene un método de pago por key diff --git a/frontend/src/lib/api/dashboard/reference_data/pedimento_codes.ts b/frontend/src/lib/api/dashboard/reference_data/pedimento_codes.ts index 6a5cb837..5d62d34f 100644 --- a/frontend/src/lib/api/dashboard/reference_data/pedimento_codes.ts +++ b/frontend/src/lib/api/dashboard/reference_data/pedimento_codes.ts @@ -31,15 +31,18 @@ export interface UpdatePedimentoCodeData { */ export const pedimentoCodesApi = { /** - * Lista todas las claves de pedimento con paginación + * Lista todas las claves de pedimento con paginación y búsqueda * @param page - Número de página (por defecto 1) * @param pageSize - Tamaño de página (por defecto 50) + * @param search - Término de búsqueda (opcional) */ - list: (page = 1, pageSize = 50) => - api.get( - // CORREGIDO: Añadido '/' antes del '?' - `/v1/public/reference_data/pedimento-codes/?page=${page}&page_size=${pageSize}` - ), + list: (page = 1, pageSize = 50, search?: string) => { + let url = `/v1/public/reference_data/pedimento-codes/?page=${page}&page_size=${pageSize}`; + if (search) { + url += `&search=${encodeURIComponent(search)}`; + } + return api.get(url); + }, /** * Obtiene una clave de pedimento por code diff --git a/frontend/src/lib/api/dashboard/reference_data/pedimento_regimens.ts b/frontend/src/lib/api/dashboard/reference_data/pedimento_regimens.ts index 9466a70d..124c9a7b 100644 --- a/frontend/src/lib/api/dashboard/reference_data/pedimento_regimens.ts +++ b/frontend/src/lib/api/dashboard/reference_data/pedimento_regimens.ts @@ -31,15 +31,18 @@ export interface UpdatePedimentoRegimenData { */ export const pedimentoRegimensApi = { /** - * Lista todos los regímenes de pedimento con paginación + * Lista todos los regímenes de pedimento con paginación y búsqueda * @param page - Número de página (por defecto 1) * @param pageSize - Tamaño de página (por defecto 50) + * @param search - Término de búsqueda (opcional) */ - list: (page = 1, pageSize = 50) => - api.get( - // CORREGIDO: Añadido '/' antes del '?' - `/v1/public/reference_data/pedimento-regimens/?page=${page}&page_size=${pageSize}` - ), + list: (page = 1, pageSize = 50, search?: string) => { + let url = `/v1/public/reference_data/pedimento-regimens/?page=${page}&page_size=${pageSize}`; + if (search) { + url += `&search=${encodeURIComponent(search)}`; + } + return api.get(url); + }, /** * Obtiene un régimen de pedimento por code diff --git a/frontend/src/lib/api/dashboard/reference_data/states.ts b/frontend/src/lib/api/dashboard/reference_data/states.ts index 93a1db35..f7baf99d 100644 --- a/frontend/src/lib/api/dashboard/reference_data/states.ts +++ b/frontend/src/lib/api/dashboard/reference_data/states.ts @@ -37,15 +37,18 @@ export interface UpdateStateData { */ export const statesApi = { /** - * Lista todos los estados con paginación + * Lista todos los estados con paginación y búsqueda * @param page - Número de página (por defecto 1) * @param pageSize - Tamaño de página (por defecto 50) + * @param search - Término de búsqueda (opcional) */ - list: (page = 1, pageSize = 50) => - api.get( - // CORREGIDO: Añadido '/' antes de '?' - `/v1/public/reference_data/states/?page=${page}&page_size=${pageSize}` - ), + list: (page = 1, pageSize = 50, search?: string) => { + let url = `/v1/public/reference_data/states/?page=${page}&page_size=${pageSize}`; + if (search) { + url += `&search=${encodeURIComponent(search)}`; + } + return api.get(url); + }, /** * Obtiene un estado por m3_key diff --git a/frontend/src/lib/api/dashboard/reference_data/transport_modes.ts b/frontend/src/lib/api/dashboard/reference_data/transport_modes.ts index 5bb23ad9..a16b9a4e 100644 --- a/frontend/src/lib/api/dashboard/reference_data/transport_modes.ts +++ b/frontend/src/lib/api/dashboard/reference_data/transport_modes.ts @@ -31,14 +31,18 @@ export interface UpdateTransportModeData { */ export const transportModesApi = { /** - * Lista todos los modos de transporte con paginación + * Lista todos los modos de transporte con paginación y búsqueda * @param page - Número de página (por defecto 1) * @param pageSize - Tamaño de página (por defecto 50) + * @param search - Término de búsqueda (opcional) */ - list: (page = 1, pageSize = 50) => - api.get( - `/v1/public/reference_data/transport-modes/?page=${page}&page_size=${pageSize}` - ), + list: (page = 1, pageSize = 50, search?: string) => { + let url = `/v1/public/reference_data/transport-modes/?page=${page}&page_size=${pageSize}`; + if (search) { + url += `&search=${encodeURIComponent(search)}`; + } + return api.get(url); + }, /** * Obtiene un modo de transporte por key diff --git a/frontend/src/lib/api/dashboard/reference_data/transport_types.ts b/frontend/src/lib/api/dashboard/reference_data/transport_types.ts index 12b162b1..6b473373 100644 --- a/frontend/src/lib/api/dashboard/reference_data/transport_types.ts +++ b/frontend/src/lib/api/dashboard/reference_data/transport_types.ts @@ -31,12 +31,18 @@ export interface UpdateTransportTypeData { */ export const transportTypesApi = { /** - * Lista todos los tipos de transporte con paginación + * Lista todos los tipos de transporte con paginación y búsqueda + * @param page - Número de página (por defecto 1) + * @param pageSize - Tamaño de página (por defecto 50) + * @param search - Término de búsqueda (opcional) */ - list: (page = 1, pageSize = 50) => - api.get( - `/v1/public/reference_data/transport-types/?page=${page}&page_size=${pageSize}` - ), + list: (page = 1, pageSize = 50, search?: string) => { + let url = `/v1/public/reference_data/transport-types/?page=${page}&page_size=${pageSize}`; + if (search) { + url += `&search=${encodeURIComponent(search)}`; + } + return api.get(url); + }, /** * Obtiene un tipo de transporte por transport_code diff --git a/frontend/src/lib/api/dashboard/reference_data/valuation_methods.ts b/frontend/src/lib/api/dashboard/reference_data/valuation_methods.ts index df4a320c..6f3badb8 100644 --- a/frontend/src/lib/api/dashboard/reference_data/valuation_methods.ts +++ b/frontend/src/lib/api/dashboard/reference_data/valuation_methods.ts @@ -31,15 +31,18 @@ export interface UpdateValuationMethodData { */ export const valuationMethodsApi = { /** - * Lista todos los métodos de valoración con paginación + * Lista todos los métodos de valoración con paginación y búsqueda * @param page - Número de página (por defecto 1) * @param pageSize - Tamaño de página (por defecto 50) + * @param search - Término de búsqueda (opcional) */ - list: (page = 1, pageSize = 50) => - api.get( - // CORREGIDO: Añadido '/' antes del '?' - `/v1/public/reference_data/valuation-methods/?page=${page}&page_size=${pageSize}` - ), + list: (page = 1, pageSize = 50, search?: string) => { + let url = `/v1/public/reference_data/valuation-methods/?page=${page}&page_size=${pageSize}`; + if (search) { + url += `&search=${encodeURIComponent(search)}`; + } + return api.get(url); + }, /** * Obtiene un método de valoración por key diff --git a/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/columns.ts b/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/columns.ts index 41ae7ce2..065745f5 100644 --- a/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/columns.ts +++ b/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/columns.ts @@ -12,19 +12,6 @@ export type CodePedimentoRegimen = { export function createColumns(onSuccess?: () => void): ColumnDef[] { return [ - { - accessorKey: "id", - header: "ID", - cell: ({ row }) => { - const idSnippet = createRawSnippet<[{ id: number }]>((getId) => { - const { id } = getId(); - return { - render: () => `
${id}
` - }; - }); - return renderSnippet(idSnippet, { id: row.original.id }); - } - }, { accessorKey: "pedimento_code", header: "Código Pedimento", diff --git a/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/data-table-actions.svelte b/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/data-table-actions.svelte index 18c91f2d..faa561f9 100644 --- a/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/data-table-actions.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/data-table-actions.svelte @@ -3,9 +3,7 @@ import { Button } from "$lib/components/ui/button/index.js"; import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js"; import type { CodePedimentoRegimen } from "./columns.js"; - import CreateEditDialog from "./create-edit-dialog.svelte"; import DetailsDialog from "./details-dialog.svelte"; - import DeleteDialog from "./delete-dialog.svelte"; let { item, @@ -16,8 +14,6 @@ } = $props(); let showDetailsDialog = $state(false); - let showEditDialog = $state(false); - let showDeleteDialog = $state(false); function handleCopyId() { navigator.clipboard.writeText(item.id.toString()); @@ -26,14 +22,6 @@ function handleViewDetails() { showDetailsDialog = true; } - - function handleEdit() { - showEditDialog = true; - } - - function handleDelete() { - showDeleteDialog = true; - } @@ -54,13 +42,8 @@ Ver detalles - Editar - - Eliminar - - diff --git a/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/data-table.svelte index 2f4a88e7..4109783b 100644 --- a/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/data-table.svelte @@ -59,68 +59,67 @@ }); -
-
- - +
+
+ + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} - + {#each headerGroup.headers as header (header.id)} - + {/each} - + {/each} - - + + {#each table.getRowModel().rows as row (row.id)} - + {#each row.getVisibleCells() as cell (cell.id)} - + {/each} - + {:else} - - + + + {/each} - + {#if hasMore} - - -
+
+ + {/if} - - + +
{#if !header.isPlaceholder} {/if} - +
- +
No hay resultados. - - +
+
{#if loading} -
-
- Cargando más... +
+
+ Cargando más registros...
{:else} -
- Desplázate para cargar más +
+ + Desplázate para cargar más +
{/if}
- - +
diff --git a/frontend/src/lib/components/dashboard/reference_data/containers/data-table-actions.svelte b/frontend/src/lib/components/dashboard/reference_data/containers/data-table-actions.svelte index 07f22b3b..9689ba27 100644 --- a/frontend/src/lib/components/dashboard/reference_data/containers/data-table-actions.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/containers/data-table-actions.svelte @@ -3,9 +3,7 @@ import { Button } from "$lib/components/ui/button/index.js"; import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js"; import type { Container } from "./columns.js"; - import CreateEditDialog from "./create-edit-dialog.svelte"; import DetailsDialog from "./details-dialog.svelte"; - import DeleteDialog from "./delete-dialog.svelte"; let { item, @@ -16,8 +14,6 @@ } = $props(); let showDetailsDialog = $state(false); - let showEditDialog = $state(false); - let showDeleteDialog = $state(false); function handleCopyId() { navigator.clipboard.writeText(item.key.toString()); @@ -26,14 +22,6 @@ function handleViewDetails() { showDetailsDialog = true; } - - function handleEdit() { - showEditDialog = true; - } - - function handleDelete() { - showDeleteDialog = true; - } @@ -54,13 +42,8 @@ Ver detalles - Editar - - Eliminar - - diff --git a/frontend/src/lib/components/dashboard/reference_data/containers/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/containers/data-table.svelte index 2f4a88e7..4109783b 100644 --- a/frontend/src/lib/components/dashboard/reference_data/containers/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/containers/data-table.svelte @@ -59,68 +59,67 @@ }); -
-
- - +
+
+ + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} - + {#each headerGroup.headers as header (header.id)} - + {/each} - + {/each} - - + + {#each table.getRowModel().rows as row (row.id)} - + {#each row.getVisibleCells() as cell (cell.id)} - + {/each} - + {:else} - - + + + {/each} - + {#if hasMore} - - -
+
+ + {/if} - - + +
{#if !header.isPlaceholder} {/if} - +
- +
No hay resultados. - - +
+
{#if loading} -
-
- Cargando más... +
+
+ Cargando más registros...
{:else} -
- Desplázate para cargar más +
+ + Desplázate para cargar más +
{/if}
- - +
diff --git a/frontend/src/lib/components/dashboard/reference_data/countries/data-table-actions.svelte b/frontend/src/lib/components/dashboard/reference_data/countries/data-table-actions.svelte index cd8ee7db..fce57c19 100644 --- a/frontend/src/lib/components/dashboard/reference_data/countries/data-table-actions.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/countries/data-table-actions.svelte @@ -3,9 +3,7 @@ import { Button } from "$lib/components/ui/button/index.js"; import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js"; import type { Country } from "./columns.js"; - import CreateEditDialog from "./create-edit-dialog.svelte"; import DetailsDialog from "./details-dialog.svelte"; - import DeleteDialog from "./delete-dialog.svelte"; let { item, @@ -16,8 +14,6 @@ } = $props(); let showDetailsDialog = $state(false); - let showEditDialog = $state(false); - let showDeleteDialog = $state(false); function handleCopyId() { navigator.clipboard.writeText(item.m3_key.toString()); @@ -26,14 +22,6 @@ function handleViewDetails() { showDetailsDialog = true; } - - function handleEdit() { - showEditDialog = true; - } - - function handleDelete() { - showDeleteDialog = true; - } @@ -54,13 +42,8 @@ Ver detalles - Editar - - Eliminar - - diff --git a/frontend/src/lib/components/dashboard/reference_data/countries/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/countries/data-table.svelte index 2f4a88e7..4109783b 100644 --- a/frontend/src/lib/components/dashboard/reference_data/countries/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/countries/data-table.svelte @@ -59,68 +59,67 @@ }); -
-
- - +
+
+ + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} - + {#each headerGroup.headers as header (header.id)} - + {/each} - + {/each} - - + + {#each table.getRowModel().rows as row (row.id)} - + {#each row.getVisibleCells() as cell (cell.id)} - + {/each} - + {:else} - - + + + {/each} - + {#if hasMore} - - -
+
+ + {/if} - - + +
{#if !header.isPlaceholder} {/if} - +
- +
No hay resultados. - - +
+
{#if loading} -
-
- Cargando más... +
+
+ Cargando más registros...
{:else} -
- Desplázate para cargar más +
+ + Desplázate para cargar más +
{/if}
- - +
diff --git a/frontend/src/lib/components/dashboard/reference_data/currency_types/data-table-actions.svelte b/frontend/src/lib/components/dashboard/reference_data/currency_types/data-table-actions.svelte index 63dd58c3..69d90ade 100644 --- a/frontend/src/lib/components/dashboard/reference_data/currency_types/data-table-actions.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/currency_types/data-table-actions.svelte @@ -3,9 +3,7 @@ import { Button } from "$lib/components/ui/button/index.js"; import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js"; import type { CurrencyType } from "./columns.js"; - import CreateEditDialog from "./create-edit-dialog.svelte"; import DetailsDialog from "./details-dialog.svelte"; - import DeleteDialog from "./delete-dialog.svelte"; let { item, @@ -16,8 +14,6 @@ } = $props(); let showDetailsDialog = $state(false); - let showEditDialog = $state(false); - let showDeleteDialog = $state(false); function handleCopyId() { navigator.clipboard.writeText(item.code.toString()); @@ -26,14 +22,6 @@ function handleViewDetails() { showDetailsDialog = true; } - - function handleEdit() { - showEditDialog = true; - } - - function handleDelete() { - showDeleteDialog = true; - } @@ -54,13 +42,8 @@ Ver detalles - Editar - - Eliminar - - diff --git a/frontend/src/lib/components/dashboard/reference_data/currency_types/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/currency_types/data-table.svelte index 2f4a88e7..4109783b 100644 --- a/frontend/src/lib/components/dashboard/reference_data/currency_types/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/currency_types/data-table.svelte @@ -59,68 +59,67 @@ }); -
-
- - +
+
+ + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} - + {#each headerGroup.headers as header (header.id)} - + {/each} - + {/each} - - + + {#each table.getRowModel().rows as row (row.id)} - + {#each row.getVisibleCells() as cell (cell.id)} - + {/each} - + {:else} - - + + + {/each} - + {#if hasMore} - - -
+
+ + {/if} - - + +
{#if !header.isPlaceholder} {/if} - +
- +
No hay resultados. - - +
+
{#if loading} -
-
- Cargando más... +
+
+ Cargando más registros...
{:else} -
- Desplázate para cargar más +
+ + Desplázate para cargar más +
{/if}
- - +
diff --git a/frontend/src/lib/components/dashboard/reference_data/customs_sections/data-table-actions.svelte b/frontend/src/lib/components/dashboard/reference_data/customs_sections/data-table-actions.svelte index 3ffd2ee3..8dcf4a58 100644 --- a/frontend/src/lib/components/dashboard/reference_data/customs_sections/data-table-actions.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/customs_sections/data-table-actions.svelte @@ -3,9 +3,7 @@ import { Button } from "$lib/components/ui/button/index.js"; import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js"; import type { CustomsSection } from "./columns.js"; - import CreateEditDialog from "./create-edit-dialog.svelte"; import DetailsDialog from "./details-dialog.svelte"; - import DeleteDialog from "./delete-dialog.svelte"; let { item, @@ -16,8 +14,6 @@ } = $props(); let showDetailsDialog = $state(false); - let showEditDialog = $state(false); - let showDeleteDialog = $state(false); function handleCopyId() { navigator.clipboard.writeText(item.customs_code.toString()); @@ -26,14 +22,6 @@ function handleViewDetails() { showDetailsDialog = true; } - - function handleEdit() { - showEditDialog = true; - } - - function handleDelete() { - showDeleteDialog = true; - } @@ -54,13 +42,8 @@ Ver detalles - Editar - - Eliminar - - diff --git a/frontend/src/lib/components/dashboard/reference_data/customs_sections/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/customs_sections/data-table.svelte index 2f4a88e7..c34967ce 100644 --- a/frontend/src/lib/components/dashboard/reference_data/customs_sections/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/customs_sections/data-table.svelte @@ -59,68 +59,67 @@ }); -
-
- - +
+
+ + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} - + {#each headerGroup.headers as header (header.id)} - + {/each} - + {/each} - - + + {#each table.getRowModel().rows as row (row.id)} - + {#each row.getVisibleCells() as cell (cell.id)} - + {/each} - + {:else} - - + + + {/each} - + {#if hasMore} - - -
+
+ + {/if} - - + +
{#if !header.isPlaceholder} {/if} - +
- +
No hay resultados. - - +
+
{#if loading} -
-
- Cargando más... +
+
+ Cargando más registros...
{:else} -
- Desplázate para cargar más +
+ + Desplázate para cargar más +
{/if}
- - +
diff --git a/frontend/src/lib/components/dashboard/reference_data/customs_warehouses/data-table-actions.svelte b/frontend/src/lib/components/dashboard/reference_data/customs_warehouses/data-table-actions.svelte index d204b9c0..3fd6f9fe 100644 --- a/frontend/src/lib/components/dashboard/reference_data/customs_warehouses/data-table-actions.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/customs_warehouses/data-table-actions.svelte @@ -3,9 +3,7 @@ import { Button } from "$lib/components/ui/button/index.js"; import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js"; import type { CustomsWarehouse } from "./columns.js"; - import CreateEditDialog from "./create-edit-dialog.svelte"; import DetailsDialog from "./details-dialog.svelte"; - import DeleteDialog from "./delete-dialog.svelte"; let { item, @@ -16,8 +14,6 @@ } = $props(); let showDetailsDialog = $state(false); - let showEditDialog = $state(false); - let showDeleteDialog = $state(false); function handleCopyId() { navigator.clipboard.writeText(`${item.key}|${item.customs}`); @@ -26,14 +22,6 @@ function handleViewDetails() { showDetailsDialog = true; } - - function handleEdit() { - showEditDialog = true; - } - - function handleDelete() { - showDeleteDialog = true; - } @@ -54,13 +42,8 @@ Ver detalles - Editar - - Eliminar - - diff --git a/frontend/src/lib/components/dashboard/reference_data/customs_warehouses/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/customs_warehouses/data-table.svelte index 2f4a88e7..4109783b 100644 --- a/frontend/src/lib/components/dashboard/reference_data/customs_warehouses/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/customs_warehouses/data-table.svelte @@ -59,68 +59,67 @@ }); -
-
- - +
+
+ + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} - + {#each headerGroup.headers as header (header.id)} - + {/each} - + {/each} - - + + {#each table.getRowModel().rows as row (row.id)} - + {#each row.getVisibleCells() as cell (cell.id)} - + {/each} - + {:else} - - + + + {/each} - + {#if hasMore} - - -
+
+ + {/if} - - + +
{#if !header.isPlaceholder} {/if} - +
- +
No hay resultados. - - +
+
{#if loading} -
-
- Cargando más... +
+
+ Cargando más registros...
{:else} -
- Desplázate para cargar más +
+ + Desplázate para cargar más +
{/if}
- - +
diff --git a/frontend/src/lib/components/dashboard/reference_data/incoterms/data-table-actions.svelte b/frontend/src/lib/components/dashboard/reference_data/incoterms/data-table-actions.svelte index ce9a6cbe..9ea03340 100644 --- a/frontend/src/lib/components/dashboard/reference_data/incoterms/data-table-actions.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/incoterms/data-table-actions.svelte @@ -3,9 +3,7 @@ import { Button } from "$lib/components/ui/button/index.js"; import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js"; import type { Incoterm } from "./columns.js"; - import CreateEditDialog from "./create-edit-dialog.svelte"; import DetailsDialog from "./details-dialog.svelte"; - import DeleteDialog from "./delete-dialog.svelte"; let { item, @@ -16,8 +14,6 @@ } = $props(); let showDetailsDialog = $state(false); - let showEditDialog = $state(false); - let showDeleteDialog = $state(false); function handleCopyId() { navigator.clipboard.writeText(item.code.toString()); @@ -26,14 +22,6 @@ function handleViewDetails() { showDetailsDialog = true; } - - function handleEdit() { - showEditDialog = true; - } - - function handleDelete() { - showDeleteDialog = true; - } @@ -54,13 +42,8 @@ Ver detalles - Editar - - Eliminar - - diff --git a/frontend/src/lib/components/dashboard/reference_data/incoterms/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/incoterms/data-table.svelte index 3d5b2a66..dd73a894 100644 --- a/frontend/src/lib/components/dashboard/reference_data/incoterms/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/incoterms/data-table.svelte @@ -3,104 +3,114 @@ type ColumnDef, getCoreRowModel } from "@tanstack/table-core"; + import { onMount } from "svelte"; import { createSvelteTable, FlexRender } from "$lib/components/ui/data-table/index.js"; import * as Table from "$lib/components/ui/table/index.js"; import { Button } from "$lib/components/ui/button"; - import { goto } from "$app/navigation"; - import { page } from "$app/stores"; type DataTableProps = { columns: ColumnDef[]; data: TData[]; - pageCount: number; - totalItems: number; + loading: boolean; + hasMore: boolean; + loadMore: () => void; }; let { data, columns, - pageCount, - totalItems + loading, + hasMore, + loadMore }: DataTableProps = $props(); const table = createSvelteTable({ get data() { return data; }, get columns() { return columns; }, - getCoreRowModel: getCoreRowModel(), - manualPagination: true, - get pageCount() { return pageCount; }, + getCoreRowModel: getCoreRowModel() }); - function handlePageChange(newPage: number) { - const url = new URL($page.url); - url.searchParams.set('page', newPage.toString()); - goto(url, { keepFocus: true, noScroll: true }); - } + let scrollContainer = $state(); + let loadingTrigger = $state(); - const currentPage = $derived(Number($page.url.searchParams.get('page') || 1)); + onMount(() => { + const observer = new IntersectionObserver( + (entries) => { + const [entry] = entries; + if (entry.isIntersecting && hasMore && !loading) { + loadMore(); + } + }, + { root: scrollContainer, threshold: 0.1 } + ); + + if (loadingTrigger) observer.observe(loadingTrigger); + + return () => observer.disconnect(); + }); -
- - - {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} - - {#each headerGroup.headers as header (header.id)} - - {#if !header.isPlaceholder} +
+
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} + + {#each headerGroup.headers as header (header.id)} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getVisibleCells() as cell (cell.id)} + + {/each} + + {:else} + + + + {/each} + + + {#if hasMore} + + + + {/if} + +
+ {#if !header.isPlaceholder} + + {/if} +
- {/if} - - {/each} - - {/each} - - - {#each table.getRowModel().rows as row (row.id)} - - {#each row.getVisibleCells() as cell (cell.id)} - - - - {/each} - - {:else} - - - No hay resultados. - - - {/each} - - - - -
-
- Total: {totalItems} registros -
-
- - +
+ No hay resultados. +
+
+ {#if loading} +
+
+ Cargando más registros... +
+ {:else} +
+ + Desplázate para cargar más + +
+ {/if} +
+
diff --git a/frontend/src/lib/components/dashboard/reference_data/invoice_types/data-table-actions.svelte b/frontend/src/lib/components/dashboard/reference_data/invoice_types/data-table-actions.svelte index be22d1e5..4fab7cbe 100644 --- a/frontend/src/lib/components/dashboard/reference_data/invoice_types/data-table-actions.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/invoice_types/data-table-actions.svelte @@ -3,9 +3,7 @@ import { Button } from "$lib/components/ui/button/index.js"; import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js"; import type { InvoiceType } from "./columns.js"; - import CreateEditDialog from "./create-edit-dialog.svelte"; import DetailsDialog from "./details-dialog.svelte"; - import DeleteDialog from "./delete-dialog.svelte"; let { item, @@ -16,8 +14,6 @@ } = $props(); let showDetailsDialog = $state(false); - let showEditDialog = $state(false); - let showDeleteDialog = $state(false); function handleCopyId() { navigator.clipboard.writeText(item.key.toString()); @@ -26,14 +22,6 @@ function handleViewDetails() { showDetailsDialog = true; } - - function handleEdit() { - showEditDialog = true; - } - - function handleDelete() { - showDeleteDialog = true; - } @@ -54,13 +42,8 @@ Ver detalles - Editar - - Eliminar - - diff --git a/frontend/src/lib/components/dashboard/reference_data/invoice_types/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/invoice_types/data-table.svelte index 2f4a88e7..4109783b 100644 --- a/frontend/src/lib/components/dashboard/reference_data/invoice_types/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/invoice_types/data-table.svelte @@ -59,68 +59,67 @@ }); -
-
- - +
+
+ + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} - + {#each headerGroup.headers as header (header.id)} - + {/each} - + {/each} - - + + {#each table.getRowModel().rows as row (row.id)} - + {#each row.getVisibleCells() as cell (cell.id)} - + {/each} - + {:else} - - + + + {/each} - + {#if hasMore} - - -
+
+ + {/if} - - + +
{#if !header.isPlaceholder} {/if} - +
- +
No hay resultados. - - +
+
{#if loading} -
-
- Cargando más... +
+
+ Cargando más registros...
{:else} -
- Desplázate para cargar más +
+ + Desplázate para cargar más +
{/if}
- - +
diff --git a/frontend/src/lib/components/dashboard/reference_data/material_types/data-table-actions.svelte b/frontend/src/lib/components/dashboard/reference_data/material_types/data-table-actions.svelte index 8b64e467..d1f070f7 100644 --- a/frontend/src/lib/components/dashboard/reference_data/material_types/data-table-actions.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/material_types/data-table-actions.svelte @@ -3,9 +3,7 @@ import { Button } from "$lib/components/ui/button/index.js"; import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js"; import type { MaterialType } from "./columns.js"; - import CreateEditDialog from "./create-edit-dialog.svelte"; import DetailsDialog from "./details-dialog.svelte"; - import DeleteDialog from "./delete-dialog.svelte"; let { item, @@ -16,8 +14,6 @@ } = $props(); let showDetailsDialog = $state(false); - let showEditDialog = $state(false); - let showDeleteDialog = $state(false); function handleCopyId() { navigator.clipboard.writeText(item.key.toString()); @@ -26,14 +22,6 @@ function handleViewDetails() { showDetailsDialog = true; } - - function handleEdit() { - showEditDialog = true; - } - - function handleDelete() { - showDeleteDialog = true; - } @@ -54,13 +42,8 @@ Ver detalles - Editar - - Eliminar - - diff --git a/frontend/src/lib/components/dashboard/reference_data/material_types/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/material_types/data-table.svelte index 2f4a88e7..4109783b 100644 --- a/frontend/src/lib/components/dashboard/reference_data/material_types/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/material_types/data-table.svelte @@ -59,68 +59,67 @@ }); -
-
- - +
+
+ + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} - + {#each headerGroup.headers as header (header.id)} - + {/each} - + {/each} - - + + {#each table.getRowModel().rows as row (row.id)} - + {#each row.getVisibleCells() as cell (cell.id)} - + {/each} - + {:else} - - + + + {/each} - + {#if hasMore} - - -
+
+ + {/if} - - + +
{#if !header.isPlaceholder} {/if} - +
- +
No hay resultados. - - +
+
{#if loading} -
-
- Cargando más... +
+
+ Cargando más registros...
{:else} -
- Desplázate para cargar más +
+ + Desplázate para cargar más +
{/if}
- - +
diff --git a/frontend/src/lib/components/dashboard/reference_data/payment_methods/data-table-actions.svelte b/frontend/src/lib/components/dashboard/reference_data/payment_methods/data-table-actions.svelte index faea7dde..f36a0c6a 100644 --- a/frontend/src/lib/components/dashboard/reference_data/payment_methods/data-table-actions.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/payment_methods/data-table-actions.svelte @@ -3,9 +3,7 @@ import { Button } from "$lib/components/ui/button/index.js"; import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js"; import type { PaymentMethod } from "./columns.js"; - import CreateEditDialog from "./create-edit-dialog.svelte"; import DetailsDialog from "./details-dialog.svelte"; - import DeleteDialog from "./delete-dialog.svelte"; let { item, @@ -16,8 +14,6 @@ } = $props(); let showDetailsDialog = $state(false); - let showEditDialog = $state(false); - let showDeleteDialog = $state(false); function handleCopyId() { navigator.clipboard.writeText(item.key.toString()); @@ -26,14 +22,6 @@ function handleViewDetails() { showDetailsDialog = true; } - - function handleEdit() { - showEditDialog = true; - } - - function handleDelete() { - showDeleteDialog = true; - } @@ -54,13 +42,8 @@ Ver detalles - Editar - - Eliminar - - diff --git a/frontend/src/lib/components/dashboard/reference_data/payment_methods/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/payment_methods/data-table.svelte index 2f4a88e7..4109783b 100644 --- a/frontend/src/lib/components/dashboard/reference_data/payment_methods/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/payment_methods/data-table.svelte @@ -59,68 +59,67 @@ }); -
-
- - +
+
+ + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} - + {#each headerGroup.headers as header (header.id)} - + {/each} - + {/each} - - + + {#each table.getRowModel().rows as row (row.id)} - + {#each row.getVisibleCells() as cell (cell.id)} - + {/each} - + {:else} - - + + + {/each} - + {#if hasMore} - - -
+
+ + {/if} - - + +
{#if !header.isPlaceholder} {/if} - +
- +
No hay resultados. - - +
+
{#if loading} -
-
- Cargando más... +
+
+ Cargando más registros...
{:else} -
- Desplázate para cargar más +
+ + Desplázate para cargar más +
{/if}
- - +
diff --git a/frontend/src/lib/components/dashboard/reference_data/pedimento_codes/data-table-actions.svelte b/frontend/src/lib/components/dashboard/reference_data/pedimento_codes/data-table-actions.svelte index 21b69276..5bfd8144 100644 --- a/frontend/src/lib/components/dashboard/reference_data/pedimento_codes/data-table-actions.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/pedimento_codes/data-table-actions.svelte @@ -3,9 +3,7 @@ import { Button } from "$lib/components/ui/button/index.js"; import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js"; import type { PedimentoCode } from "./columns.js"; - import CreateEditDialog from "./create-edit-dialog.svelte"; import DetailsDialog from "./details-dialog.svelte"; - import DeleteDialog from "./delete-dialog.svelte"; let { item, @@ -16,8 +14,6 @@ } = $props(); let showDetailsDialog = $state(false); - let showEditDialog = $state(false); - let showDeleteDialog = $state(false); function handleCopyId() { navigator.clipboard.writeText(item.code.toString()); @@ -26,14 +22,6 @@ function handleViewDetails() { showDetailsDialog = true; } - - function handleEdit() { - showEditDialog = true; - } - - function handleDelete() { - showDeleteDialog = true; - } @@ -54,13 +42,8 @@ Ver detalles - Editar - - Eliminar - - diff --git a/frontend/src/lib/components/dashboard/reference_data/pedimento_codes/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/pedimento_codes/data-table.svelte index 2f4a88e7..4109783b 100644 --- a/frontend/src/lib/components/dashboard/reference_data/pedimento_codes/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/pedimento_codes/data-table.svelte @@ -59,68 +59,67 @@ }); -
-
- - +
+
+ + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} - + {#each headerGroup.headers as header (header.id)} - + {/each} - + {/each} - - + + {#each table.getRowModel().rows as row (row.id)} - + {#each row.getVisibleCells() as cell (cell.id)} - + {/each} - + {:else} - - + + + {/each} - + {#if hasMore} - - -
+
+ + {/if} - - + +
{#if !header.isPlaceholder} {/if} - +
- +
No hay resultados. - - +
+
{#if loading} -
-
- Cargando más... +
+
+ Cargando más registros...
{:else} -
- Desplázate para cargar más +
+ + Desplázate para cargar más +
{/if}
- - +
diff --git a/frontend/src/lib/components/dashboard/reference_data/pedimento_regimens/data-table-actions.svelte b/frontend/src/lib/components/dashboard/reference_data/pedimento_regimens/data-table-actions.svelte index 42b87981..dc4d01ad 100644 --- a/frontend/src/lib/components/dashboard/reference_data/pedimento_regimens/data-table-actions.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/pedimento_regimens/data-table-actions.svelte @@ -3,9 +3,7 @@ import { Button } from "$lib/components/ui/button/index.js"; import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js"; import type { PedimentoRegimen } from "./columns.js"; - import CreateEditDialog from "./create-edit-dialog.svelte"; import DetailsDialog from "./details-dialog.svelte"; - import DeleteDialog from "./delete-dialog.svelte"; let { item, @@ -16,8 +14,6 @@ } = $props(); let showDetailsDialog = $state(false); - let showEditDialog = $state(false); - let showDeleteDialog = $state(false); function handleCopyId() { navigator.clipboard.writeText(item.code.toString()); @@ -26,14 +22,6 @@ function handleViewDetails() { showDetailsDialog = true; } - - function handleEdit() { - showEditDialog = true; - } - - function handleDelete() { - showDeleteDialog = true; - } @@ -54,13 +42,8 @@ Ver detalles - Editar - - Eliminar - - diff --git a/frontend/src/lib/components/dashboard/reference_data/pedimento_regimens/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/pedimento_regimens/data-table.svelte index 2f4a88e7..4109783b 100644 --- a/frontend/src/lib/components/dashboard/reference_data/pedimento_regimens/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/pedimento_regimens/data-table.svelte @@ -59,68 +59,67 @@ }); -
-
- - +
+
+ + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} - + {#each headerGroup.headers as header (header.id)} - + {/each} - + {/each} - - + + {#each table.getRowModel().rows as row (row.id)} - + {#each row.getVisibleCells() as cell (cell.id)} - + {/each} - + {:else} - - + + + {/each} - + {#if hasMore} - - -
+
+ + {/if} - - + +
{#if !header.isPlaceholder} {/if} - +
- +
No hay resultados. - - +
+
{#if loading} -
-
- Cargando más... +
+
+ Cargando más registros...
{:else} -
- Desplázate para cargar más +
+ + Desplázate para cargar más +
{/if}
- - +
diff --git a/frontend/src/lib/components/dashboard/reference_data/sectors/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/reference_data/sectors/create-edit-dialog.svelte index 1da14dbf..460e1202 100644 --- a/frontend/src/lib/components/dashboard/reference_data/sectors/create-edit-dialog.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/sectors/create-edit-dialog.svelte @@ -25,6 +25,19 @@ let loading = $state(false); let error = $state(null); + function getActiveCompanyId(): number | null { + const fromStore = companyStore.activeCompany?.id; + if (fromStore) return fromStore; + if (typeof document === 'undefined') return null; + const cookie = document.cookie + .split('; ') + .find((row) => row.startsWith('active_company_id=')) + ?.split('=')[1]; + if (!cookie) return null; + const parsed = Number(cookie); + return Number.isFinite(parsed) ? parsed : null; + } + // Actualizar formData cuando item cambia $effect(() => { if (item) { @@ -49,7 +62,7 @@ loading = true; error = null; - const companyId = companyStore.activeCompany?.id; + const companyId = getActiveCompanyId(); if (!companyId) { error = 'No hay empresa activa seleccionada'; loading = false; diff --git a/frontend/src/lib/components/dashboard/reference_data/sectors/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/sectors/data-table.svelte index 2f4a88e7..4109783b 100644 --- a/frontend/src/lib/components/dashboard/reference_data/sectors/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/sectors/data-table.svelte @@ -59,68 +59,67 @@ }); -
-
- - +
+
+ + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} - + {#each headerGroup.headers as header (header.id)} - + {/each} - + {/each} - - + + {#each table.getRowModel().rows as row (row.id)} - + {#each row.getVisibleCells() as cell (cell.id)} - + {/each} - + {:else} - - + + + {/each} - + {#if hasMore} - - -
+
+ + {/if} - - + +
{#if !header.isPlaceholder} {/if} - +
- +
No hay resultados. - - +
+
{#if loading} -
-
- Cargando más... +
+
+ Cargando más registros...
{:else} -
- Desplázate para cargar más +
+ + Desplázate para cargar más +
{/if}
- - +
diff --git a/frontend/src/lib/components/dashboard/reference_data/sectors/delete-dialog.svelte b/frontend/src/lib/components/dashboard/reference_data/sectors/delete-dialog.svelte index 9e92fb82..920913e9 100644 --- a/frontend/src/lib/components/dashboard/reference_data/sectors/delete-dialog.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/sectors/delete-dialog.svelte @@ -18,10 +18,23 @@ let loading = $state(false); let error = $state(null); + function getActiveCompanyId(): number | null { + const fromStore = companyStore.activeCompany?.id; + if (fromStore) return fromStore; + if (typeof document === 'undefined') return null; + const cookie = document.cookie + .split('; ') + .find((row) => row.startsWith('active_company_id=')) + ?.split('=')[1]; + if (!cookie) return null; + const parsed = Number(cookie); + return Number.isFinite(parsed) ? parsed : null; + } + async function handleDelete() { if (!item) return; - const companyId = companyStore.activeCompany?.id; + const companyId = getActiveCompanyId(); if (!companyId) { error = 'No hay empresa activa seleccionada'; return; diff --git a/frontend/src/lib/components/dashboard/reference_data/states/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/states/data-table.svelte index 2f4a88e7..4109783b 100644 --- a/frontend/src/lib/components/dashboard/reference_data/states/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/states/data-table.svelte @@ -59,68 +59,67 @@ }); -
-
- - +
+
+ + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} - + {#each headerGroup.headers as header (header.id)} - + {/each} - + {/each} - - + + {#each table.getRowModel().rows as row (row.id)} - + {#each row.getVisibleCells() as cell (cell.id)} - + {/each} - + {:else} - - + + + {/each} - + {#if hasMore} - - -
+
+ + {/if} - - + +
{#if !header.isPlaceholder} {/if} - +
- +
No hay resultados. - - +
+
{#if loading} -
-
- Cargando más... +
+
+ Cargando más registros...
{:else} -
- Desplázate para cargar más +
+ + Desplázate para cargar más +
{/if}
- - +
diff --git a/frontend/src/lib/components/dashboard/reference_data/transport_modes/data-table-actions.svelte b/frontend/src/lib/components/dashboard/reference_data/transport_modes/data-table-actions.svelte index 9159af82..5855ea10 100644 --- a/frontend/src/lib/components/dashboard/reference_data/transport_modes/data-table-actions.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/transport_modes/data-table-actions.svelte @@ -3,9 +3,7 @@ import { Button } from "$lib/components/ui/button/index.js"; import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js"; import type { TransportMode } from "./columns.js"; - import CreateEditDialog from "./create-edit-dialog.svelte"; import DetailsDialog from "./details-dialog.svelte"; - import DeleteDialog from "./delete-dialog.svelte"; let { item, @@ -16,8 +14,6 @@ } = $props(); let showDetailsDialog = $state(false); - let showEditDialog = $state(false); - let showDeleteDialog = $state(false); function handleCopyId() { navigator.clipboard.writeText(item.key.toString()); @@ -26,14 +22,6 @@ function handleViewDetails() { showDetailsDialog = true; } - - function handleEdit() { - showEditDialog = true; - } - - function handleDelete() { - showDeleteDialog = true; - } @@ -54,13 +42,8 @@ Ver detalles - Editar - - Eliminar - - diff --git a/frontend/src/lib/components/dashboard/reference_data/transport_modes/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/transport_modes/data-table.svelte index 2f4a88e7..4109783b 100644 --- a/frontend/src/lib/components/dashboard/reference_data/transport_modes/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/transport_modes/data-table.svelte @@ -59,68 +59,67 @@ }); -
-
- - +
+
+ + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} - + {#each headerGroup.headers as header (header.id)} - + {/each} - + {/each} - - + + {#each table.getRowModel().rows as row (row.id)} - + {#each row.getVisibleCells() as cell (cell.id)} - + {/each} - + {:else} - - + + + {/each} - + {#if hasMore} - - -
+
+ + {/if} - - + +
{#if !header.isPlaceholder} {/if} - +
- +
No hay resultados. - - +
+
{#if loading} -
-
- Cargando más... +
+
+ Cargando más registros...
{:else} -
- Desplázate para cargar más +
+ + Desplázate para cargar más +
{/if}
- - +
diff --git a/frontend/src/lib/components/dashboard/reference_data/transport_types/data-table-actions.svelte b/frontend/src/lib/components/dashboard/reference_data/transport_types/data-table-actions.svelte index 6c4f2e5b..de253afb 100644 --- a/frontend/src/lib/components/dashboard/reference_data/transport_types/data-table-actions.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/transport_types/data-table-actions.svelte @@ -3,9 +3,7 @@ import { Button } from "$lib/components/ui/button/index.js"; import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js"; import type { TransportType } from "./columns.js"; - import CreateEditDialog from "./create-edit-dialog.svelte"; import DetailsDialog from "./details-dialog.svelte"; - import DeleteDialog from "./delete-dialog.svelte"; let { item, @@ -16,8 +14,6 @@ } = $props(); let showDetailsDialog = $state(false); - let showEditDialog = $state(false); - let showDeleteDialog = $state(false); function handleCopyId() { navigator.clipboard.writeText(item.transport_code.toString()); @@ -26,14 +22,6 @@ function handleViewDetails() { showDetailsDialog = true; } - - function handleEdit() { - showEditDialog = true; - } - - function handleDelete() { - showDeleteDialog = true; - } @@ -54,13 +42,8 @@ Ver detalles - Editar - - Eliminar - - diff --git a/frontend/src/lib/components/dashboard/reference_data/transport_types/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/transport_types/data-table.svelte index 2f4a88e7..4109783b 100644 --- a/frontend/src/lib/components/dashboard/reference_data/transport_types/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/transport_types/data-table.svelte @@ -59,68 +59,67 @@ }); -
-
- - +
+
+ + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} - + {#each headerGroup.headers as header (header.id)} - + {/each} - + {/each} - - + + {#each table.getRowModel().rows as row (row.id)} - + {#each row.getVisibleCells() as cell (cell.id)} - + {/each} - + {:else} - - + + + {/each} - + {#if hasMore} - - -
+
+ + {/if} - - + +
{#if !header.isPlaceholder} {/if} - +
- +
No hay resultados. - - +
+
{#if loading} -
-
- Cargando más... +
+
+ Cargando más registros...
{:else} -
- Desplázate para cargar más +
+ + Desplázate para cargar más +
{/if}
- - +
diff --git a/frontend/src/lib/components/dashboard/reference_data/valuation_methods/data-table-actions.svelte b/frontend/src/lib/components/dashboard/reference_data/valuation_methods/data-table-actions.svelte index cf14da9a..f4bf003f 100644 --- a/frontend/src/lib/components/dashboard/reference_data/valuation_methods/data-table-actions.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/valuation_methods/data-table-actions.svelte @@ -3,9 +3,7 @@ import { Button } from "$lib/components/ui/button/index.js"; import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js"; import type { ValuationMethod } from "./columns.js"; - import CreateEditDialog from "./create-edit-dialog.svelte"; import DetailsDialog from "./details-dialog.svelte"; - import DeleteDialog from "./delete-dialog.svelte"; let { item, @@ -16,8 +14,6 @@ } = $props(); let showDetailsDialog = $state(false); - let showEditDialog = $state(false); - let showDeleteDialog = $state(false); function handleCopyId() { navigator.clipboard.writeText(item.key.toString()); @@ -26,14 +22,6 @@ function handleViewDetails() { showDetailsDialog = true; } - - function handleEdit() { - showEditDialog = true; - } - - function handleDelete() { - showDeleteDialog = true; - } @@ -54,13 +42,8 @@ Ver detalles - Editar - - Eliminar - - diff --git a/frontend/src/lib/components/dashboard/reference_data/valuation_methods/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/valuation_methods/data-table.svelte index 36558b68..5b63e0a2 100644 --- a/frontend/src/lib/components/dashboard/reference_data/valuation_methods/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/valuation_methods/data-table.svelte @@ -60,68 +60,67 @@ -
-
- - +
+
+ + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} - + {#each headerGroup.headers as header (header.id)} - + {/each} - + {/each} - - + + {#each table.getRowModel().rows as row (row.id)} - + {#each row.getVisibleCells() as cell (cell.id)} - + {/each} - + {:else} - - + + + {/each} - + {#if hasMore} - - -
+
+ + {/if} - - + +
{#if !header.isPlaceholder} {/if} - +
- +
No hay resultados. - - +
+
{#if loading} -
-
- Cargando más... +
+
+ Cargando más registros...
{:else} -
- Desplázate para cargar más +
+ + Desplázate para cargar más +
{/if}
- - +
diff --git a/frontend/src/lib/components/ui/sidebar/sidebar-inset.svelte b/frontend/src/lib/components/ui/sidebar/sidebar-inset.svelte index d862761d..5d9598f5 100644 --- a/frontend/src/lib/components/ui/sidebar/sidebar-inset.svelte +++ b/frontend/src/lib/components/ui/sidebar/sidebar-inset.svelte @@ -14,7 +14,7 @@ bind:this={ref} data-slot="sidebar-inset" class={cn( - "bg-background relative flex w-full flex-1 flex-col", + "bg-background relative flex min-h-0 w-full flex-1 flex-col overflow-hidden", "md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm", className )} diff --git a/frontend/src/lib/components/ui/sidebar/sidebar-provider.svelte b/frontend/src/lib/components/ui/sidebar/sidebar-provider.svelte index 5b0d0aa2..f9f8f9dc 100644 --- a/frontend/src/lib/components/ui/sidebar/sidebar-provider.svelte +++ b/frontend/src/lib/components/ui/sidebar/sidebar-provider.svelte @@ -42,7 +42,7 @@ data-slot="sidebar-wrapper" style="--sidebar-width: {SIDEBAR_WIDTH}; --sidebar-width-icon: {SIDEBAR_WIDTH_ICON}; {style}" class={cn( - "group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full", + "group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex h-svh w-full overflow-hidden", className )} bind:this={ref} diff --git a/frontend/src/lib/config/shortcuts/dashboard/reference_data/common/factory.ts b/frontend/src/lib/config/shortcuts/dashboard/reference_data/common/factory.ts index 562c133e..6a487710 100644 --- a/frontend/src/lib/config/shortcuts/dashboard/reference_data/common/factory.ts +++ b/frontend/src/lib/config/shortcuts/dashboard/reference_data/common/factory.ts @@ -4,19 +4,24 @@ import type { ShortcutDef } from '$lib/stores/shortcut-store'; * Reusable shortcuts for simple reference data catalogs. */ export const obtenerAtajosReferenceDataSimple = (acciones: { - manejarNuevo: () => void; + manejarNuevo?: () => void; manejarActualizar: () => void; }): ShortcutDef[] => { - return [ - { + const atajos: ShortcutDef[] = []; + + if (acciones.manejarNuevo) { + atajos.push({ key: 'Alt+Shift+N', description: 'Nuevo Registro', action: acciones.manejarNuevo - }, - { - key: 'Alt+Shift+R', - description: 'Actualizar Lista', - action: acciones.manejarActualizar - } - ]; + }); + } + + atajos.push({ + key: 'Alt+Shift+R', + description: 'Actualizar Lista', + action: acciones.manejarActualizar + }); + + return atajos; }; diff --git a/frontend/src/routes/dashboard/+layout.svelte b/frontend/src/routes/dashboard/+layout.svelte index 12e28e3f..b9cca54b 100644 --- a/frontend/src/routes/dashboard/+layout.svelte +++ b/frontend/src/routes/dashboard/+layout.svelte @@ -141,7 +141,7 @@ -->
-
+
{#if csvImportBanner} -
+

{m['sidebar.audit_logs_title']()}

diff --git a/frontend/src/routes/dashboard/audit_logs/bitacora-tab.svelte b/frontend/src/routes/dashboard/audit_logs/bitacora-tab.svelte index da1e2bd7..0269e9e3 100644 --- a/frontend/src/routes/dashboard/audit_logs/bitacora-tab.svelte +++ b/frontend/src/routes/dashboard/audit_logs/bitacora-tab.svelte @@ -8,7 +8,6 @@ import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; - import { Label } from '$lib/components/ui/label'; import * as Table from '$lib/components/ui/table'; import { RefreshCw, Search } from 'lucide-svelte'; @@ -153,106 +152,78 @@
-
- -
- -
- - - Filtros - - -
-
- -
- - -
-
- -
- - -
- -
- - -
- -
- - -
- -
- - -
-
-
- -
-
-
-
-
- Registros - Total: {total} registros encontrados + Bitácora de Auditoría + Mostrando {logs.length} de {total} registros +
+
+
+ + +
+ + + + - + + +
diff --git a/frontend/src/routes/dashboard/audit_logs/files-tab.svelte b/frontend/src/routes/dashboard/audit_logs/files-tab.svelte index 051df209..fc652973 100644 --- a/frontend/src/routes/dashboard/audit_logs/files-tab.svelte +++ b/frontend/src/routes/dashboard/audit_logs/files-tab.svelte @@ -80,58 +80,53 @@
-
-
-

{m['sidebar.audit_logs_files_title']()}

-

{displayPath || m['sidebar.audit_logs_files_root']()}

-
- -
- - - -
- {#each breadcrumbs as crumb, idx} - {#if idx > 0} - / - {/if} - {#if idx === breadcrumbs.length - 1} - - {crumb.display_name} - - {:else} - - {/if} - {/each} -
-
-
- - - {m['sidebar.audit_logs_files_list_title']()} + +
+
+ {m['sidebar.audit_logs_files_title']()} + +
+ {#each breadcrumbs as crumb, idx} + {#if idx > 0} + / + {/if} + {#if idx === breadcrumbs.length - 1} + + {crumb.display_name} + + {:else} + + {/if} + {/each} + {#if breadcrumbs.length === 0} + {m['sidebar.audit_logs_files_root']()} + {/if} +
+
+
+ +
- + {#if error} -
+
{m['sidebar.audit_logs_files_error_prefix']()} {error}
{/if} -
+
- + {m['sidebar.audit_logs_files_col_name']()} @@ -163,7 +158,7 @@ {:else} {#each folders as folder} void loadPath(folder.path)} > @@ -178,7 +173,7 @@ {/each} {#each files as file} - + {file.display_name} diff --git a/frontend/src/routes/dashboard/audit_logs/tasks-tab.svelte b/frontend/src/routes/dashboard/audit_logs/tasks-tab.svelte index 957e4c1e..e81ba92b 100644 --- a/frontend/src/routes/dashboard/audit_logs/tasks-tab.svelte +++ b/frontend/src/routes/dashboard/audit_logs/tasks-tab.svelte @@ -6,9 +6,9 @@ type UnifiedTaskStatus } from '$lib/api/dashboard/a76/tasks'; import { companyStore } from '$lib/stores/company.svelte'; + import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; - import * as Select from '$lib/components/ui/select'; import * as Dialog from '$lib/components/ui/dialog'; import { Badge } from '$lib/components/ui/badge'; @@ -146,106 +146,116 @@ }); -
-
- -
+
+ + +
+
+ Tareas del Sistema + Total: {total} tareas encontradas +
+
+ + + + +
+
+
+ + {#if error} +
+ {error} +
+ {/if} -
- - - - - Todos - En cola - En progreso - Completadas - Fallidas - - - -
- - {#if error} -
- {error} -
- {/if} - -
- - - - - - - - - - - - - {#if tasks.length === 0 && !loading} - - - - {:else if tasks.length === 0 && loading} - - - - {:else} - {#each tasks as task} - void openDetail(task)} - > - - - - - - +
+
Task IDTipoEstadoProgresoReintentosActualizado
Sin tareas registradas
Cargando...
{task.task_id}{task.task_group} / {task.task_name} - {statusLabel(task.status)} - ({task.celery_state_raw}) - - {formatPercent(task)} - {task.progress?.message ? ` · ${task.progress.message}` : ''} - {task.retries ?? 0}{new Date(task.updated_at).toLocaleString()}
+ + + + + + + + - {/each} - {/if} - -
Task IDTipoEstadoProgresoReintentosActualizado
-
- -
-
Total: {total}
-
- - Página {page} - + + + {#if tasks.length === 0 && !loading} + + Sin tareas registradas + + {:else if tasks.length === 0 && loading} + + Cargando... + + {:else} + {#each tasks as task} + void openDetail(task)} + > + {task.task_id} + {task.task_group} / {task.task_name} + + {statusLabel(task.status)} + ({task.celery_state_raw}) + + + {formatPercent(task)} + {task.progress?.message ? ` · ${task.progress.message}` : ''} + + {task.retries ?? 0} + {new Date(task.updated_at).toLocaleString()} + + {/each} + {/if} + + +
+ +
+
Total: {total}
+
+ + Página {page} + +
-
+
(showCreateDialog = true), manejarActualizar: reloadData }) ); - // Sincronizar token de cookies a localStorage al montar el componente + // Sincronizar token de cookies onMount(() => { if (browser) { - // Función para obtener el valor de una cookie 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; }; - - // Verificar si hay token en las cookies const cookieToken = getCookie('access_token'); const localToken = localStorage.getItem('access_token'); - - if (cookieToken && cookieToken !== localToken) { - localStorage.setItem('access_token', cookieToken); - } - - // También sincronizar refresh_token si existe - const cookieRefreshToken = getCookie('refresh_token'); - const localRefreshToken = localStorage.getItem('refresh_token'); - - if (cookieRefreshToken && cookieRefreshToken !== localRefreshToken) { - localStorage.setItem('refresh_token', cookieRefreshToken); - } + if (cookieToken && cookieToken !== localToken) localStorage.setItem('access_token', cookieToken); } }); @@ -65,116 +47,118 @@ let hasMore = $derived(allItems.length < totalItems); let error = $state(data.error || null); + // Filtros + let searchQuery = $state($page.url.searchParams.get('search') || ''); + let timeout: ReturnType; + + function handleSearch() { + if (!browser) return; + clearTimeout(timeout); + timeout = setTimeout(async () => { + loading = true; + try { + const response = await codePedimentoRegimensApi.list(1, pageSize, searchQuery); + if (!response.error && response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (e) { + console.error('Error aplicando filtros:', e); + } finally { + loading = false; + } + const url = new URL($page.url); + if (searchQuery) url.searchParams.set('search', searchQuery); + else url.searchParams.delete('search'); + history.replaceState(history.state, '', url); + }, 500); + } + async function loadMore() { if (loading || !hasMore) return; - loading = true; error = null; - - try { - const response = await codePedimentoRegimensApi.list(currentPage + 1, pageSize); - + try { + const response = await codePedimentoRegimensApi.list(currentPage + 1, pageSize, searchQuery); if (response.error) { - console.error('📊 [Page] Error en loadMore:', response.error, 'Status:', response.status); - - // Si es un error de autenticación (401 o 403) y no se pudo refrescar, mostrar mensaje específico if (response.status === 401 || response.status === 403) { error = 'Sesión expirada. Recargando página...'; - // Recargar automáticamente después de 2 segundos - setTimeout(() => { - window.location.reload(); - }, 2000); + setTimeout(() => window.location.reload(), 2000); } else { error = response.error; } return; } - - if (response.data?.items) { - // Agregar los nuevos items al array existente + if (response.data?.items) { allItems = [...allItems, ...response.data.items]; currentPage++; totalItems = response.data.total; } } catch (e) { error = 'Error cargando más datos'; - console.error('📊 [Page] Error loading more:', e); } finally { loading = false; } } function reloadData() { - // Reset y recargar desde el principio window.location.reload(); } - function handleCreateClick() { - showCreateDialog = true; - } - function handleSuccess() { - // Recargar datos después de crear/editar/eliminar reloadData(); } - // Crear columnas con el callback onSuccess const columns = createColumns(handleSuccess); -
- -
-
-

Código Pedimento - Regímenes

+
+ +
+
+

+ Pedimento - Regímenes +

- Gestiona las relaciones entre códigos de pedimento y regímenes + Relación entre códigos de pedimento y regímenes aduaneros

- +
+ +
+
+ + +
+
+ Búsqueda General + +
- {#if error} - - - Error - {error} - - +
+ {error} +
{/if} - - - -
-
- Listado de Relaciones - - Mostrando {allItems.length} de {totalItems} registros - -
- -
-
- - - - -
+ +
+ +
- - - diff --git a/frontend/src/routes/dashboard/reference_data/containers/+page.svelte b/frontend/src/routes/dashboard/reference_data/containers/+page.svelte index fdb94c31..035da5d0 100644 --- a/frontend/src/routes/dashboard/reference_data/containers/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/containers/+page.svelte @@ -3,56 +3,29 @@ import { containersApi, type Container } from '$lib/api/dashboard/reference_data/containers'; import DataTable from '$lib/components/dashboard/reference_data/containers/data-table.svelte'; import { createColumns } from '$lib/components/dashboard/reference_data/containers/columns.js'; - import CreateEditDialog from '$lib/components/dashboard/reference_data/containers/create-edit-dialog.svelte'; - import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; - import type { PageData } from './$types'; - import { useShortcuts } from '$lib/hooks/use-shortcuts'; - import { obtenerAtajosLista } from '$lib/config/shortcuts/dashboard/reference_data/containers/list'; + import { Input } from '$lib/components/ui/input'; + import { page } from '$app/stores'; import { browser } from '$app/environment'; - import { Plus, RefreshCw } from 'lucide-svelte'; + import { RefreshCw } from 'lucide-svelte'; + import { useShortcuts } from '$lib/hooks/use-shortcuts'; + import type { PageData } from './$types'; // Los datos iniciales vienen del servidor let { data }: { data: PageData } = $props(); - - // Estado para el diálogo de crear - let showCreateDialog = $state(false); - // Atajos - useShortcuts( - 'Lista Contenedores', - obtenerAtajosLista({ - manejarNuevo: () => (showCreateDialog = true), - manejarActualizar: reloadData - }) - ); - - // Sincronizar token de cookies a localStorage al montar el componente + // Sincronizar token de cookies onMount(() => { if (browser) { - // Función para obtener el valor de una cookie 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; }; - - // Verificar si hay token en las cookies const cookieToken = getCookie('access_token'); const localToken = localStorage.getItem('access_token'); - - if (cookieToken && cookieToken !== localToken) { - localStorage.setItem('access_token', cookieToken); - } - - // También sincronizar refresh_token si existe - const cookieRefreshToken = getCookie('refresh_token'); - const localRefreshToken = localStorage.getItem('refresh_token'); - - if (cookieRefreshToken && cookieRefreshToken !== localRefreshToken) { - localStorage.setItem('refresh_token', cookieRefreshToken); - } + if (cookieToken && cookieToken !== localToken) localStorage.setItem('access_token', cookieToken); } }); @@ -65,116 +38,116 @@ let hasMore = $derived(allItems.length < totalItems); let error = $state(data.error || null); + // Filtros + let searchQuery = $state($page.url.searchParams.get('search') || ''); + let timeout: ReturnType; + + function handleSearch() { + if (!browser) return; + clearTimeout(timeout); + timeout = setTimeout(async () => { + loading = true; + try { + const response = await containersApi.list(1, pageSize, searchQuery); + if (!response.error && response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (e) { + console.error('Error aplicando filtros:', e); + } finally { + loading = false; + } + const url = new URL($page.url); + if (searchQuery) url.searchParams.set('search', searchQuery); + else url.searchParams.delete('search'); + history.replaceState(history.state, '', url); + }, 500); + } + async function loadMore() { if (loading || !hasMore) return; - loading = true; error = null; - - try { - const response = await containersApi.list(currentPage + 1, pageSize); - + try { + const response = await containersApi.list(currentPage + 1, pageSize, searchQuery); if (response.error) { - console.error('📊 [Page] Error en loadMore:', response.error, 'Status:', response.status); - - // Si es un error de autenticación (401 o 403) y no se pudo refrescar, mostrar mensaje específico if (response.status === 401 || response.status === 403) { error = 'Sesión expirada. Recargando página...'; - // Recargar automáticamente después de 2 segundos - setTimeout(() => { - window.location.reload(); - }, 2000); + setTimeout(() => window.location.reload(), 2000); } else { error = response.error; } return; } - - if (response.data?.items) { - // Agregar los nuevos items al array existente + if (response.data?.items) { allItems = [...allItems, ...response.data.items]; currentPage++; totalItems = response.data.total; } } catch (e) { error = 'Error cargando más datos'; - console.error('📊 [Page] Error loading more:', e); } finally { loading = false; } } function reloadData() { - // Reset y recargar desde el principio window.location.reload(); } - function handleCreateClick() { - showCreateDialog = true; - } - function handleSuccess() { - // Recargar datos después de crear/editar/eliminar reloadData(); } - // Crear columnas con el callback onSuccess + useShortcuts('Contenedores', [ + { key: 'Alt+Shift+R', description: 'Actualizar Lista', action: reloadData } + ]); + const columns = createColumns(handleSuccess); -
- -
-
-

Contenedores

+
+ +
+
+

+ Contenedores +

- Gestiona los tipos de contenedores disponibles + Gestiona los tipos de contenedores disponibles en el sistema

- +
+ +
+
+ + +
+
+ Búsqueda General + +
- {#if error} - - - Error - {error} - - +
+ {error} +
{/if} - - - -
-
- Listado de Contenedores - - Mostrando {allItems.length} de {totalItems} registros - -
- -
-
- - - - -
+ +
+ +
- - - diff --git a/frontend/src/routes/dashboard/reference_data/countries/+page.svelte b/frontend/src/routes/dashboard/reference_data/countries/+page.svelte index 6a66336f..e3330243 100644 --- a/frontend/src/routes/dashboard/reference_data/countries/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/countries/+page.svelte @@ -3,9 +3,10 @@ import { countriesApi, type Country } from '$lib/api/dashboard/reference_data/countries'; import DataTable from '$lib/components/dashboard/reference_data/countries/data-table.svelte'; import { createColumns } from '$lib/components/dashboard/reference_data/countries/columns.js'; - import CreateEditDialog from '$lib/components/dashboard/reference_data/countries/create-edit-dialog.svelte'; import { Button } from '$lib/components/ui/button'; - import { Plus, RefreshCw } from 'lucide-svelte'; + import { Input } from '$lib/components/ui/input'; + import { RefreshCw } from 'lucide-svelte'; + import { page } from '$app/stores'; import type { PageData } from './$types'; import { useShortcuts } from '$lib/hooks/use-shortcuts'; import { obtenerAtajosLista } from '$lib/config/shortcuts/dashboard/reference_data/countries/list'; @@ -14,14 +15,10 @@ // Los datos iniciales vienen del servidor let { data }: { data: PageData } = $props(); - // Estado para el diálogo de crear - let showCreateDialog = $state(false); - // Atajos useShortcuts( 'Lista Países', obtenerAtajosLista({ - manejarNuevo: () => (showCreateDialog = true), manejarActualizar: reloadData }) ); @@ -64,6 +61,37 @@ let hasMore = $derived(allItems.length < totalItems); let error = $state(data.error || null); + // Filtros + let searchQuery = $state($page.url.searchParams.get('search') || ''); + let timeout: ReturnType; + + function handleSearch() { + if (!browser) return; + clearTimeout(timeout); + timeout = setTimeout(async () => { + loading = true; + try { + const response = await countriesApi.list(1, pageSize, searchQuery); + if (!response.error && response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (e) { + console.error('Error aplicando filtros:', e); + } finally { + loading = false; + } + + // Actualizar URL silenciosamente para mantener estado + const url = new URL($page.url); + if (searchQuery) url.searchParams.set('search', searchQuery); + else url.searchParams.delete('search'); + + history.replaceState(history.state, '', url); + }, 500); + } + async function loadMore() { if (loading || !hasMore) return; @@ -71,7 +99,7 @@ error = null; try { - const response = await countriesApi.list(currentPage + 1, pageSize); + const response = await countriesApi.list(currentPage + 1, pageSize, searchQuery); if (response.error) { console.error('📊 [Page] Error en loadMore:', response.error, 'Status:', response.status); @@ -108,10 +136,6 @@ window.location.reload(); } - function handleCreateClick() { - showCreateDialog = true; - } - function handleSuccess() { // Recargar datos después de crear/editar/eliminar reloadData(); @@ -121,33 +145,46 @@ const columns = createColumns(handleSuccess); -
-
-
-

Países

+
+ +
+
+

+ Países +

Gestiona los países disponibles en el sistema

-
- - +
+
+ + +
+
+ Búsqueda General +
{#if error} -
+
{error}
{/if} -
+ +
- - diff --git a/frontend/src/routes/dashboard/reference_data/currency_types/+page.svelte b/frontend/src/routes/dashboard/reference_data/currency_types/+page.svelte index b546fb2a..7fbafdb6 100644 --- a/frontend/src/routes/dashboard/reference_data/currency_types/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/currency_types/+page.svelte @@ -3,55 +3,29 @@ import { currencyTypesApi, type CurrencyType } from '$lib/api/dashboard/reference_data/currency_types'; import DataTable from '$lib/components/dashboard/reference_data/currency_types/data-table.svelte'; import { createColumns } from '$lib/components/dashboard/reference_data/currency_types/columns.js'; - import CreateEditDialog from '$lib/components/dashboard/reference_data/currency_types/create-edit-dialog.svelte'; import { Button } from '$lib/components/ui/button'; - import { Plus, RefreshCw } from 'lucide-svelte'; - import type { PageData } from './$types'; - import { useShortcuts } from '$lib/hooks/use-shortcuts'; - import { obtenerAtajosLista } from '$lib/config/shortcuts/dashboard/reference_data/currency_types/list'; + import { Input } from '$lib/components/ui/input'; + import { page } from '$app/stores'; import { browser } from '$app/environment'; + import { RefreshCw } from 'lucide-svelte'; + import { useShortcuts } from '$lib/hooks/use-shortcuts'; + import type { PageData } from './$types'; // Los datos iniciales vienen del servidor let { data }: { data: PageData } = $props(); - - // Estado para el diálogo de crear - let showCreateDialog = $state(false); - // Atajos - useShortcuts( - 'Lista Tipos de Moneda', - obtenerAtajosLista({ - manejarNuevo: () => (showCreateDialog = true), - manejarActualizar: reloadData - }) - ); - - // Sincronizar token de cookies a localStorage al montar el componente + // Sincronizar token de cookies onMount(() => { if (browser) { - // Función para obtener el valor de una cookie 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; }; - - // Verificar si hay token en las cookies const cookieToken = getCookie('access_token'); const localToken = localStorage.getItem('access_token'); - - if (cookieToken && cookieToken !== localToken) { - localStorage.setItem('access_token', cookieToken); - } - - // También sincronizar refresh_token si existe - const cookieRefreshToken = getCookie('refresh_token'); - const localRefreshToken = localStorage.getItem('refresh_token'); - - if (cookieRefreshToken && cookieRefreshToken !== localRefreshToken) { - localStorage.setItem('refresh_token', cookieRefreshToken); - } + if (cookieToken && cookieToken !== localToken) localStorage.setItem('access_token', cookieToken); } }); @@ -64,98 +38,116 @@ let hasMore = $derived(allItems.length < totalItems); let error = $state(data.error || null); + // Filtros + let searchQuery = $state($page.url.searchParams.get('search') || ''); + let timeout: ReturnType; + + function handleSearch() { + if (!browser) return; + clearTimeout(timeout); + timeout = setTimeout(async () => { + loading = true; + try { + const response = await currencyTypesApi.list(1, pageSize, searchQuery); + if (!response.error && response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (e) { + console.error('Error aplicando filtros:', e); + } finally { + loading = false; + } + const url = new URL($page.url); + if (searchQuery) url.searchParams.set('search', searchQuery); + else url.searchParams.delete('search'); + history.replaceState(history.state, '', url); + }, 500); + } + async function loadMore() { if (loading || !hasMore) return; - loading = true; error = null; - - try { - const response = await currencyTypesApi.list(currentPage + 1, pageSize); - + try { + const response = await currencyTypesApi.list(currentPage + 1, pageSize, searchQuery); if (response.error) { - console.error('📊 [Page] Error en loadMore:', response.error, 'Status:', response.status); - - // Si es un error de autenticación (401 o 403) y no se pudo refrescar, mostrar mensaje específico if (response.status === 401 || response.status === 403) { error = 'Sesión expirada. Recargando página...'; - // Recargar automáticamente después de 2 segundos - setTimeout(() => { - window.location.reload(); - }, 2000); + setTimeout(() => window.location.reload(), 2000); } else { error = response.error; } return; } - - if (response.data?.items) { - // Agregar los nuevos items al array existente + if (response.data?.items) { allItems = [...allItems, ...response.data.items]; currentPage++; totalItems = response.data.total; } } catch (e) { error = 'Error cargando más datos'; - console.error('📊 [Page] Error loading more:', e); } finally { loading = false; } } function reloadData() { - // Reset y recargar desde el principio window.location.reload(); } - function handleCreateClick() { - showCreateDialog = true; - } - function handleSuccess() { - // Recargar datos después de crear/editar/eliminar reloadData(); } - // Crear columnas con el callback onSuccess + useShortcuts('Tipos de Moneda', [ + { key: 'Alt+Shift+R', description: 'Actualizar Lista', action: reloadData } + ]); + const columns = createColumns(handleSuccess); -
-
-
-

Tipos de Moneda

+
+ +
+
+

+ Tipos de Moneda +

Gestiona los tipos de moneda disponibles en el sistema

-
- - +
+
+ + +
+
+ Búsqueda General +
{#if error} -
+
{error}
{/if} -
- + +
+
- - diff --git a/frontend/src/routes/dashboard/reference_data/customs_sections/+page.svelte b/frontend/src/routes/dashboard/reference_data/customs_sections/+page.svelte index 4c59e6a0..473e8157 100644 --- a/frontend/src/routes/dashboard/reference_data/customs_sections/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/customs_sections/+page.svelte @@ -3,46 +3,29 @@ import { customsSectionsApi, type CustomsSection } from '$lib/api/dashboard/reference_data/customs_sections'; import DataTable from '$lib/components/dashboard/reference_data/customs_sections/data-table.svelte'; import { createColumns } from '$lib/components/dashboard/reference_data/customs_sections/columns.js'; - import CreateEditDialog from '$lib/components/dashboard/reference_data/customs_sections/create-edit-dialog.svelte'; - import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; - import type { PageData } from './$types'; + import { Input } from '$lib/components/ui/input'; + import { page } from '$app/stores'; import { browser } from '$app/environment'; - import { Plus, RefreshCw } from 'lucide-svelte'; + import { RefreshCw } from 'lucide-svelte'; import { useShortcuts } from '$lib/hooks/use-shortcuts'; + import type { PageData } from './$types'; // Los datos iniciales vienen del servidor let { data }: { data: PageData } = $props(); - // Estado para el diálogo de crear - let showCreateDialog = $state(false); - - // Sincronizar token de cookies a localStorage al montar el componente + // Sincronizar token de cookies onMount(() => { if (browser) { - // Función para obtener el valor de una cookie 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; }; - - // Verificar si hay token en las cookies const cookieToken = getCookie('access_token'); const localToken = localStorage.getItem('access_token'); - - if (cookieToken && cookieToken !== localToken) { - localStorage.setItem('access_token', cookieToken); - } - - // También sincronizar refresh_token si existe - const cookieRefreshToken = getCookie('refresh_token'); - const localRefreshToken = localStorage.getItem('refresh_token'); - - if (cookieRefreshToken && cookieRefreshToken !== localRefreshToken) { - localStorage.setItem('refresh_token', cookieRefreshToken); - } + if (cookieToken && cookieToken !== localToken) localStorage.setItem('access_token', cookieToken); } }); @@ -55,122 +38,116 @@ let hasMore = $derived(allItems.length < totalItems); let error = $state(data.error || null); + // Filtros + let searchQuery = $state($page.url.searchParams.get('search') || ''); + let timeout: ReturnType; + + function handleSearch() { + if (!browser) return; + clearTimeout(timeout); + timeout = setTimeout(async () => { + loading = true; + try { + const response = await customsSectionsApi.list(1, pageSize, searchQuery); + if (!response.error && response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (e) { + console.error('Error aplicando filtros:', e); + } finally { + loading = false; + } + const url = new URL($page.url); + if (searchQuery) url.searchParams.set('search', searchQuery); + else url.searchParams.delete('search'); + history.replaceState(history.state, '', url); + }, 500); + } + async function loadMore() { if (loading || !hasMore) return; - loading = true; error = null; - try { - const response = await customsSectionsApi.list(currentPage + 1, pageSize); - + const response = await customsSectionsApi.list(currentPage + 1, pageSize, searchQuery); if (response.error) { - console.error('📊 [Page] Error en loadMore:', response.error, 'Status:', response.status); - - // Si es un error de autenticación (401 o 403) y no se pudo refrescar, mostrar mensaje específico if (response.status === 401 || response.status === 403) { error = 'Sesión expirada. Recargando página...'; - // Recargar automáticamente después de 2 segundos - setTimeout(() => { - window.location.reload(); - }, 2000); + setTimeout(() => window.location.reload(), 2000); } else { error = response.error; } return; } - if (response.data?.items) { - // Agregar los nuevos items al array existente allItems = [...allItems, ...response.data.items]; currentPage++; totalItems = response.data.total; } } catch (e) { error = 'Error cargando más datos'; - console.error('📊 [Page] Error loading more:', e); } finally { loading = false; } } function reloadData() { - // Reset y recargar desde el principio window.location.reload(); } - function handleCreateClick() { - showCreateDialog = true; - } - function handleSuccess() { - // Recargar datos después de crear/editar/eliminar reloadData(); } - // Keyboard Shortcuts useShortcuts('Secciones Aduanales', [ - { - key: 'Alt+Shift+N', - description: 'Nueva Sección', - action: handleCreateClick - }, - { - key: 'Alt+Shift+R', - description: 'Actualizar Lista', - action: reloadData - } + { key: 'Alt+Shift+R', description: 'Actualizar Lista', action: reloadData } ]); - // Crear columnas con el callback onSuccess const columns = createColumns(handleSuccess); -
- -
-
-

Secciones Aduanales

-

Gestiona las secciones aduanales del sistema

+
+ +
+
+

+ Secciones Aduanales +

+

+ Gestiona las secciones aduanales del sistema +

+
+
+ +
+
+ + +
+
+ Búsqueda General +
-
- {#if error} - - - Error - {error} - - +
+ {error} +
{/if} - - - -
-
- Listado de Secciones Aduanales - - Mostrando {allItems.length} de {totalItems} registros - -
- -
-
- - - - -
+ +
+ +
- - - diff --git a/frontend/src/routes/dashboard/reference_data/customs_warehouses/+page.svelte b/frontend/src/routes/dashboard/reference_data/customs_warehouses/+page.svelte index 7055ef57..409c3a09 100644 --- a/frontend/src/routes/dashboard/reference_data/customs_warehouses/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/customs_warehouses/+page.svelte @@ -3,55 +3,29 @@ import { customsWarehousesApi, type CustomsWarehouse } from '$lib/api/dashboard/reference_data/customs_warehouses'; import DataTable from '$lib/components/dashboard/reference_data/customs_warehouses/data-table.svelte'; import { createColumns } from '$lib/components/dashboard/reference_data/customs_warehouses/columns.js'; - import CreateEditDialog from '$lib/components/dashboard/reference_data/customs_warehouses/create-edit-dialog.svelte'; import { Button } from '$lib/components/ui/button'; - import type { PageData } from './$types'; - import { useShortcuts } from '$lib/hooks/use-shortcuts'; - import { obtenerAtajosLista } from '$lib/config/shortcuts/dashboard/reference_data/customs_warehouses/list'; + import { Input } from '$lib/components/ui/input'; + import { page } from '$app/stores'; import { browser } from '$app/environment'; - import { Plus, RefreshCw } from 'lucide-svelte'; + import { RefreshCw } from 'lucide-svelte'; + import { useShortcuts } from '$lib/hooks/use-shortcuts'; + import type { PageData } from './$types'; // Los datos iniciales vienen del servidor let { data }: { data: PageData } = $props(); - - // Estado para el diálogo de crear - let showCreateDialog = $state(false); - // Atajos - useShortcuts( - 'Lista Almacenes Aduanales', - obtenerAtajosLista({ - manejarNuevo: () => (showCreateDialog = true), - manejarActualizar: reloadData - }) - ); - - // Sincronizar token de cookies a localStorage al montar el componente + // Sincronizar token de cookies onMount(() => { if (browser) { - // Función para obtener el valor de una cookie 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; }; - - // Verificar si hay token en las cookies const cookieToken = getCookie('access_token'); const localToken = localStorage.getItem('access_token'); - - if (cookieToken && cookieToken !== localToken) { - localStorage.setItem('access_token', cookieToken); - } - - // También sincronizar refresh_token si existe - const cookieRefreshToken = getCookie('refresh_token'); - const localRefreshToken = localStorage.getItem('refresh_token'); - - if (cookieRefreshToken && cookieRefreshToken !== localRefreshToken) { - localStorage.setItem('refresh_token', cookieRefreshToken); - } + if (cookieToken && cookieToken !== localToken) localStorage.setItem('access_token', cookieToken); } }); @@ -64,98 +38,116 @@ let hasMore = $derived(allItems.length < totalItems); let error = $state(data.error || null); + // Filtros + let searchQuery = $state($page.url.searchParams.get('search') || ''); + let timeout: ReturnType; + + function handleSearch() { + if (!browser) return; + clearTimeout(timeout); + timeout = setTimeout(async () => { + loading = true; + try { + const response = await customsWarehousesApi.list(1, pageSize, searchQuery); + if (!response.error && response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (e) { + console.error('Error aplicando filtros:', e); + } finally { + loading = false; + } + const url = new URL($page.url); + if (searchQuery) url.searchParams.set('search', searchQuery); + else url.searchParams.delete('search'); + history.replaceState(history.state, '', url); + }, 500); + } + async function loadMore() { if (loading || !hasMore) return; - loading = true; error = null; - - try { - const response = await customsWarehousesApi.list(currentPage + 1, pageSize); - + try { + const response = await customsWarehousesApi.list(currentPage + 1, pageSize, searchQuery); if (response.error) { - console.error('📊 [Page] Error en loadMore:', response.error, 'Status:', response.status); - - // Si es un error de autenticación (401 o 403) y no se pudo refrescar, mostrar mensaje específico if (response.status === 401 || response.status === 403) { error = 'Sesión expirada. Recargando página...'; - // Recargar automáticamente después de 2 segundos - setTimeout(() => { - window.location.reload(); - }, 2000); + setTimeout(() => window.location.reload(), 2000); } else { error = response.error; } return; } - - if (response.data?.items) { - // Agregar los nuevos items al array existente + if (response.data?.items) { allItems = [...allItems, ...response.data.items]; currentPage++; totalItems = response.data.total; } } catch (e) { error = 'Error cargando más datos'; - console.error('📊 [Page] Error loading more:', e); } finally { loading = false; } } function reloadData() { - // Reset y recargar desde el principio window.location.reload(); } - function handleCreateClick() { - showCreateDialog = true; - } - function handleSuccess() { - // Recargar datos después de crear/editar/eliminar reloadData(); } - // Crear columnas con el callback onSuccess + useShortcuts('Almacenes Aduanales', [ + { key: 'Alt+Shift+R', description: 'Actualizar Lista', action: reloadData } + ]); + const columns = createColumns(handleSuccess); -
-
-
-

Recintos Fiscalizados

+
+ +
+
+

+ Recintos Fiscalizados +

Gestiona los recintos fiscalizados del sistema aduanal

-
- - +
+
+ + +
+
+ Búsqueda General +
{#if error} -
+
{error}
{/if} -
- + +
+
- - diff --git a/frontend/src/routes/dashboard/reference_data/incoterms/+page.server.ts b/frontend/src/routes/dashboard/reference_data/incoterms/+page.server.ts index 776fe196..0527ad0a 100644 --- a/frontend/src/routes/dashboard/reference_data/incoterms/+page.server.ts +++ b/frontend/src/routes/dashboard/reference_data/incoterms/+page.server.ts @@ -18,13 +18,20 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => { } try { - // Obtener parámetros de paginación de la URL + // Obtener parámetros de paginación y filtros de la URL const page = parseInt(url.searchParams.get('page') || '1'); const pageSize = parseInt(url.searchParams.get('page_size') || '50'); + const code = url.searchParams.get('code') || ''; + const description = url.searchParams.get('description') || ''; + + // Construir URL con filtros + let endpoint = `v1/public/reference_data/incoterms/?page=${page}&page_size=${pageSize}`; + if (code) endpoint += `&code=${encodeURIComponent(code)}`; + if (description) endpoint += `&description=${encodeURIComponent(description)}`; // Usar authenticatedFetch para manejar automáticamente el refresh de tokens const response = await authenticatedFetch( - `v1/public/reference_data/incoterms/?page=${page}&page_size=${pageSize}`, + endpoint, {}, cookies, fetch diff --git a/frontend/src/routes/dashboard/reference_data/incoterms/+page.svelte b/frontend/src/routes/dashboard/reference_data/incoterms/+page.svelte index 9a1b417c..fe2846fd 100644 --- a/frontend/src/routes/dashboard/reference_data/incoterms/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/incoterms/+page.svelte @@ -2,29 +2,75 @@ import { page } from '$app/stores'; import { goto } from '$app/navigation'; import { browser } from '$app/environment'; + import { incotermsApi, type Incoterm } from '$lib/api/dashboard/reference_data/incoterms'; import { createColumns } from '$lib/components/dashboard/reference_data/incoterms/columns'; import DataTable from '$lib/components/dashboard/reference_data/incoterms/data-table.svelte'; - import CreateEditDialog from '$lib/components/dashboard/reference_data/incoterms/create-edit-dialog.svelte'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; - import { Plus } from 'lucide-svelte'; + import { RefreshCw, Plus } from 'lucide-svelte'; import { useShortcuts } from '$lib/hooks/use-shortcuts'; import { obtenerAtajosLista } from '$lib/config/shortcuts/dashboard/reference_data/incoterms/list'; import type { PageData } from './$types'; let { data }: { data: PageData } = $props(); - let createDialogOpen = $state(false); - // Atajos useShortcuts( 'Lista Incoterms', obtenerAtajosLista({ - manejarNuevo: () => (createDialogOpen = true), - manejarActualizar: handleSuccess + manejarActualizar: reloadData }) ); + // Estado para infinite scroll + let allItems = $state(data.items || []); + let currentPage = $state(data.page || 1); + let pageSize = $state(50); + let totalItems = $state(data.total || 0); + let loading = $state(false); + let hasMore = $derived(allItems.length < totalItems); + let error = $state(data.error || null); + + async function loadMore() { + if (loading || !hasMore) return; + + loading = true; + error = null; + + try { + const response = await incotermsApi.list(currentPage + 1, pageSize, searchCode, searchDesc); + + if (response.error) { + console.error('📊 [Page] Error en loadMore:', response.error, 'Status:', response.status); + + if (response.status === 401 || response.status === 403) { + error = 'Sesión expirada. Recargando página...'; + setTimeout(() => { + window.location.reload(); + }, 2000); + } else { + error = response.error; + } + return; + } + + if (response.data?.items) { + allItems = [...allItems, ...response.data.items]; + currentPage++; + totalItems = response.data.total; + } + } catch (e) { + error = 'Error cargando más datos'; + console.error('📊 [Page] Error loading more:', e); + } finally { + loading = false; + } + } + + function reloadData() { + window.location.reload(); + } + // Filtros let searchCode = $state($page.url.searchParams.get('code') || ''); let searchDesc = $state($page.url.searchParams.get('description') || ''); @@ -33,7 +79,22 @@ function handleSearch() { if (!browser) return; clearTimeout(timeout); - timeout = setTimeout(() => { + timeout = setTimeout(async () => { + loading = true; + try { + const response = await incotermsApi.list(1, pageSize, searchCode, searchDesc); + if (!response.error && response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (e) { + console.error('Error aplicando filtros:', e); + } finally { + loading = false; + } + + // Actualizar URL silenciosamente para mantener estado const url = new URL($page.url); if (searchCode) url.searchParams.set('code', searchCode); else url.searchParams.delete('code'); @@ -41,8 +102,7 @@ if (searchDesc) url.searchParams.set('description', searchDesc); else url.searchParams.delete('description'); - url.searchParams.set('page', '1'); - goto(url, { keepFocus: true, noScroll: true }); + history.replaceState(history.state, '', url); }, 500); } @@ -52,44 +112,61 @@ } -
-
-
-

Incoterms

+
+ +
+
+

+ Incoterms +

- Catálogo de Incoterms + Catálogo de términos internacionales de comercio

-
+
+
+ +
-
-
+ +
+
+ Clave
-
+
+ Descripción
-
+ {#if error} +
+ {error} +
+ {/if} + + +
- -
diff --git a/frontend/src/routes/dashboard/reference_data/invoice_types/+page.svelte b/frontend/src/routes/dashboard/reference_data/invoice_types/+page.svelte index f615c7b3..65f13791 100644 --- a/frontend/src/routes/dashboard/reference_data/invoice_types/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/invoice_types/+page.svelte @@ -3,55 +3,29 @@ import { invoiceTypesApi, type InvoiceType } from '$lib/api/dashboard/reference_data/invoice_types'; import DataTable from '$lib/components/dashboard/reference_data/invoice_types/data-table.svelte'; import { createColumns } from '$lib/components/dashboard/reference_data/invoice_types/columns.js'; - import CreateEditDialog from '$lib/components/dashboard/reference_data/invoice_types/create-edit-dialog.svelte'; import { Button } from '$lib/components/ui/button'; - import { Plus, RefreshCw } from 'lucide-svelte'; - import type { PageData } from './$types'; - import { useShortcuts } from '$lib/hooks/use-shortcuts'; - import { obtenerAtajosLista } from '$lib/config/shortcuts/dashboard/reference_data/invoice_types/list'; + import { Input } from '$lib/components/ui/input'; + import { page } from '$app/stores'; import { browser } from '$app/environment'; + import { RefreshCw } from 'lucide-svelte'; + import { useShortcuts } from '$lib/hooks/use-shortcuts'; + import type { PageData } from './$types'; // Los datos iniciales vienen del servidor let { data }: { data: PageData } = $props(); - - // Estado para el diálogo de crear - let showCreateDialog = $state(false); - // Atajos - useShortcuts( - 'Lista Tipos de Factura', - obtenerAtajosLista({ - manejarNuevo: () => (showCreateDialog = true), - manejarActualizar: reloadData - }) - ); - - // Sincronizar token de cookies a localStorage al montar el componente + // Sincronizar token de cookies onMount(() => { if (browser) { - // Función para obtener el valor de una cookie 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; }; - - // Verificar si hay token en las cookies const cookieToken = getCookie('access_token'); const localToken = localStorage.getItem('access_token'); - - if (cookieToken && cookieToken !== localToken) { - localStorage.setItem('access_token', cookieToken); - } - - // También sincronizar refresh_token si existe - const cookieRefreshToken = getCookie('refresh_token'); - const localRefreshToken = localStorage.getItem('refresh_token'); - - if (cookieRefreshToken && cookieRefreshToken !== localRefreshToken) { - localStorage.setItem('refresh_token', cookieRefreshToken); - } + if (cookieToken && cookieToken !== localToken) localStorage.setItem('access_token', cookieToken); } }); @@ -64,98 +38,116 @@ let hasMore = $derived(allItems.length < totalItems); let error = $state(data.error || null); + // Filtros + let searchQuery = $state($page.url.searchParams.get('search') || ''); + let timeout: ReturnType; + + function handleSearch() { + if (!browser) return; + clearTimeout(timeout); + timeout = setTimeout(async () => { + loading = true; + try { + const response = await invoiceTypesApi.list(1, pageSize, undefined, searchQuery); + if (!response.error && response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (e) { + console.error('Error aplicando filtros:', e); + } finally { + loading = false; + } + const url = new URL($page.url); + if (searchQuery) url.searchParams.set('search', searchQuery); + else url.searchParams.delete('search'); + history.replaceState(history.state, '', url); + }, 500); + } + async function loadMore() { if (loading || !hasMore) return; - loading = true; error = null; - - try { - const response = await invoiceTypesApi.list(currentPage + 1, pageSize); - + try { + const response = await invoiceTypesApi.list(currentPage + 1, pageSize, undefined, searchQuery); if (response.error) { - console.error('📊 [Page] Error en loadMore:', response.error, 'Status:', response.status); - - // Si es un error de autenticación (401 o 403) y no se pudo refrescar, mostrar mensaje específico if (response.status === 401 || response.status === 403) { error = 'Sesión expirada. Recargando página...'; - // Recargar automáticamente después de 2 segundos - setTimeout(() => { - window.location.reload(); - }, 2000); + setTimeout(() => window.location.reload(), 2000); } else { error = response.error; } return; } - - if (response.data?.items) { - // Agregar los nuevos items al array existente + if (response.data?.items) { allItems = [...allItems, ...response.data.items]; currentPage++; totalItems = response.data.total; } } catch (e) { error = 'Error cargando más datos'; - console.error('📊 [Page] Error loading more:', e); } finally { loading = false; } } function reloadData() { - // Reset y recargar desde el principio window.location.reload(); } - function handleCreateClick() { - showCreateDialog = true; - } - function handleSuccess() { - // Recargar datos después de crear/editar/eliminar reloadData(); } - // Crear columnas con el callback onSuccess + useShortcuts('Tipos de Factura', [ + { key: 'Alt+Shift+R', description: 'Actualizar Lista', action: reloadData } + ]); + const columns = createColumns(handleSuccess); -
-
-
-

Tipos de Factura

+
+ +
+
+

+ Tipos de Factura +

- Gestiona los tipos de facturas del sistema + Gestiona los tipos de facturas disponibles en el sistema

-
- - +
+
+ + +
+
+ Búsqueda General +
{#if error} -
+
{error}
{/if} -
- + +
+
- - diff --git a/frontend/src/routes/dashboard/reference_data/material_types/+page.svelte b/frontend/src/routes/dashboard/reference_data/material_types/+page.svelte index 1847f6e2..d13df004 100644 --- a/frontend/src/routes/dashboard/reference_data/material_types/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/material_types/+page.svelte @@ -3,56 +3,29 @@ import { materialTypesApi, type MaterialType } from '$lib/api/dashboard/reference_data/material_types'; import DataTable from '$lib/components/dashboard/reference_data/material_types/data-table.svelte'; import { createColumns } from '$lib/components/dashboard/reference_data/material_types/columns.js'; - import CreateEditDialog from '$lib/components/dashboard/reference_data/material_types/create-edit-dialog.svelte'; - import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; - import type { PageData } from './$types'; - import { useShortcuts } from '$lib/hooks/use-shortcuts'; - import { obtenerAtajosLista } from '$lib/config/shortcuts/dashboard/reference_data/material_types/list'; + import { Input } from '$lib/components/ui/input'; + import { page } from '$app/stores'; import { browser } from '$app/environment'; - import { Plus, RefreshCw } from 'lucide-svelte'; + import { RefreshCw } from 'lucide-svelte'; + import { useShortcuts } from '$lib/hooks/use-shortcuts'; + import type { PageData } from './$types'; // Los datos iniciales vienen del servidor let { data }: { data: PageData } = $props(); - - // Estado para el diálogo de crear - let showCreateDialog = $state(false); - // Atajos - useShortcuts( - 'Lista Tipos de Material', - obtenerAtajosLista({ - manejarNuevo: () => (showCreateDialog = true), - manejarActualizar: reloadData - }) - ); - - // Sincronizar token de cookies a localStorage al montar el componente + // Sincronizar token de cookies onMount(() => { if (browser) { - // Función para obtener el valor de una cookie 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; }; - - // Verificar si hay token en las cookies const cookieToken = getCookie('access_token'); const localToken = localStorage.getItem('access_token'); - - if (cookieToken && cookieToken !== localToken) { - localStorage.setItem('access_token', cookieToken); - } - - // También sincronizar refresh_token si existe - const cookieRefreshToken = getCookie('refresh_token'); - const localRefreshToken = localStorage.getItem('refresh_token'); - - if (cookieRefreshToken && cookieRefreshToken !== localRefreshToken) { - localStorage.setItem('refresh_token', cookieRefreshToken); - } + if (cookieToken && cookieToken !== localToken) localStorage.setItem('access_token', cookieToken); } }); @@ -65,116 +38,116 @@ let hasMore = $derived(allItems.length < totalItems); let error = $state(data.error || null); + // Filtros + let searchQuery = $state($page.url.searchParams.get('search') || ''); + let timeout: ReturnType; + + function handleSearch() { + if (!browser) return; + clearTimeout(timeout); + timeout = setTimeout(async () => { + loading = true; + try { + const response = await materialTypesApi.list(1, pageSize, undefined, searchQuery); + if (!response.error && response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (e) { + console.error('Error aplicando filtros:', e); + } finally { + loading = false; + } + const url = new URL($page.url); + if (searchQuery) url.searchParams.set('search', searchQuery); + else url.searchParams.delete('search'); + history.replaceState(history.state, '', url); + }, 500); + } + async function loadMore() { if (loading || !hasMore) return; - loading = true; error = null; - - try { - const response = await materialTypesApi.list(currentPage + 1, pageSize); - + try { + const response = await materialTypesApi.list(currentPage + 1, pageSize, undefined, searchQuery); if (response.error) { - console.error('📊 [Page] Error en loadMore:', response.error, 'Status:', response.status); - - // Si es un error de autenticación (401 o 403) y no se pudo refrescar, mostrar mensaje específico if (response.status === 401 || response.status === 403) { error = 'Sesión expirada. Recargando página...'; - // Recargar automáticamente después de 2 segundos - setTimeout(() => { - window.location.reload(); - }, 2000); + setTimeout(() => window.location.reload(), 2000); } else { error = response.error; } return; } - - if (response.data?.items) { - // Agregar los nuevos items al array existente + if (response.data?.items) { allItems = [...allItems, ...response.data.items]; currentPage++; totalItems = response.data.total; } } catch (e) { error = 'Error cargando más datos'; - console.error('📊 [Page] Error loading more:', e); } finally { loading = false; } } function reloadData() { - // Reset y recargar desde el principio window.location.reload(); } - function handleCreateClick() { - showCreateDialog = true; - } - function handleSuccess() { - // Recargar datos después de crear/editar/eliminar reloadData(); } - // Crear columnas con el callback onSuccess + useShortcuts('Tipos de Material', [ + { key: 'Alt+Shift+R', description: 'Actualizar Lista', action: reloadData } + ]); + const columns = createColumns(handleSuccess); -
- -
-
-

Tipos de Material

+
+ +
+
+

+ Tipos de Material +

- Gestiona los tipos de materiales del sistema + Gestiona los tipos de materiales disponibles en el sistema

- +
+ +
+
+ + +
+
+ Búsqueda General + +
- {#if error} - - - Error - {error} - - +
+ {error} +
{/if} - - - -
-
- Listado de Tipos de Material - - Mostrando {allItems.length} de {totalItems} registros - -
- -
-
- - - - -
+ +
+ +
- - - diff --git a/frontend/src/routes/dashboard/reference_data/payment_methods/+page.svelte b/frontend/src/routes/dashboard/reference_data/payment_methods/+page.svelte index 53623210..c25f8372 100644 --- a/frontend/src/routes/dashboard/reference_data/payment_methods/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/payment_methods/+page.svelte @@ -3,46 +3,29 @@ import { paymentMethodsApi, type PaymentMethod } from '$lib/api/dashboard/reference_data/payment_methods'; import DataTable from '$lib/components/dashboard/reference_data/payment_methods/data-table.svelte'; import { createColumns } from '$lib/components/dashboard/reference_data/payment_methods/columns.js'; - import CreateEditDialog from '$lib/components/dashboard/reference_data/payment_methods/create-edit-dialog.svelte'; - import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; - import type { PageData } from './$types'; + import { Input } from '$lib/components/ui/input'; + import { page } from '$app/stores'; import { browser } from '$app/environment'; - import { Plus, RefreshCw } from 'lucide-svelte'; + import { RefreshCw } from 'lucide-svelte'; import { useShortcuts } from '$lib/hooks/use-shortcuts'; + import type { PageData } from './$types'; // Los datos iniciales vienen del servidor let { data }: { data: PageData } = $props(); - // Estado para el diálogo de crear - let showCreateDialog = $state(false); - - // Sincronizar token de cookies a localStorage al montar el componente + // Sincronizar token de cookies onMount(() => { if (browser) { - // Función para obtener el valor de una cookie 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; }; - - // Verificar si hay token en las cookies const cookieToken = getCookie('access_token'); const localToken = localStorage.getItem('access_token'); - - if (cookieToken && cookieToken !== localToken) { - localStorage.setItem('access_token', cookieToken); - } - - // También sincronizar refresh_token si existe - const cookieRefreshToken = getCookie('refresh_token'); - const localRefreshToken = localStorage.getItem('refresh_token'); - - if (cookieRefreshToken && cookieRefreshToken !== localRefreshToken) { - localStorage.setItem('refresh_token', cookieRefreshToken); - } + if (cookieToken && cookieToken !== localToken) localStorage.setItem('access_token', cookieToken); } }); @@ -55,122 +38,116 @@ let hasMore = $derived(allItems.length < totalItems); let error = $state(data.error || null); + // Filtros + let searchQuery = $state($page.url.searchParams.get('search') || ''); + let timeout: ReturnType; + + function handleSearch() { + if (!browser) return; + clearTimeout(timeout); + timeout = setTimeout(async () => { + loading = true; + try { + const response = await paymentMethodsApi.list(1, pageSize, searchQuery); + if (!response.error && response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (e) { + console.error('Error aplicando filtros:', e); + } finally { + loading = false; + } + const url = new URL($page.url); + if (searchQuery) url.searchParams.set('search', searchQuery); + else url.searchParams.delete('search'); + history.replaceState(history.state, '', url); + }, 500); + } + async function loadMore() { if (loading || !hasMore) return; - loading = true; error = null; - try { - const response = await paymentMethodsApi.list(currentPage + 1, pageSize); - + const response = await paymentMethodsApi.list(currentPage + 1, pageSize, searchQuery); if (response.error) { - console.error('📊 [Page] Error en loadMore:', response.error, 'Status:', response.status); - - // Si es un error de autenticación (401 o 403) y no se pudo refrescar, mostrar mensaje específico if (response.status === 401 || response.status === 403) { error = 'Sesión expirada. Recargando página...'; - // Recargar automáticamente después de 2 segundos - setTimeout(() => { - window.location.reload(); - }, 2000); + setTimeout(() => window.location.reload(), 2000); } else { error = response.error; } return; } - if (response.data?.items) { - // Agregar los nuevos items al array existente allItems = [...allItems, ...response.data.items]; currentPage++; totalItems = response.data.total; } } catch (e) { error = 'Error cargando más datos'; - console.error('📊 [Page] Error loading more:', e); } finally { loading = false; } } function reloadData() { - // Reset y recargar desde el principio window.location.reload(); } - function handleCreateClick() { - showCreateDialog = true; - } - function handleSuccess() { - // Recargar datos después de crear/editar/eliminar reloadData(); } - // Keyboard Shortcuts useShortcuts('Métodos de Pago', [ - { - key: 'Alt+Shift+N', - description: 'Nuevo Método', - action: handleCreateClick - }, - { - key: 'Alt+Shift+R', - description: 'Actualizar Lista', - action: reloadData - } + { key: 'Alt+Shift+R', description: 'Actualizar Lista', action: reloadData } ]); - // Crear columnas con el callback onSuccess const columns = createColumns(handleSuccess); -
- -
-
-

Métodos de Pago

-

Gestiona las formas de pago disponibles en el sistema

+
+ +
+
+

+ Métodos de Pago +

+

+ Gestiona las formas de pago disponibles en el sistema +

+
+
+ +
+
+ + +
+
+ Búsqueda General +
-
- {#if error} - - - Error - {error} - - +
+ {error} +
{/if} - - - -
-
- Listado de Métodos de Pago - - Mostrando {allItems.length} de {totalItems} registros - -
- -
-
- - - - -
+ +
+ +
- - - diff --git a/frontend/src/routes/dashboard/reference_data/pedimento_codes/+page.svelte b/frontend/src/routes/dashboard/reference_data/pedimento_codes/+page.svelte index 15db3620..cb541aee 100644 --- a/frontend/src/routes/dashboard/reference_data/pedimento_codes/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/pedimento_codes/+page.svelte @@ -3,46 +3,29 @@ import { pedimentoCodesApi, type PedimentoCode } from '$lib/api/dashboard/reference_data/pedimento_codes'; import DataTable from '$lib/components/dashboard/reference_data/pedimento_codes/data-table.svelte'; import { createColumns } from '$lib/components/dashboard/reference_data/pedimento_codes/columns.js'; - import CreateEditDialog from '$lib/components/dashboard/reference_data/pedimento_codes/create-edit-dialog.svelte'; - import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; - import { Plus, RefreshCw } from 'lucide-svelte'; + import { Input } from '$lib/components/ui/input'; + import { page } from '$app/stores'; + import { browser } from '$app/environment'; + import { RefreshCw } from 'lucide-svelte'; import { useShortcuts } from '$lib/hooks/use-shortcuts'; import type { PageData } from './$types'; - import { browser } from '$app/environment'; // Los datos iniciales vienen del servidor let { data }: { data: PageData } = $props(); - // Estado para el diálogo de crear - let showCreateDialog = $state(false); - - // Sincronizar token de cookies a localStorage al montar el componente + // Sincronizar token de cookies onMount(() => { if (browser) { - // Función para obtener el valor de una cookie 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; }; - - // Verificar si hay token en las cookies const cookieToken = getCookie('access_token'); const localToken = localStorage.getItem('access_token'); - - if (cookieToken && cookieToken !== localToken) { - localStorage.setItem('access_token', cookieToken); - } - - // También sincronizar refresh_token si existe - const cookieRefreshToken = getCookie('refresh_token'); - const localRefreshToken = localStorage.getItem('refresh_token'); - - if (cookieRefreshToken && cookieRefreshToken !== localRefreshToken) { - localStorage.setItem('refresh_token', cookieRefreshToken); - } + if (cookieToken && cookieToken !== localToken) localStorage.setItem('access_token', cookieToken); } }); @@ -55,122 +38,116 @@ let hasMore = $derived(allItems.length < totalItems); let error = $state(data.error || null); + // Filtros + let searchQuery = $state($page.url.searchParams.get('search') || ''); + let timeout: ReturnType; + + function handleSearch() { + if (!browser) return; + clearTimeout(timeout); + timeout = setTimeout(async () => { + loading = true; + try { + const response = await pedimentoCodesApi.list(1, pageSize, searchQuery); + if (!response.error && response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (e) { + console.error('Error aplicando filtros:', e); + } finally { + loading = false; + } + const url = new URL($page.url); + if (searchQuery) url.searchParams.set('search', searchQuery); + else url.searchParams.delete('search'); + history.replaceState(history.state, '', url); + }, 500); + } + async function loadMore() { if (loading || !hasMore) return; - loading = true; error = null; - try { - const response = await pedimentoCodesApi.list(currentPage + 1, pageSize); - + const response = await pedimentoCodesApi.list(currentPage + 1, pageSize, searchQuery); if (response.error) { - console.error('📊 [Page] Error en loadMore:', response.error, 'Status:', response.status); - - // Si es un error de autenticación (401 o 403) y no se pudo refrescar, mostrar mensaje específico if (response.status === 401 || response.status === 403) { error = 'Sesión expirada. Recargando página...'; - // Recargar automáticamente después de 2 segundos - setTimeout(() => { - window.location.reload(); - }, 2000); + setTimeout(() => window.location.reload(), 2000); } else { error = response.error; } return; } - if (response.data?.items) { - // Agregar los nuevos items al array existente allItems = [...allItems, ...response.data.items]; currentPage++; totalItems = response.data.total; } } catch (e) { error = 'Error cargando más datos'; - console.error('📊 [Page] Error loading more:', e); } finally { loading = false; } } function reloadData() { - // Reset y recargar desde el principio window.location.reload(); } - function handleCreateClick() { - showCreateDialog = true; - } - function handleSuccess() { - // Recargar datos después de crear/editar/eliminar reloadData(); } - // Keyboard Shortcuts useShortcuts('Claves de Pedimento', [ - { - key: 'Alt+Shift+N', - description: 'Nueva Clave', - action: handleCreateClick - }, - { - key: 'Alt+Shift+R', - description: 'Actualizar Lista', - action: reloadData - } + { key: 'Alt+Shift+R', description: 'Actualizar Lista', action: reloadData } ]); - // Crear columnas con el callback onSuccess const columns = createColumns(handleSuccess); -
- -
-
-

Claves de Pedimento

-

Gestiona las claves de pedimento del sistema aduanero

+
+ +
+
+

+ Claves de Pedimento +

+

+ Gestiona las claves de pedimento del sistema aduanero +

+
+
+ +
+
+ + +
+
+ Búsqueda General +
-
- {#if error} - - - Error - {error} - - +
+ {error} +
{/if} - - - -
-
- Listado de Claves de Pedimento - - Mostrando {allItems.length} de {totalItems} registros - -
- -
-
- - - - -
+ +
+ +
- - - diff --git a/frontend/src/routes/dashboard/reference_data/pedimento_regimens/+page.svelte b/frontend/src/routes/dashboard/reference_data/pedimento_regimens/+page.svelte index e2222c2f..27a53484 100644 --- a/frontend/src/routes/dashboard/reference_data/pedimento_regimens/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/pedimento_regimens/+page.svelte @@ -3,46 +3,29 @@ import { pedimentoRegimensApi, type PedimentoRegimen } from '$lib/api/dashboard/reference_data/pedimento_regimens'; import DataTable from '$lib/components/dashboard/reference_data/pedimento_regimens/data-table.svelte'; import { createColumns } from '$lib/components/dashboard/reference_data/pedimento_regimens/columns.js'; - import CreateEditDialog from '$lib/components/dashboard/reference_data/pedimento_regimens/create-edit-dialog.svelte'; - import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; - import type { PageData } from './$types'; + import { Input } from '$lib/components/ui/input'; + import { page } from '$app/stores'; import { browser } from '$app/environment'; - import { Plus, RefreshCw } from 'lucide-svelte'; + import { RefreshCw } from 'lucide-svelte'; import { useShortcuts } from '$lib/hooks/use-shortcuts'; + import type { PageData } from './$types'; // Los datos iniciales vienen del servidor let { data }: { data: PageData } = $props(); - // Estado para el diálogo de crear - let showCreateDialog = $state(false); - - // Sincronizar token de cookies a localStorage al montar el componente + // Sincronizar token de cookies onMount(() => { if (browser) { - // Función para obtener el valor de una cookie 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; }; - - // Verificar si hay token en las cookies const cookieToken = getCookie('access_token'); const localToken = localStorage.getItem('access_token'); - - if (cookieToken && cookieToken !== localToken) { - localStorage.setItem('access_token', cookieToken); - } - - // También sincronizar refresh_token si existe - const cookieRefreshToken = getCookie('refresh_token'); - const localRefreshToken = localStorage.getItem('refresh_token'); - - if (cookieRefreshToken && cookieRefreshToken !== localRefreshToken) { - localStorage.setItem('refresh_token', cookieRefreshToken); - } + if (cookieToken && cookieToken !== localToken) localStorage.setItem('access_token', cookieToken); } }); @@ -55,122 +38,116 @@ let hasMore = $derived(allItems.length < totalItems); let error = $state(data.error || null); + // Filtros + let searchQuery = $state($page.url.searchParams.get('search') || ''); + let timeout: ReturnType; + + function handleSearch() { + if (!browser) return; + clearTimeout(timeout); + timeout = setTimeout(async () => { + loading = true; + try { + const response = await pedimentoRegimensApi.list(1, pageSize, searchQuery); + if (!response.error && response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (e) { + console.error('Error aplicando filtros:', e); + } finally { + loading = false; + } + const url = new URL($page.url); + if (searchQuery) url.searchParams.set('search', searchQuery); + else url.searchParams.delete('search'); + history.replaceState(history.state, '', url); + }, 500); + } + async function loadMore() { if (loading || !hasMore) return; - loading = true; error = null; - try { - const response = await pedimentoRegimensApi.list(currentPage + 1, pageSize); - + const response = await pedimentoRegimensApi.list(currentPage + 1, pageSize, searchQuery); if (response.error) { - console.error('📊 [Page] Error en loadMore:', response.error, 'Status:', response.status); - - // Si es un error de autenticación (401 o 403) y no se pudo refrescar, mostrar mensaje específico if (response.status === 401 || response.status === 403) { error = 'Sesión expirada. Recargando página...'; - // Recargar automáticamente después de 2 segundos - setTimeout(() => { - window.location.reload(); - }, 2000); + setTimeout(() => window.location.reload(), 2000); } else { error = response.error; } return; } - if (response.data?.items) { - // Agregar los nuevos items al array existente allItems = [...allItems, ...response.data.items]; currentPage++; totalItems = response.data.total; } } catch (e) { error = 'Error cargando más datos'; - console.error('📊 [Page] Error loading more:', e); } finally { loading = false; } } function reloadData() { - // Reset y recargar desde el principio window.location.reload(); } - function handleCreateClick() { - showCreateDialog = true; - } - function handleSuccess() { - // Recargar datos después de crear/editar/eliminar reloadData(); } - // Keyboard Shortcuts useShortcuts('Regímenes', [ - { - key: 'Alt+Shift+N', - description: 'Nuevo Régimen', - action: handleCreateClick - }, - { - key: 'Alt+Shift+R', - description: 'Actualizar Lista', - action: reloadData - } + { key: 'Alt+Shift+R', description: 'Actualizar Lista', action: reloadData } ]); - // Crear columnas con el callback onSuccess const columns = createColumns(handleSuccess); -
- -
-
-

Regímenes de Pedimento

-

Gestiona los regímenes aduaneros de pedimento

+
+ +
+
+

+ Regímenes +

+

+ Gestiona los regímenes aduaneros de pedimento +

+
+
+ +
+
+ + +
+
+ Búsqueda General +
-
- {#if error} - - - Error - {error} - - +
+ {error} +
{/if} - - - -
-
- Listado de Regímenes de Pedimento - - Mostrando {allItems.length} de {totalItems} registros - -
- -
-
- - - - -
+ +
+ +
- - - diff --git a/frontend/src/routes/dashboard/reference_data/sectors/+page.svelte b/frontend/src/routes/dashboard/reference_data/sectors/+page.svelte index f944f18f..f01672d4 100644 --- a/frontend/src/routes/dashboard/reference_data/sectors/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/sectors/+page.svelte @@ -4,56 +4,29 @@ import { companyStore } from '$lib/stores/company.svelte'; import DataTable from '$lib/components/dashboard/reference_data/sectors/data-table.svelte'; import { createColumns } from '$lib/components/dashboard/reference_data/sectors/columns.js'; - import CreateEditDialog from '$lib/components/dashboard/reference_data/sectors/create-edit-dialog.svelte'; - import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; - import { Plus, RefreshCw } from 'lucide-svelte'; - import type { PageData } from './$types'; - import { useShortcuts } from '$lib/hooks/use-shortcuts'; - import { obtenerAtajosLista } from '$lib/config/shortcuts/dashboard/reference_data/sectors/list'; + import { Input } from '$lib/components/ui/input'; + import { page } from '$app/stores'; import { browser } from '$app/environment'; + import { RefreshCw } from 'lucide-svelte'; + import { useShortcuts } from '$lib/hooks/use-shortcuts'; + import type { PageData } from './$types'; // Los datos iniciales vienen del servidor let { data }: { data: PageData } = $props(); - - // Estado para el diálogo de crear - let showCreateDialog = $state(false); - // Atajos - useShortcuts( - 'Lista Sectores', - obtenerAtajosLista({ - manejarNuevo: () => (showCreateDialog = true), - manejarActualizar: reloadData - }) - ); - - // Sincronizar token de cookies a localStorage al montar el componente + // Sincronizar token de cookies onMount(() => { if (browser) { - // Función para obtener el valor de una cookie 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; }; - - // Verificar si hay token en las cookies const cookieToken = getCookie('access_token'); const localToken = localStorage.getItem('access_token'); - - if (cookieToken && cookieToken !== localToken) { - localStorage.setItem('access_token', cookieToken); - } - - // También sincronizar refresh_token si existe - const cookieRefreshToken = getCookie('refresh_token'); - const localRefreshToken = localStorage.getItem('refresh_token'); - - if (cookieRefreshToken && cookieRefreshToken !== localRefreshToken) { - localStorage.setItem('refresh_token', cookieRefreshToken); - } + if (cookieToken && cookieToken !== localToken) localStorage.setItem('access_token', cookieToken); } }); @@ -66,121 +39,139 @@ let hasMore = $derived(allItems.length < totalItems); let error = $state(data.error || null); + // Filtros + let searchQuery = $state($page.url.searchParams.get('search') || ''); + let timeout: ReturnType; + + function getActiveCompanyId(): number | null { + const fromStore = companyStore.activeCompany?.id; + if (fromStore) return fromStore; + if (!browser) return null; + const cookie = document.cookie + .split('; ') + .find((row) => row.startsWith('active_company_id=')) + ?.split('=')[1]; + if (!cookie) return null; + const parsed = Number(cookie); + return Number.isFinite(parsed) ? parsed : null; + } + + function handleSearch() { + if (!browser) return; + clearTimeout(timeout); + timeout = setTimeout(async () => { + loading = true; + try { + const companyId = getActiveCompanyId(); + if (!companyId) { + error = 'No hay empresa activa seleccionada'; + return; + } + const response = await sectorsApi.list(1, pageSize, companyId, searchQuery); + if (!response.error && response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (e) { + console.error('Error aplicando filtros:', e); + } finally { + loading = false; + } + const url = new URL($page.url); + if (searchQuery) url.searchParams.set('search', searchQuery); + else url.searchParams.delete('search'); + history.replaceState(history.state, '', url); + }, 500); + } + async function loadMore() { if (loading || !hasMore) return; - loading = true; error = null; - - try { - const companyId = companyStore.activeCompany?.id; - if (!companyId) { - error = 'No hay empresa activa seleccionada'; - return; - } - const response = await sectorsApi.list(currentPage + 1, pageSize, companyId); - + try { + const companyId = getActiveCompanyId(); + if (!companyId) { + error = 'No hay empresa activa seleccionada'; + return; + } + const response = await sectorsApi.list(currentPage + 1, pageSize, companyId, searchQuery); if (response.error) { - console.error('📊 [Page] Error en loadMore:', response.error, 'Status:', response.status); - - // Si es un error de autenticación (401 o 403) y no se pudo refrescar, mostrar mensaje específico if (response.status === 401 || response.status === 403) { error = 'Sesión expirada. Recargando página...'; - // Recargar automáticamente después de 2 segundos - setTimeout(() => { - window.location.reload(); - }, 2000); + setTimeout(() => window.location.reload(), 2000); } else { error = response.error; } return; } - - if (response.data?.items) { - // Agregar los nuevos items al array existente + if (response.data?.items) { allItems = [...allItems, ...response.data.items]; currentPage++; totalItems = response.data.total; } } catch (e) { error = 'Error cargando más datos'; - console.error('📊 [Page] Error loading more:', e); } finally { loading = false; } } function reloadData() { - // Reset y recargar desde el principio window.location.reload(); } - function handleCreateClick() { - showCreateDialog = true; - } - function handleSuccess() { - // Recargar datos después de crear/editar/eliminar reloadData(); } - // Crear columnas con el callback onSuccess + useShortcuts('Sectores', [ + { key: 'Alt+Shift+R', description: 'Actualizar Lista', action: reloadData } + ]); + const columns = createColumns(handleSuccess); -
- -
-
-

Sectores

+
+ +
+
+

+ Sectores +

- Gestiona los sectores económicos + Gestiona los sectores económicos del sistema

- +
+ +
+
+ + +
+
+ Búsqueda por Clave + +
- {#if error} - - - Error - {error} - - +
+ {error} +
{/if} - - - -
-
- Listado de Sectores - - Mostrando {allItems.length} de {totalItems} registros - -
- -
-
- - - - -
+ +
+ +
- - - diff --git a/frontend/src/routes/dashboard/reference_data/states/+page.svelte b/frontend/src/routes/dashboard/reference_data/states/+page.svelte index bae3c744..fabc9948 100644 --- a/frontend/src/routes/dashboard/reference_data/states/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/states/+page.svelte @@ -3,29 +3,25 @@ import { statesApi, type State } from '$lib/api/dashboard/reference_data/states'; import DataTable from '$lib/components/dashboard/reference_data/states/data-table.svelte'; import { createColumns } from '$lib/components/dashboard/reference_data/states/columns.js'; - import CreateEditDialog from '$lib/components/dashboard/reference_data/states/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 type { PageData } from './$types'; import { useShortcuts } from '$lib/hooks/use-shortcuts'; - import { obtenerAtajosLista } from '$lib/config/shortcuts/dashboard/reference_data/states/list'; import { browser } from '$app/environment'; - import { Plus, RefreshCw } from 'lucide-svelte'; + import { page } from '$app/stores'; + import { RefreshCw } from 'lucide-svelte'; // Los datos iniciales vienen del servidor let { data }: { data: PageData } = $props(); - - // Estado para el diálogo de crear - let showCreateDialog = $state(false); - // Atajos - useShortcuts( - 'Lista Estados', - obtenerAtajosLista({ - manejarNuevo: () => (showCreateDialog = true), - manejarActualizar: reloadData - }) - ); + // Keyboard Shortcuts + useShortcuts('Estados', [ + { + key: 'Alt+Shift+R', + description: 'Actualizar Lista', + action: reloadData + } + ]); // Sincronizar token de cookies a localStorage al montar el componente onMount(() => { @@ -65,6 +61,34 @@ let hasMore = $derived(allItems.length < totalItems); let error = $state(data.error || null); + // Filtros + let searchQuery = $state($page.url.searchParams.get('search') || ''); + let timeout: ReturnType; + + function handleSearch() { + if (!browser) return; + clearTimeout(timeout); + timeout = setTimeout(async () => { + loading = true; + try { + const response = await statesApi.list(1, pageSize, searchQuery); + if (!response.error && response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (e) { + console.error('Error aplicando filtros:', e); + } finally { + loading = false; + } + const url = new URL($page.url); + if (searchQuery) url.searchParams.set('search', searchQuery); + else url.searchParams.delete('search'); + history.replaceState(history.state, '', url); + }, 500); + } + async function loadMore() { if (loading || !hasMore) return; @@ -72,7 +96,7 @@ error = null; try { - const response = await statesApi.list(currentPage + 1, pageSize); + const response = await statesApi.list(currentPage + 1, pageSize, searchQuery); if (response.error) { console.error('📊 [Page] Error en loadMore:', response.error, 'Status:', response.status); @@ -109,10 +133,6 @@ window.location.reload(); } - function handleCreateClick() { - showCreateDialog = true; - } - function handleSuccess() { // Recargar datos después de crear/editar/eliminar reloadData(); @@ -122,59 +142,52 @@ const columns = createColumns(handleSuccess); -
- -
-
-

Estados

+
+ +
+
+

+ Estados +

Gestiona los estados y sus claves de identificación

- +
+ +
+
+ + +
+
+ Búsqueda General + +
- {#if error} - - - Error - {error} - - +
+ {error} +
{/if} - - - -
-
- Listado de Estados - - Mostrando {allItems.length} de {totalItems} registros - -
- -
-
- - - - -
+ +
+ +
- - - diff --git a/frontend/src/routes/dashboard/reference_data/transport_modes/+page.svelte b/frontend/src/routes/dashboard/reference_data/transport_modes/+page.svelte index 511ac0e4..b0574e29 100644 --- a/frontend/src/routes/dashboard/reference_data/transport_modes/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/transport_modes/+page.svelte @@ -3,56 +3,29 @@ import { transportModesApi, type TransportMode } from '$lib/api/dashboard/reference_data/transport_modes'; import DataTable from '$lib/components/dashboard/reference_data/transport_modes/data-table.svelte'; import { createColumns } from '$lib/components/dashboard/reference_data/transport_modes/columns.js'; - import CreateEditDialog from '$lib/components/dashboard/reference_data/transport_modes/create-edit-dialog.svelte'; - import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; - import type { PageData } from './$types'; - import { useShortcuts } from '$lib/hooks/use-shortcuts'; - import { obtenerAtajosLista } from '$lib/config/shortcuts/dashboard/reference_data/transport_modes/list'; + import { Input } from '$lib/components/ui/input'; + import { page } from '$app/stores'; import { browser } from '$app/environment'; - import { Plus, RefreshCw } from 'lucide-svelte'; + import { RefreshCw } from 'lucide-svelte'; + import { useShortcuts } from '$lib/hooks/use-shortcuts'; + import type { PageData } from './$types'; // Los datos iniciales vienen del servidor let { data }: { data: PageData } = $props(); - - // Estado para el diálogo de crear - let showCreateDialog = $state(false); - // Atajos - useShortcuts( - 'Lista Modos de Transporte', - obtenerAtajosLista({ - manejarNuevo: () => (showCreateDialog = true), - manejarActualizar: reloadData - }) - ); - - // Sincronizar token de cookies a localStorage al montar el componente + // Sincronizar token de cookies onMount(() => { if (browser) { - // Función para obtener el valor de una cookie 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; }; - - // Verificar si hay token en las cookies const cookieToken = getCookie('access_token'); const localToken = localStorage.getItem('access_token'); - - if (cookieToken && cookieToken !== localToken) { - localStorage.setItem('access_token', cookieToken); - } - - // También sincronizar refresh_token si existe - const cookieRefreshToken = getCookie('refresh_token'); - const localRefreshToken = localStorage.getItem('refresh_token'); - - if (cookieRefreshToken && cookieRefreshToken !== localRefreshToken) { - localStorage.setItem('refresh_token', cookieRefreshToken); - } + if (cookieToken && cookieToken !== localToken) localStorage.setItem('access_token', cookieToken); } }); @@ -65,116 +38,116 @@ let hasMore = $derived(allItems.length < totalItems); let error = $state(data.error || null); + // Filtros + let searchQuery = $state($page.url.searchParams.get('search') || ''); + let timeout: ReturnType; + + function handleSearch() { + if (!browser) return; + clearTimeout(timeout); + timeout = setTimeout(async () => { + loading = true; + try { + const response = await transportModesApi.list(1, pageSize, searchQuery); + if (!response.error && response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (e) { + console.error('Error aplicando filtros:', e); + } finally { + loading = false; + } + const url = new URL($page.url); + if (searchQuery) url.searchParams.set('search', searchQuery); + else url.searchParams.delete('search'); + history.replaceState(history.state, '', url); + }, 500); + } + async function loadMore() { if (loading || !hasMore) return; - loading = true; error = null; - - try { - const response = await transportModesApi.list(currentPage + 1, pageSize); - + try { + const response = await transportModesApi.list(currentPage + 1, pageSize, searchQuery); if (response.error) { - console.error('📊 [Page] Error en loadMore:', response.error, 'Status:', response.status); - - // Si es un error de autenticación (401 o 403) y no se pudo refrescar, mostrar mensaje específico if (response.status === 401 || response.status === 403) { error = 'Sesión expirada. Recargando página...'; - // Recargar automáticamente después de 2 segundos - setTimeout(() => { - window.location.reload(); - }, 2000); + setTimeout(() => window.location.reload(), 2000); } else { error = response.error; } return; } - - if (response.data?.items) { - // Agregar los nuevos items al array existente + if (response.data?.items) { allItems = [...allItems, ...response.data.items]; currentPage++; totalItems = response.data.total; } } catch (e) { error = 'Error cargando más datos'; - console.error('📊 [Page] Error loading more:', e); } finally { loading = false; } } function reloadData() { - // Reset y recargar desde el principio window.location.reload(); } - function handleCreateClick() { - showCreateDialog = true; - } - function handleSuccess() { - // Recargar datos después de crear/editar/eliminar reloadData(); } - // Crear columnas con el callback onSuccess + useShortcuts('Modos de Transporte', [ + { key: 'Alt+Shift+R', description: 'Actualizar Lista', action: reloadData } + ]); + const columns = createColumns(handleSuccess); -
- -
-
-

Modos de Transporte

+
+ +
+
+

+ Modos de Transporte +

- Gestiona los modos de transporte disponibles + Gestiona los modos de transporte disponibles en el sistema

- +
+ +
+
+ + +
+
+ Búsqueda General + +
- {#if error} - - - Error - {error} - - +
+ {error} +
{/if} - - - -
-
- Listado de Modos de Transporte - - Mostrando {allItems.length} de {totalItems} registros - -
- -
-
- - - - -
+ +
+ +
- - - diff --git a/frontend/src/routes/dashboard/reference_data/transport_types/+page.svelte b/frontend/src/routes/dashboard/reference_data/transport_types/+page.svelte index 6506fd52..5247408a 100644 --- a/frontend/src/routes/dashboard/reference_data/transport_types/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/transport_types/+page.svelte @@ -3,56 +3,29 @@ import { transportTypesApi, type TransportType } from '$lib/api/dashboard/reference_data/transport_types'; import DataTable from '$lib/components/dashboard/reference_data/transport_types/data-table.svelte'; import { createColumns } from '$lib/components/dashboard/reference_data/transport_types/columns.js'; - import CreateEditDialog from '$lib/components/dashboard/reference_data/transport_types/create-edit-dialog.svelte'; - import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; - import { Plus, RefreshCw } from 'lucide-svelte'; - import type { PageData } from './$types'; - import { useShortcuts } from '$lib/hooks/use-shortcuts'; - import { obtenerAtajosLista } from '$lib/config/shortcuts/dashboard/reference_data/transport_types/list'; + import { Input } from '$lib/components/ui/input'; + import { page } from '$app/stores'; import { browser } from '$app/environment'; + import { RefreshCw } from 'lucide-svelte'; + import { useShortcuts } from '$lib/hooks/use-shortcuts'; + import type { PageData } from './$types'; // Los datos iniciales vienen del servidor let { data }: { data: PageData } = $props(); - - // Estado para el diálogo de crear - let showCreateDialog = $state(false); - // Atajos - useShortcuts( - 'Lista Tipos de Transporte', - obtenerAtajosLista({ - manejarNuevo: () => (showCreateDialog = true), - manejarActualizar: reloadData - }) - ); - - // Sincronizar token de cookies a localStorage al montar el componente + // Sincronizar token de cookies onMount(() => { if (browser) { - // Función para obtener el valor de una cookie 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; }; - - // Verificar si hay token en las cookies const cookieToken = getCookie('access_token'); const localToken = localStorage.getItem('access_token'); - - if (cookieToken && cookieToken !== localToken) { - localStorage.setItem('access_token', cookieToken); - } - - // También sincronizar refresh_token si existe - const cookieRefreshToken = getCookie('refresh_token'); - const localRefreshToken = localStorage.getItem('refresh_token'); - - if (cookieRefreshToken && cookieRefreshToken !== localRefreshToken) { - localStorage.setItem('refresh_token', cookieRefreshToken); - } + if (cookieToken && cookieToken !== localToken) localStorage.setItem('access_token', cookieToken); } }); @@ -65,116 +38,116 @@ let hasMore = $derived(allItems.length < totalItems); let error = $state(data.error || null); + // Filtros + let searchQuery = $state($page.url.searchParams.get('search') || ''); + let timeout: ReturnType; + + function handleSearch() { + if (!browser) return; + clearTimeout(timeout); + timeout = setTimeout(async () => { + loading = true; + try { + const response = await transportTypesApi.list(1, pageSize, searchQuery); + if (!response.error && response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (e) { + console.error('Error aplicando filtros:', e); + } finally { + loading = false; + } + const url = new URL($page.url); + if (searchQuery) url.searchParams.set('search', searchQuery); + else url.searchParams.delete('search'); + history.replaceState(history.state, '', url); + }, 500); + } + async function loadMore() { if (loading || !hasMore) return; - loading = true; error = null; - - try { - const response = await transportTypesApi.list(currentPage + 1, pageSize); - + try { + const response = await transportTypesApi.list(currentPage + 1, pageSize, searchQuery); if (response.error) { - console.error('📊 [Page] Error en loadMore:', response.error, 'Status:', response.status); - - // Si es un error de autenticación (401 o 403) y no se pudo refrescar, mostrar mensaje específico if (response.status === 401 || response.status === 403) { error = 'Sesión expirada. Recargando página...'; - // Recargar automáticamente después de 2 segundos - setTimeout(() => { - window.location.reload(); - }, 2000); + setTimeout(() => window.location.reload(), 2000); } else { error = response.error; } return; } - - if (response.data?.items) { - // Agregar los nuevos items al array existente + if (response.data?.items) { allItems = [...allItems, ...response.data.items]; currentPage++; totalItems = response.data.total; } } catch (e) { error = 'Error cargando más datos'; - console.error('📊 [Page] Error loading more:', e); } finally { loading = false; } } function reloadData() { - // Reset y recargar desde el principio window.location.reload(); } - function handleCreateClick() { - showCreateDialog = true; - } - function handleSuccess() { - // Recargar datos después de crear/editar/eliminar reloadData(); } - // Crear columnas con el callback onSuccess + useShortcuts('Tipos de Transporte', [ + { key: 'Alt+Shift+R', description: 'Actualizar Lista', action: reloadData } + ]); + const columns = createColumns(handleSuccess); -
- -
-
-

Tipos de Transporte

+
+ +
+
+

+ Tipos de Transporte +

Gestiona los tipos de transporte según código SAT

- +
+ +
+
+ + +
+
+ Búsqueda General + +
- {#if error} - - - Error - {error} - - +
+ {error} +
{/if} - - - -
-
- Listado de Tipos de Transporte - - Mostrando {allItems.length} de {totalItems} registros - -
- -
-
- - - - -
+ +
+ +
- - - diff --git a/frontend/src/routes/dashboard/reference_data/valuation_methods/+page.svelte b/frontend/src/routes/dashboard/reference_data/valuation_methods/+page.svelte index 59c8a0fc..c58c4ec2 100644 --- a/frontend/src/routes/dashboard/reference_data/valuation_methods/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/valuation_methods/+page.svelte @@ -3,55 +3,29 @@ import { valuationMethodsApi, type ValuationMethod } from '$lib/api/dashboard/reference_data/valuation_methods'; import DataTable from '$lib/components/dashboard/reference_data/valuation_methods/data-table.svelte'; import { createColumns } from '$lib/components/dashboard/reference_data/valuation_methods/columns.js'; - import CreateEditDialog from '$lib/components/dashboard/reference_data/valuation_methods/create-edit-dialog.svelte'; import { Button } from '$lib/components/ui/button'; - import { Plus, RefreshCw } from 'lucide-svelte'; - import { useShortcuts } from '$lib/hooks/use-shortcuts'; - import { obtenerAtajosLista } from '$lib/config/shortcuts/dashboard/reference_data/valuation_methods/list'; - import type { PageData } from './$types'; + import { Input } from '$lib/components/ui/input'; + import { page } from '$app/stores'; import { browser } from '$app/environment'; + import { RefreshCw } from 'lucide-svelte'; + import { useShortcuts } from '$lib/hooks/use-shortcuts'; + import type { PageData } from './$types'; // Los datos iniciales vienen del servidor let { data }: { data: PageData } = $props(); - - // Estado para el diálogo de crear - let showCreateDialog = $state(false); - // Atajos - useShortcuts( - 'Lista Métodos de Valoración', - obtenerAtajosLista({ - manejarNuevo: () => (showCreateDialog = true), - manejarActualizar: reloadData - }) - ); - - // Sincronizar token de cookies a localStorage al montar el componente + // Sincronizar token de cookies onMount(() => { if (browser) { - // Función para obtener el valor de una cookie 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; }; - - // Verificar si hay token en las cookies const cookieToken = getCookie('access_token'); const localToken = localStorage.getItem('access_token'); - - if (cookieToken && cookieToken !== localToken) { - localStorage.setItem('access_token', cookieToken); - } - - // También sincronizar refresh_token si existe - const cookieRefreshToken = getCookie('refresh_token'); - const localRefreshToken = localStorage.getItem('refresh_token'); - - if (cookieRefreshToken && cookieRefreshToken !== localRefreshToken) { - localStorage.setItem('refresh_token', cookieRefreshToken); - } + if (cookieToken && cookieToken !== localToken) localStorage.setItem('access_token', cookieToken); } }); @@ -64,98 +38,116 @@ let hasMore = $derived(allItems.length < totalItems); let error = $state(data.error || null); + // Filtros + let searchQuery = $state($page.url.searchParams.get('search') || ''); + let timeout: ReturnType; + + function handleSearch() { + if (!browser) return; + clearTimeout(timeout); + timeout = setTimeout(async () => { + loading = true; + try { + const response = await valuationMethodsApi.list(1, pageSize, searchQuery); + if (!response.error && response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (e) { + console.error('Error aplicando filtros:', e); + } finally { + loading = false; + } + const url = new URL($page.url); + if (searchQuery) url.searchParams.set('search', searchQuery); + else url.searchParams.delete('search'); + history.replaceState(history.state, '', url); + }, 500); + } + async function loadMore() { if (loading || !hasMore) return; - loading = true; error = null; - - try { - const response = await valuationMethodsApi.list(currentPage + 1, pageSize); - + try { + const response = await valuationMethodsApi.list(currentPage + 1, pageSize, searchQuery); if (response.error) { - console.error('📊 [Page] Error en loadMore:', response.error, 'Status:', response.status); - - // Si es un error de autenticación (401 o 403) y no se pudo refrescar, mostrar mensaje específico if (response.status === 401 || response.status === 403) { error = 'Sesión expirada. Recargando página...'; - // Recargar automáticamente después de 2 segundos - setTimeout(() => { - window.location.reload(); - }, 2000); + setTimeout(() => window.location.reload(), 2000); } else { error = response.error; } return; } - - if (response.data?.items) { - // Agregar los nuevos items al array existente + if (response.data?.items) { allItems = [...allItems, ...response.data.items]; currentPage++; totalItems = response.data.total; } } catch (e) { error = 'Error cargando más datos'; - console.error('📊 [Page] Error loading more:', e); } finally { loading = false; } } function reloadData() { - // Reset y recargar desde el principio window.location.reload(); } - function handleCreateClick() { - showCreateDialog = true; - } - function handleSuccess() { - // Recargar datos después de crear/editar/eliminar reloadData(); } - // Crear columnas con el callback onSuccess + useShortcuts('Métodos de Valoración', [ + { key: 'Alt+Shift+R', description: 'Actualizar Lista', action: reloadData } + ]); + const columns = createColumns(handleSuccess); -
-
-
-

Métodos de Valoración

+
+ +
+
+

+ Métodos de Valoración +

- Gestiona los métodos de valoración aduanera + Gestiona los métodos de valoración aduanera autorizados

-
- - +
+
+ + +
+
+ Búsqueda General +
{#if error} -
+
{error}
{/if} -
- + +
+
- - From aa8891145c5de40e46db6bce640825d1d4178836 Mon Sep 17 00:00:00 2001 From: hreyes Date: Fri, 10 Apr 2026 11:26:15 -0600 Subject: [PATCH 09/13] feature/checkpoint --- .../reference_data/code_pedimento_regimens/+page.svelte | 8 ++++---- .../dashboard/reference_data/containers/+page.svelte | 8 ++++---- .../dashboard/reference_data/countries/+page.svelte | 8 ++++---- .../dashboard/reference_data/currency_types/+page.svelte | 8 ++++---- .../reference_data/customs_sections/+page.svelte | 8 ++++---- .../reference_data/customs_warehouses/+page.svelte | 8 ++++---- .../dashboard/reference_data/incoterms/+page.svelte | 8 ++++---- .../dashboard/reference_data/invoice_types/+page.svelte | 8 ++++---- .../dashboard/reference_data/material_types/+page.svelte | 8 ++++---- .../dashboard/reference_data/payment_methods/+page.svelte | 8 ++++---- .../dashboard/reference_data/pedimento_codes/+page.svelte | 8 ++++---- .../reference_data/pedimento_regimens/+page.svelte | 8 ++++---- .../routes/dashboard/reference_data/sectors/+page.svelte | 8 ++++---- .../routes/dashboard/reference_data/states/+page.svelte | 8 ++++---- .../dashboard/reference_data/transport_modes/+page.svelte | 8 ++++---- .../dashboard/reference_data/transport_types/+page.svelte | 8 ++++---- .../reference_data/valuation_methods/+page.svelte | 8 ++++---- 17 files changed, 68 insertions(+), 68 deletions(-) diff --git a/frontend/src/routes/dashboard/reference_data/code_pedimento_regimens/+page.svelte b/frontend/src/routes/dashboard/reference_data/code_pedimento_regimens/+page.svelte index 47d73905..0524a8b9 100644 --- a/frontend/src/routes/dashboard/reference_data/code_pedimento_regimens/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/code_pedimento_regimens/+page.svelte @@ -113,7 +113,7 @@ const columns = createColumns(handleSuccess); -
+
@@ -133,7 +133,7 @@
-
+
Búsqueda General {#if error} -
+
{error}
{/if} -
+
-
+
@@ -128,7 +128,7 @@
-
+
Búsqueda General {#if error} -
+
{error}
{/if} -
+
diff --git a/frontend/src/routes/dashboard/reference_data/countries/+page.svelte b/frontend/src/routes/dashboard/reference_data/countries/+page.svelte index e3330243..eb7b9999 100644 --- a/frontend/src/routes/dashboard/reference_data/countries/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/countries/+page.svelte @@ -145,7 +145,7 @@ const columns = createColumns(handleSuccess); -
+
@@ -165,7 +165,7 @@
-
+
Búsqueda General {#if error} -
+
{error}
{/if} -
+
-
+
@@ -128,7 +128,7 @@
-
+
Búsqueda General {#if error} -
+
{error}
{/if} -
+
diff --git a/frontend/src/routes/dashboard/reference_data/customs_sections/+page.svelte b/frontend/src/routes/dashboard/reference_data/customs_sections/+page.svelte index 473e8157..16273e5e 100644 --- a/frontend/src/routes/dashboard/reference_data/customs_sections/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/customs_sections/+page.svelte @@ -108,7 +108,7 @@ const columns = createColumns(handleSuccess); -
+
@@ -128,7 +128,7 @@
-
+
Búsqueda General {#if error} -
+
{error}
{/if} -
+
diff --git a/frontend/src/routes/dashboard/reference_data/customs_warehouses/+page.svelte b/frontend/src/routes/dashboard/reference_data/customs_warehouses/+page.svelte index 409c3a09..da44fb5c 100644 --- a/frontend/src/routes/dashboard/reference_data/customs_warehouses/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/customs_warehouses/+page.svelte @@ -108,7 +108,7 @@ const columns = createColumns(handleSuccess); -
+
@@ -128,7 +128,7 @@
-
+
Búsqueda General {#if error} -
+
{error}
{/if} -
+
diff --git a/frontend/src/routes/dashboard/reference_data/incoterms/+page.svelte b/frontend/src/routes/dashboard/reference_data/incoterms/+page.svelte index fe2846fd..c146aa14 100644 --- a/frontend/src/routes/dashboard/reference_data/incoterms/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/incoterms/+page.svelte @@ -112,7 +112,7 @@ } -
+
@@ -132,7 +132,7 @@
-
+
Clave {#if error} -
+
{error}
{/if} -
+
-
+
@@ -128,7 +128,7 @@
-
+
Búsqueda General {#if error} -
+
{error}
{/if} -
+
diff --git a/frontend/src/routes/dashboard/reference_data/material_types/+page.svelte b/frontend/src/routes/dashboard/reference_data/material_types/+page.svelte index d13df004..aecef109 100644 --- a/frontend/src/routes/dashboard/reference_data/material_types/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/material_types/+page.svelte @@ -108,7 +108,7 @@ const columns = createColumns(handleSuccess); -
+
@@ -128,7 +128,7 @@
-
+
Búsqueda General {#if error} -
+
{error}
{/if} -
+
diff --git a/frontend/src/routes/dashboard/reference_data/payment_methods/+page.svelte b/frontend/src/routes/dashboard/reference_data/payment_methods/+page.svelte index c25f8372..ba845902 100644 --- a/frontend/src/routes/dashboard/reference_data/payment_methods/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/payment_methods/+page.svelte @@ -108,7 +108,7 @@ const columns = createColumns(handleSuccess); -
+
@@ -128,7 +128,7 @@
-
+
Búsqueda General {#if error} -
+
{error}
{/if} -
+
diff --git a/frontend/src/routes/dashboard/reference_data/pedimento_codes/+page.svelte b/frontend/src/routes/dashboard/reference_data/pedimento_codes/+page.svelte index cb541aee..a157eeca 100644 --- a/frontend/src/routes/dashboard/reference_data/pedimento_codes/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/pedimento_codes/+page.svelte @@ -108,7 +108,7 @@ const columns = createColumns(handleSuccess); -
+
@@ -128,7 +128,7 @@
-
+
Búsqueda General {#if error} -
+
{error}
{/if} -
+
diff --git a/frontend/src/routes/dashboard/reference_data/pedimento_regimens/+page.svelte b/frontend/src/routes/dashboard/reference_data/pedimento_regimens/+page.svelte index 27a53484..050c3433 100644 --- a/frontend/src/routes/dashboard/reference_data/pedimento_regimens/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/pedimento_regimens/+page.svelte @@ -108,7 +108,7 @@ const columns = createColumns(handleSuccess); -
+
@@ -128,7 +128,7 @@
-
+
Búsqueda General {#if error} -
+
{error}
{/if} -
+
diff --git a/frontend/src/routes/dashboard/reference_data/sectors/+page.svelte b/frontend/src/routes/dashboard/reference_data/sectors/+page.svelte index f01672d4..d311ff04 100644 --- a/frontend/src/routes/dashboard/reference_data/sectors/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/sectors/+page.svelte @@ -132,7 +132,7 @@ const columns = createColumns(handleSuccess); -
+
@@ -152,7 +152,7 @@
-
+
Búsqueda por Clave {#if error} -
+
{error}
{/if} -
+
diff --git a/frontend/src/routes/dashboard/reference_data/states/+page.svelte b/frontend/src/routes/dashboard/reference_data/states/+page.svelte index fabc9948..fc63ced7 100644 --- a/frontend/src/routes/dashboard/reference_data/states/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/states/+page.svelte @@ -142,7 +142,7 @@ const columns = createColumns(handleSuccess); -
+
@@ -162,7 +162,7 @@
-
+
Búsqueda General {#if error} -
+
{error}
{/if} -
+
-
+
@@ -128,7 +128,7 @@
-
+
Búsqueda General {#if error} -
+
{error}
{/if} -
+
diff --git a/frontend/src/routes/dashboard/reference_data/transport_types/+page.svelte b/frontend/src/routes/dashboard/reference_data/transport_types/+page.svelte index 5247408a..1ad8d604 100644 --- a/frontend/src/routes/dashboard/reference_data/transport_types/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/transport_types/+page.svelte @@ -108,7 +108,7 @@ const columns = createColumns(handleSuccess); -
+
@@ -128,7 +128,7 @@
-
+
Búsqueda General {#if error} -
+
{error}
{/if} -
+
diff --git a/frontend/src/routes/dashboard/reference_data/valuation_methods/+page.svelte b/frontend/src/routes/dashboard/reference_data/valuation_methods/+page.svelte index c58c4ec2..f00f9ca4 100644 --- a/frontend/src/routes/dashboard/reference_data/valuation_methods/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/valuation_methods/+page.svelte @@ -108,7 +108,7 @@ const columns = createColumns(handleSuccess); -
+
@@ -128,7 +128,7 @@
-
+
Búsqueda General {#if error} -
+
{error}
{/if} -
+
From a0a6b52fc1ddaedfeb8a92cdc21bcff6ac30daf9 Mon Sep 17 00:00:00 2001 From: hreyes Date: Mon, 13 Apr 2026 11:16:54 -0600 Subject: [PATCH 10/13] feature/homogenizacion-estilos-catalogos-fijos-y-generales --- frontend/src/app.css | 42 ++++ .../general_catalogs/multi-currency-types.ts | 5 +- .../common/infinite-data-table.svelte | 152 ++++++++++++ .../general_catalogs/doda/data-table.svelte | 20 +- .../general_catalogs/inpc/data-table.svelte | 120 +++++----- .../legends/data-table.svelte | 122 +++++----- .../sectors/SectorsList.svelte | 60 ++--- .../fractions/CanadianFractionList.svelte | 184 +++++++-------- .../fractions/HistoricalFractionList.svelte | 212 ++++++++--------- .../goods/fractions/TariffFractionList.svelte | 189 ++++++++------- .../dashboard/invoices/data-table.svelte | 29 ++- .../dashboard/pedimentos/data-table.svelte | 32 ++- .../code_pedimento_regimens/data-table.svelte | 2 +- .../containers/data-table.svelte | 2 +- .../countries/data-table.svelte | 2 +- .../currency_types/data-table.svelte | 2 +- .../customs_sections/data-table.svelte | 2 +- .../customs_warehouses/data-table.svelte | 2 +- .../incoterms/data-table.svelte | 2 +- .../invoice_types/data-table.svelte | 2 +- .../material_types/data-table.svelte | 2 +- .../payment_methods/data-table.svelte | 2 +- .../pedimento_codes/data-table.svelte | 2 +- .../pedimento_regimens/data-table.svelte | 2 +- .../reference_data/sectors/data-table.svelte | 2 +- .../reference_data/states/data-table.svelte | 2 +- .../transport_modes/data-table.svelte | 2 +- .../transport_types/data-table.svelte | 2 +- .../valuation_methods/data-table.svelte | 2 +- .../dashboard/seal/data-table.svelte | 49 ++-- .../dashboard/customs_brokers/+page.svelte | 32 +-- .../classification_concepts/+page.svelte | 201 +++++++++++++--- .../company_information/+page.svelte | 169 ++++++++++--- .../general_catalogs/concepts/+page.svelte | 181 +++++++++++--- .../customs_broker_concepts/+page.svelte | 202 ++++++++++++---- .../general_catalogs/doda/+page.svelte | 128 ++++------ .../doda/edit/[[id]]/+page.svelte | 2 +- .../general_catalogs/drivers/+page.svelte | 72 +++--- .../electronic_notices/+page.svelte | 156 +++++++++--- .../equivalencies/+page.svelte | 147 +++++++++--- .../error_catalogs/+page.svelte | 152 +++++++++--- .../exchange-rate/+page.svelte | 134 ++++++++--- .../general_catalogs/identifiers/+page.svelte | 181 +++++++++++--- .../general_catalogs/inpc/+page.svelte | 180 +++++++++++--- .../general_catalogs/legends/+page.svelte | 189 ++++++++++++--- .../multi_currency_types/+page.svelte | 179 +++++++++++--- .../general_catalogs/packages/+page.svelte | 183 +++++++++++--- .../general_catalogs/ports/+page.svelte | 173 +++++++++++--- .../prevalidators/+page.svelte | 159 ++++++++++--- .../general_catalogs/seal/+page.svelte | 64 +++-- .../general_catalogs/signatures/+page.svelte | 146 +++++++++--- .../tariff-fractions/+page.svelte | 223 +++++++----------- .../tariff-fractions/canadian/+page.svelte | 14 +- .../general_catalogs/trailers/+page.svelte | 74 +++--- .../transporters/+page.svelte | 74 +++--- .../unit_conversions/+page.svelte | 148 +++++++++--- .../units_of_measure/ace/+page.svelte | 152 +++++++++--- .../units_of_measure/american/+page.svelte | 152 +++++++++--- .../units_of_measure/customs/+page.svelte | 152 +++++++++--- .../units_of_measure/general/+page.svelte | 152 +++++++++--- .../units_of_measure/oma/+page.svelte | 152 +++++++++--- .../general_catalogs/vehicles/+page.svelte | 74 +++--- .../goods/fixed-asset-classes/+page.svelte | 47 ++-- .../routes/dashboard/goods/parts/+page.svelte | 51 ++-- .../routes/dashboard/invoices/+page.svelte | 53 +++-- .../routes/dashboard/pedimentos/+page.svelte | 49 ++-- .../code_pedimento_regimens/+page.svelte | 32 +-- .../reference_data/containers/+page.svelte | 33 ++- .../reference_data/countries/+page.svelte | 32 +-- .../currency_types/+page.svelte | 26 +- .../customs_sections/+page.svelte | 26 +- .../customs_warehouses/+page.svelte | 26 +- .../reference_data/incoterms/+page.svelte | 49 ++-- .../reference_data/invoice_types/+page.svelte | 26 +- .../material_types/+page.svelte | 26 +- .../payment_methods/+page.svelte | 26 +- .../pedimento_codes/+page.svelte | 26 +- .../pedimento_regimens/+page.svelte | 26 +- .../reference_data/sectors/+page.svelte | 26 +- .../reference_data/states/+page.svelte | 32 +-- .../transport_modes/+page.svelte | 26 +- .../transport_types/+page.svelte | 26 +- .../valuation_methods/+page.svelte | 26 +- 83 files changed, 4293 insertions(+), 2044 deletions(-) create mode 100644 frontend/src/lib/components/dashboard/common/infinite-data-table.svelte diff --git a/frontend/src/app.css b/frontend/src/app.css index 6c344082..627e3836 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -121,3 +121,45 @@ @apply bg-background text-foreground overflow-x-hidden; } } + +@layer components { + .catalog-table-shell { + @apply rounded-md border border-border/80 bg-card shadow-sm; + } + + .catalog-table-scroll { + @apply relative w-full flex-1 overflow-auto bg-card; + } + + .catalog-table-header { + @apply sticky top-0 z-20 border-b border-border/80 bg-card/95 shadow-sm backdrop-blur-md; + } + + .catalog-table-head-cell { + @apply whitespace-nowrap text-sm font-semibold text-foreground/90; + } + + .catalog-table-row { + @apply transition-colors hover:bg-accent/35; + } + + .catalog-table-row-selected { + @apply bg-accent/65 text-accent-foreground hover:bg-accent/65; + } + + .catalog-table-sticky-left { + @apply sticky left-0 border-r border-border/70 bg-card shadow-[4px_0_12px_-6px_rgba(0,0,0,0.12)] dark:shadow-[4px_0_12px_-6px_rgba(0,0,0,0.35)]; + } + + .catalog-table-sticky-right { + @apply sticky right-0 border-l border-border/70 bg-card shadow-[-4px_0_12px_-6px_rgba(0,0,0,0.08)] dark:shadow-[-4px_0_12px_-6px_rgba(0,0,0,0.25)]; + } + + .catalog-table-sticky-row-hover { + @apply bg-card group-hover/inv-list:bg-accent/35; + } + + .catalog-table-sticky-row-selected { + @apply bg-accent/65 text-accent-foreground; + } +} diff --git a/frontend/src/lib/api/dashboard/a76/general_catalogs/multi-currency-types.ts b/frontend/src/lib/api/dashboard/a76/general_catalogs/multi-currency-types.ts index fb415397..a565026a 100644 --- a/frontend/src/lib/api/dashboard/a76/general_catalogs/multi-currency-types.ts +++ b/frontend/src/lib/api/dashboard/a76/general_catalogs/multi-currency-types.ts @@ -40,11 +40,14 @@ import type { ApiResponse } from '$lib/api'; export async function getMultiCurrencyTypes( companyId: number, page?: number, - pageSize?: number + pageSize?: number, + filters?: { currency_type_code?: string; country_key?: string } ): Promise> { const params = new URLSearchParams({ company_id: companyId.toString() }); if (page) params.append('page', page.toString()); if (pageSize) params.append('page_size', pageSize.toString()); + if (filters?.currency_type_code) params.append('currency_type_code', filters.currency_type_code); + if (filters?.country_key) params.append('country_key', filters.country_key); return api.get(`/v1/a76/multi-currency-types/?${params.toString()}`); } diff --git a/frontend/src/lib/components/dashboard/common/infinite-data-table.svelte b/frontend/src/lib/components/dashboard/common/infinite-data-table.svelte new file mode 100644 index 00000000..5fbee4e3 --- /dev/null +++ b/frontend/src/lib/components/dashboard/common/infinite-data-table.svelte @@ -0,0 +1,152 @@ + + +
+
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} + {@const headerList = headerGroup.headers} + {@const lastHeaderColId = headerList[headerList.length - 1]?.column.id} + + {#each headerList as header (header.id)} + {@const colId = header.column.id} + + {#if !header.isPlaceholder} + + {/if} + + {/each} + + {/each} + + + {#if table.getRowModel().rows.length} + {#each table.getRowModel().rows as row (row.id)} + {@const visibleCells = row.getVisibleCells()} + {@const lastCellColId = visibleCells[visibleCells.length - 1]?.column.id} + + {#each visibleCells as cell (cell.id)} + {@const colId = cell.column.id} + + + + {/each} + + {/each} + {:else} + + + {emptyMessage} + + + {/if} + + {#if hasMore} + + +
+ {#if loading} +
+
+ Cargando más registros... +
+ {:else} +
+ + Desplázate para cargar más + +
+ {/if} +
+
+
+ {/if} +
+
+
+
diff --git a/frontend/src/lib/components/dashboard/general_catalogs/doda/data-table.svelte b/frontend/src/lib/components/dashboard/general_catalogs/doda/data-table.svelte index 7b79bea9..483cc79e 100644 --- a/frontend/src/lib/components/dashboard/general_catalogs/doda/data-table.svelte +++ b/frontend/src/lib/components/dashboard/general_catalogs/doda/data-table.svelte @@ -67,17 +67,18 @@ }); -
+
- - +
+ + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} {#each headerGroup.headers as header (header.id)} - + {#if !header.isPlaceholder} onRowClick?.(row.original)} - class="cursor-pointer transition-colors {row.getIsSelected() - ? 'bg-gray-300 dark:bg-gray-600' - : 'hover:bg-gray-100 dark:hover:bg-gray-700'}" + class="cursor-pointer {row.getIsSelected() ? 'catalog-table-row-selected' : 'catalog-table-row'}" > {#each row.getVisibleCells() as cell (cell.id)} @@ -106,7 +105,7 @@ {:else} - + No hay resultados. @@ -131,6 +130,7 @@ {/if} - + +
diff --git a/frontend/src/lib/components/dashboard/general_catalogs/inpc/data-table.svelte b/frontend/src/lib/components/dashboard/general_catalogs/inpc/data-table.svelte index 3712b6f3..21ade40a 100644 --- a/frontend/src/lib/components/dashboard/general_catalogs/inpc/data-table.svelte +++ b/frontend/src/lib/components/dashboard/general_catalogs/inpc/data-table.svelte @@ -38,67 +38,69 @@ } -
- - - {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} - - {#each headerGroup.headers as header (header.id)} - - {#if !header.isPlaceholder} - - {/if} - - {/each} - - {/each} - - - {#if table.getRowModel().rows.length} - {#each table.getRowModel().rows as row (row.id)} - - {#each row.getVisibleCells() as cell (cell.id)} - - - +
+
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} + + {#each headerGroup.headers as header (header.id)} + + {#if !header.isPlaceholder} + + {/if} + {/each} {/each} - {:else} - - - No hay resultados. - - - {/if} - - -
- -
-
- Total: {totalItems} registros + + + {#if table.getRowModel().rows.length} + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getVisibleCells() as cell (cell.id)} + + + + {/each} + + {/each} + {:else} + + + No hay resultados. + + + {/if} + + +
+ +
+
+ Total: {totalItems} registros +
+ +
- -
diff --git a/frontend/src/lib/components/dashboard/general_catalogs/legends/data-table.svelte b/frontend/src/lib/components/dashboard/general_catalogs/legends/data-table.svelte index 72fb9bf3..e1efce5b 100644 --- a/frontend/src/lib/components/dashboard/general_catalogs/legends/data-table.svelte +++ b/frontend/src/lib/components/dashboard/general_catalogs/legends/data-table.svelte @@ -40,67 +40,69 @@ const currentPage = $derived(Number($page.url.searchParams.get('page') || 1)); -
- - - {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} - - {#each headerGroup.headers as header (header.id)} - - {#if !header.isPlaceholder} - - {/if} - +
+
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} + + {#each headerGroup.headers as header (header.id)} + + {#if !header.isPlaceholder} + + {/if} + + {/each} + {/each} - - {/each} - - - {#each table.getRowModel().rows as row (row.id)} - - {#each row.getVisibleCells() as cell (cell.id)} - - - + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getVisibleCells() as cell (cell.id)} + + + + {/each} + + {:else} + + + No hay resultados. + + {/each} - - {:else} - - - No hay resultados. - - - {/each} - - -
+ + +
-
-
- Total: {totalItems} registros +
+
+ Total: {totalItems} registros +
+
+ + +
+
-
- - -
-
diff --git a/frontend/src/lib/components/dashboard/general_catalogs/sectors/SectorsList.svelte b/frontend/src/lib/components/dashboard/general_catalogs/sectors/SectorsList.svelte index 78137b1f..dd8be92c 100644 --- a/frontend/src/lib/components/dashboard/general_catalogs/sectors/SectorsList.svelte +++ b/frontend/src/lib/components/dashboard/general_catalogs/sectors/SectorsList.svelte @@ -1,9 +1,8 @@ -
+ +
-

{title}

+

{title}

- + -
-
- - +
+ Listado de Sectores +
+
+ + +
- -
+ +
+
- + - Clave - Descripción - Estatus + Clave + Descripción + Estatus @@ -150,7 +154,7 @@ {:else} {#each sectors as sector} - + {sector.key} {sector.description} @@ -174,15 +178,13 @@ {/if} -
- -
-
- Mostrando {sectors.length} de {total} registros
- -
+ +
Mostrando {sectors.length} de {total} registros
+ + +
diff --git a/frontend/src/lib/components/dashboard/goods/fractions/CanadianFractionList.svelte b/frontend/src/lib/components/dashboard/goods/fractions/CanadianFractionList.svelte index 7513162c..17d936e1 100644 --- a/frontend/src/lib/components/dashboard/goods/fractions/CanadianFractionList.svelte +++ b/frontend/src/lib/components/dashboard/goods/fractions/CanadianFractionList.svelte @@ -1,5 +1,5 @@ -
-
-
-
- -
- - +
+ + +
+ Listado de Fracciones Canadienses +
+
+ + +
+
-
- -
- -
- - - - Fracción - Descripción - País - Unidad - ADV - Acciones - - - - {#if fractions.length === 0 && !loading} - - No se encontraron resultados - - {:else} - {#each fractions as fraction} + + +
+
+ + - {fraction.fraction} - {fraction.description || '-'} - {fraction.country_code} - {fraction.unit_of_measure || '-'} - {fraction.ad_valorem ?? '-'} - -
- - -
-
+ Fracción + Descripción + País + Unidad + ADV + Acciones
- {/each} - {/if} - {#if loading} - - -
- -
-
-
- {/if} - -
-
+ + + {#if fractions.length === 0 && !loading} + + No se encontraron resultados + + {:else} + {#each fractions as fraction} + + {fraction.fraction} + {fraction.description || '-'} + {fraction.country_code} + {fraction.unit_of_measure || '-'} + {fraction.ad_valorem ?? '-'} + +
+ + +
+
+
+ {/each} + {/if} + {#if loading} + + +
+ +
+
+
+ {/if} +
+ +
+
+
+ + - -
+
Mostrando {fractions.length} de {totalItems} registros
- import { onMount, untrack } from 'svelte'; + import { untrack } from 'svelte'; import { getHistoricalFractions, deleteHistoricalFraction, @@ -8,6 +8,7 @@ import * as Table from '$lib/components/ui/table'; import { Input } from '$lib/components/ui/input'; import { Button } from '$lib/components/ui/button'; + import * as Card from '$lib/components/ui/card'; import { Search, Loader2, Plus, Pencil, Trash2 } from 'lucide-svelte'; import { toast } from 'svelte-sonner'; import HistoricalFractionDialog from './HistoricalFractionDialog.svelte'; @@ -169,11 +170,12 @@ let scrollContainer: HTMLDivElement; }); -
+ +
-

{title}

+

{title}

Gestiona las fracciones históricas de la tarifa.

@@ -184,122 +186,100 @@ let scrollContainer: HTMLDivElement;
- -
-
-
- -
- - + + +
+ Listado de Fracciones Históricas +
+
+ + +
-
-
+ + +
+
+ + + + Fracción + Tipo + UM + País + Fecha Pub. + Fecha Fin + IGI + IGE + Acciones + + + + {#if fractions.length === 0 && !loading} + + No se encontraron resultados + + {:else} + {#each fractions as fraction} + + {fraction.historical_fraction} + {fraction.fraction_type || '-'} + {fraction.unit_of_measure_code || '-'} + {fraction.country || '-'} + {fraction.publication_date ? new Date(fraction.publication_date).toLocaleDateString() : '-'} + {fraction.end_date ? new Date(fraction.end_date).toLocaleDateString() : '-'} + {fraction.import_tax_rate ?? '-'} + {fraction.export_tax_rate ?? '-'} + +
+ + +
+
+
+ {/each} + {/if} + {#if loading} + + +
+ +
+
+
+ {/if} +
+
+
+
+
+
+ - -
-
- - - - Fracción - Tipo - UM - País - Fecha Pub. - Fecha Fin - IGI - IGE - Acciones - - - - {#if fractions.length === 0 && !loading} - - No se encontraron resultados - - {:else} - {#each fractions as fraction} - - {fraction.historical_fraction} - {fraction.fraction_type || '-'} - {fraction.unit_of_measure_code || '-'} - {fraction.country || '-'} - {fraction.publication_date - ? new Date(fraction.publication_date).toLocaleDateString() - : '-'} - {fraction.end_date - ? new Date(fraction.end_date).toLocaleDateString() - : '-'} - {fraction.import_tax_rate ?? '-'} - {fraction.export_tax_rate ?? '-'} - -
- - -
-
-
- {/each} - {/if} - {#if loading} - - -
- -
-
-
- {/if} -
-
- - -
-
-
+
Mostrando {fractions.length} de {totalItems} registros
import { Input } from '$lib/components/ui/input'; import { Button } from '$lib/components/ui/button'; - import { Label } from '$lib/components/ui/label'; + import * as Card from '$lib/components/ui/card'; import { Table, TableBody, @@ -17,7 +17,7 @@ type TariffFraction } from '$lib/api/dashboard/a76/general_catalogs/tariff-fractions'; import { companyStore } from '$lib/stores/company.svelte'; - import { onMount, untrack } from 'svelte'; + import { untrack } from 'svelte'; import * as AlertDialog from '$lib/components/ui/alert-dialog'; import TariffFractionFormDialog from './TariffFractionFormDialog.svelte'; import { toast } from 'svelte-sonner'; @@ -43,6 +43,7 @@ let searchTimeout: ReturnType; let observer: IntersectionObserver; let sentinel: HTMLDivElement; + let scrollContainer: HTMLDivElement; // Infinite scroll state let hasMore = $state(true); @@ -121,7 +122,7 @@ handlePageChange(currentPage + 1); } }, - { rootMargin: '100px' } + { root: scrollContainer || null, rootMargin: '100px' } ); if (sentinel) observer.observe(sentinel); @@ -180,108 +181,118 @@ }); -
+
-

{title}

+

{title}

{#if !readOnly} - {/if}
-
-
- - -
-
- -
- - - - Clave - Fracción - Descripción - {#if catalog === 'mex'} - NICO - U.M.T - {:else} - Unidad - {/if} - Adv. Impo - Adv. Expo - {#if !readOnly} - Acciones - {/if} - - - - {#if fractions.length === 0 && !isLoading} - - - No se encontraron resultados - - - {:else} - {#each fractions as fraction} + + +
+ Listado de Fracciones +
+
+ + +
+
+
+
+ +
+
+
+ - {fraction.um_code || fraction.code} - {fraction.fraction} - - {fraction.description} - + Clave + Fracción + Descripción {#if catalog === 'mex'} - {fraction.nico || '-'} - {fraction.umt || '-'} + NICO + U.M.T {:else} - {fraction.umt || '-'} + Unidad {/if} - {fraction.adv_impo || '-'} - {fraction.adv_expo || '-'} + Adv. Impo + Adv. Expo {#if !readOnly} - -
- - -
-
+ Acciones {/if}
- {/each} - {/if} - {#if isLoading} - - -
- -
-
-
- {/if} - -
-
+ + + {#if fractions.length === 0 && !isLoading} + + + No se encontraron resultados + + + {:else} + {#each fractions as fraction} + + {fraction.um_code || fraction.code} + {fraction.fraction} + + {fraction.description} + + {#if catalog === 'mex'} + {fraction.nico || '-'} + {fraction.umt || '-'} + {:else} + {fraction.umt || '-'} + {/if} + {fraction.adv_impo || '-'} + {fraction.adv_expo || '-'} + {#if !readOnly} + +
+ + +
+
+ {/if} +
+ {/each} + {/if} + {#if isLoading} + + +
+ +
+
+
+ {/if} +
+ +
+
+
+ + - -
+
Mostrando {fractions.length} de {totalFractions} registros
diff --git a/frontend/src/lib/components/dashboard/invoices/data-table.svelte b/frontend/src/lib/components/dashboard/invoices/data-table.svelte index f8be72af..3fc036ce 100644 --- a/frontend/src/lib/components/dashboard/invoices/data-table.svelte +++ b/frontend/src/lib/components/dashboard/invoices/data-table.svelte @@ -99,9 +99,13 @@
-
- - +
+
+ + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} {@const headerList = headerGroup.headers} {@const lastHeaderColId = headerList[headerList.length - 1]?.column.id} @@ -111,9 +115,9 @@ onRowClick && onRowClick(row.original)} > {#each visibleCells as cell (cell.id)} @@ -202,12 +204,12 @@ {/if} - + +
diff --git a/frontend/src/lib/components/dashboard/pedimentos/data-table.svelte b/frontend/src/lib/components/dashboard/pedimentos/data-table.svelte index 3b32dada..eb51c5d2 100644 --- a/frontend/src/lib/components/dashboard/pedimentos/data-table.svelte +++ b/frontend/src/lib/components/dashboard/pedimentos/data-table.svelte @@ -98,18 +98,24 @@
- - +
+ + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} {#each headerGroup.headers as header (header.id)} - + {@const colId = header.column.id} + {#if !header.isPlaceholder}
diff --git a/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/data-table.svelte index 4109783b..1e44175d 100644 --- a/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/data-table.svelte @@ -60,7 +60,7 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/reference_data/containers/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/containers/data-table.svelte index 4109783b..1e44175d 100644 --- a/frontend/src/lib/components/dashboard/reference_data/containers/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/containers/data-table.svelte @@ -60,7 +60,7 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/reference_data/countries/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/countries/data-table.svelte index 4109783b..1e44175d 100644 --- a/frontend/src/lib/components/dashboard/reference_data/countries/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/countries/data-table.svelte @@ -60,7 +60,7 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/reference_data/currency_types/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/currency_types/data-table.svelte index 4109783b..1e44175d 100644 --- a/frontend/src/lib/components/dashboard/reference_data/currency_types/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/currency_types/data-table.svelte @@ -60,7 +60,7 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/reference_data/customs_sections/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/customs_sections/data-table.svelte index c34967ce..0e0ae059 100644 --- a/frontend/src/lib/components/dashboard/reference_data/customs_sections/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/customs_sections/data-table.svelte @@ -60,7 +60,7 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/reference_data/customs_warehouses/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/customs_warehouses/data-table.svelte index 4109783b..1e44175d 100644 --- a/frontend/src/lib/components/dashboard/reference_data/customs_warehouses/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/customs_warehouses/data-table.svelte @@ -60,7 +60,7 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/reference_data/incoterms/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/incoterms/data-table.svelte index dd73a894..c144ded1 100644 --- a/frontend/src/lib/components/dashboard/reference_data/incoterms/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/incoterms/data-table.svelte @@ -51,7 +51,7 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/reference_data/invoice_types/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/invoice_types/data-table.svelte index 4109783b..1e44175d 100644 --- a/frontend/src/lib/components/dashboard/reference_data/invoice_types/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/invoice_types/data-table.svelte @@ -60,7 +60,7 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/reference_data/material_types/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/material_types/data-table.svelte index 4109783b..1e44175d 100644 --- a/frontend/src/lib/components/dashboard/reference_data/material_types/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/material_types/data-table.svelte @@ -60,7 +60,7 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/reference_data/payment_methods/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/payment_methods/data-table.svelte index 4109783b..1e44175d 100644 --- a/frontend/src/lib/components/dashboard/reference_data/payment_methods/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/payment_methods/data-table.svelte @@ -60,7 +60,7 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/reference_data/pedimento_codes/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/pedimento_codes/data-table.svelte index 4109783b..1e44175d 100644 --- a/frontend/src/lib/components/dashboard/reference_data/pedimento_codes/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/pedimento_codes/data-table.svelte @@ -60,7 +60,7 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/reference_data/pedimento_regimens/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/pedimento_regimens/data-table.svelte index 4109783b..1e44175d 100644 --- a/frontend/src/lib/components/dashboard/reference_data/pedimento_regimens/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/pedimento_regimens/data-table.svelte @@ -60,7 +60,7 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/reference_data/sectors/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/sectors/data-table.svelte index 4109783b..1e44175d 100644 --- a/frontend/src/lib/components/dashboard/reference_data/sectors/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/sectors/data-table.svelte @@ -60,7 +60,7 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/reference_data/states/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/states/data-table.svelte index 4109783b..1e44175d 100644 --- a/frontend/src/lib/components/dashboard/reference_data/states/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/states/data-table.svelte @@ -60,7 +60,7 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/reference_data/transport_modes/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/transport_modes/data-table.svelte index 4109783b..1e44175d 100644 --- a/frontend/src/lib/components/dashboard/reference_data/transport_modes/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/transport_modes/data-table.svelte @@ -60,7 +60,7 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/reference_data/transport_types/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/transport_types/data-table.svelte index 4109783b..1e44175d 100644 --- a/frontend/src/lib/components/dashboard/reference_data/transport_types/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/transport_types/data-table.svelte @@ -60,7 +60,7 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/reference_data/valuation_methods/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/valuation_methods/data-table.svelte index 5b63e0a2..17039f91 100644 --- a/frontend/src/lib/components/dashboard/reference_data/valuation_methods/data-table.svelte +++ b/frontend/src/lib/components/dashboard/reference_data/valuation_methods/data-table.svelte @@ -61,7 +61,7 @@
-
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} diff --git a/frontend/src/lib/components/dashboard/seal/data-table.svelte b/frontend/src/lib/components/dashboard/seal/data-table.svelte index 6a70e449..954785f8 100644 --- a/frontend/src/lib/components/dashboard/seal/data-table.svelte +++ b/frontend/src/lib/components/dashboard/seal/data-table.svelte @@ -18,8 +18,8 @@ let { data, columns, loading = false, hasMore = false, loadMore }: Props = $props(); - let scrollContainer: HTMLDivElement; - let observer: IntersectionObserver; + let scrollContainer = $state(); + let loadingTrigger = $state(); let options = $derived>({ get data() { @@ -34,8 +34,7 @@ onMount(() => { if (!loadMore) return; - // Create intersection observer for infinite scroll - observer = new IntersectionObserver( + const observer = new IntersectionObserver( (entries) => { const [entry] = entries; if (entry.isIntersecting && hasMore && !loading && loadMore) { @@ -48,29 +47,24 @@ } ); - // Observe the last row - const lastRow = scrollContainer?.querySelector('tbody tr:last-child'); - if (lastRow) { - observer.observe(lastRow); + if (loadingTrigger) { + observer.observe(loadingTrigger); } return () => { - observer?.disconnect(); + observer.disconnect(); }; }); -
-
+
+
- + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} {#each headerGroup.headers as header (header.id)} - + {#if !header.isPlaceholder} {#each row.getVisibleCells() as cell (cell.id)} - + {/each} - - {#if loading} + + {#if hasMore} - - Cargando... + +
+ {#if loading} +
+
+ Cargando más registros... +
+ {:else} +
+ + Desplázate para cargar más + +
+ {/if} +
{/if} diff --git a/frontend/src/routes/dashboard/customs_brokers/+page.svelte b/frontend/src/routes/dashboard/customs_brokers/+page.svelte index 611ce839..f54d465c 100644 --- a/frontend/src/routes/dashboard/customs_brokers/+page.svelte +++ b/frontend/src/routes/dashboard/customs_brokers/+page.svelte @@ -187,15 +187,15 @@ const brokerColumns = createBrokerColumns(handleActionSuccess); -
-
-
-

GESTIÓN ADUANAL

-

Administración de Agentes y Secciones Aduanales

+
+
+
+

Gestión Aduanal

+

Administración de Agentes y Secciones Aduanales

- + -
-
+
+

Filtros

@@ -228,7 +228,7 @@
@@ -237,7 +237,7 @@
@@ -246,8 +246,8 @@
-
-
+
+

Listado

@@ -260,7 +260,7 @@
-
+
-
+

Detalles del Agente @@ -407,6 +405,8 @@ + +

import { page } from '$app/stores'; - import DataTable from '$lib/components/dashboard/general_catalogs/classification_concepts/data-table.svelte'; + import InfiniteDataTable from '$lib/components/dashboard/common/infinite-data-table.svelte'; + import * as Card from '$lib/components/ui/card'; import { createColumns } from '$lib/components/dashboard/general_catalogs/classification_concepts/columns'; import CreateDialog from '$lib/components/dashboard/general_catalogs/classification/create-edit-dialog.svelte'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; - import { Plus } from 'lucide-svelte'; + import { Plus, RefreshCw } from 'lucide-svelte'; import { goto } from '$app/navigation'; import { browser } from '$app/environment'; import { useShortcuts } from '$lib/hooks/use-shortcuts'; import { obtenerAtajosListaClasificacion } from '$lib/config/shortcuts/dashboard/general_catalogs/classification/list'; + import { getClassificationConcepts } from '$lib/api/dashboard/a76/general_catalogs/classification-concepts'; + import { companyStore } from '$lib/stores/company.svelte'; let { data } = $props(); let dialogOpen = $state(false); + let error = $state(data.error || null); // Atajos useShortcuts( @@ -28,17 +32,54 @@ let searchDesc = $state($page.url.searchParams.get('description') || ''); let timeout: ReturnType; - function handleSuccess() { - const url = new URL($page.url); - goto(url, { invalidateAll: true }); - } - const columns = $derived(createColumns(handleSuccess)); - function handleSearch() { + let allItems = $state(data.classifications?.items || []); + let currentPage = $state(data.classifications?.page || 1); + let pageSize = $state(data.classifications?.page_size || 50); + let totalItems = $state(data.classifications?.total || 0); + let loading = $state(false); + let hasMore = $derived(allItems.length < totalItems); + + $effect(() => { + if (data.classifications) { + allItems = data.classifications.items || []; + currentPage = data.classifications.page || 1; + totalItems = data.classifications.total || 0; + pageSize = data.classifications.page_size || pageSize; + } + }); + + async function handleSearch() { if (!browser) return; clearTimeout(timeout); - timeout = setTimeout(() => { + timeout = setTimeout(async () => { + if (!companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchClassification) filters.classification = searchClassification; + if (searchDesc) filters.description = searchDesc; + + const response = await getClassificationConcepts( + 1, + pageSize, + companyStore.activeCompany.id, + filters + ); + if (response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (err) { + error = 'Error aplicando filtros'; + console.error('Error applying filters:', err); + } finally { + loading = false; + } + const url = new URL($page.url); if (searchClassification) url.searchParams.set('classification', searchClassification); else url.searchParams.delete('classification'); @@ -46,47 +87,131 @@ if (searchDesc) url.searchParams.set('description', searchDesc); else url.searchParams.delete('description'); - url.searchParams.set('page', '1'); - goto(url, { keepFocus: true, noScroll: true }); + history.replaceState(history.state, '', url); }, 500); } + + async function loadMore() { + if (loading || !hasMore || !companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchClassification) filters.classification = searchClassification; + if (searchDesc) filters.description = searchDesc; + + const response = await getClassificationConcepts( + currentPage + 1, + pageSize, + companyStore.activeCompany.id, + filters + ); + if (response.data?.items) { + allItems = [...allItems, ...response.data.items]; + currentPage += 1; + totalItems = response.data.total; + } + } catch (err) { + error = 'Error cargando mas datos'; + console.error('Error loading more classifications:', err); + } finally { + loading = false; + } + } + + async function reloadData() { + if (!companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchClassification) filters.classification = searchClassification; + if (searchDesc) filters.description = searchDesc; + + const response = await getClassificationConcepts( + 1, + pageSize, + companyStore.activeCompany.id, + filters + ); + if (response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (err) { + error = 'Error al recargar datos'; + console.error('Error reloading classifications:', err); + } finally { + loading = false; + } + } + + function handleSuccess() { + reloadData(); + } -
+

Clasificaciones de Conceptos

Gestión del catálogo de clasificaciones de conceptos

- -
- -
-
- -
-
- +
+ +
- + {#if error} +
+ {error} +
+ {/if} + + + +
+ Listado de Clasificaciones +
+ + +
+
+
+ +
+ +
+
+
+ +
+ Mostrando {allItems.length} de {totalItems} registros +
import { page } from '$app/stores'; - import DataTable from '$lib/components/dashboard/general_catalogs/company/data-table.svelte'; + import InfiniteDataTable from '$lib/components/dashboard/common/infinite-data-table.svelte'; import { createColumns } from '$lib/components/dashboard/general_catalogs/company/columns'; + import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; - import { Plus } from 'lucide-svelte'; + import { Plus, RefreshCw } from 'lucide-svelte'; import { goto } from '$app/navigation'; import { browser } from '$app/environment'; import * as m from '$lib/paraglide/messages.js'; import { useShortcuts } from '$lib/hooks/use-shortcuts'; import { obtenerAtajosListaEmpresa } from '$lib/config/shortcuts/dashboard/general_catalogs/company_information/list'; + import { getCompanies } from '$lib/api/dashboard/a76/general_catalogs/company'; let { data } = $props(); @@ -24,16 +26,53 @@ }) ); let dialogOpen = $state(false); + let error = $state(data.error || null); // Filtros let searchName = $state($page.url.searchParams.get('name') || ''); let searchRfc = $state($page.url.searchParams.get('rfc') || ''); let timeout: ReturnType; - function handleSearch() { + let allItems = $state(data.companies?.items || []); + let currentPage = $state(data.companies?.page || 1); + let pageSize = $state(data.companies?.page_size || 50); + let totalItems = $state(data.companies?.total || 0); + let loading = $state(false); + let hasMore = $derived(allItems.length < totalItems); + + $effect(() => { + if (data.companies) { + allItems = data.companies.items || []; + currentPage = data.companies.page || 1; + totalItems = data.companies.total || 0; + pageSize = data.companies.page_size || pageSize; + } + }); + + async function handleSearch() { if (!browser) return; clearTimeout(timeout); - timeout = setTimeout(() => { + timeout = setTimeout(async () => { + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchName) filters.name = searchName; + if (searchRfc) filters.rfc = searchRfc; + + const response = await getCompanies(1, pageSize, filters); + if (response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (err) { + error = 'Error aplicando filtros'; + console.error('Error applying filters:', err); + } finally { + loading = false; + } + const url = new URL($page.url); if (searchName) url.searchParams.set('name', searchName); else url.searchParams.delete('name'); @@ -41,44 +80,118 @@ if (searchRfc) url.searchParams.set('rfc', searchRfc); else url.searchParams.delete('rfc'); - url.searchParams.set('page', '1'); - goto(url, { keepFocus: true, noScroll: true }); + history.replaceState(history.state, '', url); }, 500); } + async function loadMore() { + if (loading || !hasMore) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchName) filters.name = searchName; + if (searchRfc) filters.rfc = searchRfc; + + const response = await getCompanies(currentPage + 1, pageSize, filters); + if (response.data?.items) { + allItems = [...allItems, ...response.data.items]; + currentPage += 1; + totalItems = response.data.total; + } + } catch (err) { + error = 'Error cargando mas datos'; + console.error('Error loading more companies:', err); + } finally { + loading = false; + } + } + + async function reloadData() { + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchName) filters.name = searchName; + if (searchRfc) filters.rfc = searchRfc; + + const response = await getCompanies(1, pageSize, filters); + if (response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (err) { + error = 'Error al recargar datos'; + console.error('Error reloading companies:', err); + } finally { + loading = false; + } + } + function handleSuccess() { - const url = new URL($page.url); - goto(url, { invalidateAll: true }); + reloadData(); } -
+

Información de Empresas

Gestión de información de empresas

- -
- -
-
- -
-
- +
+ +
-
- + {#if error} +
+ {error} +
+ {/if} + + + +
+ Listado de Empresas +
+ + +
+
+
+ +
+ +
+
+
+ +
+ Mostrando {allItems.length} de {totalItems} registros
diff --git a/frontend/src/routes/dashboard/general_catalogs/concepts/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/concepts/+page.svelte index 63507986..33c48136 100644 --- a/frontend/src/routes/dashboard/general_catalogs/concepts/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/concepts/+page.svelte @@ -1,21 +1,25 @@ -
+

Conceptos

Gestión del catálogo de conceptos

- -
- -
-
- -
-
- +
+ +
-
- + {#if error} +
+ {error} +
+ {/if} + + + +
+ Listado de Conceptos +
+ + +
+
+
+ +
+ +
+
+
+ +
+ Mostrando {allItems.length} de {totalItems} registros
{#if dialogOpen} diff --git a/frontend/src/routes/dashboard/general_catalogs/customs_broker_concepts/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/customs_broker_concepts/+page.svelte index 0f19a0c1..a049231d 100644 --- a/frontend/src/routes/dashboard/general_catalogs/customs_broker_concepts/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/customs_broker_concepts/+page.svelte @@ -1,21 +1,24 @@ -
+

Conceptos de Agente Aduanal

Gestión del catálogo de conceptos de agente aduanal

- -
- -
-
- -
-
- +
+ +
-
- + {#if error} +
+ {error} +
+ {/if} + + + +
+ Listado de Conceptos de Agente Aduanal +
+ + +
+
+
+ +
+ +
+
+
+ +
+ Mostrando {allItems.length} de {totalItems} registros
{#if activeCompanyId} diff --git a/frontend/src/routes/dashboard/general_catalogs/doda/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/doda/+page.svelte index 80684dce..258c3647 100644 --- a/frontend/src/routes/dashboard/general_catalogs/doda/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/doda/+page.svelte @@ -3,7 +3,6 @@ import { goto } from '$app/navigation'; import { browser } from '$app/environment'; import { getDodas, deleteDoda, type Doda } from '$lib/api/dashboard/a76/general_catalogs/doda'; - import * as Card from '$lib/components/ui/card'; import { toast } from 'svelte-sonner'; import { Plus, @@ -16,6 +15,7 @@ LayoutGrid, Printer } from 'lucide-svelte'; + import * as Card from '$lib/components/ui/card'; import * as Select from '$lib/components/ui/select'; import { companyStore } from '$lib/stores/company.svelte'; import DataTable from '$lib/components/dashboard/general_catalogs/doda/data-table.svelte'; @@ -23,7 +23,6 @@ import CreateEditDialog from '$lib/components/dashboard/general_catalogs/doda/create-edit-dialog.svelte'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; - import { Label } from '$lib/components/ui/label'; import { Separator } from '$lib/components/ui/separator'; import { useShortcuts } from '$lib/hooks/use-shortcuts'; import { obtenerAtajosListaDoda } from '$lib/config/shortcuts/dashboard/general_catalogs/doda/list'; @@ -194,60 +193,45 @@ } -
-
-
-

DODA

+
+
+
+

DODA

Gestiona tus Documentos de Operación Aduanera (DODA)

- +
+ + +
- + -
-
- Filtros Avanzados - Refina tu búsqueda mediante múltiples criterios -
- -
-
- -
-
- +
+ Listado de DODA +
- +
-
- -
- - -
- -
- + (filters.status = v)} > - - {filters.status || 'Todos los estatus'} + + {filters.status || 'Estatus'} Todos @@ -257,21 +241,17 @@ ELIMINADO -
- -
- (filters.operation_type = v)} > - + {filters.operation_type === 'I' ? 'Importación' : filters.operation_type === 'E' ? 'Exportación' - : 'Todas'} + : 'Operación'} Todas @@ -279,38 +259,35 @@ E - Exportación +
+ + +
+ +
- - -
-
- Listado de DODAs - - Mostrando {allItems.length} de {totalItems} registros - -
- -
-
- - - -
+
+ Mostrando {allItems.length} de {totalItems} registros + + Filtros activos: {Object.values(filters).filter((value) => value !== '').length} +
+ +
-
- - {#if dialogOpen} {/if} diff --git a/frontend/src/routes/dashboard/general_catalogs/doda/edit/[[id]]/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/doda/edit/[[id]]/+page.svelte index 67e241a0..db7208ec 100644 --- a/frontend/src/routes/dashboard/general_catalogs/doda/edit/[[id]]/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/doda/edit/[[id]]/+page.svelte @@ -262,7 +262,7 @@ > -

{title}

+

{title}

Catálogos Generales / Doda

diff --git a/frontend/src/routes/dashboard/general_catalogs/drivers/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/drivers/+page.svelte index 03a29c1c..0f089351 100644 --- a/frontend/src/routes/dashboard/general_catalogs/drivers/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/drivers/+page.svelte @@ -1,11 +1,12 @@ -
+
-

Conductores

+

Conductores

Gestión del catálogo de conductores

- +
-
- - -
+ +
Listado de Conductores
+ {#if loading && data.length === 0}
Cargando conductores...
{:else}
{/if}
+
- {#if loading && data.length === 0} -
- Cargando conductores... -
- {:else} - - {/if} +
Mostrando {data.length} de {totalItems} registros
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 3c3fa9f7..7aa48435 100644 --- a/frontend/src/routes/dashboard/general_catalogs/electronic_notices/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/electronic_notices/+page.svelte @@ -2,17 +2,21 @@ import { page } from '$app/stores'; import { goto } from '$app/navigation'; import { browser } from '$app/environment'; - import DataTable from '$lib/components/dashboard/general_catalogs/electronic_notices/data-table.svelte'; + import InfiniteDataTable from '$lib/components/dashboard/common/infinite-data-table.svelte'; import { createColumns } from '$lib/components/dashboard/general_catalogs/electronic_notices/columns.js'; import CreateEditDialog from '$lib/components/dashboard/general_catalogs/electronic_notices/create-edit-dialog.svelte'; + import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; - import { Plus } from 'lucide-svelte'; + import { Plus, RefreshCw } from 'lucide-svelte'; import { useShortcuts } from '$lib/hooks/use-shortcuts'; import { obtenerAtajosListaAvisosElectrónicos } from '$lib/config/shortcuts/dashboard/general_catalogs/electronic_notices/list'; + import { getElectronicNotices } from '$lib/api/dashboard/a76/general_catalogs/electronic-notices'; + import { companyStore } from '$lib/stores/company.svelte'; let { data } = $props(); let dialogOpen = $state(false); + let error = $state(data.error || null); // Atajos useShortcuts( @@ -28,10 +32,52 @@ let searchPedimento = $state($page.url.searchParams.get('pedimento') || ''); let timeout: ReturnType; - function handleSearch() { + let allItems = $state(data.notices?.items || []); + let currentPage = $state(data.notices?.page || 1); + let pageSize = $state(data.notices?.page_size || 50); + let totalItems = $state(data.notices?.total || 0); + let loading = $state(false); + let hasMore = $derived(allItems.length < totalItems); + + $effect(() => { + if (data.notices) { + allItems = data.notices.items || []; + currentPage = data.notices.page || 1; + totalItems = data.notices.total || 0; + pageSize = data.notices.page_size || pageSize; + } + }); + + async function handleSearch() { if (!browser) return; clearTimeout(timeout); - timeout = setTimeout(() => { + timeout = setTimeout(async () => { + if (!companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchNotice) filters.notice_number = searchNotice; + if (searchPedimento) filters.pedimento = searchPedimento; + + const response = await getElectronicNotices( + 1, + pageSize, + filters, + companyStore.activeCompany.id + ); + if (response?.items) { + allItems = response.items; + currentPage = 1; + totalItems = response.total; + } + } catch (err) { + error = 'Error aplicando filtros'; + console.error('Error applying filters:', err); + } finally { + loading = false; + } + const url = new URL($page.url); if (searchNotice) url.searchParams.set('notice_number', searchNotice); else url.searchParams.delete('notice_number'); @@ -39,54 +85,92 @@ if (searchPedimento) url.searchParams.set('pedimento', searchPedimento); else url.searchParams.delete('pedimento'); - url.searchParams.set('page', '1'); - goto(url, { keepFocus: true, noScroll: true }); + history.replaceState(history.state, '', url); }, 500); } + async function loadMore() { + if (loading || !hasMore || !companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchNotice) filters.notice_number = searchNotice; + if (searchPedimento) filters.pedimento = searchPedimento; + + const response = await getElectronicNotices( + currentPage + 1, + pageSize, + filters, + companyStore.activeCompany.id + ); + if (response?.items) { + allItems = [...allItems, ...response.items]; + currentPage += 1; + totalItems = response.total; + } + } catch (err) { + error = 'Error cargando mas datos'; + console.error('Error loading more electronic notices:', err); + } finally { + loading = false; + } + } + + async function reloadData() { + if (!companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchNotice) filters.notice_number = searchNotice; + if (searchPedimento) filters.pedimento = searchPedimento; + + const response = await getElectronicNotices( + 1, + pageSize, + filters, + companyStore.activeCompany.id + ); + if (response?.items) { + allItems = response.items; + currentPage = 1; + totalItems = response.total; + } + } catch (err) { + error = 'Error al recargar datos'; + console.error('Error reloading electronic notices:', err); + } finally { + loading = false; + } + } + function handleSuccess() { - const url = new URL($page.url); - goto(url, { invalidateAll: true }); + reloadData(); } -
+

Avisos Electrónicos

Gestión del catálogo de avisos electrónicos

- +
-
-
- + {#if error} +
+ {error}
-
- -
-
+ {/if} -
- -
+ +
Listado de Avisos
+
+
+ +
Mostrando {allItems.length} de {totalItems} registros
diff --git a/frontend/src/routes/dashboard/general_catalogs/equivalencies/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/equivalencies/+page.svelte index d86f780d..51a76f07 100644 --- a/frontend/src/routes/dashboard/general_catalogs/equivalencies/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/equivalencies/+page.svelte @@ -4,16 +4,20 @@ import { browser } from '$app/environment'; import type { Equivalency } from '$lib/api/dashboard/a76/general_catalogs/equivalencies'; import { createCatalogColumns } from '$lib/components/dashboard/general_catalogs/equivalencies/catalog-columns'; - import DataTable from '$lib/components/dashboard/general_catalogs/equivalencies/data-table.svelte'; + 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 { Plus } from 'lucide-svelte'; + import { Plus, RefreshCw } 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'; import type { PageData } from './$types'; + import { getEquivalencies } from '$lib/api/dashboard/a76/general_catalogs/equivalencies'; + import { companyStore } from '$lib/stores/company.svelte'; let { data }: { data: PageData } = $props(); + let error = $state(data.error || null); let dataDialogOpen = $state(false); let selectedEquivalency = $state(null); @@ -36,22 +40,115 @@ let searchFrom = $state($page.url.searchParams.get('from_unit_code') || ''); let timeout: ReturnType; - function handleSearch() { + 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 || []; + currentPage = data.equivalencies.page || 1; + totalItems = data.equivalencies.total || 0; + pageSize = data.equivalencies.page_size || pageSize; + } + }); + + async function handleSearch() { if (!browser) return; clearTimeout(timeout); - timeout = setTimeout(() => { + timeout = setTimeout(async () => { + if (!companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchFrom) filters.from_unit_code = searchFrom; + + const response = await getEquivalencies( + 1, + pageSize, + companyStore.activeCompany.id, + filters + ); + if (response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (err) { + error = 'Error aplicando filtros'; + console.error('Error applying filters:', err); + } finally { + loading = false; + } + const url = new URL($page.url); if (searchFrom) url.searchParams.set('from_unit_code', searchFrom); else url.searchParams.delete('from_unit_code'); - url.searchParams.set('page', '1'); - goto(url, { keepFocus: true, noScroll: true }); + history.replaceState(history.state, '', url); }, 500); } + async function loadMore() { + if (loading || !hasMore || !companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchFrom) filters.from_unit_code = searchFrom; + + const response = await getEquivalencies( + currentPage + 1, + pageSize, + companyStore.activeCompany.id, + filters + ); + if (response.data?.items) { + allItems = [...allItems, ...response.data.items]; + currentPage += 1; + totalItems = response.data.total; + } + } catch (err) { + error = 'Error cargando mas datos'; + console.error('Error loading more equivalencies:', err); + } finally { + loading = false; + } + } + + async function reloadData() { + if (!companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchFrom) filters.from_unit_code = searchFrom; + + const response = await getEquivalencies( + 1, + pageSize, + companyStore.activeCompany.id, + filters + ); + if (response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (err) { + error = 'Error al recargar datos'; + console.error('Error reloading equivalencies:', err); + } finally { + loading = false; + } + } + function handleSuccess() { - const url = new URL($page.url); - goto(url, { invalidateAll: true }); + reloadData(); } function handleOpenInsertItems(equivalency: Equivalency) { @@ -67,13 +164,13 @@ } -
+

Equivalencias

Catálogo de equivalencias

- +
-
-
- + {#if error} +
+ {error}
-
+ {/if} - + +
Listado de Equivalencias
+
+
+ +
Mostrando {allItems.length} de {totalItems} registros
(data.error || null); // Atajos useShortcuts( @@ -29,10 +33,52 @@ let searchDesc = $state($page.url.searchParams.get('description') || ''); let timeout: ReturnType; - function handleSearch() { + let allItems = $state(data.errors?.items || []); + let currentPage = $state(data.errors?.page || 1); + let pageSize = $state(data.errors?.page_size || 50); + let totalItems = $state(data.errors?.total || 0); + let loading = $state(false); + let hasMore = $derived(allItems.length < totalItems); + + $effect(() => { + if (data.errors) { + allItems = data.errors.items || []; + currentPage = data.errors.page || 1; + totalItems = data.errors.total || 0; + pageSize = data.errors.page_size || pageSize; + } + }); + + async function handleSearch() { if (!browser) return; clearTimeout(timeout); - timeout = setTimeout(() => { + timeout = setTimeout(async () => { + if (!companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchCode) filters.code = searchCode; + if (searchDesc) filters.description = searchDesc; + + const response = await getErrorCatalogs( + companyStore.activeCompany.id, + 1, + pageSize, + filters + ); + if (response?.items) { + allItems = response.items; + currentPage = 1; + totalItems = response.total; + } + } catch (err) { + error = 'Error aplicando filtros'; + console.error('Error applying filters:', err); + } finally { + loading = false; + } + const url = new URL($page.url); if (searchCode) url.searchParams.set('code', searchCode); else url.searchParams.delete('code'); @@ -40,50 +86,92 @@ if (searchDesc) url.searchParams.set('description', searchDesc); else url.searchParams.delete('description'); - url.searchParams.set('page', '1'); - goto(url, { keepFocus: true, noScroll: true }); + history.replaceState(history.state, '', url); }, 500); } + async function loadMore() { + if (loading || !hasMore || !companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchCode) filters.code = searchCode; + if (searchDesc) filters.description = searchDesc; + + const response = await getErrorCatalogs( + companyStore.activeCompany.id, + currentPage + 1, + pageSize, + filters + ); + if (response?.items) { + allItems = [...allItems, ...response.items]; + currentPage += 1; + totalItems = response.total; + } + } catch (err) { + error = 'Error cargando mas datos'; + console.error('Error loading more error catalogs:', err); + } finally { + loading = false; + } + } + + async function reloadData() { + if (!companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchCode) filters.code = searchCode; + if (searchDesc) filters.description = searchDesc; + + const response = await getErrorCatalogs( + companyStore.activeCompany.id, + 1, + pageSize, + filters + ); + if (response?.items) { + allItems = response.items; + currentPage = 1; + totalItems = response.total; + } + } catch (err) { + error = 'Error al recargar datos'; + console.error('Error reloading error catalogs:', err); + } finally { + loading = false; + } + } + function handleSuccess() { - const url = new URL($page.url); - goto(url, { invalidateAll: true }); + reloadData(); } -
+

Errores de Facturación

Gestión del catálogo de errores de facturación

- +
-
-
- + {#if error} +
+ {error}
-
- -
-
+ {/if} -
- -
+ +
Listado de Errores
+
+
+ +
Mostrando {allItems.length} de {totalItems} registros
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 d2c20235..a68435cb 100644 --- a/frontend/src/routes/dashboard/general_catalogs/exchange-rate/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/exchange-rate/+page.svelte @@ -4,17 +4,21 @@ import { browser } from '$app/environment'; import { createColumns } from '$lib/components/dashboard/exchange_rate/columns'; import CreateEditDialog from '$lib/components/dashboard/exchange_rate/create-edit-dialog.svelte'; - import DataTable from '$lib/components/dashboard/exchange_rate/data-table.svelte'; + import 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 { Plus } from 'lucide-svelte'; + import { Plus, RefreshCw } from 'lucide-svelte'; import { useShortcuts } from '$lib/hooks/use-shortcuts'; import { obtenerAtajosListaTiposCambio } from '$lib/config/shortcuts/dashboard/general_catalogs/exchange_rate/list'; import type { PageData } from './$types'; + import { getExchangeRates } from '$lib/api/dashboard/a76/general_catalogs/exchange-rate'; + import { companyStore } from '$lib/stores/company.svelte'; let { data }: { data: PageData } = $props(); let createDialogOpen = $state(false); + let error = $state(data.error || null); // Atajos useShortcuts( @@ -36,55 +40,127 @@ let searchCurrency = $state($page.url.searchParams.get('local_currency') || ''); let timeout: ReturnType; - function handleSearch() { + 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; + } + }); + + async function handleSearch() { if (!browser) return; clearTimeout(timeout); - timeout = setTimeout(() => { + timeout = setTimeout(async () => { + if (!companyStore.activeCompany) return; + loading = true; + error = null; + try { + const response = await getExchangeRates(companyStore.activeCompany.id, { + local_currency: searchCurrency || undefined, + page: 1, + page_size: pageSize + }); + if (response.data) { + allItems = response.data.items; + currentPage = response.data.page || 1; + totalItems = response.data.total || 0; + } + } catch (err) { + error = 'Error aplicando filtros'; + console.error('Error applying filters:', err); + } finally { + loading = false; + } + const url = new URL($page.url); if (searchCurrency) url.searchParams.set('local_currency', searchCurrency); else url.searchParams.delete('local_currency'); - url.searchParams.set('page', '1'); - goto(url, { keepFocus: true, noScroll: true }); + history.replaceState(history.state, '', url); }, 500); } + async function loadMore() { + if (loading || !hasMore || !companyStore.activeCompany) return; + loading = true; + error = null; + try { + 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 || 0; + } + } catch (err) { + error = 'Error cargando mas datos'; + console.error('Error loading more exchange rates:', err); + } finally { + loading = false; + } + } + + async function reloadData() { + if (!companyStore.activeCompany) return; + loading = true; + error = null; + try { + const response = await getExchangeRates(companyStore.activeCompany.id, { + local_currency: searchCurrency || undefined, + page: 1, + page_size: pageSize + }); + if (response.data) { + allItems = response.data.items; + currentPage = response.data.page || 1; + totalItems = response.data.total || 0; + } + } catch (err) { + error = 'Error al recargar datos'; + console.error('Error reloading exchange rates:', err); + } finally { + loading = false; + } + } + function handleSuccess() { - const url = new URL($page.url); - goto(url, { invalidateAll: true }); + reloadData(); } -
+

Tipos de Cambio

Catálogo de tipos de cambio

- +
-
-
- + {#if error} +
+ {error}
-
+ {/if} -
- -
+ +
Listado de Tipos de Cambio
+
+
+ +
Mostrando {allItems.length} de {totalItems} registros
diff --git a/frontend/src/routes/dashboard/general_catalogs/identifiers/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/identifiers/+page.svelte index 31be43a1..d986d669 100644 --- a/frontend/src/routes/dashboard/general_catalogs/identifiers/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/identifiers/+page.svelte @@ -4,17 +4,21 @@ import { browser } from '$app/environment'; import { createColumns } from '$lib/components/dashboard/general_catalogs/identifiers/columns'; import CreateEditDialog from '$lib/components/dashboard/general_catalogs/identifiers/create-edit-dialog.svelte'; - import DataTable from '$lib/components/dashboard/general_catalogs/identifiers/data-table.svelte'; + 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 { Plus } from 'lucide-svelte'; + import { Plus, RefreshCw } from 'lucide-svelte'; import type { PageData } from './$types'; import { useShortcuts } from '$lib/hooks/use-shortcuts'; import { obtenerAtajosListaIdentificadores } from '$lib/config/shortcuts/dashboard/general_catalogs/identifiers/list'; + import { getIdentifiers } from '$lib/api/dashboard/a76/general_catalogs/identifiers'; + import { companyStore } from '$lib/stores/company.svelte'; let { data }: { data: PageData } = $props(); let createDialogOpen = $state(false); + let error = $state(data.error || null); // Atajos useShortcuts( @@ -30,10 +34,47 @@ let searchDesc = $state($page.url.searchParams.get('description') || ''); let timeout: ReturnType; - function handleSearch() { + let allItems = $state(data.identifiers?.items || []); + let currentPage = $state(data.identifiers?.page || 1); + let pageSize = $state(data.identifiers?.page_size || 50); + let totalItems = $state(data.identifiers?.total || 0); + let loading = $state(false); + let hasMore = $derived(allItems.length < totalItems); + + $effect(() => { + if (data.identifiers) { + allItems = data.identifiers.items || []; + currentPage = data.identifiers.page || 1; + totalItems = data.identifiers.total || 0; + pageSize = data.identifiers.page_size || pageSize; + } + }); + + async function handleSearch() { if (!browser) return; clearTimeout(timeout); - timeout = setTimeout(() => { + timeout = setTimeout(async () => { + if (!companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchCode) filters.code = searchCode; + if (searchDesc) filters.description = searchDesc; + + const response = await getIdentifiers(1, pageSize, companyStore.activeCompany.id, filters); + if (response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (err) { + error = 'Error aplicando filtros'; + console.error('Error applying filters:', err); + } finally { + loading = false; + } + const url = new URL($page.url); if (searchCode) url.searchParams.set('code', searchCode); else url.searchParams.delete('code'); @@ -41,49 +82,125 @@ if (searchDesc) url.searchParams.set('description', searchDesc); else url.searchParams.delete('description'); - url.searchParams.set('page', '1'); - goto(url, { keepFocus: true, noScroll: true }); + history.replaceState(history.state, '', url); }, 500); } + async function loadMore() { + if (loading || !hasMore || !companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchCode) filters.code = searchCode; + if (searchDesc) filters.description = searchDesc; + + const response = await getIdentifiers( + currentPage + 1, + pageSize, + companyStore.activeCompany.id, + filters + ); + if (response.data?.items) { + allItems = [...allItems, ...response.data.items]; + currentPage += 1; + totalItems = response.data.total; + } + } catch (err) { + error = 'Error cargando mas datos'; + console.error('Error loading more identifiers:', err); + } finally { + loading = false; + } + } + + async function reloadData() { + if (!companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchCode) filters.code = searchCode; + if (searchDesc) filters.description = searchDesc; + + const response = await getIdentifiers(1, pageSize, companyStore.activeCompany.id, filters); + if (response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (err) { + error = 'Error al recargar datos'; + console.error('Error reloading identifiers:', err); + } finally { + loading = false; + } + } + function handleSuccess() { - const url = new URL($page.url); - goto(url, { invalidateAll: true }); + reloadData(); } -
+

Identificadores

Catálogo de identificadores

- -
- -
-
- -
-
- +
+ +
-
- + {#if error} +
+ {error} +
+ {/if} + + + +
+ Listado de Identificadores +
+ + +
+
+
+ +
+ +
+
+
+ +
+ Mostrando {allItems.length} de {totalItems} registros
diff --git a/frontend/src/routes/dashboard/general_catalogs/inpc/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/inpc/+page.svelte index bc626c29..92990890 100644 --- a/frontend/src/routes/dashboard/general_catalogs/inpc/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/inpc/+page.svelte @@ -4,17 +4,21 @@ import { browser } from '$app/environment'; import { createColumns } from '$lib/components/dashboard/general_catalogs/inpc/columns'; import CreateEditDialog from '$lib/components/dashboard/general_catalogs/inpc/create-edit-dialog.svelte'; - import DataTable from '$lib/components/dashboard/general_catalogs/inpc/data-table.svelte'; + 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 { Plus } from 'lucide-svelte'; + import { Plus, RefreshCw } from 'lucide-svelte'; import type { PageData } from './$types'; import { useShortcuts } from '$lib/hooks/use-shortcuts'; import { obtenerAtajosListaInpc } from '$lib/config/shortcuts/dashboard/general_catalogs/inpc/list'; + import { getINPCs } from '$lib/api/dashboard/a76/general_catalogs/inpc'; + import { companyStore } from '$lib/stores/company.svelte'; let { data }: { data: PageData } = $props(); let createDialogOpen = $state(false); + let error = $state(data.error || null); // Atajos useShortcuts( @@ -30,10 +34,48 @@ let searchMonth = $state($page.url.searchParams.get('month') || ''); let timeout: ReturnType; - function handleSearch() { + // Infinite scroll + let allItems = $state(data.inpc?.items || []); + let currentPage = $state(data.inpc?.page || 1); + let pageSize = $state(data.inpc?.page_size || 50); + let totalItems = $state(data.inpc?.total || 0); + let loading = $state(false); + let hasMore = $derived(allItems.length < totalItems); + + $effect(() => { + if (data.inpc) { + allItems = data.inpc.items || []; + currentPage = data.inpc.page || 1; + totalItems = data.inpc.total || 0; + pageSize = data.inpc.page_size || pageSize; + } + }); + + async function handleSearch() { if (!browser) return; clearTimeout(timeout); - timeout = setTimeout(() => { + timeout = setTimeout(async () => { + if (!companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchYear) filters.year = searchYear; + if (searchMonth) filters.month = searchMonth; + + const response = await getINPCs(1, pageSize, companyStore.activeCompany.id, filters); + if (response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (err) { + error = 'Error aplicando filtros'; + console.error('Error applying filters:', err); + } finally { + loading = false; + } + const url = new URL($page.url); if (searchYear) url.searchParams.set('year', searchYear); else url.searchParams.delete('year'); @@ -41,45 +83,125 @@ if (searchMonth) url.searchParams.set('month', searchMonth); else url.searchParams.delete('month'); - url.searchParams.set('page', '1'); - goto(url, { keepFocus: true, noScroll: true }); + history.replaceState(history.state, '', url); }, 500); } + async function loadMore() { + if (loading || !hasMore || !companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchYear) filters.year = searchYear; + if (searchMonth) filters.month = searchMonth; + + const response = await getINPCs( + currentPage + 1, + pageSize, + companyStore.activeCompany.id, + filters + ); + if (response.data?.items) { + allItems = [...allItems, ...response.data.items]; + currentPage += 1; + totalItems = response.data.total; + } + } catch (err) { + error = 'Error cargando mas datos'; + console.error('Error loading more INPC:', err); + } finally { + loading = false; + } + } + + async function reloadData() { + if (!companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchYear) filters.year = searchYear; + if (searchMonth) filters.month = searchMonth; + + const response = await getINPCs(1, pageSize, companyStore.activeCompany.id, filters); + if (response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (err) { + error = 'Error al recargar datos'; + console.error('Error reloading INPC:', err); + } finally { + loading = false; + } + } + function handleSuccess() { - const url = new URL($page.url); - goto(url, { invalidateAll: true }); + reloadData(); } -
-
+
+

INPC

Índice Nacional de Precios al Consumidor

- -
- -
-
- -
-
- +
+ +
-
- + {#if error} +
+ {error} +
+ {/if} + + + +
+ Listado de INPC +
+ + +
+
+
+ +
+ +
+
+
+ +
+ Mostrando {allItems.length} de {totalItems} registros
diff --git a/frontend/src/routes/dashboard/general_catalogs/legends/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/legends/+page.svelte index 2c9ecc30..9632ba7c 100644 --- a/frontend/src/routes/dashboard/general_catalogs/legends/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/legends/+page.svelte @@ -1,18 +1,22 @@ -
-
+
+

Leyendas Fijas

Gestión del catálogo de leyendas fijas

- -
- -
-
- -
-
- +
+ +
-
- + {#if error} +
+ {error} +
+ {/if} + + + +
+ Listado de Leyendas +
+ + +
+
+
+ +
+ +
+
+
+ +
+ Mostrando {allItems.length} de {totalItems} registros
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 1dbfa9d1..004b399a 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 @@ -1,17 +1,25 @@ -
+

Tipos de Moneda Múltiple

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

- +
-
-
- + {#if error} +
+ {error}
-
- -
-
+ {/if} -
- -
+ +
Listado de Monedas
+
+
+ +
Mostrando {allItems.length} de {totalItems} registros
diff --git a/frontend/src/routes/dashboard/general_catalogs/packages/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/packages/+page.svelte index 12777b93..659451d8 100644 --- a/frontend/src/routes/dashboard/general_catalogs/packages/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/packages/+page.svelte @@ -1,21 +1,25 @@ -
+

Bultos y Embalajes

Gestión del catálogo de bultos y embalajes

- -
- -
-
- -
-
- +
+ +
-
- + {#if error} +
+ {error} +
+ {/if} + + + +
+ Listado de Bultos +
+ + +
+
+
+ +
+ +
+
+
+ +
+ Mostrando {allItems.length} de {totalItems} registros
diff --git a/frontend/src/routes/dashboard/general_catalogs/ports/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/ports/+page.svelte index 88c42d3c..2ad73e0c 100644 --- a/frontend/src/routes/dashboard/general_catalogs/ports/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/ports/+page.svelte @@ -4,17 +4,21 @@ import { browser } from '$app/environment'; import { createColumns } from '$lib/components/dashboard/ports/columns'; import CreateEditDialog from '$lib/components/dashboard/ports/create-edit-dialog.svelte'; - import DataTable from '$lib/components/dashboard/general_catalogs/units_of_measure/ace/data-table.svelte'; + 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 { Plus } from 'lucide-svelte'; + import { Plus, RefreshCw } from 'lucide-svelte'; import type { PageData } from './$types'; import { useShortcuts } from '$lib/hooks/use-shortcuts'; import { obtenerAtajosListaPuertos } from '$lib/config/shortcuts/dashboard/general_catalogs/ports/list'; + import { portsApi } from '$lib/api/dashboard/a76/general_catalogs/ports'; + import { companyStore } from '$lib/stores/company.svelte'; let { data }: { data: PageData } = $props(); let createDialogOpen = $state(false); + let error = $state(data.items?.error || null); // Atajos useShortcuts( @@ -30,10 +34,50 @@ let searchDesc = $state($page.url.searchParams.get('description') || ''); let timeout: ReturnType; - function handleSearch() { + let allItems = $state(data.items?.items || data.items || []); + let currentPage = $state(data.items?.page || 1); + let pageSize = $state(data.items?.pageSize || data.items?.page_size || 50); + let totalItems = $state(data.items?.total || 0); + let loading = $state(false); + let hasMore = $derived(allItems.length < totalItems); + + $effect(() => { + if (data.items?.items) { + allItems = data.items.items || []; + currentPage = data.items.page || 1; + totalItems = data.items.total || 0; + pageSize = data.items.pageSize || data.items.page_size || pageSize; + } + }); + + async function handleSearch() { if (!browser) return; clearTimeout(timeout); - timeout = setTimeout(() => { + timeout = setTimeout(async () => { + if (!companyStore.activeCompany) return; + loading = true; + error = null; + try { + const params: Record = { + page: '1', + page_size: pageSize.toString() + }; + if (searchCode) params.port_code = searchCode; + if (searchDesc) params.description = searchDesc; + + const response = await portsApi.list(companyStore.activeCompany.id, params); + if (response.data?.items) { + allItems = response.data.items; + currentPage = response.data.page || 1; + totalItems = response.data.total || 0; + } + } catch (err) { + error = 'Error aplicando filtros'; + console.error('Error applying filters:', err); + } finally { + loading = false; + } + const url = new URL($page.url); if (searchCode) url.searchParams.set('port_code', searchCode); else url.searchParams.delete('port_code'); @@ -41,58 +85,117 @@ if (searchDesc) url.searchParams.set('description', searchDesc); else url.searchParams.delete('description'); - url.searchParams.set('page', '1'); - goto(url, { keepFocus: true, noScroll: true }); + history.replaceState(history.state, '', url); }, 500); } + async function loadMore() { + if (loading || !hasMore || !companyStore.activeCompany) return; + loading = true; + error = null; + try { + const params: Record = { + page: (currentPage + 1).toString(), + page_size: pageSize.toString() + }; + if (searchCode) params.port_code = searchCode; + if (searchDesc) params.description = searchDesc; + + const response = await portsApi.list(companyStore.activeCompany.id, params); + if (response.data?.items) { + allItems = [...allItems, ...response.data.items]; + currentPage += 1; + totalItems = response.data.total || 0; + } + } catch (err) { + error = 'Error cargando mas datos'; + console.error('Error loading more ports:', err); + } finally { + loading = false; + } + } + + async function reloadData() { + if (!companyStore.activeCompany) return; + loading = true; + error = null; + try { + const params: Record = { + page: '1', + page_size: pageSize.toString() + }; + if (searchCode) params.port_code = searchCode; + if (searchDesc) params.description = searchDesc; + + const response = await portsApi.list(companyStore.activeCompany.id, params); + if (response.data?.items) { + allItems = response.data.items; + currentPage = response.data.page || 1; + totalItems = response.data.total || 0; + } + } catch (err) { + error = 'Error al recargar datos'; + console.error('Error reloading ports:', err); + } finally { + loading = false; + } + } + function handleSuccess() { - const url = new URL($page.url); - goto(url, { invalidateAll: true }); + reloadData(); } -
+

Puertos

Catálogo de puertos

- +
+ + +
- {#if data.items?.error} + {#if error}
- {data.items.error} + {error}
{/if} -
-
- -
-
- -
-
+ + +
+ Listado de Puertos +
+ + +
+
+
+ +
+ +
+
+
-
- -
+
Mostrando {allItems.length} de {totalItems} registros
diff --git a/frontend/src/routes/dashboard/general_catalogs/prevalidators/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/prevalidators/+page.svelte index 81899bfd..fd4ed81e 100644 --- a/frontend/src/routes/dashboard/general_catalogs/prevalidators/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/prevalidators/+page.svelte @@ -2,17 +2,21 @@ import { goto } from '$app/navigation'; import { page } from '$app/stores'; import { browser } from '$app/environment'; + import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; - import { Plus } from 'lucide-svelte'; - import DataTable from '$lib/components/dashboard/general_catalogs/prevalidators/data-table.svelte'; + import { Plus, RefreshCw } from 'lucide-svelte'; + import InfiniteDataTable from '$lib/components/dashboard/common/infinite-data-table.svelte'; import { createColumns } from '$lib/components/dashboard/general_catalogs/prevalidators/columns'; import CreateEditDialog from '$lib/components/dashboard/general_catalogs/prevalidators/create-edit-dialog.svelte'; import { useShortcuts } from '$lib/hooks/use-shortcuts'; import { obtenerAtajosListaPrevalidadores } from '$lib/config/shortcuts/dashboard/general_catalogs/prevalidators/list'; + import { getPrevalidators } from '$lib/api/dashboard/a76/general_catalogs/prevalidators'; + import { companyStore } from '$lib/stores/company.svelte'; let { data } = $props(); let dialogOpen = $state(false); + let error = $state(data.error || null); // Atajos useShortcuts( @@ -27,15 +31,47 @@ let searchDesc = $state($page.url.searchParams.get('description') || ''); let timeout: ReturnType; - function handleSuccess() { - const url = new URL($page.url); - goto(url, { invalidateAll: true }); - } + let allItems = $state(data.prevalidators?.items || []); + let currentPage = $state(data.prevalidators?.page || 1); + let pageSize = $state(data.prevalidators?.page_size || 50); + let totalItems = $state(data.prevalidators?.total || 0); + let loading = $state(false); + let hasMore = $derived(allItems.length < totalItems); - function handleSearch() { + $effect(() => { + if (data.prevalidators) { + allItems = data.prevalidators.items || []; + currentPage = data.prevalidators.page || 1; + totalItems = data.prevalidators.total || 0; + pageSize = data.prevalidators.page_size || pageSize; + } + }); + + async function handleSearch() { if (!browser) return; clearTimeout(timeout); - timeout = setTimeout(() => { + timeout = setTimeout(async () => { + if (!companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchCode) filters.code = searchCode; + if (searchDesc) filters.description = searchDesc; + + const response = await getPrevalidators(1, pageSize, filters, companyStore.activeCompany.id); + if (response?.items) { + allItems = response.items; + currentPage = 1; + totalItems = response.total; + } + } catch (err) { + error = 'Error aplicando filtros'; + console.error('Error applying filters:', err); + } finally { + loading = false; + } + const url = new URL($page.url); if (searchCode) url.searchParams.set('code', searchCode); else url.searchParams.delete('code'); @@ -43,45 +79,98 @@ if (searchDesc) url.searchParams.set('description', searchDesc); else url.searchParams.delete('description'); - url.searchParams.set('page', '1'); - goto(url, { keepFocus: true, noScroll: true }); + history.replaceState(history.state, '', url); }, 500); } + + async function loadMore() { + if (loading || !hasMore || !companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchCode) filters.code = searchCode; + if (searchDesc) filters.description = searchDesc; + + const response = await getPrevalidators( + currentPage + 1, + pageSize, + filters, + companyStore.activeCompany.id + ); + if (response?.items) { + allItems = [...allItems, ...response.items]; + currentPage += 1; + totalItems = response.total; + } + } catch (err) { + error = 'Error cargando mas datos'; + console.error('Error loading more prevalidators:', err); + } finally { + loading = false; + } + } + + async function reloadData() { + if (!companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchCode) filters.code = searchCode; + if (searchDesc) filters.description = searchDesc; + + const response = await getPrevalidators(1, pageSize, filters, companyStore.activeCompany.id); + if (response?.items) { + allItems = response.items; + currentPage = 1; + totalItems = response.total; + } + } catch (err) { + error = 'Error al recargar datos'; + console.error('Error reloading prevalidators:', err); + } finally { + loading = false; + } + } + + function handleSuccess() { + reloadData(); + } -
+

Prevalidadores

Catálogo de prevalidadores

- -
- -
-
- -
-
- +
+ +
-
- -
+ {#if error} +
+ {error} +
+ {/if} + + + +
+ Listado de Prevalidadores +
+ + +
+
+
+
+
+ +
Mostrando {allItems.length} de {totalItems} registros
diff --git a/frontend/src/routes/dashboard/general_catalogs/seal/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/seal/+page.svelte index 562bb586..fac039f8 100644 --- a/frontend/src/routes/dashboard/general_catalogs/seal/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/seal/+page.svelte @@ -1,8 +1,9 @@ -
-
+
+

Sellos

Gestiona los sellos de tu empresa

- +
+ + +
{#if error} -
+

{error}

{/if} -
-
- -
-
+ + +
+ Listado de Sellos +
+ +
+
+
+ +
+ +
+
+
-
- +
+ Mostrando {allItems.length} de {totalItems || allItems.length} registros
diff --git a/frontend/src/routes/dashboard/general_catalogs/signatures/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/signatures/+page.svelte index 34af6e59..a0e46d78 100644 --- a/frontend/src/routes/dashboard/general_catalogs/signatures/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/signatures/+page.svelte @@ -4,15 +4,19 @@ import { browser } from '$app/environment'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; - import { Plus } from 'lucide-svelte'; - import DataTable from '$lib/components/dashboard/general_catalogs/signatures/data-table.svelte'; + import * as Card from '$lib/components/ui/card'; + import { Plus, RefreshCw } from 'lucide-svelte'; + import InfiniteDataTable from '$lib/components/dashboard/common/infinite-data-table.svelte'; import { createColumns } from '$lib/components/dashboard/general_catalogs/signatures/columns'; import CreateEditDialog from '$lib/components/dashboard/general_catalogs/signatures/create-edit-dialog.svelte'; import { useShortcuts } from '$lib/hooks/use-shortcuts'; import { obtenerAtajosListaFirmas } from '$lib/config/shortcuts/dashboard/general_catalogs/signatures/list'; + import { getSignatures } from '$lib/api/dashboard/a76/general_catalogs/signatures'; + import { companyStore } from '$lib/stores/company.svelte'; let { data } = $props(); let dialogOpen = $state(false); + let error = $state(data.error || null); // Atajos useShortcuts( @@ -26,50 +30,138 @@ let searchCode = $state($page.url.searchParams.get('code') || ''); let timeout: ReturnType; - function handleSuccess() { - const url = new URL($page.url); - goto(url, { invalidateAll: true }); - } + let allItems = $state(data.signatures?.items || []); + let currentPage = $state(data.signatures?.page || 1); + let pageSize = $state(data.signatures?.page_size || 50); + let totalItems = $state(data.signatures?.total || 0); + let loading = $state(false); + let hasMore = $derived(allItems.length < totalItems); - function handleSearch() { + $effect(() => { + if (data.signatures) { + allItems = data.signatures.items || []; + currentPage = data.signatures.page || 1; + totalItems = data.signatures.total || 0; + pageSize = data.signatures.page_size || pageSize; + } + }); + + async function handleSearch() { if (!browser) return; clearTimeout(timeout); - timeout = setTimeout(() => { + timeout = setTimeout(async () => { + if (!companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchCode) filters.code = searchCode; + + const response = await getSignatures( + 1, + pageSize, + companyStore.activeCompany.id, + filters + ); + if (response?.items) { + allItems = response.items; + currentPage = 1; + totalItems = response.total; + } + } catch (err) { + error = 'Error aplicando filtros'; + console.error('Error applying filters:', err); + } finally { + loading = false; + } + const url = new URL($page.url); if (searchCode) url.searchParams.set('code', searchCode); else url.searchParams.delete('code'); - url.searchParams.set('page', '1'); - goto(url, { keepFocus: true, noScroll: true }); + history.replaceState(history.state, '', url); }, 500); } + + async function loadMore() { + if (loading || !hasMore || !companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchCode) filters.code = searchCode; + + const response = await getSignatures( + currentPage + 1, + pageSize, + companyStore.activeCompany.id, + filters + ); + if (response?.items) { + allItems = [...allItems, ...response.items]; + currentPage += 1; + totalItems = response.total; + } + } catch (err) { + error = 'Error cargando mas datos'; + console.error('Error loading more signatures:', err); + } finally { + loading = false; + } + } + + async function reloadData() { + if (!companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchCode) filters.code = searchCode; + + const response = await getSignatures( + 1, + pageSize, + companyStore.activeCompany.id, + filters + ); + if (response?.items) { + allItems = response.items; + currentPage = 1; + totalItems = response.total; + } + } catch (err) { + error = 'Error al recargar datos'; + console.error('Error reloading signatures:', err); + } finally { + loading = false; + } + } + + function handleSuccess() { + reloadData(); + } -
+

Firmas Electrónicas

Gestión del catálogo de firmas electrónicas

- +
-
-
- + {#if error} +
+ {error}
-
+ {/if} -
- -
+ +
Listado de Firmas
+
+
+ +
Mostrando {allItems.length} de {totalItems} registros
diff --git a/frontend/src/routes/dashboard/general_catalogs/tariff-fractions/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/tariff-fractions/+page.svelte index 9ad3119a..8fc9313b 100644 --- a/frontend/src/routes/dashboard/general_catalogs/tariff-fractions/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/tariff-fractions/+page.svelte @@ -89,145 +89,100 @@ } -
- - - - Catálogo de Fracciones SITAR - SCAII - -

Nomenclatura arancelaria mexicana completa

+
+
+
+

Catálogo de Fracciones SITAR - SCAII

+

Nomenclatura arancelaria mexicana completa

+
+ +
+ + + +
+ Listado de Fracciones +
+ + +
+
- - -
- -
-
- - -
- -
- - -
-
- {#if isLoading} -
- - Cargando... -
- {:else} - Mostrando {tariffFractions.length} de {totalRecords} fracciones arancelarias - {#if searchQuery} - (filtrado) - {/if} - {/if} -
- {#if totalPages > 1} -
- Página {currentPage} de {totalPages} -
- {/if} -
- - -
- - + +
+ + + + Código + Fracción + Descripción + NICO + UMT + Adv. Impo + Adv. Expo + + + + {#if tariffFractions.length === 0} - Código - Fracción - Descripción - NICO - UMT - Adv. Impo - Adv. Expo + + {#if isLoading} + Cargando fracciones arancelarias... + {:else if searchQuery} + No se encontraron fracciones que coincidan con la búsqueda + {:else} + No hay fracciones arancelarias disponibles + {/if} + - - - {#if tariffFractions.length === 0} - - - {#if isLoading} - Cargando fracciones arancelarias... - {:else if searchQuery} - No se encontraron fracciones que coincidan con la búsqueda - {:else} - No hay fracciones arancelarias disponibles - {/if} - + {:else} + {#each tariffFractions as fraction (fraction.id)} + + {fraction.code} + {fraction.fraction} + {fraction.description || '-'} + {fraction.nico || '-'} + {fraction.umt || '-'} + {fraction.adv_impo || '-'} + {fraction.adv_expo || '-'} - {:else} - {#each tariffFractions as fraction (fraction.id)} - - {fraction.code} - {fraction.fraction} - - {fraction.description || '-'} - - {fraction.nico || '-'} - {fraction.umt || '-'} - {fraction.adv_impo || '-'} - {fraction.adv_expo || '-'} - - {/each} - {/if} - - -
- - - {#if totalPages > 1} -
- - - - Página {currentPage} de {totalPages} - - - -
- {/if} + {/each} + {/if} + +
+ +
+ Mostrando {tariffFractions.length} de {totalRecords} fracciones arancelarias + {#if searchQuery} + + Filtrado + {/if} +
+ + {#if totalPages > 1} +
+ + + + +
+ {/if}
diff --git a/frontend/src/routes/dashboard/general_catalogs/tariff-fractions/canadian/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/tariff-fractions/canadian/+page.svelte index 5a67d55e..63fab84b 100644 --- a/frontend/src/routes/dashboard/general_catalogs/tariff-fractions/canadian/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/tariff-fractions/canadian/+page.svelte @@ -3,9 +3,15 @@ import * as m from '$lib/paraglide/messages.js'; -
-
-

{m['sidebar.fractions.canadian']()}

+
+
+
+

{m['sidebar.fractions.canadian']()}

+

Catálogo de fracciones arancelarias canadienses

+
+
+ +
+
-
diff --git a/frontend/src/routes/dashboard/general_catalogs/trailers/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/trailers/+page.svelte index 596c3d13..0331dc38 100644 --- a/frontend/src/routes/dashboard/general_catalogs/trailers/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/trailers/+page.svelte @@ -1,12 +1,13 @@ -
+
-

Trailers

+

Trailers

Gestión del catálogo de trailers de la compañía

- +
-
- - -
+ +
Listado de Trailers
+ {#if loading && data.length === 0}
Cargando trailers...
{:else}
{/if}
+
- {#if loading && data.length === 0} -
- Cargando trailers... -
- {:else} - - {/if} +
Mostrando {data.length} de {totalItems} registros
diff --git a/frontend/src/routes/dashboard/general_catalogs/transporters/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/transporters/+page.svelte index 150e006b..f14bf171 100644 --- a/frontend/src/routes/dashboard/general_catalogs/transporters/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/transporters/+page.svelte @@ -1,12 +1,13 @@ -
+
-

Transportistas

+

Transportistas

Gestión del catálogo de líneas transportistas

- +
-
- - -
+ +
Listado de Transportistas
+ {#if loading && data.length === 0}
Cargando transportistas...
{:else}
{/if}
+
- {#if loading && data.length === 0} -
- Cargando transportistas... -
- {:else} - - {/if} +
Mostrando {data.length} de {totalItems} registros
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 bc274ff2..ea00c786 100644 --- a/frontend/src/routes/dashboard/general_catalogs/unit_conversions/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/unit_conversions/+page.svelte @@ -4,17 +4,21 @@ import { browser } from '$app/environment'; import { createColumns } from '$lib/components/dashboard/general_catalogs/unit_conversions/columns'; import CreateEditDialog from '$lib/components/dashboard/general_catalogs/unit_conversions/create-edit-dialog.svelte'; - import DataTable from '$lib/components/dashboard/general_catalogs/unit_conversions/data-table.svelte'; + 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 { Plus } from 'lucide-svelte'; + import { Plus, RefreshCw } from 'lucide-svelte'; import type { PageData } from './$types'; import { useShortcuts } from '$lib/hooks/use-shortcuts'; import { obtenerAtajosListaConversiones } from '$lib/config/shortcuts/dashboard/general_catalogs/unit_conversions/list'; + import { getUnitConversions } from '$lib/api/dashboard/a76/general_catalogs/unit-conversions'; + import { companyStore } from '$lib/stores/company.svelte'; let { data }: { data: PageData } = $props(); let createDialogOpen = $state(false); + let error = $state(data.error || null); // Atajos useShortcuts( @@ -30,10 +34,52 @@ let searchTo = $state($page.url.searchParams.get('to_unit_code') || ''); let timeout: ReturnType; - function handleSearch() { + let allItems = $state(data.conversions?.items || []); + let currentPage = $state(data.conversions?.page || 1); + let pageSize = $state(data.conversions?.page_size || 50); + let totalItems = $state(data.conversions?.total || 0); + let loading = $state(false); + let hasMore = $derived(allItems.length < totalItems); + + $effect(() => { + if (data.conversions) { + allItems = data.conversions.items || []; + currentPage = data.conversions.page || 1; + totalItems = data.conversions.total || 0; + pageSize = data.conversions.page_size || pageSize; + } + }); + + async function handleSearch() { if (!browser) return; clearTimeout(timeout); - timeout = setTimeout(() => { + timeout = setTimeout(async () => { + if (!companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchFrom) filters.from_unit_code = searchFrom; + if (searchTo) filters.to_unit_code = searchTo; + + const response = await getUnitConversions( + 1, + pageSize, + companyStore.activeCompany.id, + filters + ); + if (response?.items) { + allItems = response.items; + currentPage = 1; + totalItems = response.total; + } + } catch (err) { + error = 'Error aplicando filtros'; + console.error('Error applying filters:', err); + } finally { + loading = false; + } + const url = new URL($page.url); if (searchFrom) url.searchParams.set('from_unit_code', searchFrom); else url.searchParams.delete('from_unit_code'); @@ -41,46 +87,92 @@ if (searchTo) url.searchParams.set('to_unit_code', searchTo); else url.searchParams.delete('to_unit_code'); - url.searchParams.set('page', '1'); - goto(url, { keepFocus: true, noScroll: true }); + history.replaceState(history.state, '', url); }, 500); } + async function loadMore() { + if (loading || !hasMore || !companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchFrom) filters.from_unit_code = searchFrom; + if (searchTo) filters.to_unit_code = searchTo; + + const response = await getUnitConversions( + currentPage + 1, + pageSize, + companyStore.activeCompany.id, + filters + ); + if (response?.items) { + allItems = [...allItems, ...response.items]; + currentPage += 1; + totalItems = response.total; + } + } catch (err) { + error = 'Error cargando mas datos'; + console.error('Error loading more unit conversions:', err); + } finally { + loading = false; + } + } + + async function reloadData() { + if (!companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchFrom) filters.from_unit_code = searchFrom; + if (searchTo) filters.to_unit_code = searchTo; + + const response = await getUnitConversions( + 1, + pageSize, + companyStore.activeCompany.id, + filters + ); + if (response?.items) { + allItems = response.items; + currentPage = 1; + totalItems = response.total; + } + } catch (err) { + error = 'Error al recargar datos'; + console.error('Error reloading unit conversions:', err); + } finally { + loading = false; + } + } + function handleSuccess() { - const url = new URL($page.url); - goto(url, { invalidateAll: true }); + reloadData(); } -
+

Conversiones de Unidades

Catálogo de conversiones de unidades de medida

- +
-
-
- + {#if error} +
+ {error}
-
- -
-
+ {/if} -
- -
+ +
Listado de Conversiones
+
+
+ +
Mostrando {allItems.length} de {totalItems} registros
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 664840ef..a4bc68fb 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 @@ -4,17 +4,21 @@ import { browser } from '$app/environment'; import { createColumns } from '$lib/components/dashboard/general_catalogs/units_of_measure/ace/columns'; import CreateEditDialog from '$lib/components/dashboard/general_catalogs/units_of_measure/ace/create-edit-dialog.svelte'; - import DataTable from '$lib/components/dashboard/general_catalogs/units_of_measure/ace/data-table.svelte'; + 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 { Plus } from 'lucide-svelte'; + import { Plus, RefreshCw } from 'lucide-svelte'; import type { PageData } from './$types'; import { useShortcuts } from '$lib/hooks/use-shortcuts'; import { obtenerAtajosListaUnidadesACE } from '$lib/config/shortcuts/dashboard/general_catalogs/units_of_measure/ace/list'; + import { getUnitsOfMeasureACE } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure'; + import { companyStore } from '$lib/stores/company.svelte'; let { data }: { data: PageData } = $props(); let createDialogOpen = $state(false); + let error = $state(data.error || null); // Atajos useShortcuts( @@ -30,10 +34,52 @@ let searchDesc = $state($page.url.searchParams.get('description') || ''); let timeout: ReturnType; - function handleSearch() { + let allItems = $state(data.ace_units?.items || []); + let currentPage = $state(data.ace_units?.page || 1); + let pageSize = $state(data.ace_units?.page_size || 50); + let totalItems = $state(data.ace_units?.total || 0); + let loading = $state(false); + let hasMore = $derived(allItems.length < totalItems); + + $effect(() => { + if (data.ace_units) { + allItems = data.ace_units.items || []; + currentPage = data.ace_units.page || 1; + totalItems = data.ace_units.total || 0; + pageSize = data.ace_units.page_size || pageSize; + } + }); + + async function handleSearch() { if (!browser) return; clearTimeout(timeout); - timeout = setTimeout(() => { + timeout = setTimeout(async () => { + if (!companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchCode) filters.code = searchCode; + if (searchDesc) filters.description = searchDesc; + + const response = await getUnitsOfMeasureACE( + 1, + pageSize, + companyStore.activeCompany.id, + filters + ); + if (response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (err) { + error = 'Error aplicando filtros'; + console.error('Error applying filters:', err); + } finally { + loading = false; + } + const url = new URL($page.url); if (searchCode) url.searchParams.set('code', searchCode); else url.searchParams.delete('code'); @@ -41,50 +87,92 @@ if (searchDesc) url.searchParams.set('description', searchDesc); else url.searchParams.delete('description'); - url.searchParams.set('page', '1'); - goto(url, { keepFocus: true, noScroll: true }); + history.replaceState(history.state, '', url); }, 500); } + async function loadMore() { + if (loading || !hasMore || !companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchCode) filters.code = searchCode; + if (searchDesc) filters.description = searchDesc; + + const response = await getUnitsOfMeasureACE( + currentPage + 1, + pageSize, + companyStore.activeCompany.id, + filters + ); + if (response.data?.items) { + allItems = [...allItems, ...response.data.items]; + currentPage += 1; + totalItems = response.data.total; + } + } catch (err) { + error = 'Error cargando mas datos'; + console.error('Error loading more ACE units:', err); + } finally { + loading = false; + } + } + + async function reloadData() { + if (!companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchCode) filters.code = searchCode; + if (searchDesc) filters.description = searchDesc; + + const response = await getUnitsOfMeasureACE( + 1, + pageSize, + companyStore.activeCompany.id, + filters + ); + if (response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (err) { + error = 'Error al recargar datos'; + console.error('Error reloading ACE units:', err); + } finally { + loading = false; + } + } + function handleSuccess() { - const url = new URL($page.url); - goto(url, { invalidateAll: true }); + reloadData(); } -
+

Unidades de Medida ACE

Catálogo de unidades de medida ACE

- +
-
-
- + {#if error} +
+ {error}
-
- -
-
+ {/if} -
- -
+ +
Listado de Unidades ACE
+
+
+ +
Mostrando {allItems.length} de {totalItems} registros
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 41f27a3f..38bfedf3 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 @@ -3,18 +3,22 @@ import { goto } from '$app/navigation'; import { browser } from '$app/environment'; import { createColumns } from '$lib/components/dashboard/general_catalogs/units_of_measure/american/columns'; - import DataTable from '$lib/components/dashboard/general_catalogs/units_of_measure/american/data-table.svelte'; + import InfiniteDataTable from '$lib/components/dashboard/common/infinite-data-table.svelte'; import CreateEditDialog from '$lib/components/dashboard/general_catalogs/units_of_measure/american/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 { Plus } from 'lucide-svelte'; + import { Plus, RefreshCw } from 'lucide-svelte'; import type { PageData } from './$types'; import { useShortcuts } from '$lib/hooks/use-shortcuts'; import { obtenerAtajosListaUnidadesAmericanas } from '$lib/config/shortcuts/dashboard/general_catalogs/units_of_measure/american/list'; + import { getUnitsOfMeasureAmerican } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure'; + import { companyStore } from '$lib/stores/company.svelte'; let { data }: { data: PageData } = $props(); let createDialogOpen = $state(false); + let error = $state(data.error || null); // Atajos useShortcuts( @@ -30,10 +34,52 @@ let searchDesc = $state($page.url.searchParams.get('description') || ''); let timeout: ReturnType; - function handleSearch() { + let allItems = $state(data.american_units?.items || []); + let currentPage = $state(data.american_units?.page || 1); + let pageSize = $state(data.american_units?.page_size || 50); + let totalItems = $state(data.american_units?.total || 0); + let loading = $state(false); + let hasMore = $derived(allItems.length < totalItems); + + $effect(() => { + if (data.american_units) { + allItems = data.american_units.items || []; + currentPage = data.american_units.page || 1; + totalItems = data.american_units.total || 0; + pageSize = data.american_units.page_size || pageSize; + } + }); + + async function handleSearch() { if (!browser) return; clearTimeout(timeout); - timeout = setTimeout(() => { + timeout = setTimeout(async () => { + if (!companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchCode) filters.code = searchCode; + if (searchDesc) filters.description = searchDesc; + + const response = await getUnitsOfMeasureAmerican( + 1, + pageSize, + companyStore.activeCompany.id, + filters + ); + if (response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (err) { + error = 'Error aplicando filtros'; + console.error('Error applying filters:', err); + } finally { + loading = false; + } + const url = new URL($page.url); if (searchCode) url.searchParams.set('code', searchCode); else url.searchParams.delete('code'); @@ -41,50 +87,92 @@ if (searchDesc) url.searchParams.set('description', searchDesc); else url.searchParams.delete('description'); - url.searchParams.set('page', '1'); - goto(url, { keepFocus: true, noScroll: true }); + history.replaceState(history.state, '', url); }, 500); } + async function loadMore() { + if (loading || !hasMore || !companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchCode) filters.code = searchCode; + if (searchDesc) filters.description = searchDesc; + + const response = await getUnitsOfMeasureAmerican( + currentPage + 1, + pageSize, + companyStore.activeCompany.id, + filters + ); + if (response.data?.items) { + allItems = [...allItems, ...response.data.items]; + currentPage += 1; + totalItems = response.data.total; + } + } catch (err) { + error = 'Error cargando mas datos'; + console.error('Error loading more American units:', err); + } finally { + loading = false; + } + } + + async function reloadData() { + if (!companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchCode) filters.code = searchCode; + if (searchDesc) filters.description = searchDesc; + + const response = await getUnitsOfMeasureAmerican( + 1, + pageSize, + companyStore.activeCompany.id, + filters + ); + if (response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (err) { + error = 'Error al recargar datos'; + console.error('Error reloading American units:', err); + } finally { + loading = false; + } + } + function handleSuccess() { - const url = new URL($page.url); - goto(url, { invalidateAll: true }); + reloadData(); } -
+

Unidades de Medida Americanas

Catálogo de unidades de medida Americanas

- +
-
-
- + {#if error} +
+ {error}
-
- -
-
+ {/if} -
- -
+ +
Listado de Unidades Americanas
+
+
+ +
Mostrando {allItems.length} de {totalItems} registros
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 671625b1..e8e8fe79 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 @@ -3,18 +3,22 @@ import { goto } from '$app/navigation'; import { browser } from '$app/environment'; import { createColumns } from '$lib/components/dashboard/general_catalogs/units_of_measure/customs/columns'; - import DataTable from '$lib/components/dashboard/general_catalogs/units_of_measure/customs/data-table.svelte'; + import InfiniteDataTable from '$lib/components/dashboard/common/infinite-data-table.svelte'; import CreateEditDialog from '$lib/components/dashboard/general_catalogs/units_of_measure/customs/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 { Plus } from 'lucide-svelte'; + import { Plus, RefreshCw } from 'lucide-svelte'; import type { PageData } from './$types'; import { useShortcuts } from '$lib/hooks/use-shortcuts'; import { obtenerAtajosListaUnidadesAduanas } from '$lib/config/shortcuts/dashboard/general_catalogs/units_of_measure/customs/list'; + import { getUnitsOfMeasureCustoms } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure'; + import { companyStore } from '$lib/stores/company.svelte'; let { data }: { data: PageData } = $props(); let createDialogOpen = $state(false); + let error = $state(data.error || null); // Atajos useShortcuts( @@ -30,10 +34,52 @@ let searchDesc = $state($page.url.searchParams.get('description') || ''); let timeout: ReturnType; - function handleSearch() { + let allItems = $state(data.customs_units?.items || []); + let currentPage = $state(data.customs_units?.page || 1); + let pageSize = $state(data.customs_units?.page_size || 50); + let totalItems = $state(data.customs_units?.total || 0); + let loading = $state(false); + let hasMore = $derived(allItems.length < totalItems); + + $effect(() => { + if (data.customs_units) { + allItems = data.customs_units.items || []; + currentPage = data.customs_units.page || 1; + totalItems = data.customs_units.total || 0; + pageSize = data.customs_units.page_size || pageSize; + } + }); + + async function handleSearch() { if (!browser) return; clearTimeout(timeout); - timeout = setTimeout(() => { + timeout = setTimeout(async () => { + if (!companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchCode) filters.code = searchCode; + if (searchDesc) filters.description = searchDesc; + + const response = await getUnitsOfMeasureCustoms( + 1, + pageSize, + companyStore.activeCompany.id, + filters + ); + if (response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (err) { + error = 'Error aplicando filtros'; + console.error('Error applying filters:', err); + } finally { + loading = false; + } + const url = new URL($page.url); if (searchCode) url.searchParams.set('code', searchCode); else url.searchParams.delete('code'); @@ -41,50 +87,92 @@ if (searchDesc) url.searchParams.set('description', searchDesc); else url.searchParams.delete('description'); - url.searchParams.set('page', '1'); - goto(url, { keepFocus: true, noScroll: true }); + history.replaceState(history.state, '', url); }, 500); } + async function loadMore() { + if (loading || !hasMore || !companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchCode) filters.code = searchCode; + if (searchDesc) filters.description = searchDesc; + + const response = await getUnitsOfMeasureCustoms( + currentPage + 1, + pageSize, + companyStore.activeCompany.id, + filters + ); + if (response.data?.items) { + allItems = [...allItems, ...response.data.items]; + currentPage += 1; + totalItems = response.data.total; + } + } catch (err) { + error = 'Error cargando mas datos'; + console.error('Error loading more customs units:', err); + } finally { + loading = false; + } + } + + async function reloadData() { + if (!companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchCode) filters.code = searchCode; + if (searchDesc) filters.description = searchDesc; + + const response = await getUnitsOfMeasureCustoms( + 1, + pageSize, + companyStore.activeCompany.id, + filters + ); + if (response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (err) { + error = 'Error al recargar datos'; + console.error('Error reloading customs units:', err); + } finally { + loading = false; + } + } + function handleSuccess() { - const url = new URL($page.url); - goto(url, { invalidateAll: true }); + reloadData(); } -
+

Unidades de Medida Aduanas MEX

Catálogo de unidades de medida para aduanas mexicanas

- +
-
-
- + {#if error} +
+ {error}
-
- -
-
+ {/if} -
- -
+ +
Listado de Unidades Aduanas MX
+
+
+ +
Mostrando {allItems.length} de {totalItems} registros
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 92a5cdd1..00f06498 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 @@ -3,18 +3,22 @@ import { goto } from '$app/navigation'; import { browser } from '$app/environment'; import { createColumns } from '$lib/components/dashboard/general_catalogs/units_of_measure/general/columns'; - import DataTable from '$lib/components/dashboard/general_catalogs/units_of_measure/general/data-table.svelte'; + import InfiniteDataTable from '$lib/components/dashboard/common/infinite-data-table.svelte'; import CreateEditDialog from '$lib/components/dashboard/general_catalogs/units_of_measure/general/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 { Plus } from 'lucide-svelte'; + import { Plus, RefreshCw } from 'lucide-svelte'; import type { PageData } from './$types'; import { useShortcuts } from '$lib/hooks/use-shortcuts'; import { obtenerAtajosListaUnidadesGeneral } from '$lib/config/shortcuts/dashboard/general_catalogs/units_of_measure/general/list'; + import { getUnitsOfMeasureGeneral } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure'; + import { companyStore } from '$lib/stores/company.svelte'; let { data }: { data: PageData } = $props(); let createDialogOpen = $state(false); + let error = $state(data.error || null); // Atajos useShortcuts( @@ -30,10 +34,52 @@ let searchDesc = $state($page.url.searchParams.get('description') || ''); let timeout: ReturnType; - function handleSearch() { + 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); + + $effect(() => { + if (data.general_units) { + allItems = data.general_units.items || []; + currentPage = data.general_units.page || 1; + totalItems = data.general_units.total || 0; + pageSize = data.general_units.page_size || pageSize; + } + }); + + async function handleSearch() { if (!browser) return; clearTimeout(timeout); - timeout = setTimeout(() => { + timeout = setTimeout(async () => { + if (!companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchCode) filters.code = searchCode; + if (searchDesc) filters.description = searchDesc; + + const response = await getUnitsOfMeasureGeneral( + 1, + pageSize, + companyStore.activeCompany.id, + filters + ); + if (response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (err) { + error = 'Error aplicando filtros'; + console.error('Error applying filters:', err); + } finally { + loading = false; + } + const url = new URL($page.url); if (searchCode) url.searchParams.set('code', searchCode); else url.searchParams.delete('code'); @@ -41,50 +87,92 @@ if (searchDesc) url.searchParams.set('description', searchDesc); else url.searchParams.delete('description'); - url.searchParams.set('page', '1'); - goto(url, { keepFocus: true, noScroll: true }); + history.replaceState(history.state, '', url); }, 500); } + async function loadMore() { + if (loading || !hasMore || !companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchCode) filters.code = searchCode; + if (searchDesc) filters.description = searchDesc; + + const response = await getUnitsOfMeasureGeneral( + currentPage + 1, + pageSize, + companyStore.activeCompany.id, + filters + ); + if (response.data?.items) { + allItems = [...allItems, ...response.data.items]; + currentPage += 1; + totalItems = response.data.total; + } + } catch (err) { + error = 'Error cargando mas datos'; + console.error('Error loading more general units:', err); + } finally { + loading = false; + } + } + + async function reloadData() { + if (!companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchCode) filters.code = searchCode; + if (searchDesc) filters.description = searchDesc; + + const response = await getUnitsOfMeasureGeneral( + 1, + pageSize, + companyStore.activeCompany.id, + filters + ); + if (response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (err) { + error = 'Error al recargar datos'; + console.error('Error reloading general units:', err); + } finally { + loading = false; + } + } + function handleSuccess() { - const url = new URL($page.url); - goto(url, { invalidateAll: true }); + reloadData(); } -
+

Unidades de Medida

Catálogo general de unidades de medida

- +
-
-
- + {#if error} +
+ {error}
-
- -
-
+ {/if} -
- -
+ +
Listado de Unidades
+
+
+ +
Mostrando {allItems.length} de {totalItems} registros
diff --git a/frontend/src/routes/dashboard/general_catalogs/units_of_measure/oma/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/units_of_measure/oma/+page.svelte index eab0d9a6..da28b5ba 100644 --- a/frontend/src/routes/dashboard/general_catalogs/units_of_measure/oma/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/units_of_measure/oma/+page.svelte @@ -3,18 +3,22 @@ import { goto } from '$app/navigation'; import { browser } from '$app/environment'; import { createColumns } from '$lib/components/dashboard/general_catalogs/units_of_measure/oma/columns'; - import DataTable from '$lib/components/dashboard/general_catalogs/units_of_measure/oma/data-table.svelte'; + import InfiniteDataTable from '$lib/components/dashboard/common/infinite-data-table.svelte'; import CreateEditDialog from '$lib/components/dashboard/general_catalogs/units_of_measure/oma/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 { Plus } from 'lucide-svelte'; + import { Plus, RefreshCw } from 'lucide-svelte'; import type { PageData } from './$types'; import { useShortcuts } from '$lib/hooks/use-shortcuts'; import { obtenerAtajosListaUnidadesOMA } from '$lib/config/shortcuts/dashboard/general_catalogs/units_of_measure/oma/list'; + import { getUnitsOfMeasureOMA } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure'; + import { companyStore } from '$lib/stores/company.svelte'; let { data }: { data: PageData } = $props(); let createDialogOpen = $state(false); + let error = $state(data.error || null); // Atajos useShortcuts( @@ -30,10 +34,52 @@ let searchDesc = $state($page.url.searchParams.get('description') || ''); let timeout: ReturnType; - function handleSearch() { + let allItems = $state(data.oma_units?.items || []); + let currentPage = $state(data.oma_units?.page || 1); + let pageSize = $state(data.oma_units?.page_size || 50); + let totalItems = $state(data.oma_units?.total || 0); + let loading = $state(false); + let hasMore = $derived(allItems.length < totalItems); + + $effect(() => { + if (data.oma_units) { + allItems = data.oma_units.items || []; + currentPage = data.oma_units.page || 1; + totalItems = data.oma_units.total || 0; + pageSize = data.oma_units.page_size || pageSize; + } + }); + + async function handleSearch() { if (!browser) return; clearTimeout(timeout); - timeout = setTimeout(() => { + timeout = setTimeout(async () => { + if (!companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchCode) filters.code = searchCode; + if (searchDesc) filters.description = searchDesc; + + const response = await getUnitsOfMeasureOMA( + 1, + pageSize, + companyStore.activeCompany.id, + filters + ); + if (response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (err) { + error = 'Error aplicando filtros'; + console.error('Error applying filters:', err); + } finally { + loading = false; + } + const url = new URL($page.url); if (searchCode) url.searchParams.set('code', searchCode); else url.searchParams.delete('code'); @@ -41,50 +87,92 @@ if (searchDesc) url.searchParams.set('description', searchDesc); else url.searchParams.delete('description'); - url.searchParams.set('page', '1'); - goto(url, { keepFocus: true, noScroll: true }); + history.replaceState(history.state, '', url); }, 500); } + async function loadMore() { + if (loading || !hasMore || !companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchCode) filters.code = searchCode; + if (searchDesc) filters.description = searchDesc; + + const response = await getUnitsOfMeasureOMA( + currentPage + 1, + pageSize, + companyStore.activeCompany.id, + filters + ); + if (response.data?.items) { + allItems = [...allItems, ...response.data.items]; + currentPage += 1; + totalItems = response.data.total; + } + } catch (err) { + error = 'Error cargando mas datos'; + console.error('Error loading more OMA units:', err); + } finally { + loading = false; + } + } + + async function reloadData() { + if (!companyStore.activeCompany) return; + loading = true; + error = null; + try { + const filters: Record = {}; + if (searchCode) filters.code = searchCode; + if (searchDesc) filters.description = searchDesc; + + const response = await getUnitsOfMeasureOMA( + 1, + pageSize, + companyStore.activeCompany.id, + filters + ); + if (response.data) { + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (err) { + error = 'Error al recargar datos'; + console.error('Error reloading OMA units:', err); + } finally { + loading = false; + } + } + function handleSuccess() { - const url = new URL($page.url); - goto(url, { invalidateAll: true }); + reloadData(); } -
+

Unidades de Medida OMA

Catálogo de unidades de medida OMA

- +
-
-
- + {#if error} +
+ {error}
-
- -
-
+ {/if} -
- -
+ +
Listado de Unidades OMA
+
+
+ +
Mostrando {allItems.length} de {totalItems} registros
diff --git a/frontend/src/routes/dashboard/general_catalogs/vehicles/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/vehicles/+page.svelte index 1bcc8916..39ee3719 100644 --- a/frontend/src/routes/dashboard/general_catalogs/vehicles/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/vehicles/+page.svelte @@ -1,12 +1,13 @@ -
+
-

Vehículos (Transporte)

+

Vehículos (Transporte)

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

- +
-
- - -
+ +
Listado de Vehículos
+ {#if loading && data.length === 0}
Cargando vehículos...
{:else}
{/if}
+
- {#if loading && data.length === 0} -
- Cargando vehículos... -
- {:else} - - {/if} +
Mostrando {data.length} de {totalItems} registros
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 3b9bf873..7f46bab4 100644 --- a/frontend/src/routes/dashboard/goods/fixed-asset-classes/+page.svelte +++ b/frontend/src/routes/dashboard/goods/fixed-asset-classes/+page.svelte @@ -233,19 +233,30 @@ ]); -
- -
-

CATALOGO DE CLASES DE ACTIVO FIJO

-

Gestiona y consulta las clases de activo fijo

+
+
+
+

Clases de Activo Fijo

+

Gestiona y consulta las clases de activo fijo

+
+
+ + +
-
+
-
+
-
+

Filtros

@@ -256,37 +267,37 @@
- +
- +
- +
-
-
+
+

Listado de Clases

Mostrando de {filteredClasses.length} registros - @@ -309,9 +320,7 @@
-
+

Código de Clase @@ -382,6 +391,8 @@

+
+
-
- -
-

CATÁLOGO DE PARTES

-

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

+
+
+
+

Catálogo de Partes

+

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

+
+
+ + +
-
+
-
+
-
+

Filtros

@@ -215,23 +226,23 @@
- +
- +
- +
@@ -267,14 +278,14 @@
-
-
+
+

Listado de Partes

Mostrando {filteredParts.length} registros - @@ -298,9 +309,7 @@
-
+

Número de Parte @@ -414,6 +423,8 @@

+
+
-
+
-

Facturas

+

Facturas

Gestiona las facturas del sistema

{#each invoiceTypeOptions() as option} @@ -914,32 +914,29 @@ {/if} - +
Listado de Facturas - - Mostrando {allItems.length} de {totalItems} registros -
-
- - (sorting = newSorting)} - /> + +
+ (sorting = newSorting)} + /> +
+
+ Mostrando {allItems.length} de {totalItems} registros + + Filtros activos: {Object.values(filters).filter((value) => value !== '').length} +
+ +
+ -
+
-

Pedimentos

+

Pedimentos

Gestiona los pedimentos del sistema

@@ -451,7 +451,7 @@ let filters = $state({ id="filter-status" bind:value={filters.status} onchange={applyFilters} - class="flex h-9 w-[220px] rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none" + class="flex h-9 w-[220px] rounded-md border border-input bg-card px-3 py-1 text-sm shadow-sm ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none" title="Estado" > {#each statusOptions as option} @@ -478,14 +478,11 @@ let filters = $state({ {/if} - +
Listado de Pedimentos - - Mostrando {allItems.length} de {totalItems} registros -
- + - (sorting = newSorting)} - /> +
+ (sorting = newSorting)} + /> +
+
+ Mostrando {allItems.length} de {totalItems} registros + + Filtros activos: {Object.values(filters).filter((value) => value !== '').length} +
+ +
+
-

+

Pedimento - Regímenes

@@ -132,33 +133,16 @@

- -
-
- Búsqueda General - -
-
- {#if error}
{error}
{/if} - -
- -
+ +
Listado de Pedimento - Regímenes
+
+
+ +
Mostrando {allItems.length} de {totalItems} registros
diff --git a/frontend/src/routes/dashboard/reference_data/containers/+page.svelte b/frontend/src/routes/dashboard/reference_data/containers/+page.svelte index 0747b0f9..f25afd54 100644 --- a/frontend/src/routes/dashboard/reference_data/containers/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/containers/+page.svelte @@ -3,6 +3,7 @@ import { containersApi, type Container } from '$lib/api/dashboard/reference_data/containers'; import DataTable from '$lib/components/dashboard/reference_data/containers/data-table.svelte'; import { createColumns } from '$lib/components/dashboard/reference_data/containers/columns.js'; + import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; import { page } from '$app/stores'; @@ -112,7 +113,7 @@
-

+

Contenedores

@@ -127,27 +128,23 @@

- -
-
- Búsqueda General - -
-
- {#if error}
{error}
{/if} - -
- -
+ + +
+ Listado de Contenedores +
+ +
+
+
+
+
+ +
Mostrando {allItems.length} de {totalItems} registros
diff --git a/frontend/src/routes/dashboard/reference_data/countries/+page.svelte b/frontend/src/routes/dashboard/reference_data/countries/+page.svelte index eb7b9999..378f0264 100644 --- a/frontend/src/routes/dashboard/reference_data/countries/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/countries/+page.svelte @@ -3,6 +3,7 @@ import { countriesApi, type Country } from '$lib/api/dashboard/reference_data/countries'; import DataTable from '$lib/components/dashboard/reference_data/countries/data-table.svelte'; import { createColumns } from '$lib/components/dashboard/reference_data/countries/columns.js'; + import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; import { RefreshCw } from 'lucide-svelte'; @@ -149,7 +150,7 @@
-

+

Países

@@ -164,33 +165,16 @@

- -
-
- Búsqueda General - -
-
- {#if error}
{error}
{/if} - -
- -
+ +
Listado de Países
+
+
+ +
Mostrando {allItems.length} de {totalItems} registros
diff --git a/frontend/src/routes/dashboard/reference_data/currency_types/+page.svelte b/frontend/src/routes/dashboard/reference_data/currency_types/+page.svelte index ee9ee8ef..50b1085f 100644 --- a/frontend/src/routes/dashboard/reference_data/currency_types/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/currency_types/+page.svelte @@ -3,6 +3,7 @@ import { currencyTypesApi, type CurrencyType } from '$lib/api/dashboard/reference_data/currency_types'; import DataTable from '$lib/components/dashboard/reference_data/currency_types/data-table.svelte'; import { createColumns } from '$lib/components/dashboard/reference_data/currency_types/columns.js'; + import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; import { page } from '$app/stores'; @@ -112,7 +113,7 @@
-

+

Tipos de Moneda

@@ -127,27 +128,16 @@

- -
-
- Búsqueda General - -
-
- {#if error}
{error}
{/if} - -
- -
+ +
Listado de Tipos de Moneda
+
+
+ +
Mostrando {allItems.length} de {totalItems} registros
diff --git a/frontend/src/routes/dashboard/reference_data/customs_sections/+page.svelte b/frontend/src/routes/dashboard/reference_data/customs_sections/+page.svelte index 16273e5e..2a6da5e7 100644 --- a/frontend/src/routes/dashboard/reference_data/customs_sections/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/customs_sections/+page.svelte @@ -3,6 +3,7 @@ import { customsSectionsApi, type CustomsSection } from '$lib/api/dashboard/reference_data/customs_sections'; import DataTable from '$lib/components/dashboard/reference_data/customs_sections/data-table.svelte'; import { createColumns } from '$lib/components/dashboard/reference_data/customs_sections/columns.js'; + import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; import { page } from '$app/stores'; @@ -112,7 +113,7 @@
-

+

Secciones Aduanales

@@ -127,27 +128,16 @@

- -
-
- Búsqueda General - -
-
- {#if error}
{error}
{/if} - -
- -
+ +
Listado de Secciones Aduanales
+
+
+ +
Mostrando {allItems.length} de {totalItems} registros
diff --git a/frontend/src/routes/dashboard/reference_data/customs_warehouses/+page.svelte b/frontend/src/routes/dashboard/reference_data/customs_warehouses/+page.svelte index da44fb5c..649546c2 100644 --- a/frontend/src/routes/dashboard/reference_data/customs_warehouses/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/customs_warehouses/+page.svelte @@ -3,6 +3,7 @@ import { customsWarehousesApi, type CustomsWarehouse } from '$lib/api/dashboard/reference_data/customs_warehouses'; import DataTable from '$lib/components/dashboard/reference_data/customs_warehouses/data-table.svelte'; import { createColumns } from '$lib/components/dashboard/reference_data/customs_warehouses/columns.js'; + import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; import { page } from '$app/stores'; @@ -112,7 +113,7 @@
-

+

Recintos Fiscalizados

@@ -127,27 +128,16 @@

- -
-
- Búsqueda General - -
-
- {#if error}
{error}
{/if} - -
- -
+ +
Listado de Recintos Fiscalizados
+
+
+ +
Mostrando {allItems.length} de {totalItems} registros
diff --git a/frontend/src/routes/dashboard/reference_data/incoterms/+page.svelte b/frontend/src/routes/dashboard/reference_data/incoterms/+page.svelte index c146aa14..5288a8a8 100644 --- a/frontend/src/routes/dashboard/reference_data/incoterms/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/incoterms/+page.svelte @@ -5,6 +5,7 @@ import { incotermsApi, type Incoterm } from '$lib/api/dashboard/reference_data/incoterms'; import { createColumns } from '$lib/components/dashboard/reference_data/incoterms/columns'; import DataTable from '$lib/components/dashboard/reference_data/incoterms/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 { RefreshCw, Plus } from 'lucide-svelte'; @@ -116,7 +117,7 @@
-

+

Incoterms

@@ -131,42 +132,24 @@

- -
-
- Clave - -
-
- Descripción - -
-
- {#if error}
{error}
{/if} - -
- -
+ + +
+ Listado de Incoterms +
+ + +
+
+
+
+
+ +
Mostrando {allItems.length} de {totalItems} registros
diff --git a/frontend/src/routes/dashboard/reference_data/invoice_types/+page.svelte b/frontend/src/routes/dashboard/reference_data/invoice_types/+page.svelte index 825081e0..9467a74a 100644 --- a/frontend/src/routes/dashboard/reference_data/invoice_types/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/invoice_types/+page.svelte @@ -3,6 +3,7 @@ import { invoiceTypesApi, type InvoiceType } from '$lib/api/dashboard/reference_data/invoice_types'; import DataTable from '$lib/components/dashboard/reference_data/invoice_types/data-table.svelte'; import { createColumns } from '$lib/components/dashboard/reference_data/invoice_types/columns.js'; + import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; import { page } from '$app/stores'; @@ -112,7 +113,7 @@
-

+

Tipos de Factura

@@ -127,27 +128,16 @@

- -
-
- Búsqueda General - -
-
- {#if error}
{error}
{/if} - -
- -
+ +
Listado de Tipos de Factura
+
+
+ +
Mostrando {allItems.length} de {totalItems} registros
diff --git a/frontend/src/routes/dashboard/reference_data/material_types/+page.svelte b/frontend/src/routes/dashboard/reference_data/material_types/+page.svelte index aecef109..b77a5332 100644 --- a/frontend/src/routes/dashboard/reference_data/material_types/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/material_types/+page.svelte @@ -3,6 +3,7 @@ import { materialTypesApi, type MaterialType } from '$lib/api/dashboard/reference_data/material_types'; import DataTable from '$lib/components/dashboard/reference_data/material_types/data-table.svelte'; import { createColumns } from '$lib/components/dashboard/reference_data/material_types/columns.js'; + import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; import { page } from '$app/stores'; @@ -112,7 +113,7 @@
-

+

Tipos de Material

@@ -127,27 +128,16 @@

- -
-
- Búsqueda General - -
-
- {#if error}
{error}
{/if} - -
- -
+ +
Listado de Tipos de Material
+
+
+ +
Mostrando {allItems.length} de {totalItems} registros
diff --git a/frontend/src/routes/dashboard/reference_data/payment_methods/+page.svelte b/frontend/src/routes/dashboard/reference_data/payment_methods/+page.svelte index ba845902..54553e47 100644 --- a/frontend/src/routes/dashboard/reference_data/payment_methods/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/payment_methods/+page.svelte @@ -3,6 +3,7 @@ import { paymentMethodsApi, type PaymentMethod } from '$lib/api/dashboard/reference_data/payment_methods'; import DataTable from '$lib/components/dashboard/reference_data/payment_methods/data-table.svelte'; import { createColumns } from '$lib/components/dashboard/reference_data/payment_methods/columns.js'; + import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; import { page } from '$app/stores'; @@ -112,7 +113,7 @@
-

+

Métodos de Pago

@@ -127,27 +128,16 @@

- -
-
- Búsqueda General - -
-
- {#if error}
{error}
{/if} - -
- -
+ +
Listado de Métodos de Pago
+
+
+ +
Mostrando {allItems.length} de {totalItems} registros
diff --git a/frontend/src/routes/dashboard/reference_data/pedimento_codes/+page.svelte b/frontend/src/routes/dashboard/reference_data/pedimento_codes/+page.svelte index a157eeca..4b763276 100644 --- a/frontend/src/routes/dashboard/reference_data/pedimento_codes/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/pedimento_codes/+page.svelte @@ -3,6 +3,7 @@ import { pedimentoCodesApi, type PedimentoCode } from '$lib/api/dashboard/reference_data/pedimento_codes'; import DataTable from '$lib/components/dashboard/reference_data/pedimento_codes/data-table.svelte'; import { createColumns } from '$lib/components/dashboard/reference_data/pedimento_codes/columns.js'; + import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; import { page } from '$app/stores'; @@ -112,7 +113,7 @@
-

+

Claves de Pedimento

@@ -127,27 +128,16 @@

- -
-
- Búsqueda General - -
-
- {#if error}
{error}
{/if} - -
- -
+ +
Listado de Claves de Pedimento
+
+
+ +
Mostrando {allItems.length} de {totalItems} registros
diff --git a/frontend/src/routes/dashboard/reference_data/pedimento_regimens/+page.svelte b/frontend/src/routes/dashboard/reference_data/pedimento_regimens/+page.svelte index 050c3433..10256756 100644 --- a/frontend/src/routes/dashboard/reference_data/pedimento_regimens/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/pedimento_regimens/+page.svelte @@ -3,6 +3,7 @@ import { pedimentoRegimensApi, type PedimentoRegimen } from '$lib/api/dashboard/reference_data/pedimento_regimens'; import DataTable from '$lib/components/dashboard/reference_data/pedimento_regimens/data-table.svelte'; import { createColumns } from '$lib/components/dashboard/reference_data/pedimento_regimens/columns.js'; + import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; import { page } from '$app/stores'; @@ -112,7 +113,7 @@
-

+

Regímenes

@@ -127,27 +128,16 @@

- -
-
- Búsqueda General - -
-
- {#if error}
{error}
{/if} - -
- -
+ +
Listado de Regímenes
+
+
+ +
Mostrando {allItems.length} de {totalItems} registros
diff --git a/frontend/src/routes/dashboard/reference_data/sectors/+page.svelte b/frontend/src/routes/dashboard/reference_data/sectors/+page.svelte index d311ff04..863da60e 100644 --- a/frontend/src/routes/dashboard/reference_data/sectors/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/sectors/+page.svelte @@ -4,6 +4,7 @@ import { companyStore } from '$lib/stores/company.svelte'; import DataTable from '$lib/components/dashboard/reference_data/sectors/data-table.svelte'; import { createColumns } from '$lib/components/dashboard/reference_data/sectors/columns.js'; + import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; import { page } from '$app/stores'; @@ -136,7 +137,7 @@
-

+

Sectores

@@ -151,27 +152,16 @@

- -
-
- Búsqueda por Clave - -
-
- {#if error}
{error}
{/if} - -
- -
+ +
Listado de Sectores
+
+
+ +
Mostrando {allItems.length} de {totalItems} registros
diff --git a/frontend/src/routes/dashboard/reference_data/states/+page.svelte b/frontend/src/routes/dashboard/reference_data/states/+page.svelte index fc63ced7..9153e117 100644 --- a/frontend/src/routes/dashboard/reference_data/states/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/states/+page.svelte @@ -3,6 +3,7 @@ import { statesApi, type State } from '$lib/api/dashboard/reference_data/states'; import DataTable from '$lib/components/dashboard/reference_data/states/data-table.svelte'; import { createColumns } from '$lib/components/dashboard/reference_data/states/columns.js'; + import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; import type { PageData } from './$types'; @@ -146,7 +147,7 @@
-

+

Estados

@@ -161,33 +162,16 @@

- -
-
- Búsqueda General - -
-
- {#if error}
{error}
{/if} - -
- -
+ +
Listado de Estados
+
+
+ +
Mostrando {allItems.length} de {totalItems} registros
diff --git a/frontend/src/routes/dashboard/reference_data/transport_modes/+page.svelte b/frontend/src/routes/dashboard/reference_data/transport_modes/+page.svelte index 580a159c..15541683 100644 --- a/frontend/src/routes/dashboard/reference_data/transport_modes/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/transport_modes/+page.svelte @@ -3,6 +3,7 @@ import { transportModesApi, type TransportMode } from '$lib/api/dashboard/reference_data/transport_modes'; import DataTable from '$lib/components/dashboard/reference_data/transport_modes/data-table.svelte'; import { createColumns } from '$lib/components/dashboard/reference_data/transport_modes/columns.js'; + import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; import { page } from '$app/stores'; @@ -112,7 +113,7 @@
-

+

Modos de Transporte

@@ -127,27 +128,16 @@

- -
-
- Búsqueda General - -
-
- {#if error}
{error}
{/if} - -
- -
+ +
Listado de Modos de Transporte
+
+
+ +
Mostrando {allItems.length} de {totalItems} registros
diff --git a/frontend/src/routes/dashboard/reference_data/transport_types/+page.svelte b/frontend/src/routes/dashboard/reference_data/transport_types/+page.svelte index 1ad8d604..dd9fcc29 100644 --- a/frontend/src/routes/dashboard/reference_data/transport_types/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/transport_types/+page.svelte @@ -3,6 +3,7 @@ import { transportTypesApi, type TransportType } from '$lib/api/dashboard/reference_data/transport_types'; import DataTable from '$lib/components/dashboard/reference_data/transport_types/data-table.svelte'; import { createColumns } from '$lib/components/dashboard/reference_data/transport_types/columns.js'; + import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; import { page } from '$app/stores'; @@ -112,7 +113,7 @@
-

+

Tipos de Transporte

@@ -127,27 +128,16 @@

- -
-
- Búsqueda General - -
-
- {#if error}
{error}
{/if} - -
- -
+ +
Listado de Tipos de Transporte
+
+
+ +
Mostrando {allItems.length} de {totalItems} registros
diff --git a/frontend/src/routes/dashboard/reference_data/valuation_methods/+page.svelte b/frontend/src/routes/dashboard/reference_data/valuation_methods/+page.svelte index f00f9ca4..03389645 100644 --- a/frontend/src/routes/dashboard/reference_data/valuation_methods/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/valuation_methods/+page.svelte @@ -3,6 +3,7 @@ import { valuationMethodsApi, type ValuationMethod } from '$lib/api/dashboard/reference_data/valuation_methods'; import DataTable from '$lib/components/dashboard/reference_data/valuation_methods/data-table.svelte'; import { createColumns } from '$lib/components/dashboard/reference_data/valuation_methods/columns.js'; + import * as Card from '$lib/components/ui/card'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; import { page } from '$app/stores'; @@ -112,7 +113,7 @@
-

+

Métodos de Valoración

@@ -127,27 +128,16 @@

- -
-
- Búsqueda General - -
-
- {#if error}
{error}
{/if} - -
- -
+ +
Listado de Métodos de Valoración
+
+
+ +
Mostrando {allItems.length} de {totalItems} registros
From 233da55f403b7c93cda619d655ba9ecde9b8b9fa Mon Sep 17 00:00:00 2001 From: hreyes Date: Mon, 13 Apr 2026 14:12:25 -0600 Subject: [PATCH 11/13] feature/COVE-api-email-selector --- .../api/v1/modules/a76/factura_cove/routes.py | 10 +- .../v1/modules/a76/factura_cove/schemas.py | 1 + .../v1/modules/a76/factura_cove/service.py | 23 +- .../api/v1/modules/a76/factura_cove/tasks.py | 13 +- .../tests/unit/factura_cove/test_service.py | 29 ++ .../src/lib/api/dashboard/a76/invoices.ts | 12 +- .../routes/dashboard/invoices/+page.svelte | 326 +++++++++++++++++- 7 files changed, 389 insertions(+), 25 deletions(-) create mode 100644 backend/tests/unit/factura_cove/test_service.py diff --git a/backend/api/v1/modules/a76/factura_cove/routes.py b/backend/api/v1/modules/a76/factura_cove/routes.py index 9c20bb3a..9a311de6 100644 --- a/backend/api/v1/modules/a76/factura_cove/routes.py +++ b/backend/api/v1/modules/a76/factura_cove/routes.py @@ -59,12 +59,16 @@ def trigger_cove_for_invoice( task=factura_cove_generate, tenant_id=tenant_id, company_id=body.company_id, - # Hardcodeado a petición: siempre registrar esta tarea con el correo de Hugo Reyes. - requested_by_user="hreyes@aduanasoft.com.mx", + requested_by_user=( + current_user.get("email") + or current_user.get("preferred_username") + or current_user.get("username") + or "system" + ), task_name="factura_cove_generate", task_group="factura_cove", task_origin="a76/factura_cove/invoices/cove", - args=[invoice_id, int(tenant_id), body.company_id], + args=[invoice_id, int(tenant_id), body.company_id, body.recipient_email], ) return FacturaCoveResponse( diff --git a/backend/api/v1/modules/a76/factura_cove/schemas.py b/backend/api/v1/modules/a76/factura_cove/schemas.py index 0afcc2a2..d9365630 100644 --- a/backend/api/v1/modules/a76/factura_cove/schemas.py +++ b/backend/api/v1/modules/a76/factura_cove/schemas.py @@ -105,6 +105,7 @@ class GenerateCoveFromInvoiceRequest(BaseModel): company_id: int force_regen: Optional[bool] = False + recipient_email: Optional[EmailStr] = None class GenerateCoveResult(BaseModel): diff --git a/backend/api/v1/modules/a76/factura_cove/service.py b/backend/api/v1/modules/a76/factura_cove/service.py index 7f51c469..91decc9f 100644 --- a/backend/api/v1/modules/a76/factura_cove/service.py +++ b/backend/api/v1/modules/a76/factura_cove/service.py @@ -107,6 +107,17 @@ class FacturaCoveDomainService: """ vu = ctx.vu + if not vu: + errors.add_error( + field="vu", + message="La factura no tiene configuración VU asociada en el agente aduanal", + solution=[ + "Configura los datos VU del agente aduanal y sube certificado (.cer) y llave (.key) antes de generar COVE." + ], + code="MISSING_VU_CONFIGURATION", + ) + return None + # Determinar usuario efectivo de WebService: # - Preferimos el usuario configurado en VU (web_service_user) # - Si no existe, usamos el de DODA-PITA (doda_web_service_user) @@ -567,7 +578,11 @@ class FacturaCoveDomainService: return mercancias def build_factura_cove_request( - self, invoice_id: int, tenant_id: int, company_id: int + self, + invoice_id: int, + tenant_id: int, + company_id: int, + recipient_email: str | None = None, ) -> FacturaCoveRequest: """ Construye el FacturaCoveRequest completo a partir de una factura, @@ -646,6 +661,8 @@ class FacturaCoveDomainService: # Fallback: recortar a máximo 10 caracteres para cumplir el esquema tipo_figura = raw_figura[:10] + correo_destino = (recipient_email or (ctx.vu.vu_email if ctx.vu else None) or "").strip() or None + return FacturaCoveRequest( configuracion_vu=configuracion_vu, # El RFC de consulta NO debe ser igual al RFC del que registra el comprobante. @@ -660,8 +677,7 @@ class FacturaCoveDomainService: patente_aduanal=patente_aduanal, fecha_expedicion=fecha_expedicion, observaciones=ctx.invoice.vu_observations or None, - # Correo hardcodeado temporalmente para pruebas de COVE - correo_electronico="hreyes@aduanasoft.com.mx", + correo_electronico=correo_destino, tiene_subdivision=bool(ctx.invoice.logistics and ctx.invoice.logistics.is_subdivision), certificado_origen=False, numero_exportador_autorizado=None, @@ -675,7 +691,6 @@ class FacturaCoveDomainService: Versión "ligera" para frontend: evalúa si la factura puede generar COVE e informa por qué no, sin disparar la tarea Celery. """ - db = self.db or CoreSessionLocal() errors = ErrorCollector() try: diff --git a/backend/api/v1/modules/a76/factura_cove/tasks.py b/backend/api/v1/modules/a76/factura_cove/tasks.py index c0f2b4c5..76a6a09c 100644 --- a/backend/api/v1/modules/a76/factura_cove/tasks.py +++ b/backend/api/v1/modules/a76/factura_cove/tasks.py @@ -152,7 +152,13 @@ def _poll_external_status( @celery_app.task(bind=True, name="factura_cove_generate") -def factura_cove_generate(self: Task, invoice_id: int, tenant_id: int, company_id: int) -> dict: +def factura_cove_generate( + self: Task, + invoice_id: int, + tenant_id: int, + company_id: int, + recipient_email: str | None = None, +) -> dict: """ Tarea Celery para preparar (y en el futuro generar) un COVE a partir de una factura. @@ -173,7 +179,10 @@ def factura_cove_generate(self: Task, invoice_id: int, tenant_id: int, company_i # Esta llamada valida todo y construye el payload; si algo falla, lanza ValidationException request_payload = service.build_factura_cove_request( - invoice_id=invoice_id, tenant_id=tenant_id, company_id=company_id + invoice_id=invoice_id, + tenant_id=tenant_id, + company_id=company_id, + recipient_email=recipient_email, ) _progress(self, 80, "Enviando solicitud al servicio COVE...") diff --git a/backend/tests/unit/factura_cove/test_service.py b/backend/tests/unit/factura_cove/test_service.py new file mode 100644 index 00000000..0751308a --- /dev/null +++ b/backend/tests/unit/factura_cove/test_service.py @@ -0,0 +1,29 @@ +from types import SimpleNamespace + +from api.v1.modules.a76.factura_cove.service import FacturaCoveDomainService, InvoiceContext +from core.exceptions import ErrorCollector + + +def test_build_configuracion_vu_returns_validation_error_when_vu_is_missing() -> None: + service = FacturaCoveDomainService(db=SimpleNamespace()) + ctx = InvoiceContext( + invoice=SimpleNamespace(), + broker=None, + vu=None, + ) + errors = ErrorCollector() + + configuracion = service._build_configuracion_vu(ctx, errors) + + assert configuracion is None + assert errors.has_errors() + assert errors.get_errors() == [ + { + "field": "vu", + "message": "La factura no tiene configuración VU asociada en el agente aduanal", + "solution": [ + "Configura los datos VU del agente aduanal y sube certificado (.cer) y llave (.key) antes de generar COVE." + ], + "code": "MISSING_VU_CONFIGURATION", + } + ] \ No newline at end of file diff --git a/frontend/src/lib/api/dashboard/a76/invoices.ts b/frontend/src/lib/api/dashboard/a76/invoices.ts index 4b876838..e8828c7d 100644 --- a/frontend/src/lib/api/dashboard/a76/invoices.ts +++ b/frontend/src/lib/api/dashboard/a76/invoices.ts @@ -545,15 +545,19 @@ export const invoicesApi = { }>(`/v1/a76/invoices/revert/${taskId}/status`); }, - generateCove: (invoiceId: number, companyId: number) => { + generateCove: (invoiceId: number, companyId: number, recipientEmail?: string | null) => { const params = new URLSearchParams({ company_id: companyId.toString() }); + const body: Record = { + company_id: companyId + }; + if (recipientEmail) { + body.recipient_email = recipientEmail; + } return api.post<{ task_id: string }>( `/v1/a76/factura-cove/invoices/${invoiceId}/cove?${params.toString()}`, - { - company_id: companyId - } + body ); }, diff --git a/frontend/src/routes/dashboard/invoices/+page.svelte b/frontend/src/routes/dashboard/invoices/+page.svelte index db8b883e..c50517f8 100644 --- a/frontend/src/routes/dashboard/invoices/+page.svelte +++ b/frontend/src/routes/dashboard/invoices/+page.svelte @@ -15,12 +15,17 @@ import { createColumns } from '$lib/components/dashboard/invoices/columns.js'; import * as Card from '$lib/components/ui/card'; import * as AlertDialog from '$lib/components/ui/alert-dialog'; + import * as Dialog from '$lib/components/ui/dialog'; + import * as Select from '$lib/components/ui/select'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; import { Label } from '$lib/components/ui/label'; import type { PageData } from './$types'; import { browser } from '$app/environment'; + import { currentUser } from '$lib/auth'; import { companyStore } from '$lib/stores/company.svelte'; + import { getCompany } from '$lib/api/dashboard/a76/general_catalogs/company'; + import { usersAPI } from '$lib/api/dashboard/users'; import { Plus, RefreshCw, @@ -41,7 +46,8 @@ BadgeCent, ArrowRightLeft, Database, - ChevronUp + ChevronUp, + Mail } from 'lucide-svelte'; import DetailsDialog from '$lib/components/dashboard/invoices/details-dialog.svelte'; import DeleteDialog from '$lib/components/dashboard/invoices/delete-dialog.svelte'; @@ -57,6 +63,13 @@ // Los datos iniciales vienen del servidor let { data }: { data: PageData } = $props(); + type CoveRecipientOption = { + email: string; + label: string; + description: string; + source: 'company' | 'user'; + }; + // Estado para filtros // Nota: Los query parameters invoice_type y operation_type se pueden usar para filtrar // Ejemplo: /dashboard/invoices?invoice_type=TEM&operation_type=imp @@ -425,10 +438,189 @@ // Estado para el diálogo de progreso let showProgressDialog = $state(false); let isWinsaiiConfirmOpen = $state(false); + let isCoveDialogOpen = $state(false); let currentTaskId = $state(null); let currentStatusFunction = $state<((taskId: string) => Promise) | null>(null); let progressDialogTitle = $state('Generando documento'); let progressDialogSteps = $state<{ label: string; percent: number }[] | null>(null); + let coveRecipientsLoading = $state(false); + let coveRecipientsError = $state(null); + let coveRecipientEmail = $state(''); + let coveRecipientOptions = $state([]); + let coveRecipientsRequestId = 0; + + const currentUserEmail = $derived(($currentUser?.email || '').trim()); + const selectedCoveRecipient = $derived( + coveRecipientOptions.find( + (option) => option.email.trim().toLowerCase() === coveRecipientEmail.trim().toLowerCase() + ) ?? null + ); + + function normalizeEmail(email?: string | null) { + return (email || '').trim().toLowerCase(); + } + + function createRecipientLabel(prefix: string, email: string) { + return `${prefix} - ${email}`; + } + + async function loadCoveRecipients() { + const companyId = companyStore.activeCompany?.id; + if (!companyId) { + coveRecipientsError = null; + coveRecipientOptions = []; + coveRecipientEmail = ''; + return; + } + + const requestId = ++coveRecipientsRequestId; + coveRecipientsLoading = true; + coveRecipientsError = null; + + try { + const [companyResult, usersResult] = await Promise.allSettled([ + getCompany(companyId), + usersAPI.list(companyId, { page_size: 100 }) + ]); + + if (requestId !== coveRecipientsRequestId) return; + + const recipientMap = new Map(); + + const addRecipient = ( + email?: string | null, + label?: string, + description?: string, + source: 'company' | 'user' = 'user' + ) => { + const normalizedEmail = normalizeEmail(email); + if (!normalizedEmail) return; + if (recipientMap.has(normalizedEmail)) return; + recipientMap.set(normalizedEmail, { + email: email!.trim(), + label: label || email!.trim(), + description: description || email!.trim(), + source + }); + }; + + if (companyResult.status === 'fulfilled' && companyResult.value.data) { + const company = companyResult.value.data as Record; + addRecipient( + company.vu_email, + 'Correo VU de la empresa', + company.name ? `Empresa ${company.name}` : 'Correo de ventanilla única', + 'company' + ); + addRecipient( + company.main_email, + 'Correo principal de la empresa', + company.name ? `Empresa ${company.name}` : 'Correo principal', + 'company' + ); + addRecipient( + company.ind1_email, + 'Correo industrial 1', + company.name ? `Empresa ${company.name}` : 'Correo industrial 1', + 'company' + ); + addRecipient( + company.ind2_email, + 'Correo industrial 2', + company.name ? `Empresa ${company.name}` : 'Correo industrial 2', + 'company' + ); + } + + if (usersResult.status === 'fulfilled' && usersResult.value.users) { + for (const user of usersResult.value.users) { + const fullName = `${user.first_name || ''} ${user.last_name || ''}`.trim(); + addRecipient( + user.email, + fullName || user.username || user.email, + createRecipientLabel('Usuario de la empresa', user.email), + 'user' + ); + } + } + + if (currentUserEmail) { + addRecipient( + currentUserEmail, + 'Mi correo', + createRecipientLabel('Usuario autenticado', currentUserEmail), + 'user' + ); + } + + const recipients = [...recipientMap.values()].sort((left, right) => { + if (left.source !== right.source) { + return left.source === 'company' ? -1 : 1; + } + return left.label.localeCompare(right.label, 'es'); + }); + + const sourceFailures = [companyResult, usersResult].filter( + (result) => result.status === 'rejected' + ).length; + + coveRecipientOptions = recipients; + + const preferredEmail = normalizeEmail(currentUserEmail); + const existingSelection = + recipients.find((option) => normalizeEmail(option.email) === normalizeEmail(coveRecipientEmail)) || + null; + const preferredSelection = + recipients.find((option) => normalizeEmail(option.email) === preferredEmail) || recipients[0] || null; + + if (existingSelection) { + coveRecipientEmail = existingSelection.email; + } else if (preferredSelection) { + coveRecipientEmail = preferredSelection.email; + } else { + coveRecipientEmail = ''; + } + + if (recipientMap.size === 0 && sourceFailures > 0) { + coveRecipientsError = 'No se pudieron cargar los correos disponibles para COVE'; + } else if (recipientMap.size === 0) { + coveRecipientsError = 'No hay correos configurados para COVE'; + } + } catch (error) { + if (requestId !== coveRecipientsRequestId) return; + console.error('Error cargando correos de COVE:', error); + coveRecipientsError = 'No se pudieron cargar los correos disponibles para COVE'; + } finally { + if (requestId === coveRecipientsRequestId) { + coveRecipientsLoading = false; + } + } + } + + $effect(() => { + const companyId = companyStore.activeCompany?.id; + const userEmail = currentUserEmail; + + if (!companyId) { + coveRecipientsError = null; + coveRecipientOptions = []; + coveRecipientEmail = ''; + return; + } + + void userEmail; + void loadCoveRecipients(); + }); + + function openCoveDialog() { + if (!selectedInvoice || !companyStore.activeCompany) { + toast.info('Selecciona una factura para generar COVE'); + return; + } + + void loadCoveRecipients(); + isCoveDialogOpen = true; + } // Utilidad para convertir Base64 a Blob function base64ToBlob(base64: string, type: string) { @@ -808,12 +1000,18 @@ } } - async function handleGenerateCove() { + async function handleGenerateCove(recipientEmail = coveRecipientEmail) { if (!selectedInvoice || !companyStore.activeCompany) { toast.info('Selecciona una factura para generar COVE'); return; } + const selectedRecipientEmail = normalizeEmail(recipientEmail); + if (!selectedRecipientEmail) { + toast.error('Selecciona un correo para enviar el COVE'); + return; + } + const companyId = companyStore.activeCompany.id; // Paso 1: Checar elegibilidad antes de disparar la tarea @@ -840,13 +1038,19 @@ // Paso 2: Disparar tarea Celery de COVE try { - const response = await invoicesApi.generateCove(selectedInvoice.id, companyId); + const response = await invoicesApi.generateCove( + selectedInvoice.id, + companyId, + selectedRecipientEmail + ); if (response.error) { toast.error(`Error al iniciar generación de COVE: ${response.error}`); return; } + isCoveDialogOpen = false; + currentTaskId = response.data!.task_id; currentStatusFunction = invoicesApi.getCoveStatus; progressDialogTitle = 'Validando datos para COVE'; @@ -1065,6 +1269,101 @@
+ + + + Generar COVE + + Selecciona el correo destinatario para la factura + {selectedInvoice?.invoice_number}. + + + + + +
+ + + + {#snippet children()} +
+
+ +
+
+
+ Destino COVE +
+
+ {selectedCoveRecipient?.label || 'Selecciona un correo'} +
+
+ {selectedCoveRecipient?.email || 'Se enviará al correo del usuario que generó la factura'} +
+
+
+ {/snippet} +
+ + {#if coveRecipientsLoading} +
+ Cargando correos disponibles... +
+ {:else if coveRecipientOptions.length === 0} +
+ No hay correos disponibles para COVE. +
+ {:else} + {#each coveRecipientOptions as recipient} + + {#snippet children({ selected })} +
+ + {recipient.label} + {#if selected} + Seleccionado + {/if} + + + {recipient.description} + +
+ {/snippet} +
+ {/each} + {/if} +
+
+ {#if coveRecipientsError} +

{coveRecipientsError}

+ {/if} +
+
+
+ + + + + +
+
+
(showVuSubmenu = false)} - on:contextmenu|preventDefault={() => (showVuSubmenu = false)} - /> + onclick={() => (showVuSubmenu = false)} + oncontextmenu={(event) => { + event.preventDefault(); + showVuSubmenu = false; + }} + >
{ + onclick={() => { showVuSubmenu = false; toast.info('Consulta VU - Próximamente'); }} @@ -1154,7 +1456,7 @@