diff --git a/backend/api/v1/modules/a76/customs_brokers/dto.py b/backend/api/v1/modules/a76/customs_brokers/dto.py index fa11b268..7f65b264 100644 --- a/backend/api/v1/modules/a76/customs_brokers/dto.py +++ b/backend/api/v1/modules/a76/customs_brokers/dto.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import BaseModel +from pydantic import BaseModel, Field class CustomsBrokerBaseDTO(BaseModel): @@ -19,7 +19,7 @@ class CustomsBrokerBaseDTO(BaseModel): tax_id: Optional[str] = None personal_id: Optional[str] = None position: Optional[str] = None - license: Optional[str] = None + license: Optional[str] = Field(None, max_length=4, pattern=r"^\d*$") company: Optional[str] = None contact: Optional[str] = None @@ -27,7 +27,7 @@ class CustomsBrokerBaseDTO(BaseModel): class CustomsBrokerCreateDTO(CustomsBrokerBaseDTO): """Schema for creating a new CustomsBroker""" - broker_key: str + broker_key: str = Field(..., max_length=5, pattern=r"^[a-zA-Z0-9]+$", description="Clave única del agente aduanal (máx 5 caracteres)") class CustomsBrokerUpdateDTO(CustomsBrokerBaseDTO): @@ -51,7 +51,7 @@ class CustomsBrokerResponseDTO(CustomsBrokerBaseDTO): # Legacy DTO for backwards compatibility (if needed elsewhere) class CustomsBrokerDTO(BaseModel): type: Optional[str] = None - broker_key: str + broker_key: str = Field(..., max_length=5, pattern=r"^[a-zA-Z0-9]+$") name: Optional[str] = None address: Optional[str] = None postal_code: Optional[str] = None @@ -64,7 +64,7 @@ class CustomsBrokerDTO(BaseModel): tax_id: Optional[str] = None personal_id: Optional[str] = None position: Optional[str] = None - license: Optional[str] = None + license: Optional[str] = Field(None, max_length=4, pattern=r"^\d*$") company: Optional[str] = None contact: Optional[str] = None tenant_id: str @@ -100,13 +100,13 @@ class CustomsBrokerVUCreateDTO(BaseModel): class CustomsBrokerPersonnelDTO(BaseModel): - broker_key: str + broker_key: str = Field(..., max_length=5, pattern=r"^[a-zA-Z0-9]+$") line: int name: Optional[str] tax_id: Optional[str] personal_id: Optional[str] position: Optional[str] - license: Optional[str] + license: Optional[str] = Field(None, max_length=4, pattern=r"^\d*$") first_name: Optional[str] last_name: Optional[str] middle_name: Optional[str] diff --git a/frontend/src/lib/api/dashboard/a76/customs-brokers.ts b/frontend/src/lib/api/dashboard/a76/customs-brokers.ts index 4fe32672..981ef884 100644 --- a/frontend/src/lib/api/dashboard/a76/customs-brokers.ts +++ b/frontend/src/lib/api/dashboard/a76/customs-brokers.ts @@ -117,7 +117,7 @@ export const customsBrokersApi = { * Elimina un agente aduanal */ delete: (brokerKey: string, companyId: string) => { - return api.delete(`/v1/a76/customs-brokers/${brokerKey}/?company_id=${companyId}`); + return api.delete(`/v1/a76/customs-brokers/${brokerKey}?company_id=${companyId}`); }, diff --git a/frontend/src/lib/api/dashboard/a76/general_catalogs/ports.ts b/frontend/src/lib/api/dashboard/a76/general_catalogs/ports.ts index d397e245..fe2caaba 100644 --- a/frontend/src/lib/api/dashboard/a76/general_catalogs/ports.ts +++ b/frontend/src/lib/api/dashboard/a76/general_catalogs/ports.ts @@ -57,7 +57,7 @@ class PortsApi { const queryParams = new URLSearchParams({ company_id: companyId.toString() }); - return api.get(`${this.baseUrl}/${id}/?${queryParams.toString()}`); + return api.get(`${this.baseUrl}/${id}?${queryParams.toString()}`); } async create(data: PortCreate, companyId: string | number): Promise> { @@ -78,7 +78,7 @@ class PortsApi { const queryParams = new URLSearchParams({ company_id: companyId.toString() }); - return api.delete(`${this.baseUrl}/${id}/?${queryParams.toString()}`); + return api.delete(`${this.baseUrl}/${id}?${queryParams.toString()}`); } } diff --git a/frontend/src/lib/components/dashboard/customs_brokers/create-dialog.svelte b/frontend/src/lib/components/dashboard/customs_brokers/create-dialog.svelte index 0b4393ff..44bca734 100644 --- a/frontend/src/lib/components/dashboard/customs_brokers/create-dialog.svelte +++ b/frontend/src/lib/components/dashboard/customs_brokers/create-dialog.svelte @@ -1,221 +1,310 @@ - - - - {mode === "create" ? "Nuevo Agente Aduanal" : "Editar Agente Aduanal"} - - - Ingresa los datos generales del agente. La configuración de VU y Personal se gestiona aparte. - - + + + + {mode === 'create' ? 'Nuevo Agente Aduanal' : 'Editar Agente Aduanal'} + + + Ingresa los datos generales del agente. La configuración de VU y Personal se gestiona + aparte. + + -
- -
-

