diff --git a/backend/api/v1/modules/a76/classes/dto.py b/backend/api/v1/modules/a76/classes/dto.py index 68054f2a..b8047e2f 100644 --- a/backend/api/v1/modules/a76/classes/dto.py +++ b/backend/api/v1/modules/a76/classes/dto.py @@ -221,6 +221,10 @@ class ClassWithFADataResponse(BaseModel): # FA-specific fields (embedded from a24.fa_classes) fa_class_id: Optional[int] = None + import_tariff_code: Optional[str] = None + import_tariff_type: Optional[str] = None + export_tariff_code: Optional[str] = None + export_tariff_type: Optional[str] = None depreciation_rate: Optional[Decimal] = None fda_code: Optional[str] = None eccn_code: Optional[str] = None diff --git a/backend/api/v1/modules/a76/classes/service.py b/backend/api/v1/modules/a76/classes/service.py index 4decee1c..904adddf 100644 --- a/backend/api/v1/modules/a76/classes/service.py +++ b/backend/api/v1/modules/a76/classes/service.py @@ -150,6 +150,10 @@ class ClassService: "updated_at": base_class.updated_at, # FA extension fields (None if no FA record exists) "fa_class_id": fa_class.id if fa_class else None, + "import_tariff_code": fa_class.import_tariff_code if fa_class else None, + "import_tariff_type": fa_class.import_tariff_type if fa_class else None, + "export_tariff_code": fa_class.export_tariff_code if fa_class else None, + "export_tariff_type": fa_class.export_tariff_type if fa_class else None, "depreciation_rate": fa_class.depreciation_rate if fa_class else None, "fda_code": fa_class.fda_code if fa_class else None, "eccn_code": fa_class.eccn_code if fa_class else None, diff --git a/backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/service.py b/backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/service.py index 42e8b956..1409b2f9 100644 --- a/backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/service.py +++ b/backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/service.py @@ -23,7 +23,60 @@ class USTariffFractionService: limit: int = 100, filters: Optional[Dict[str, Any]] = None, ) -> Tuple[List[USTariffFraction], int]: - """Obtiene todas las fracciones locales con filtros opcionales.""" + """ + Obtiene todas las fracciones arancelarias americanas con filtros opcionales. + Estrategia: Sitar API -> Fallback Local DB + """ + + # 1. Try Sitar API + try: + sitar_service = FraccionesUSAService.get_instance() + + sitar_fraccion = None + has_filters = False + + if filters and filters.get("search"): + term = filters["search"] + # Sitar only filters by fraction code + if term.replace(".", "").isdigit(): + sitar_fraccion = term + has_filters = True + + sitar_items = await sitar_service.search( + fraccion=sitar_fraccion, + skip=skip, + limit=limit + ) + + # If Sitar returns empty list, attempt fallback to local DB + if not sitar_items: + logger.info(f"Sitar returned no results for USA query (filters={has_filters}). Attempting fallback to local DB.") + return USTariffFractionService._get_all_local(db, tenant_id, company_id, skip, limit, filters) + + # Map items + items = [USTariffFractionMapper.to_domain(item, tenant_id, company_id) for item in sitar_items] + + # Estimate total + total = len(items) + skip + if len(items) == limit: + total += 1 + + return items, total + + except Exception as e: + logger.error(f"Error fetching USA Fractions from Sitar API, falling back to local DB: {e}") + return USTariffFractionService._get_all_local(db, tenant_id, company_id, skip, limit, filters) + + @staticmethod + def _get_all_local( + db: Session, + tenant_id: int, + company_id: int, + skip: int = 0, + limit: int = 100, + filters: Optional[Dict[str, Any]] = None, + ) -> Tuple[List[USTariffFraction], int]: + """Lógica original de consulta local""" query = db.query(USTariffFraction).filter( USTariffFraction.tenant_id == tenant_id, USTariffFraction.company_id == company_id, diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index aed649d2..baa8f75c 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -270,7 +270,7 @@ async function fetchApi( } return { - error: data.message || data.detail || 'Error en la petición', + error: data.message || (typeof data.detail === 'string' ? data.detail : JSON.stringify(data.detail)) || 'Error en la petición', status: response.status }; } diff --git a/frontend/src/lib/components/dashboard/goods/classes/forms/FixedAssetClassForm.svelte b/frontend/src/lib/components/dashboard/goods/classes/forms/FixedAssetClassForm.svelte index fb406f32..d5578a0c 100644 --- a/frontend/src/lib/components/dashboard/goods/classes/forms/FixedAssetClassForm.svelte +++ b/frontend/src/lib/components/dashboard/goods/classes/forms/FixedAssetClassForm.svelte @@ -541,7 +541,7 @@ class="uppercase {validationErrors.class_code ? 'border-red-500 focus-visible:ring-red-500' : ''}" - maxlength={20} + maxlength={8} oninput={() => { if (validationErrors.class_code) { const errors = { ...validationErrors }; 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 5a0af337..35ccccde 100644 --- a/frontend/src/routes/dashboard/goods/fixed-asset-classes/+page.svelte +++ b/frontend/src/routes/dashboard/goods/fixed-asset-classes/+page.svelte @@ -14,6 +14,10 @@ // Tipo extendido que combina A76Class y FAClass interface FixedAssetClassExtended extends A76Class { fa_class_id?: number; + import_tariff_code?: string | null; + import_tariff_type?: string | null; + export_tariff_code?: string | null; + export_tariff_type?: string | null; depreciation_rate?: number | null; fda_code?: string | null; eccn_code?: string | null; @@ -578,6 +582,10 @@ cleanData.depreciation_rate != null ? Number(cleanData.depreciation_rate) : null, + import_tariff_code: cleanData.import_tariff_code || null, + import_tariff_type: cleanData.import_tariff_type || null, + export_tariff_code: cleanData.export_tariff_code || null, + export_tariff_type: cleanData.export_tariff_type || null, fda_code: (cleanData.fda_key ?? cleanData.fda_code) || null, eccn_code: cleanData.eccn_code || null },