- {% if totales.bultos_total != 0 %}{{ totales.bultos_total }}{% endif %}
+ {% if totales.bultos_total != 0 %}{{ totales.bultos_total }}{% endif %}
{{ totales.clave_bultos or '' }}
@@ -355,17 +611,20 @@
{{ cliente_proveedor.nombre }}
-
Los valores expresados en esta factura son en: {{ factura.moneda }}
+
Los valores expresados en esta factura
+ son en: {{ factura.moneda }}
-
+
-
+
Normal Por Parte
-
Declaro bajo protesta de decir verdad que la información contenida en este documento es verdadera y me hago responsable de comprobar lo aquí declarado.
+
Declaro bajo protesta de decir verdad que la información contenida en
+ este documento es verdadera y me hago responsable de comprobar lo aquí declarado.
-
-
-
+
+
+
+
\ No newline at end of file
diff --git a/backend/api/v1/modules/a76/reports/importacion/facturas/templates/factura_tem_hor.html b/backend/api/v1/modules/a76/reports/importacion/facturas/templates/factura_tem_hor.html
deleted file mode 100644
index e69de29b..00000000
diff --git a/backend/api/v1/modules/public/reference_data/containers/models.py b/backend/api/v1/modules/public/reference_data/containers/models.py
index b8d3c630..c6081c34 100644
--- a/backend/api/v1/modules/public/reference_data/containers/models.py
+++ b/backend/api/v1/modules/public/reference_data/containers/models.py
@@ -7,11 +7,11 @@ class Container(Base):
__tablename__ = "containers" # GContenedores
__table_args__ = (
PrimaryKeyConstraint("key", name="containers_pkey"),
- {"schema": "public", "extend_existing": True}, # opcional
+ {"extend_existing": True}, # opcional
)
key: Mapped[str] = mapped_column(
- String(3), nullable=False
+ String(3), primary_key=True, nullable=False
) # mantiene ceros iniciales
description: Mapped[str] = mapped_column(
String(500), nullable=False
diff --git a/backend/api/v1/modules/public/reference_data/material_types/models.py b/backend/api/v1/modules/public/reference_data/material_types/models.py
index 04fa9478..5862649a 100644
--- a/backend/api/v1/modules/public/reference_data/material_types/models.py
+++ b/backend/api/v1/modules/public/reference_data/material_types/models.py
@@ -11,7 +11,7 @@ class MaterialType(Base):
)
key: Mapped[str] = mapped_column(
- String(10), nullable=False) # clave del material
+ String(10), primary_key=True, nullable=False) # clave del material
type: Mapped[str] = mapped_column(String(15), nullable=False) # tipo
description: Mapped[str] = mapped_column(
String(256), nullable=False
diff --git a/backend/app_data/logos/1/AS.png b/backend/app_data/logos/1/AS.png
new file mode 100644
index 00000000..0ac0a679
Binary files /dev/null and b/backend/app_data/logos/1/AS.png differ
diff --git a/backend/app_data/logos/1/Agenda.png b/backend/app_data/logos/1/Agenda.png
new file mode 100644
index 00000000..e7b87dfd
Binary files /dev/null and b/backend/app_data/logos/1/Agenda.png differ
diff --git a/backend/app_data/logos/1/a519dfd146c1bd477bcd841afe02d7de.jpg b/backend/app_data/logos/1/a519dfd146c1bd477bcd841afe02d7de.jpg
new file mode 100644
index 00000000..5723bd91
Binary files /dev/null and b/backend/app_data/logos/1/a519dfd146c1bd477bcd841afe02d7de.jpg differ
diff --git a/backend/app_data/logos/1/footer.png b/backend/app_data/logos/1/footer.png
new file mode 100644
index 00000000..bd1967bd
Binary files /dev/null and b/backend/app_data/logos/1/footer.png differ
diff --git a/backend/app_data/logos/1/logo2.jpg b/backend/app_data/logos/1/logo2.jpg
new file mode 100644
index 00000000..e676a700
Binary files /dev/null and b/backend/app_data/logos/1/logo2.jpg differ
diff --git a/backend/app_data/logos/company_1_logo.png b/backend/app_data/logos/company_1_logo.png
new file mode 100644
index 00000000..0ac0a679
Binary files /dev/null and b/backend/app_data/logos/company_1_logo.png differ
diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts
index 5d863da1..6f625e19 100644
--- a/frontend/src/lib/api.ts
+++ b/frontend/src/lib/api.ts
@@ -45,7 +45,7 @@ async function refreshToken(): Promise {
if (!browser) return null;
let refreshTokenValue = localStorage.getItem('refresh_token');
-
+
// Si no está en localStorage, intentar obtenerlo de las cookies
if (!refreshTokenValue) {
const getCookie = (name: string): string | null => {
@@ -54,18 +54,18 @@ async function refreshToken(): Promise {
if (parts.length === 2) return parts.pop()?.split(';').shift() || null;
return null;
};
-
+
refreshTokenValue = getCookie('refresh_token');
- if (refreshTokenValue) {
+ if (refreshTokenValue) {
localStorage.setItem('refresh_token', refreshTokenValue);
}
}
-
+
if (!refreshTokenValue) {
console.error('❌ [API] No hay refresh token disponible');
return null;
- }
-
+ }
+
try {
const response = await fetch(`${API_BASE_URL}/v1/auth/refresh`, {
method: 'POST',
@@ -93,25 +93,25 @@ async function refreshToken(): Promise {
return null;
}
- const data = await response.json();
+ const data = await response.json();
// Guardar los nuevos tokens
if (data.access_token) {
localStorage.setItem('access_token', data.access_token);
-
+
if (data.refresh_token) {
localStorage.setItem('refresh_token', data.refresh_token);
}
-
+
// Actualizar también las cookies
const isSecure = window.location.protocol === 'https:';
const secureFlag = isSecure ? '; Secure' : '';
-
+
document.cookie = `access_token=${data.access_token}; path=/; max-age=${60 * 60 * 24 * 7}; SameSite=Lax${secureFlag}`;
if (data.refresh_token) {
document.cookie = `refresh_token=${data.refresh_token}; path=/; max-age=${60 * 60 * 24 * 30}; SameSite=Lax${secureFlag}`;
}
-
+
// Actualizar el authStore si está disponible
try {
const { authStore } = await import('./auth');
@@ -120,7 +120,7 @@ async function refreshToken(): Promise {
// Si no se puede importar authStore, no es crítico
console.warn('⚠️ [API] No se pudo actualizar authStore:', e);
}
-
+
return data.access_token;
}
@@ -140,7 +140,7 @@ async function fetchApi(
retryCount = 0
): Promise> {
// Si ya estamos refrescando el token, esperar
- if (isRefreshing && retryCount === 0) {
+ if (isRefreshing && retryCount === 0) {
return new Promise((resolve) => {
subscribeTokenRefresh((newToken) => {
resolve(fetchApi(endpoint, options, 1));
@@ -149,16 +149,20 @@ async function fetchApi(
}
const token = getToken();
-
+
if (!token && !endpoint.includes('/auth/login')) {
console.warn('⚠️ [API] No hay token disponible para', endpoint);
}
const headers: Record = {
- 'Content-Type': 'application/json',
...((options.headers as Record) || {})
};
+ // Only set Content-Type to application/json if not already set and body is not FormData
+ if (!headers['Content-Type'] && !(options.body instanceof FormData)) {
+ headers['Content-Type'] = 'application/json';
+ }
+
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
@@ -171,7 +175,7 @@ async function fetchApi(
});
// Si recibimos 401 o 403 y no es el endpoint de refresh, intentar refrescar el token
- if ((response.status === 401 || response.status === 403) && !endpoint.includes('/auth/refresh') && retryCount === 0) {
+ if ((response.status === 401 || response.status === 403) && !endpoint.includes('/auth/refresh') && retryCount === 0) {
isRefreshing = true;
try {
@@ -226,7 +230,7 @@ async function fetchApi(
// Errores de validación de FastAPI (con detail)
else if (data.detail) {
let errorMessage = 'Error de validación: ';
-
+
// FastAPI devuelve errores de validación en data.detail como array
if (Array.isArray(data.detail)) {
const errors = data.detail.map((err: any) => {
@@ -239,14 +243,14 @@ async function fetchApi(
} else {
errorMessage += JSON.stringify(data.detail);
}
-
+
return {
error: errorMessage,
status: response.status
};
}
}
-
+
return {
error: data.message || data.detail || 'Error en la petición',
status: response.status
@@ -281,7 +285,7 @@ export const api = {
method: 'PUT',
body: JSON.stringify(body)
}),
-
+
patch: (endpoint: string, body: any) =>
fetchApi(endpoint, {
method: 'PATCH',
@@ -314,5 +318,8 @@ export const api = {
myLicense: () => api.get('/v1/licenses/my-license'),
usage: (tenantId: number) => api.get(`/v1/licenses/usage/${tenantId}`),
validate: (tenantId: number) => api.get(`/v1/licenses/validate/${tenantId}`)
- }
+ },
+
+ // Generic request for custom needs (like file uploads)
+ request: (endpoint: string, options: RequestInit = {}) => fetchApi(endpoint, options)
};
diff --git a/frontend/src/lib/api/dashboard/a76/customs-brokers.ts b/frontend/src/lib/api/dashboard/a76/customs-brokers.ts
index f90c3404..76b7e33e 100644
--- a/frontend/src/lib/api/dashboard/a76/customs-brokers.ts
+++ b/frontend/src/lib/api/dashboard/a76/customs-brokers.ts
@@ -87,7 +87,7 @@ export interface CreateCustomsBrokerData {
*/
export const customsBrokersApi = {
list: (companyId: string) => {
- return api.get(`/v1/a76/customs-brokers/?company_id=${companyId}`);
+ return api.get(`/v1/a76/customs-brokers?company_id=${companyId}`);
},
get: (brokerKey: string, companyId: string) => {
@@ -95,7 +95,7 @@ export const customsBrokersApi = {
},
create: (data: CreateCustomsBrokerData, companyId: string) => {
- return api.post(`/v1/a76/customs-brokers/?company_id=${companyId}`, data);
+ return api.post(`/v1/a76/customs-brokers?company_id=${companyId}`, data);
},
update: (brokerKey: string, data: CreateCustomsBrokerData, companyId: string) => {
diff --git a/frontend/src/lib/api/dashboard/a76/general_catalogs/company.ts b/frontend/src/lib/api/dashboard/a76/general_catalogs/company.ts
index d31e6aba..82add8d4 100644
--- a/frontend/src/lib/api/dashboard/a76/general_catalogs/company.ts
+++ b/frontend/src/lib/api/dashboard/a76/general_catalogs/company.ts
@@ -25,6 +25,13 @@ export interface Company {
order_format_type?: string | null;
ctpat_svi?: string | null;
trusted_exporter_number?: string | null;
+ logo?: string | null;
+ previous_code?: number | null;
+ client_name?: string | null;
+ subassembly_mode?: string | null;
+ inter_db_name?: string | null;
+ prevalidator_key?: string | null;
+ seventh_amendment?: boolean;
created_at: string | null;
updated_at: string | null;
}
@@ -113,3 +120,13 @@ export async function updateCompany(id: number, data: CompanyUpdate): Promise> {
return await api.delete(`/v1/a76/company/${id}`);
}
+
+export async function uploadCompanyLogo(id: number, file: File): Promise> {
+ const formData = new FormData();
+ formData.append('file', file);
+ // Use api.request to pass FormData directly without JSON.stringify
+ return await api.request(`/v1/a76/company/${id}/upload-logo`, {
+ method: 'POST',
+ body: formData
+ });
+}
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 a624e361..fb415397 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
@@ -1,5 +1,11 @@
import { api } from '$lib/api';
-import type { PaginatedResponse } from '$lib/types';
+
+export interface PaginatedResponse {
+ page: number;
+ page_size: number;
+ total: number;
+ total_pages: number;
+}
export interface MultiCurrencyType {
id: number;
@@ -29,11 +35,13 @@ export interface MultiCurrencyTypeListResponse extends PaginatedResponse {
items: MultiCurrencyType[];
}
+import type { ApiResponse } from '$lib/api';
+
export async function getMultiCurrencyTypes(
companyId: number,
page?: number,
pageSize?: number
-): Promise {
+): Promise> {
const params = new URLSearchParams({ company_id: companyId.toString() });
if (page) params.append('page', page.toString());
if (pageSize) params.append('page_size', pageSize.toString());
@@ -44,7 +52,7 @@ export async function getMultiCurrencyTypes(
export async function getMultiCurrencyType(
multiCurrencyTypeId: number,
companyId: number
-): Promise {
+): Promise> {
const params = new URLSearchParams({ company_id: companyId.toString() });
return api.get(`/v1/a76/multi-currency-types/${multiCurrencyTypeId}?${params.toString()}`);
}
@@ -52,7 +60,7 @@ export async function getMultiCurrencyType(
export async function createMultiCurrencyType(
data: MultiCurrencyTypeCreate,
companyId: number
-): Promise {
+): Promise> {
const params = new URLSearchParams({ company_id: companyId.toString() });
return api.post(`/v1/a76/multi-currency-types/?${params.toString()}`, data);
}
@@ -61,7 +69,7 @@ export async function updateMultiCurrencyType(
multiCurrencyTypeId: number,
data: MultiCurrencyTypeUpdate,
companyId: number
-): Promise {
+): Promise> {
const params = new URLSearchParams({ company_id: companyId.toString() });
return api.put(
`/v1/a76/multi-currency-types/${multiCurrencyTypeId}?${params.toString()}`,
@@ -72,7 +80,7 @@ export async function updateMultiCurrencyType(
export async function deleteMultiCurrencyType(
multiCurrencyTypeId: number,
companyId: number
-): Promise {
+): Promise> {
const params = new URLSearchParams({ company_id: companyId.toString() });
return api.delete(`/v1/a76/multi-currency-types/${multiCurrencyTypeId}?${params.toString()}`);
}
\ No newline at end of file
diff --git a/frontend/src/lib/api/dashboard/a76/parts.ts b/frontend/src/lib/api/dashboard/a76/parts.ts
index 88538db8..492c93ea 100644
--- a/frontend/src/lib/api/dashboard/a76/parts.ts
+++ b/frontend/src/lib/api/dashboard/a76/parts.ts
@@ -55,24 +55,24 @@ export interface Part {
tenant_id: number;
company_id: number;
client_id: number;
-
+
// Identificación
part_number: string;
commercial_part_number: string | null;
-
+
// Descripciones y Clase
description_spanish: string | null;
description_english: string | null;
part_class: string | null;
- unit_of_measure: string | null;
-
+ unit_of_measure: string | null;
+
// Costos y Pesos
unit_cost: number | null;
currency_key: string | null;
currency_type: string | null;
unit_weight: number | null;
weight_type: string | null;
-
+
// Regulatorio
fraction: string | null;
us_fraction: string | null;
@@ -82,14 +82,14 @@ export interface Part {
eccn: string | null;
export_code: string | null;
exclusion_symbol: string | null;
-
+
// Estado y Media
is_active: boolean;
part_photo: string | null;
created_at: string;
updated_at: string;
-
+
fa_data?: FaData | null;
inv_data?: InvData | null;
}
@@ -99,7 +99,7 @@ export interface PartCreate extends Omit {}
+export interface PartUpdate extends Partial { }
export interface PartListResponse {
@@ -112,20 +112,20 @@ export interface PartListResponse {
export const partsApi = {
- list: (params: {
- company_id: number;
- page?: number;
- page_size?: number;
- q?: string
+ list: (params: {
+ company_id: number;
+ page?: number;
+ page_size?: number;
+ q?: string
}) => {
const { company_id, page = 1, page_size = 50, q = '' } = params;
const skip = (page - 1) * page_size;
-
+
const query = new URLSearchParams({
company_id: company_id.toString(),
skip: skip.toString(),
limit: page_size.toString(),
- description: q
+ description: q
});
return api.get(`/v1/a76/parts/?${query.toString()}`);
@@ -140,11 +140,11 @@ export const partsApi = {
},
update: (id: number, data: PartUpdate, company_id: number) => {
- return api.put(`/v1/a76/parts/${id}?company_id=${company_id}`, data);
+ return api.put(`/v1/a76/parts/${id}/?company_id=${company_id}`, data);
},
delete: (id: number, company_id: number) => {
- return api.delete(`/v1/a76/parts/${id}?company_id=${company_id}`);
+ return api.delete(`/v1/a76/parts/${id}/?company_id=${company_id}`);
}
};
\ No newline at end of file
diff --git a/frontend/src/lib/components/dashboard/goods/modales/client-selector-dialog.svelte b/frontend/src/lib/components/dashboard/goods/modales/client-selector-dialog.svelte
index ed216be2..c6a45fce 100644
--- a/frontend/src/lib/components/dashboard/goods/modales/client-selector-dialog.svelte
+++ b/frontend/src/lib/components/dashboard/goods/modales/client-selector-dialog.svelte
@@ -24,9 +24,10 @@
// Filtro reactivo local
let filteredClients = $derived(
clients.filter(c =>
- c.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ (c.client_or_provider === 'client' || c.client_or_provider === 'both') &&
+ (c.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
c.rfc.toLowerCase().includes(searchTerm.toLowerCase()) ||
- c.id.toString().includes(searchTerm)
+ c.id.toString().includes(searchTerm))
)
);
@@ -42,10 +43,8 @@
loading = true;
try {
- // Petición a la API
- const res = await clientsProvidersApi.list(companyStore.activeCompany.id, 1, 100, {
- type: 'client'
- });
+ // Petición a la API - Traer todos para filtrar localmente
+ const res = await clientsProvidersApi.list(companyStore.activeCompany.id, 1, 1000);
// Normalización de respuesta
const responseData = (res as any).data || res;
diff --git a/frontend/src/lib/components/dashboard/goods/modales/country-selector-dialog.svelte b/frontend/src/lib/components/dashboard/goods/modales/country-selector-dialog.svelte
index 27cea07f..27943e6b 100644
--- a/frontend/src/lib/components/dashboard/goods/modales/country-selector-dialog.svelte
+++ b/frontend/src/lib/components/dashboard/goods/modales/country-selector-dialog.svelte
@@ -5,6 +5,7 @@
import * as Table from "$lib/components/ui/table";
import { Search, Loader2, Globe } from "lucide-svelte";
import { countriesApi, type Country } from "$lib/api/dashboard/refrence_data/countries";
+ import { toast } from "svelte-sonner";
// --- PROPS ---
let {
@@ -33,6 +34,7 @@
// Cargar datos al abrir
$effect(() => {
+ console.log("CountrySelectorDialog: open changed", open);
if (open) {
loadCountries();
}
@@ -40,10 +42,17 @@
async function loadCountries() {
loading = true;
- console.log("Cargando países...");
+ console.log("CountrySelectorDialog: loading countries...");
try {
- const response = await countriesApi.list(1, 300);
+ // FIX: Reducir tamaño de página para evitar timeouts y manejo de errores
+ const response = await countriesApi.list(1, 100);
console.log("Respuesta países FULL:", response);
+
+ if (response.error) {
+ console.error("Error API:", response.error);
+ toast.error(`Error al cargar países: ${response.error}`);
+ return;
+ }
// Caso 1: Estructura esperada { data: { items: [...] } }
if (response.data?.items && Array.isArray(response.data.items)) {
@@ -69,16 +78,19 @@
loaded = true;
} else {
console.warn("Estructura de datos no reconocida en countriesApi.list:", response.data);
+ toast.error("Formato de datos de países no reconocido");
}
}
else {
console.warn("No se encontraron países o formato incorrecto:", response);
+ toast.error("No se encontraron países");
}
console.log(`Países cargados: ${items.length}`);
- } catch (e) {
- console.error("Error cargando países:", e);
+ } catch (e: any) {
+ console.error("Error cargando países (excepción):", e);
+ toast.error(`Excepción al cargar países: ${e.message || e}`);
} finally {
loading = false;
}
diff --git a/frontend/src/lib/components/dashboard/goods/modales/currency-selector-dialog.svelte b/frontend/src/lib/components/dashboard/goods/modales/currency-selector-dialog.svelte
index 2325fe34..4add0b4a 100644
--- a/frontend/src/lib/components/dashboard/goods/modales/currency-selector-dialog.svelte
+++ b/frontend/src/lib/components/dashboard/goods/modales/currency-selector-dialog.svelte
@@ -3,9 +3,9 @@
import { Input } from "$lib/components/ui/input";
import * as Dialog from "$lib/components/ui/dialog";
import * as Table from "$lib/components/ui/table";
+ import { toast } from "svelte-sonner";
import { Search, Loader2, DollarSign } from "lucide-svelte";
- import { getMultiCurrencyTypes, type MultiCurrencyType } from "$lib/api/dashboard/a76/general_catalogs/multi-currency-types";
- import { companyStore } from "$lib/stores/company.svelte";
+ import { currencyTypesApi, type CurrencyType } from "$lib/api/dashboard/refrence_data/currency_types";
// --- PROPS ---
let {
@@ -13,11 +13,11 @@
onSelect
}: {
open: boolean,
- onSelect: (item: MultiCurrencyType) => void
+ onSelect: (item: CurrencyType) => void
} = $props();
// --- ESTADO ---
- let items = $state([]);
+ let items = $state([]);
let loading = $state(false);
let searchTerm = $state("");
let loaded = $state(false);
@@ -25,39 +25,50 @@
// Filtro local
let filteredItems = $derived(
items.filter(i =>
- (i.currency_type_code || "").toLowerCase().includes(searchTerm.toLowerCase()) ||
- (i.country_key || "").toLowerCase().includes(searchTerm.toLowerCase())
+ (i.code || "").toLowerCase().includes(searchTerm.toLowerCase()) ||
+ (i.currency_name || "").toLowerCase().includes(searchTerm.toLowerCase()) ||
+ (i.country_description || "").toLowerCase().includes(searchTerm.toLowerCase())
)
);
// Cargar datos al abrir
$effect(() => {
- if (open && !loaded && companyStore.activeCompany?.id) {
+ if (open && !loaded) {
loadCurrencies();
}
});
async function loadCurrencies() {
- if (!companyStore.activeCompany?.id) return;
-
loading = true;
+ console.log("CurrencySelectorDialog: loading currencies (public)...");
try {
- const response = await getMultiCurrencyTypes(companyStore.activeCompany.id, 1, 100);
+ // FIX: Usar API pública, sin company_id
+ const response = await currencyTypesApi.list(1, 100);
+ console.log("Respuesta Monedas Public FULL:", response);
- if (response?.items) {
- items = response.items;
- loaded = true;
- } else {
- console.warn("No se encontraron monedas:", response);
+ if (response.error) {
+ console.error("CurrencySelectorDialog Error:", response.error);
+ toast.error(`Error al cargar monedas: ${response.error}`);
+ return;
}
- } catch (e) {
+
+ if (response.data?.items) {
+ items = response.data.items;
+ loaded = true;
+ console.log("CurrencySelectorDialog: loaded items", items.length);
+ } else {
+ console.warn("No se encontraron monedas (public):", response);
+ toast.error("No se encontraron monedas");
+ }
+ } catch (e: any) {
console.error("Error cargando monedas:", e);
+ toast.error(`Excepción al cargar monedas: ${e.message || e}`);
} finally {
loading = false;
}
}
- function handleSelect(item: MultiCurrencyType) {
+ function handleSelect(item: CurrencyType) {
if (onSelect) onSelect(item);
open = false;
}
@@ -68,7 +79,7 @@
Seleccionar Moneda
- Seleccione el tipo de moneda del catálogo.
+ Seleccione el tipo de moneda del catálogo público.
@@ -76,7 +87,7 @@
@@ -96,9 +107,9 @@
- Código
- País
- Factor Conversión
+ Código
+ Moneda
+ País
@@ -111,15 +122,15 @@
- {item.currency_type_code}
+ {item.code}
- {item.country_key || '-'}
+ {item.currency_name}
-
- {item.conversion_factor?.toFixed(4) || '-'}
+
+ {item.country_description || '-'}
{/each}
diff --git a/frontend/src/lib/components/dashboard/goods/modales/fraction-selector-dialog.svelte b/frontend/src/lib/components/dashboard/goods/modales/fraction-selector-dialog.svelte
index 56db8e62..ea9819ed 100644
--- a/frontend/src/lib/components/dashboard/goods/modales/fraction-selector-dialog.svelte
+++ b/frontend/src/lib/components/dashboard/goods/modales/fraction-selector-dialog.svelte
@@ -3,8 +3,9 @@
import { Input } from "$lib/components/ui/input";
import * as Dialog from "$lib/components/ui/dialog";
import * as Table from "$lib/components/ui/table";
+ import { toast } from "svelte-sonner";
import { Search, Loader2, Hash } from "lucide-svelte";
- import { classesApi, type A76Class } from "$lib/api/dashboard/a76/classes";
+ import { getTariffFractions, type TariffFraction } from "$lib/api/dashboard/a76/general_catalogs/tariff-fractions";
import { companyStore } from "$lib/stores/company.svelte";
// --- PROPS ---
@@ -13,32 +14,23 @@
onSelect
}: {
open: boolean,
- onSelect: (item: { fraction: string; description: string; class_code: string }) => void
+ onSelect: (item: TariffFraction) => void
} = $props();
// --- ESTADO ---
- let classes = $state([]);
+ let items = $state([]);
let loading = $state(false);
let searchTerm = $state("");
let loaded = $state(false);
- // Extraer fracciones únicas
- let uniqueFractions = $derived(
- Array.from(new Set(classes.map(c => c.fraction)))
- .filter(f => f && f.trim())
- .map(fraction => {
- const cls = classes.find(c => c.fraction === fraction);
- return {
- fraction,
- description: cls?.description_es || '',
- class_code: cls?.class_code || ''
- };
- })
- .filter(item =>
- item.fraction.toLowerCase().includes(searchTerm.toLowerCase()) ||
- item.description.toLowerCase().includes(searchTerm.toLowerCase()) ||
- item.class_code.toLowerCase().includes(searchTerm.toLowerCase())
- )
+ // Filtro local
+ let filteredItems = $derived(
+ items.filter(i =>
+ (i.fraction || "").includes(searchTerm) ||
+ (i.description || "").toLowerCase().includes(searchTerm.toLowerCase()) ||
+ (i.nico || "").includes(searchTerm) ||
+ (i.code || "").toLowerCase().includes(searchTerm.toLowerCase())
+ )
);
// Cargar datos al abrir
@@ -49,41 +41,48 @@
});
async function loadFractions() {
- if (!companyStore.activeCompany?.id) return;
+ if (!companyStore.activeCompany?.id) {
+ toast.error("No hay empresa seleccionada");
+ return;
+ }
loading = true;
try {
- const response = await classesApi.list({
- company_id: companyStore.activeCompany.id,
- page: 1,
- page_size: 1000
- });
+ const response = await getTariffFractions(1, 1000, companyStore.activeCompany.id);
+ if (response.error) {
+ console.error("Error al cargar fracciones:", response.error);
+ toast.error(`Error: ${response.error}`);
+ return;
+ }
+
if (response.data?.items) {
- classes = response.data.items;
+ items = response.data.items;
loaded = true;
} else {
- console.warn("No se encontraron clases:", response);
+ console.warn("No se encontraron fracciones:", response);
+ toast.info("No se encontraron fracciones registradas");
}
- } catch (e) {
- console.error("Error cargando fracciones:", e);
+ } catch (e: any) {
+ console.error("Excepción cargando fracciones:", e);
+ toast.error(`Error de conexión: ${e.message || e}`);
} finally {
loading = false;
}
}
- function handleSelect(item: { fraction: string; description: string; class_code: string }) {
+ function handleSelect(item: TariffFraction) {
if (onSelect) onSelect(item);
open = false;
}
-
+ Seleccionar Fracción Arancelaria
- Seleccione la fracción arancelaria del catálogo de clases.
+ Seleccione la fracción arancelaria del catálogo.
@@ -91,7 +90,7 @@
@@ -103,7 +102,7 @@
Cargando catálogo...
- {:else if uniqueFractions.length === 0}
+ {:else if filteredItems.length === 0}