Identificación

-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
- - -
-
-
+
+
+

Identificación

+
+
+ + { + const val = e.currentTarget.value.toUpperCase(); + if (val.length > 5) { + brokerKeyError = true; + formData.broker_key = val.slice(0, 5); + e.currentTarget.value = formData.broker_key; - + clearTimeout(brokerKeyTimeout); + brokerKeyTimeout = setTimeout(() => { + brokerKeyError = false; + }, 3000); + } else { + brokerKeyError = false; + formData.broker_key = val; + } + }} + placeholder="Ej. 550" + maxlength="6" + class={brokerKeyError ? 'border-red-500 focus-visible:ring-red-500' : ''} + disabled={mode === 'edit' || loading} + /> + {#if brokerKeyError} +

+ La clave no debe superar los 5 caracteres +

+ {/if} +
+
+ + { + const val = e.currentTarget.value.replace(/\D/g, ''); + if (val.length > 4) { + licenseError = true; + formData.license = val.slice(0, 4); + e.currentTarget.value = formData.license; -
-

Contacto

-
-
- - -
-
- - -
-
-
+ clearTimeout(licenseTimeout); + licenseTimeout = setTimeout(() => { + licenseError = false; + }, 3000); + } else { + licenseError = false; + formData.license = val; + } + }} + placeholder="Ej. 3421" + maxlength="5" + class={licenseError ? 'border-red-500 focus-visible:ring-red-500' : ''} + disabled={loading} + /> + {#if licenseError} +

+ La patente no debe superar los 4 dígitos +

+ {/if} +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
- + -
-

Dirección Fiscal

- -
- - -
+
+

Contacto

+
+
+ + +
+
+ + +
+
+
-
-
- - -
-
- - -
-
- - -
-
-
-
- - -
-
-
+ -
+
+

Dirección Fiscal

- - - - - - \ No newline at end of file +
+ + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+
+
+ + + + + +
+
diff --git a/frontend/src/lib/components/dashboard/customs_brokers/data-table.svelte b/frontend/src/lib/components/dashboard/customs_brokers/data-table.svelte index ca2d16aa..abbbbaac 100644 --- a/frontend/src/lib/components/dashboard/customs_brokers/data-table.svelte +++ b/frontend/src/lib/components/dashboard/customs_brokers/data-table.svelte @@ -1,18 +1,17 @@ - { open = v; if (!v) onClose(); }}> + { + open = v; + if (!v) onClose(); + }} +> Detalles de Factura #{invoice?.id} - - Información completa de la factura - + Información completa de la factura {#if invoice} @@ -74,7 +79,15 @@

Tipo de Factura

-

{invoice.invoice_type || '-'}

+

+ {#if invoice.invoice_type} + {invoice.invoice_type} + {:else} + - + {/if} +

@@ -102,10 +115,11 @@

{invoice.traffic_light_status || '-'}

-
-

CFDI UUID

-

{invoice.cfdi_uuid || '-'}

-
+
+

CFDI UUID

+

{invoice.cfdi_uuid || '-'}

+
+

Actualizado

{invoice.is_updated ? 'Sí' : 'No'}

@@ -196,11 +210,11 @@

{invoice.compliance_mx.edocument || '-'}

-
-

Firma Electrónica

-

{invoice.compliance_mx.electronic_signature || '-'}

+
+

Firma Electrónica

+

{invoice.compliance_mx.electronic_signature || '-'}

+
- {:else}

No hay información de cumplimiento disponible.

{/if} @@ -362,7 +376,9 @@
-

Descripción de Colores

+

+ Descripción de Colores +

{detail.colors_description || '-'}

@@ -399,7 +415,9 @@
-

Fecha de Cobranza

+

+ Fecha de Cobranza +

{formatDate(collection.collection_date)}

@@ -427,9 +445,7 @@ {/if} - +
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/country-dialog.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/country-dialog.svelte index ddb6b92d..80cdbf0b 100644 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/country-dialog.svelte +++ b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/country-dialog.svelte @@ -83,7 +83,7 @@ - + CATALOGO DE PAISES @@ -91,11 +91,7 @@
- +
@@ -113,9 +109,7 @@ - + @@ -134,11 +128,11 @@ class="border-b hover:bg-zinc-100 dark:hover:bg-zinc-800 cursor-pointer transition-colors" onclick={() => handleSelect(country)} > - - - - - + + + + + {/each} {#if filteredCountries.length === 0} @@ -152,7 +146,9 @@
Clave M3Clave M3 Clave Mexicana{country.m3_key || ''}{country.mex_key || ''}{country.description_es || ''}{country.ame_key || ''}{country.description_en || ''}{country.m3_key || ''}{country.mex_key || ''}{country.description_es || ''}{country.ame_key || ''}{country.description_en || ''}
-
+
+ {/each} diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tariff-fraction-dialog.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tariff-fraction-dialog.svelte index 02721e57..cdf26bb0 100644 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tariff-fraction-dialog.svelte +++ b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tariff-fraction-dialog.svelte @@ -17,7 +17,7 @@ let loadingMore = $state(false); let searchTerm = $state(''); let error = $state(''); - + // Pagination state let currentPage = $state(1); let totalPages = $state(1); @@ -34,7 +34,7 @@ loadingMore = true; } error = ''; - + try { const params = new URLSearchParams({ page: page.toString(), @@ -45,11 +45,11 @@ const response = await fetch(`/api-sveltekit/tariff-fractions?${params}`, { credentials: 'include' }); - + if (response.ok) { const data = await response.json(); console.log('Tariff fractions data received:', data); - + if (data.items && Array.isArray(data.items)) { if (append) { fractions = [...fractions, ...data.items]; @@ -79,10 +79,10 @@ function handleScroll(e: Event) { if (!scrollContainer || loading || loadingMore || !hasMore) return; - + const target = e.target as HTMLDivElement; const scrollBottom = target.scrollHeight - target.scrollTop - target.clientHeight; - + // Load more when within 200px of bottom if (scrollBottom < 200) { loadFractions(currentPage + 1, true); @@ -120,7 +120,7 @@ - + FRACCIONES ARANCELARIAS @@ -139,11 +139,7 @@

-
+
{#if loading}
@@ -157,18 +153,12 @@ - - + + - + @@ -204,9 +194,7 @@ {/if} {#if !hasMore && fractions.length > 0} -
- Todos los resultados cargados -
+
Todos los resultados cargados
{/if} {/if} diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/unit-of-measure-dialog.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/unit-of-measure-dialog.svelte index 55dab050..fa12288a 100644 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/unit-of-measure-dialog.svelte +++ b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/unit-of-measure-dialog.svelte @@ -84,7 +84,7 @@ - + CATALOGOS DE UNIDADES DE MEDIDA @@ -92,11 +92,7 @@
- +
@@ -114,9 +110,7 @@
CódigoFracciónCódigoFracción DescripciónNICONICO UMT
- + @@ -157,7 +151,9 @@
U.M.U.M. Descripción Español
-
+
+ + diff --git a/frontend/src/routes/dashboard/customs_brokers/edit/[[id]]/+page.svelte b/frontend/src/routes/dashboard/customs_brokers/edit/[[id]]/+page.svelte index 4db6ad1c..264e7d8a 100644 --- a/frontend/src/routes/dashboard/customs_brokers/edit/[[id]]/+page.svelte +++ b/frontend/src/routes/dashboard/customs_brokers/edit/[[id]]/+page.svelte @@ -62,6 +62,11 @@ company_id: '' }); + let brokerKeyError = $state(false); + let licenseError = $state(false); + let brokerKeyTimeout: ReturnType; + let licenseTimeout: ReturnType; + // --- 3. CARGA DE DATOS REACTIVA --- $effect(() => { const company = companyStore.activeCompany; @@ -126,6 +131,18 @@ return; } + if (formData.broker_key.length > 5) { + error = 'La Clave del Agente no debe superar los 5 caracteres'; + toast.error(error); + return; + } + + if (formData.license && formData.license.length > 4) { + error = 'La Patente no debe superar los 4 dígitos'; + toast.error(error); + return; + } + loading = true; error = null; try { @@ -213,19 +230,66 @@ >Clave Agente * { + const val = e.currentTarget.value.toUpperCase(); + if (val.length > 5) { + brokerKeyError = true; + formData.broker_key = val.slice(0, 5); + e.currentTarget.value = formData.broker_key; + + clearTimeout(brokerKeyTimeout); + brokerKeyTimeout = setTimeout(() => { + brokerKeyError = false; + }, 3000); + } else { + brokerKeyError = false; + formData.broker_key = val; + } + }} placeholder="Ej. 550" + maxlength="6" + class={brokerKeyError ? 'border-red-500 focus-visible:ring-red-500' : ''} disabled={isEdit || loading} /> -

- Clave interna o número de patente único. -

+ {#if brokerKeyError} +

+ La clave no debe superar los 5 caracteres +

+ {/if}
- + { + const val = e.currentTarget.value.replace(/\D/g, ''); + if (val.length > 4) { + licenseError = true; + formData.license = val.slice(0, 4); + e.currentTarget.value = formData.license; + + clearTimeout(licenseTimeout); + licenseTimeout = setTimeout(() => { + licenseError = false; + }, 3000); + } else { + licenseError = false; + formData.license = val; + } + }} + placeholder="Ej. 3421" + maxlength="5" + class={licenseError ? 'border-red-500 focus-visible:ring-red-500' : ''} + disabled={loading} + /> + {#if licenseError} +

+ La patente no debe superar los 4 dígitos +

+ {/if}
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 04a35476..36f524bf 100644 --- a/frontend/src/routes/dashboard/goods/fixed-asset-classes/+page.svelte +++ b/frontend/src/routes/dashboard/goods/fixed-asset-classes/+page.svelte @@ -660,7 +660,7 @@ - + {selectedClass ? 'Editar' : 'Nueva'} Clase de Activo Fijo