Se agrego un modal provicional para el fomrulario y creacion de los registros en la base de datos. Aun faltan 3 catalogos para la integracion con la base de datos

This commit is contained in:
2025-12-19 17:25:12 -06:00
parent 827c5319bb
commit 153fca9ad7
71 changed files with 3316 additions and 1121 deletions

View File

@@ -2,60 +2,71 @@ import { api } from '$lib/api';
import type { ApiResponse } from '$lib/api';
export interface ClassificationConcept {
id: number;
classification: string;
description?: string;
tenant_id: string;
company_id?: string;
created_at: string;
updated_at?: string;
id: number;
classification: string;
tenant_id: number;
company_id: number;
created_at: string;
updated_at?: string;
}
export interface ClassificationConceptCreate {
classification: string;
description?: string;
classification: string;
}
export interface ClassificationConceptUpdate extends Partial<ClassificationConceptCreate> {}
export interface ClassificationConceptListResponse {
items: ClassificationConcept[];
total: number;
page: number;
page_size: number;
pages: number;
items: ClassificationConcept[];
total: number;
page: number;
page_size: number;
pages: number;
}
export async function getClassificationConcepts(
page: number = 1,
pageSize: number = 50,
filters: Record<string, any> = {}
page: number = 1,
pageSize: number = 50,
companyId: number,
filters: Record<string, any> = {}
): Promise<ApiResponse<ClassificationConceptListResponse>> {
const params = new URLSearchParams({
page: page.toString(),
page_size: pageSize.toString(),
...filters
});
const params = new URLSearchParams({
page: page.toString(),
page_size: pageSize.toString(),
company_id: companyId.toString(),
...filters
});
const response = await api.get(`/a76/classification_concepts?${params.toString()}`);
return response.data;
const response = await api.get(`/v1/a76/classification-concepts/?${params.toString()}`);
return response.data;
}
export async function getClassificationConcept(id: number): Promise<ClassificationConcept> {
const response = await api.get(`/a76/classification_concepts/${id}`);
return response.data;
export async function getClassificationConcept(id: number, companyId: number): Promise<ClassificationConcept> {
const response = await api.get(`/v1/a76/classification-concepts/${id}/?company_id=${companyId}`);
return response.data;
}
export async function createClassificationConcept(data: ClassificationConceptCreate): Promise<ClassificationConcept> {
const response = await api.post('/a76/classification_concepts', data);
return response.data;
export async function createClassificationConcept(
data: ClassificationConceptCreate,
companyId: number
): Promise<ClassificationConcept> {
// 👇 AQUÍ ESTABA EL ERROR GRAVE
const response = await api.post(`/v1/a76/classification-concepts/?company_id=${companyId}`, data);
return response.data;
}
export async function updateClassificationConcept(id: number, data: ClassificationConceptUpdate): Promise<ClassificationConcept> {
const response = await api.patch(`/a76/classification_concepts/${id}`, data);
return response.data;
export async function updateClassificationConcept(
id: number,
data: ClassificationConceptUpdate,
companyId: number // <-- Faltaba esto
): Promise<ClassificationConcept> {
const response = await api.put(`/v1/a76/classification-concepts/${id}/?company_id=${companyId}`, data);
return response.data;
}
export async function deleteClassificationConcept(id: number): Promise<void> {
await api.delete(`/a76/classification_concepts/${id}`);
}
export async function deleteClassificationConcept(id: number, companyId: number): Promise<void> {
await api.delete(`/v1/a76/classification-concepts/${id}/?company_id=${companyId}`);
}

View File

@@ -1,91 +1,91 @@
import { api } from '$lib/api';
import type { ApiResponse } from '$lib/api';
import { api } from '$lib/api';
import type { ApiResponse } from '$lib/api';
export interface Company {
id: number;
tenant_id: number;
name: string | null;
rfc: string | null;
main_activity: string | null;
program: string | null;
program_number: string | null;
prosec: number | null;
prosec_authorization: string | null;
manufacturer_id: string | null;
broker_company: string | null;
responsible: string | null;
responsible_name: string | null;
responsible_last_name: string | null;
responsible_mother_last_name: string | null;
created_at: string | null;
updated_at: string | null;
}
export interface Company {
id: number;
tenant_id: number;
name: string | null;
rfc: string | null;
main_activity: string | null;
program: string | null;
program_number: string | null;
prosec: number | null;
prosec_authorization: string | null;
manufacturer_id: string | null;
broker_company: string | null;
responsible: string | null;
responsible_name: string | null;
responsible_last_name: string | null;
responsible_mother_last_name: string | null;
created_at: string | null;
updated_at: string | null;
}
export interface CompanyCreate {
name?: string | null;
rfc?: string | null;
main_activity?: string | null;
program?: string | null;
program_number?: string | null;
prosec?: number | null;
prosec_authorization?: string | null;
manufacturer_id?: string | null;
broker_company?: string | null;
responsible?: string | null;
responsible_name?: string | null;
responsible_last_name?: string | null;
responsible_mother_last_name?: string | null;
}
export interface CompanyCreate {
name?: string | null;
rfc?: string | null;
main_activity?: string | null;
program?: string | null;
program_number?: string | null;
prosec?: number | null;
prosec_authorization?: string | null;
manufacturer_id?: string | null;
broker_company?: string | null;
responsible?: string | null;
responsible_name?: string | null;
responsible_last_name?: string | null;
responsible_mother_last_name?: string | null;
}
export interface CompanyUpdate {
name?: string | null;
rfc?: string | null;
main_activity?: string | null;
program?: string | null;
program_number?: string | null;
prosec?: number | null;
prosec_authorization?: string | null;
manufacturer_id?: string | null;
broker_company?: string | null;
responsible?: string | null;
responsible_name?: string | null;
responsible_last_name?: string | null;
responsible_mother_last_name?: string | null;
}
export interface CompanyUpdate {
name?: string | null;
rfc?: string | null;
main_activity?: string | null;
program?: string | null;
program_number?: string | null;
prosec?: number | null;
prosec_authorization?: string | null;
manufacturer_id?: string | null;
broker_company?: string | null;
responsible?: string | null;
responsible_name?: string | null;
responsible_last_name?: string | null;
responsible_mother_last_name?: string | null;
}
export interface CompanyListResponse {
items: Company[];
total: number;
page: number;
page_size: number;
pages: number;
}
export interface CompanyListResponse {
items: Company[];
total: number;
page: number;
page_size: number;
pages: number;
}
export async function getCompanies(
page = 1,
pageSize = 50,
filters: Record<string, any> = {}
): Promise<ApiResponse<CompanyListResponse>> {
const queryParams = new URLSearchParams({
page: page.toString(),
page_size: pageSize.toString(),
...filters
});
return await api.get(`/a76/company?${queryParams.toString()}`);
}
export async function getCompanies(
page = 1,
pageSize = 50,
filters: Record<string, any> = {}
): Promise<ApiResponse<CompanyListResponse>> {
const queryParams = new URLSearchParams({
page: page.toString(),
page_size: pageSize.toString(),
...filters
});
return await api.get(`/a76/company?${queryParams.toString()}`);
}
export async function getCompany(id: number): Promise<ApiResponse<Company>> {
return await api.get(`/a76/company/${id}`);
}
export async function getCompany(id: number): Promise<ApiResponse<Company>> {
return await api.get(`/a76/company/${id}`);
}
export async function createCompany(data: CompanyCreate): Promise<ApiResponse<Company>> {
return await api.post(`/a76/company`, data);
}
export async function createCompany(data: CompanyCreate): Promise<ApiResponse<Company>> {
return await api.post(`/v1/a76/company`, data);
}
export async function updateCompany(id: number, data: CompanyUpdate): Promise<ApiResponse<Company>> {
return await api.put(`/a76/company/${id}`, data);
}
export async function updateCompany(id: number, data: CompanyUpdate): Promise<ApiResponse<Company>> {
return await api.put(`/a76/company/${id}`, data);
}
export async function deleteCompany(id: number): Promise<ApiResponse<void>> {
return await api.delete(`/a76/company/${id}`);
}
export async function deleteCompany(id: number): Promise<ApiResponse<void>> {
return await api.delete(`/a76/company/${id}`);
}

View File

@@ -2,78 +2,88 @@ import { api } from '$lib/api';
import type { ApiResponse } from '$lib/api';
export interface Concept {
id: number;
code: string;
description?: string;
description_en?: string;
detailed_description?: string;
priority?: number;
priority_ame?: number;
first_total?: boolean;
type?: string;
is_printed?: boolean;
section?: number;
classification?: string;
tenant_id: string;
company_id?: string;
created_at: string;
updated_at?: string;
id: number;
code: string;
description?: string;
description_en?: string;
detailed_description?: string;
priority?: number;
priority_ame?: number;
first_total?: boolean;
type?: string;
is_printed?: boolean;
section?: number;
classification?: string;
tenant_id: string;
company_id?: string;
created_at: string;
updated_at?: string;
}
export interface ConceptCreate {
code: string;
description?: string;
description_en?: string;
detailed_description?: string;
priority?: number;
priority_ame?: number;
first_total?: boolean;
type?: string;
is_printed?: boolean;
section?: number;
classification?: string;
code: string;
description?: string;
description_en?: string;
detailed_description?: string;
priority?: number;
priority_ame?: number;
first_total?: boolean;
type?: string;
is_printed?: boolean;
section?: number;
classification?: string;
}
export interface ConceptUpdate extends Partial<ConceptCreate> {}
export interface ConceptListResponse {
items: Concept[];
total: number;
page: number;
page_size: number;
pages: number;
items: Concept[];
total: number;
page: number;
page_size: number;
pages: number;
}
export async function getConcepts(
page: number = 1,
pageSize: number = 50,
filters: Record<string, any> = {}
page: number = 1,
pageSize: number = 50,
companyId: number,
filters: Record<string, any> = {},
): Promise<ApiResponse<ConceptListResponse>> {
const params = new URLSearchParams({
page: page.toString(),
page_size: pageSize.toString(),
...filters
});
const params = new URLSearchParams({
page: page.toString(),
page_size: pageSize.toString(),
company_id: companyId.toString(),
...filters
});
const response = await api.get(`/a76/concepts?${params.toString()}`);
return response.data;
const response = await api.get(`/v1/a76/concepts/?${params.toString()}`);
return response.data;
}
export async function getConcept(id: number): Promise<Concept> {
const response = await api.get(`/a76/concepts/${id}`);
return response.data;
export async function getConcept(id: number, companyId: number): Promise<Concept> {
const response = await api.get(`/v1/a76/concepts/${id}/?company_id=${companyId}`);
return response.data;
}
export async function createConcept(data: ConceptCreate): Promise<Concept> {
const response = await api.post('/a76/concepts', data);
return response.data;
export async function createConcept(data: ConceptCreate, companyId: number): Promise<Concept> {
const response = await api.post(`/v1/a76/concepts/?company_id=${companyId}`, data);
return response.data;
}
export async function updateConcept(id: number, data: ConceptUpdate): Promise<Concept> {
const response = await api.patch(`/a76/concepts/${id}`, data);
return response.data;
export async function updateConcept(id: number, data: ConceptUpdate, companyId: number): Promise<Concept> {
const response = await api.put(`/v1/a76/concepts/${id}/?company_id=${companyId}`, data);
return response.data;
}
export async function deleteConcept(id: number): Promise<void> {
await api.delete(`/a76/concepts/${id}`);
}
export async function deleteConcept(id: number, companyId: number): Promise<void> {
await api.delete(`/v1/a76/concepts/${id}/?company_id=${companyId}`);
}

View File

@@ -2,76 +2,68 @@ import { api } from '$lib/api';
import type { ApiResponse } from '$lib/api';
export interface CustomsBrokerConcept {
id: number;
code: string;
description?: string;
description_en?: string;
detailed_description?: string;
priority?: number;
first_total?: boolean;
type?: string;
is_printed?: boolean;
section?: number;
classification?: string;
tenant_id: string;
company_id?: string;
created_at: string;
updated_at?: string;
id: number;
broker_key: string;
concept: string;
amount?: number;
priority?: number;
}
export interface CustomsBrokerConceptCreate {
code: string;
description?: string;
description_en?: string;
detailed_description?: string;
priority?: number;
first_total?: boolean;
type?: string;
is_printed?: boolean;
section?: number;
classification?: string;
broker_key: string;
concept?: string;
amount?: number;
priority?: number;
}
export interface CustomsBrokerConceptUpdate extends Partial<CustomsBrokerConceptCreate> {}
export interface CustomsBrokerConceptListResponse {
items: CustomsBrokerConcept[];
total: number;
page: number;
page_size: number;
pages: number;
items: CustomsBrokerConcept[];
total: number;
page: number;
page_size: number;
pages: number;
}
export async function getCustomsBrokerConcepts(
page: number = 1,
pageSize: number = 50,
filters: Record<string, any> = {}
page: number = 1,
pageSize: number = 50,
companyId: number, // <-- Nuevo
filters: Record<string, any> = {}
): Promise<ApiResponse<CustomsBrokerConceptListResponse>> {
const params = new URLSearchParams({
page: page.toString(),
page_size: pageSize.toString(),
...filters
});
const params = new URLSearchParams({
page: page.toString(),
page_size: pageSize.toString(),
company_id: companyId.toString(),
...filters
});
const response = await api.get(`/a76/customs_broker_concepts?${params.toString()}`);
return response.data;
const response = await api.get(`/v1/a76/customs-broker-concepts/?${params.toString()}`);
return response.data;
}
export async function getCustomsBrokerConcept(id: number): Promise<CustomsBrokerConcept> {
const response = await api.get(`/a76/customs_broker_concepts/${id}`);
return response.data;
export async function createCustomsBrokerConcept(
data: CustomsBrokerConceptCreate,
companyId: number
): Promise<CustomsBrokerConcept> {
const response = await api.post(`/v1/a76/customs-broker-concepts/?company_id=${companyId}`, data);
return response.data;
}
export async function createCustomsBrokerConcept(data: CustomsBrokerConceptCreate): Promise<CustomsBrokerConcept> {
const response = await api.post('/a76/customs_broker_concepts', data);
return response.data;
export async function updateCustomsBrokerConcept(
id: number,
data: CustomsBrokerConceptUpdate,
companyId: number
): Promise<CustomsBrokerConcept> {
const response = await api.put(`/v1/a76/customs-broker-concepts/${id}/?company_id=${companyId}`, data);
return response.data;
}
export async function updateCustomsBrokerConcept(id: number, data: CustomsBrokerConceptUpdate): Promise<CustomsBrokerConcept> {
const response = await api.patch(`/a76/customs_broker_concepts/${id}`, data);
return response.data;
}
export async function deleteCustomsBrokerConcept(id: number): Promise<void> {
await api.delete(`/a76/customs_broker_concepts/${id}`);
}
export async function deleteCustomsBrokerConcept(id: number, companyId: number): Promise<void> {
await api.delete(`/v1/a76/customs-broker-concepts/${id}/?company_id=${companyId}`);
}

View File

@@ -1,61 +1,134 @@
import { api } from '$lib/api';
import type { ApiResponse } from '$lib/api';
// ==========================================
// ERROR CLASSIFICATION
// ==========================================
export interface ErrorClassification {
id: number;
code: string;
level?: string;
errors?: ErrorCatalog[];
tenant_id?: string;
created_at?: string;
updated_at?: string;
}
export interface ErrorClassificationCreate {
code: string;
level?: string;
}
export interface ErrorClassificationUpdate {
level?: string;
}
export interface ErrorClassificationListResponse {
items: ErrorClassification[];
total: number;
page: number;
page_size: number;
pages: number;
}
export interface ErrorCatalog {
id: number;
code: string;
description?: string;
tenant_id: string;
company_id?: string;
created_at: string;
updated_at?: string;
id: number;
code: string;
description?: string;
classification_id?: number;
classification?: ErrorClassification;
tenant_id?: string;
created_at?: string;
updated_at?: string;
}
export interface ErrorCatalogCreate {
code: string;
description?: string;
code: string;
description?: string;
classification_id?: number;
}
export interface ErrorCatalogUpdate extends Partial<ErrorCatalogCreate> {}
export interface ErrorCatalogUpdate {
description?: string;
classification_id?: number;
}
export interface ErrorCatalogListResponse {
items: ErrorCatalog[];
total: number;
page: number;
page_size: number;
pages: number;
items: ErrorCatalog[];
total: number;
page: number;
page_size: number;
pages: number;
}
export async function getErrorCatalogs(
page: number = 1,
pageSize: number = 50,
filters: Record<string, any> = {}
): Promise<ApiResponse<ErrorCatalogListResponse>> {
const params = new URLSearchParams({
page: page.toString(),
page_size: pageSize.toString(),
...filters
});
export async function getErrorClassifications(
page: number = 1,
pageSize: number = 50,
filters: Record<string, any> = {}
): Promise<ApiResponse<ErrorClassificationListResponse>> {
const params = new URLSearchParams({
page: page.toString(),
page_size: pageSize.toString(),
...filters
});
const response = await api.get(`/a76/error_catalogs?${params.toString()}`);
return response.data;
const response = await api.get(`/a76/error_classifications?${params.toString()}`);
return response.data;
}
export async function getErrorClassification(id: number): Promise<ErrorClassification> {
const response = await api.get(`/a76/error_classifications/${id}`);
return response.data;
}
export async function createErrorClassification(data: ErrorClassificationCreate): Promise<ErrorClassification> {
const response = await api.post('/a76/error_classifications', data);
return response.data;
}
export async function updateErrorClassification(id: number, data: ErrorClassificationUpdate): Promise<ErrorClassification> {
const response = await api.patch(`/a76/error_classifications/${id}`, data);
return response.data;
}
export async function deleteErrorClassification(id: number): Promise<void> {
await api.delete(`/a76/error_classifications/${id}`);
}
// --- Catalogs ---
export async function getErrorCatalogs(
page: number = 1,
pageSize: number = 50,
filters: Record<string, any> = {}
): Promise<ApiResponse<ErrorCatalogListResponse>> {
const params = new URLSearchParams({
page: page.toString(),
page_size: pageSize.toString(),
...filters
});
const response = await api.get(`/a76/error_catalogs?${params.toString()}`);
return response.data;
}
export async function getErrorCatalog(id: number): Promise<ErrorCatalog> {
const response = await api.get(`/a76/error_catalogs/${id}`);
return response.data;
const response = await api.get(`/a76/error_catalogs/${id}`);
return response.data;
}
export async function createErrorCatalog(data: ErrorCatalogCreate): Promise<ErrorCatalog> {
const response = await api.post('/a76/error_catalogs', data);
return response.data;
const response = await api.post('/a76/error_catalogs', data);
return response.data;
}
export async function updateErrorCatalog(id: number, data: ErrorCatalogUpdate): Promise<ErrorCatalog> {
const response = await api.patch(`/a76/error_catalogs/${id}`, data);
return response.data;
const response = await api.patch(`/a76/error_catalogs/${id}`, data);
return response.data;
}
export async function deleteErrorCatalog(id: number): Promise<void> {
await api.delete(`/a76/error_catalogs/${id}`);
}
await api.delete(`/a76/error_catalogs/${id}`);
}

View File

@@ -7,8 +7,8 @@ export interface Identifier {
description: string | null;
level: string | null;
complement: string | null;
company_id: number;
tenant_id: number;
company_id: number;
tenant_id: number;
created_at: string | null;
updated_at: string | null;
}
@@ -18,7 +18,6 @@ export interface IdentifierCreate {
description?: string | null;
level?: string | null;
complement?: string | null;
company_id: number;
}
export interface IdentifierUpdate {
@@ -39,24 +38,37 @@ export interface IdentifierListResponse {
export async function getIdentifiers(
page = 1,
pageSize = 50,
companyId: number,
filters: Record<string, any> = {}
): Promise<ApiResponse<IdentifierListResponse>> {
const queryParams = new URLSearchParams({
page: page.toString(),
page_size: pageSize.toString(),
company_id: companyId.toString(),
...filters
});
return await api.get(`/a76/identifiers?${queryParams.toString()}`);
return await api.get(`/v1/a76/identifiers/?${queryParams.toString()}`);
}
export async function createIdentifier(data: IdentifierCreate): Promise<ApiResponse<Identifier>> {
return await api.post('/a76/identifiers', data);
export async function createIdentifier(
data: IdentifierCreate,
companyId: number
): Promise<ApiResponse<Identifier>> {
return await api.post(`/v1/a76/identifiers/?company_id=${companyId}`, data);
}
export async function updateIdentifier(id: number, data: IdentifierUpdate): Promise<ApiResponse<Identifier>> {
return await api.put(`/a76/identifiers/${id}`, data);
export async function updateIdentifier(
id: number,
data: IdentifierUpdate,
companyId: number
): Promise<ApiResponse<Identifier>> {
return await api.put(`/v1/a76/identifiers/${id}/?company_id=${companyId}`, data);
}
export async function deleteIdentifier(id: number): Promise<ApiResponse<void>> {
return await api.delete(`/a76/identifiers/${id}`);
}
export async function deleteIdentifier(
id: number,
companyId: number
): Promise<ApiResponse<void>> {
return await api.delete(`/v1/a76/identifiers/${id}/?company_id=${companyId}`);
}

View File

@@ -2,62 +2,77 @@ import { api } from '$lib/api';
import type { ApiResponse } from '$lib/api';
export interface INPC {
id: number;
year: string;
month: string;
value?: number;
tenant_id: string;
company_id?: string;
created_at: string;
updated_at?: string;
id: number;
year: string;
month: string;
value?: number;
tenant_id: number;
company_id: number;
created_at: string;
updated_at?: string;
}
export interface INPCCreate {
year: string;
month: string;
value?: number;
year: string;
month: string;
value?: number;
}
export interface INPCUpdate extends Partial<INPCCreate> {}
export interface INPCListResponse {
items: INPC[];
total: number;
page: number;
page_size: number;
pages: number;
items: INPC[];
total: number;
page: number;
page_size: number;
pages: number;
}
export async function getINPCs(
page: number = 1,
pageSize: number = 50,
filters: Record<string, any> = {}
page: number = 1,
pageSize: number = 50,
companyId: number,
filters: Record<string, any> = {}
): Promise<ApiResponse<INPCListResponse>> {
const params = new URLSearchParams({
page: page.toString(),
page_size: pageSize.toString(),
...filters
});
const params = new URLSearchParams({
page: page.toString(),
page_size: pageSize.toString(),
company_id: companyId.toString(),
...filters
});
const response = await api.get(`/a76/inpc?${params.toString()}`);
return response.data;
const response = await api.get(`/v1/a76/inpc/?${params.toString()}`);
return response.data;
}
export async function getINPC(id: number): Promise<INPC> {
const response = await api.get(`/a76/inpc/${id}`);
return response.data;
export async function getINPC(id: number, companyId: number): Promise<INPC> {
const response = await api.get(`/v1/a76/inpc/${id}/?company_id=${companyId}`);
return response.data;
}
export async function createINPC(data: INPCCreate): Promise<INPC> {
const response = await api.post('/a76/inpc', data);
return response.data;
export async function createINPC(
data: INPCCreate,
companyId: number
): Promise<INPC> {
const response = await api.post(`/v1/a76/inpc/?company_id=${companyId}`, data);
return response.data;
}
export async function updateINPC(id: number, data: INPCUpdate): Promise<INPC> {
const response = await api.patch(`/a76/inpc/${id}`, data);
return response.data;
export async function updateINPC(
id: number,
data: INPCUpdate,
companyId: number
): Promise<INPC> {
const response = await api.put(`/v1/a76/inpc/${id}/?company_id=${companyId}`, data);
return response.data;
}
export async function deleteINPC(id: number): Promise<void> {
await api.delete(`/a76/inpc/${id}`);
}
export async function deleteINPC(id: number, companyId: number): Promise<void> {
await api.delete(`/v1/a76/inpc/${id}/?company_id=${companyId}`);
}

View File

@@ -2,60 +2,71 @@ import { api } from '$lib/api';
import type { ApiResponse } from '$lib/api';
export interface Legend {
id: number;
code: string;
description?: string;
tenant_id: string;
company_id?: string;
created_at: string;
updated_at?: string;
id: number;
code: number;
description?: string;
tenant_id: number;
company_id: number;
created_at: string;
updated_at?: string;
}
export interface LegendCreate {
code: string;
description?: string;
code: number;
description?: string;
}
export interface LegendUpdate extends Partial<LegendCreate> {}
export interface LegendListResponse {
items: Legend[];
total: number;
page: number;
page_size: number;
pages: number;
items: Legend[];
total: number;
page: number;
page_size: number;
pages: number;
}
export async function getLegends(
page: number = 1,
pageSize: number = 50,
filters: Record<string, any> = {}
page: number = 1,
pageSize: number = 50,
companyId: number,
filters: Record<string, any> = {}
): Promise<ApiResponse<LegendListResponse>> {
const params = new URLSearchParams({
page: page.toString(),
page_size: pageSize.toString(),
...filters
});
const response = await api.get(`/a76/legends?${params.toString()}`);
return response.data;
const params = new URLSearchParams({
page: page.toString(),
page_size: pageSize.toString(),
company_id: companyId.toString(),
...filters
});
const response = await api.get(`/v1/a76/legends/?${params.toString()}`);
return response.data;
}
export async function getLegend(id: number): Promise<Legend> {
const response = await api.get(`/a76/legends/${id}`);
return response.data;
export async function getLegend(id: number, companyId: number): Promise<Legend> {
const response = await api.get(`/v1/a76/legends/${id}/?company_id=${companyId}`);
return response.data;
}
export async function createLegend(data: LegendCreate): Promise<Legend> {
const response = await api.post('/a76/legends', data);
return response.data;
export async function createLegend(
data: LegendCreate,
companyId: number
): Promise<Legend> {
const response = await api.post(`/v1/a76/legends/?company_id=${companyId}`, data);
return response.data;
}
export async function updateLegend(id: number, data: LegendUpdate): Promise<Legend> {
const response = await api.patch(`/a76/legends/${id}`, data);
return response.data;
export async function updateLegend(
id: number,
data: LegendUpdate,
companyId: number
): Promise<Legend> {
const response = await api.put(`/v1/a76/legends/${id}/?company_id=${companyId}`, data);
return response.data;
}
export async function deleteLegend(id: number): Promise<void> {
await api.delete(`/a76/legends/${id}`);
}
export async function deleteLegend(id: number, companyId: number): Promise<void> {
await api.delete(`/v1/a76/legends/${id}/?company_id=${companyId}`);
}

View File

@@ -1,52 +1,69 @@
/**
* API Client para Locations - Ubicaciones relacionadas con puertos
* Basado en los campos location_code y location_description del módulo de puertos
*/
import { api } from '$lib/api';
import type { ApiResponse } from '$lib/api';
export interface Location {
location_code: string;
location_description: string | null;
id: number;
code: string;
description?: string;
tenant_id?: string;
created_at?: string;
updated_at?: string;
}
/**
* Nota: Las ubicaciones están integradas en el módulo de puertos.
* Este archivo proporciona tipos para trabajar con ubicaciones,
* pero las operaciones se realizan a través del módulo de puertos.
*
* Ver: /a76/ports para operaciones relacionadas con ubicaciones
*/
/**
* Obtiene ubicaciones únicas de los puertos
* Esta función extrae las ubicaciones únicas de la lista de puertos
*/
export async function getLocationsFromPorts(): Promise<ApiResponse<Location[]>> {
const portsResponse = await api.get('/a76/ports?page_size=1000');
if (portsResponse.error || !portsResponse.data) {
return {
error: portsResponse.error || 'Error al obtener puertos',
status: portsResponse.status
};
}
export interface LocationCreate {
code: string;
description?: string;
}
// Extraer ubicaciones únicas
const locationMap = new Map<string, Location>();
const ports = portsResponse.data.items || [];
ports.forEach((port: any) => {
if (port.location_code && !locationMap.has(port.location_code)) {
locationMap.set(port.location_code, {
location_code: port.location_code,
location_description: port.location_description
});
}
export interface LocationUpdate {
code?: string;
description?: string;
}
export interface LocationListResponse {
items: Location[];
total: number;
page: number;
page_size: number;
pages: number;
}
export async function getLocations(
page: number = 1,
pageSize: number = 50,
filters: Record<string, any> = {}
): Promise<ApiResponse<LocationListResponse>> {
const params = new URLSearchParams({
page: page.toString(),
page_size: pageSize.toString(),
...filters
});
return {
data: Array.from(locationMap.values()),
status: 200
};
const response = await api.get(`/a24/locations?${params.toString()}`);
return response.data;
}
export async function getLocation(id: number): Promise<Location> {
const response = await api.get(`/a24/locations/${id}`);
return response.data;
}
export async function createLocation(data: LocationCreate): Promise<Location> {
const response = await api.post('/a24/locations', data);
return response.data;
}
export async function updateLocation(id: number, data: LocationUpdate): Promise<Location> {
const response = await api.patch(`/a24/locations/${id}`, data);
return response.data;
}
export async function deleteLocation(id: number): Promise<void> {
await api.delete(`/a24/locations/${id}`);
}

View File

@@ -2,60 +2,78 @@ import { api } from '$lib/api';
import type { ApiResponse } from '$lib/api';
export interface MultiCurrencyType {
id: number;
key: string;
description?: string;
tenant_id: string;
company_id?: string;
created_at: string;
updated_at?: string;
id: number;
currency_type_code: string;
country_key: string | null;
conversion_factor: number | null;
publication_date: number;
tenant_id: number;
company_id: number;
created_at: string;
updated_at?: string;
}
export interface MultiCurrencyTypeCreate {
key: string;
description?: string;
currency_type_code: string;
country_key?: string | null;
conversion_factor?: number | null;
publication_date: number;
}
export interface MultiCurrencyTypeUpdate extends Partial<MultiCurrencyTypeCreate> {}
export interface MultiCurrencyTypeListResponse {
items: MultiCurrencyType[];
total: number;
page: number;
page_size: number;
pages: number;
items: MultiCurrencyType[];
total: number;
page: number;
page_size: number;
pages: number;
}
export async function getMultiCurrencyTypes(
page: number = 1,
pageSize: number = 50,
filters: Record<string, any> = {}
page: number = 1,
pageSize: number = 50,
companyId: number,
filters: Record<string, any> = {}
): Promise<ApiResponse<MultiCurrencyTypeListResponse>> {
const params = new URLSearchParams({
page: page.toString(),
page_size: pageSize.toString(),
...filters
});
const params = new URLSearchParams({
page: page.toString(),
page_size: pageSize.toString(),
company_id: companyId.toString(),
...filters
});
const response = await api.get(`/a76/multi_currency_types?${params.toString()}`);
return response.data;
// Agregamos /v1 y el path correcto
const response = await api.get(`/v1/a76/multi_currency_types/?${params.toString()}`);
return response.data;
}
export async function getMultiCurrencyType(id: number): Promise<MultiCurrencyType> {
const response = await api.get(`/a76/multi_currency_types/${id}`);
return response.data;
export async function getMultiCurrencyType(id: number, companyId: number): Promise<MultiCurrencyType> {
const response = await api.get(`/v1/a76/multi_currency_types/${id}/?company_id=${companyId}`);
return response.data;
}
export async function createMultiCurrencyType(data: MultiCurrencyTypeCreate): Promise<MultiCurrencyType> {
const response = await api.post('/a76/multi_currency_types', data);
return response.data;
export async function createMultiCurrencyType(
data: MultiCurrencyTypeCreate,
companyId: number
): Promise<MultiCurrencyType> {
const response = await api.post(`/v1/a76/multi_currency_types/?company_id=${companyId}`, data);
return response.data;
}
export async function updateMultiCurrencyType(id: number, data: MultiCurrencyTypeUpdate): Promise<MultiCurrencyType> {
const response = await api.patch(`/a76/multi_currency_types/${id}`, data);
return response.data;
export async function updateMultiCurrencyType(
id: number,
data: MultiCurrencyTypeUpdate,
companyId: number
): Promise<MultiCurrencyType> {
const response = await api.put(`/v1/a76/multi_currency_types/${id}/?company_id=${companyId}`, data);
return response.data;
}
export async function deleteMultiCurrencyType(id: number): Promise<void> {
await api.delete(`/a76/multi_currency_types/${id}`);
}
export async function deleteMultiCurrencyType(id: number, companyId: number): Promise<void> {
await api.delete(`/v1/a76/multi_currency_types/${id}/?company_id=${companyId}`);
}

View File

@@ -4,17 +4,17 @@ import type { ApiResponse } from '$lib/api';
export interface Package {
id: number;
tenant_id: number;
company_id: number;
key: string;
description_es: string | null;
description_en: string | null;
weight_unit: number | null;
plurals: string | null;
plural_in: string | null;
code_ace: string | null;
code_aamex: string | null;
created_at: string | null;
updated_at: string | null;
company_id: number;
key: string;
description_es: string | null;
description_en: string | null;
weight_unit: number | null;
plurals: string | null;
plural_in: string | null;
code_ace: string | null;
code_aamex: string | null;
created_at: string;
updated_at?: string;
}
export interface PackageCreate {
@@ -26,19 +26,9 @@ export interface PackageCreate {
plural_in?: string | null;
code_ace?: string | null;
code_aamex?: string | null;
company_id: number;
}
export interface PackageUpdate {
key?: string;
description_es?: string | null;
description_en?: string | null;
weight_unit?: number | null;
plurals?: string | null;
plural_in?: string | null;
code_ace?: string | null;
code_aamex?: string | null;
}
export interface PackageUpdate extends Partial<PackageCreate> {}
export interface PackageListResponse {
items: Package[];
@@ -48,31 +38,48 @@ export interface PackageListResponse {
pages: number;
}
export async function getPackages(
page = 1,
pageSize = 50,
page: number = 1,
pageSize: number = 50,
companyId: number, // Obligatorio por el Mixin
filters: Record<string, any> = {}
): Promise<ApiResponse<PackageListResponse>> {
const queryParams = new URLSearchParams({
const params = new URLSearchParams({
page: page.toString(),
page_size: pageSize.toString(),
company_id: companyId.toString(),
...filters
});
return await api.get(`/a76/packages?${queryParams.toString()}`);
const response = await api.get(`/v1/a76/packages/?${params.toString()}`);
return response.data;
}
export async function getPackage(id: number): Promise<ApiResponse<Package>> {
return await api.get(`/a76/packages/${id}`);
export async function getPackage(id: number, companyId: number): Promise<Package> {
const response = await api.get(`/v1/a76/packages/${id}/?company_id=${companyId}`);
return response.data;
}
export async function createPackage(data: PackageCreate): Promise<ApiResponse<Package>> {
return await api.post(`/a76/packages`, data);
export async function createPackage(
data: PackageCreate,
companyId: number
): Promise<Package> {
const response = await api.post(`/v1/a76/packages/?company_id=${companyId}`, data);
return response.data;
}
export async function updatePackage(id: number, data: PackageUpdate): Promise<ApiResponse<Package>> {
return await api.put(`/a76/packages/${id}`, data);
export async function updatePackage(
id: number,
data: PackageUpdate,
companyId: number
): Promise<Package> {
const response = await api.put(`/v1/a76/packages/${id}/?company_id=${companyId}`, data);
return response.data;
}
export async function deletePackage(id: number): Promise<ApiResponse<void>> {
return await api.delete(`/a76/packages/${id}`);
}
export async function deletePackage(id: number, companyId: number): Promise<void> {
await api.delete(`/v1/a76/packages/${id}/?company_id=${companyId}`);
}

View File

@@ -2,62 +2,79 @@ import { api } from '$lib/api';
import type { ApiResponse } from '$lib/api';
export interface Signature {
id: number;
name: string;
position?: string;
certificate?: string;
tenant_id: string;
company_id?: string;
created_at: string;
updated_at?: string;
id: number;
code: string;
signature: string | null;
photo_path: string | null;
tenant_id: number;
company_id: number;
created_at: string;
updated_at?: string;
}
export interface SignatureCreate {
name: string;
position?: string;
certificate?: string;
code: string;
signature?: string | null;
photo_path?: string | null;
}
export interface SignatureUpdate extends Partial<SignatureCreate> {}
export interface SignatureListResponse {
items: Signature[];
total: number;
page: number;
page_size: number;
pages: number;
items: Signature[];
total: number;
page: number;
page_size: number;
pages: number;
}
export async function getSignatures(
page: number = 1,
pageSize: number = 50,
filters: Record<string, any> = {}
page: number = 1,
pageSize: number = 50,
companyId: number,
filters: Record<string, any> = {}
): Promise<ApiResponse<SignatureListResponse>> {
const params = new URLSearchParams({
page: page.toString(),
page_size: pageSize.toString(),
...filters
});
const params = new URLSearchParams({
page: page.toString(),
page_size: pageSize.toString(),
company_id: companyId.toString(),
...filters
});
const response = await api.get(`/a76/signatures?${params.toString()}`);
return response.data;
const response = await api.get(`/v1/a76/signatures/?${params.toString()}`);
return response.data;
}
export async function getSignature(id: number): Promise<Signature> {
const response = await api.get(`/a76/signatures/${id}`);
return response.data;
export async function getSignature(id: number, companyId: number): Promise<Signature> {
const response = await api.get(`/v1/a76/signatures/${id}/?company_id=${companyId}`);
return response.data;
}
export async function createSignature(data: SignatureCreate): Promise<Signature> {
const response = await api.post('/a76/signatures', data);
return response.data;
export async function createSignature(
data: SignatureCreate,
companyId: number
): Promise<Signature> {
const response = await api.post(`/v1/a76/signatures/?company_id=${companyId}`, data);
return response.data;
}
export async function updateSignature(id: number, data: SignatureUpdate): Promise<Signature> {
const response = await api.patch(`/a76/signatures/${id}`, data);
return response.data;
export async function updateSignature(
id: number,
data: SignatureUpdate,
companyId: number
): Promise<Signature> {
const response = await api.put(`/v1/a76/signatures/${id}/?company_id=${companyId}`, data);
return response.data;
}
export async function deleteSignature(id: number): Promise<void> {
await api.delete(`/a76/signatures/${id}`);
}
export async function deleteSignature(id: number, companyId: number): Promise<void> {
await api.delete(`/v1/a76/signatures/${id}/?company_id=${companyId}`);
}

View File

@@ -2,62 +2,76 @@ import { api } from '$lib/api';
import type { ApiResponse } from '$lib/api';
export interface UnitConversion {
id: number;
from_unit_id: number;
to_unit_id: number;
conversion_factor: number;
tenant_id: string;
company_id?: string;
created_at: string;
updated_at?: string;
id: number;
from_unit_code: string;
to_unit_code: string;
conversion_factor: number;
tenant_id: number; // number
company_id: number; // number
created_at: string;
updated_at?: string;
}
export interface UnitConversionCreate {
from_unit_id: number;
to_unit_id: number;
conversion_factor: number;
from_unit_code: string; // Ej: "KGM"
to_unit_code: string; // Ej: "LBR"
conversion_factor: number;
// company_id va en la URL
}
export interface UnitConversionUpdate extends Partial<UnitConversionCreate> {}
export interface UnitConversionListResponse {
items: UnitConversion[];
total: number;
page: number;
page_size: number;
pages: number;
items: UnitConversion[];
total: number;
page: number;
page_size: number;
pages: number;
}
export async function getUnitConversions(
page: number = 1,
pageSize: number = 50,
filters: Record<string, any> = {}
page: number = 1,
pageSize: number = 50,
companyId: number, // 👈 Obligatorio
filters: Record<string, any> = {}
): Promise<ApiResponse<UnitConversionListResponse>> {
const params = new URLSearchParams({
page: page.toString(),
page_size: pageSize.toString(),
...filters
});
const params = new URLSearchParams({
page: page.toString(),
page_size: pageSize.toString(),
company_id: companyId.toString(),
...filters
});
const response = await api.get(`/a76/unit_conversions?${params.toString()}`);
return response.data;
// Agregamos /v1 y prefijo.
// NOTA: Revisa si en tu router definiste "unit_conversions" o "unit-conversions"
const response = await api.get(`/v1/a76/unit-conversions/?${params.toString()}`);
return response.data;
}
export async function getUnitConversion(id: number): Promise<UnitConversion> {
const response = await api.get(`/a76/unit_conversions/${id}`);
return response.data;
export async function getUnitConversion(id: number, companyId: number): Promise<UnitConversion> {
const response = await api.get(`/v1/a76/unit-conversions/${id}/?company_id=${companyId}`);
return response.data;
}
export async function createUnitConversion(data: UnitConversionCreate): Promise<UnitConversion> {
const response = await api.post('/a76/unit_conversions', data);
return response.data;
export async function createUnitConversion(
data: UnitConversionCreate,
companyId: number
): Promise<UnitConversion> {
const response = await api.post(`/v1/a76/unit-conversions/?company_id=${companyId}`, data);
return response.data;
}
export async function updateUnitConversion(id: number, data: UnitConversionUpdate): Promise<UnitConversion> {
const response = await api.patch(`/a76/unit_conversions/${id}`, data);
return response.data;
export async function updateUnitConversion(
id: number,
data: UnitConversionUpdate,
companyId: number
): Promise<UnitConversion> {
const response = await api.put(`/v1/a76/unit-conversions/${id}/?company_id=${companyId}`, data);
return response.data;
}
export async function deleteUnitConversion(id: number): Promise<void> {
await api.delete(`/a76/unit_conversions/${id}`);
}
export async function deleteUnitConversion(id: number, companyId: number): Promise<void> {
await api.delete(`/v1/a76/unit_conversions/${id}/?company_id=${companyId}`);
}

View File

@@ -124,7 +124,7 @@ export interface InvoiceListResponse {
page_size: number;
}
export interface CreateInvoiceData {
export interface InvoiceData {
operation_type?: OperationType | null;
invoice_type?: string | null;
invoice_number?: string | null;

View File

@@ -1,127 +0,0 @@
<script lang="ts">
import { Button } from "$lib/components/ui/button";
import * as Dialog from "$lib/components/ui/dialog";
import { Input } from "$lib/components/ui/input";
import { Label } from "$lib/components/ui/label";
import { createCompany, updateCompany, type Company } from "$lib/api/dashboard/a76/general_catalogs/company";
let {
open = $bindable(false),
item = null,
onSuccess
}: {
open: boolean;
item?: Company | null;
onSuccess?: () => void;
} = $props();
const isEdit = $derived(!!item);
const title = $derived(isEdit ? "Editar Empresa" : "Nueva Empresa");
let formData = $state({
name: item?.name || '',
rfc: item?.rfc || '',
main_activity: item?.main_activity || '',
program: item?.program || '',
program_number: item?.program_number || ''
});
let loading = $state(false);
let error = $state<string | null>(null);
$effect(() => {
if (item) {
formData = {
name: item.name || '',
rfc: item.rfc || '',
main_activity: item.main_activity || '',
program: item.program || '',
program_number: item.program_number || ''
};
} else {
formData = {
name: '',
rfc: '',
main_activity: '',
program: '',
program_number: ''
};
}
});
async function handleSubmit() {
error = null;
loading = true;
try {
if (!formData.name.trim()) throw new Error('El nombre es requerido');
const dataToSend = {
name: formData.name.trim(),
rfc: formData.rfc.trim() || null,
main_activity: formData.main_activity.trim() || null,
program: formData.program.trim() || null,
program_number: formData.program_number.trim() || null
};
let response;
if (isEdit && item) {
response = await updateCompany(item.id, dataToSend);
} else {
response = await createCompany(dataToSend);
}
if (response.error) {
throw new Error(response.error);
}
open = false;
if (onSuccess) onSuccess();
} catch (e) {
error = e instanceof Error ? e.message : 'Error al guardar';
} finally {
loading = false;
}
}
</script>
<Dialog.Root bind:open>
<Dialog.Content class="max-w-md">
<Dialog.Header>
<Dialog.Title>{title}</Dialog.Title>
</Dialog.Header>
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} class="space-y-4">
{#if error}
<div class="text-destructive text-sm">{error}</div>
{/if}
<div class="grid gap-2">
<Label for="name">Nombre <span class="text-destructive">*</span></Label>
<Input id="name" bind:value={formData.name} />
</div>
<div class="grid gap-2">
<Label for="rfc">RFC</Label>
<Input id="rfc" bind:value={formData.rfc} />
</div>
<div class="grid gap-2">
<Label for="program">Programa</Label>
<Input id="program" bind:value={formData.program} />
</div>
<div class="grid gap-2">
<Label for="program_number">No. Programa</Label>
<Input id="program_number" bind:value={formData.program_number} />
</div>
<Dialog.Footer>
<Button type="button" variant="outline" onclick={() => open = false}>Cancelar</Button>
<Button type="submit" disabled={loading}>
{loading ? 'Guardando...' : 'Guardar'}
</Button>
</Dialog.Footer>
</form>
</Dialog.Content>
</Dialog.Root>

View File

@@ -1,164 +1,187 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button';
import * as Dialog from '$lib/components/ui/dialog';
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import { LoaderCircle } from 'lucide-svelte';
import type {
ExchangeRate,
ExchangeRateCreate,
ExchangeRateUpdate
} from '$lib/api/dashboard/a76/general_catalogs/exchange-rate';
import { createExchangeRate, updateExchangeRate } from '$lib/api/dashboard/a76/general_catalogs/exchange-rate';
import { companyStore } from '$lib/stores/company.svelte';
import { Button } from "$lib/components/ui/button";
import * as Dialog from "$lib/components/ui/dialog";
import { Input } from "$lib/components/ui/input";
import { Label } from "$lib/components/ui/label";
import {
createExchangeRate,
updateExchangeRate,
type ExchangeRate
} from "$lib/api/dashboard/a76/general_catalogs/exchange-rate";
import { companyStore } from "$lib/stores/company.svelte";
interface Props {
open: boolean;
item?: ExchangeRate | null;
onOpenChange: (open: boolean) => void;
onSuccess?: (item: ExchangeRate) => void;
}
let {
open = $bindable(false),
item = null,
onSuccess
}: {
open: boolean;
item?: ExchangeRate | null;
onSuccess?: () => void;
} = $props();
let { open = $bindable(false), item = null, onOpenChange, onSuccess }: Props = $props();
const isEdit = $derived(!!item);
const title = $derived(isEdit ? "Editar Tipo de Cambio" : "Nuevo Tipo de Cambio");
let formData = $state<ExchangeRateCreate | ExchangeRateUpdate>({
date: '',
value: null,
local_currency: null,
foreign_currency: null
});
// Estado del formulario
let formData = $state({
date: '', // Se usará con input type="date" (YYYY-MM-DD)
value: null as number | null,
local_currency: '',
foreign_currency: ''
});
let loading = $state(false);
let error = $state<string | null>(null);
let loading = $state(false);
let error = $state<string | null>(null);
let isEdit = $derived(!!item);
// Cargar datos al abrir
$effect(() => {
if (open) {
if (item && item.date) {
// Truco: Si la fecha viene con hora (ej: 2023-01-01T12:00:00),
// solo tomamos la parte de la fecha para el input.
const formattedDate = item.date.includes('T') ? item.date.split('T')[0] : item.date;
$effect(() => {
if (item) {
const date = new Date(item.date);
const dateStr = date.toISOString().split('T')[0];
formData = {
date: dateStr,
value: item.value,
local_currency: item.local_currency,
foreign_currency: item.foreign_currency
};
} else {
const today = new Date();
const dateStr = today.toISOString().split('T')[0];
formData = {
date: dateStr,
value: null,
local_currency: null,
foreign_currency: null
};
}
error = null;
});
formData = {
date: formattedDate,
value: item.value,
local_currency: item.local_currency || '',
foreign_currency: item.foreign_currency || ''
};
} else {
// Reset para nuevo registro. Ponemos la fecha de hoy por default.
formData = {
date: new Date().toISOString().split('T')[0],
value: null,
local_currency: 'MXN', // Default común
foreign_currency: 'USD' // Default común
};
}
error = null;
}
});
async function handleSubmit(e: Event) {
e.preventDefault();
loading = true;
error = null;
async function handleSubmit() {
loading = true;
error = null;
const companyId = companyStore.activeCompany?.id;
if (!companyId) {
error = 'No hay una empresa seleccionada';
loading = false;
return;
}
try {
const companyId = companyStore.activeCompany?.id;
if (!companyId) throw new Error('No hay una compañía seleccionada');
try {
let result: ExchangeRate;
if (isEdit && item) {
result = await updateExchangeRate(item.id, formData as ExchangeRateUpdate, companyId);
} else {
result = await createExchangeRate(formData as ExchangeRateCreate, companyId);
}
// Validaciones básicas
if (!formData.date) throw new Error('La fecha es requerida');
if (formData.value === null || formData.value === undefined) throw new Error('El valor es requerido');
// Preparar datos
// Pydantic suele aceptar YYYY-MM-DD para campos datetime sin problema.
const dataToSend = {
date: formData.date,
value: Number(formData.value),
// Estandarizamos a mayúsculas las monedas
local_currency: formData.local_currency?.trim().toUpperCase() || null,
foreign_currency: formData.foreign_currency?.trim().toUpperCase() || null
};
if (onSuccess) {
onSuccess(result);
}
onOpenChange(false);
} catch (err: any) {
error = err.message || `Error al ${isEdit ? 'actualizar' : 'crear'} el tipo de cambio`;
} finally {
loading = false;
}
}
let response;
// 👇 companyId va como tercer argumento, ¡bien ahí!
if (isEdit && item) {
response = await updateExchangeRate(item.id, dataToSend, companyId);
} else {
response = await createExchangeRate(dataToSend, companyId);
}
open = false;
if (onSuccess) onSuccess();
} catch (e) {
// Si el API wrapper no lanza error, revisa cómo manejar la respuesta de error aquí
error = e instanceof Error ? e.message : 'Error al guardar el tipo de cambio';
} finally {
loading = false;
}
}
</script>
<Dialog.Root {open} onOpenChange={onOpenChange}>
<Dialog.Content class="sm:max-w-[500px]">
<Dialog.Header>
<Dialog.Title>{isEdit ? 'Editar' : 'Crear'} Tipo de Cambio</Dialog.Title>
</Dialog.Header>
<Dialog.Root bind:open>
<Dialog.Content class="sm:max-w-[500px]">
<Dialog.Header>
<Dialog.Title>{title}</Dialog.Title>
</Dialog.Header>
<form onsubmit={handleSubmit} class="space-y-4">
<div class="space-y-2">
<Label for="date">Fecha *</Label>
<Input
id="date"
type="date"
bind:value={formData.date}
required
disabled={loading}
/>
</div>
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} class="space-y-4 py-4">
{#if error}
<div class="rounded-md bg-destructive/15 p-3 text-sm text-destructive">
{error}
</div>
{/if}
<div class="space-y-2">
<Label for="value">Tipo de Cambio *</Label>
<Input
id="value"
type="number"
step="0.000001"
bind:value={formData.value}
placeholder="0.000000"
required
disabled={loading}
/>
</div>
<div class="grid gap-4">
<div class="grid grid-cols-4 items-center gap-4">
<Label for="date" class="text-right">Fecha <span class="text-destructive">*</span></Label>
<div class="col-span-3">
<Input
id="date"
type="date"
bind:value={formData.date}
disabled={loading}
required
/>
</div>
</div>
<div class="space-y-2">
<Label for="local_currency">Moneda Local</Label>
<Input
id="local_currency"
type="text"
maxlength="7"
bind:value={formData.local_currency}
placeholder="MXN"
disabled={loading}
/>
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label for="value" class="text-right">Valor <span class="text-destructive">*</span></Label>
<div class="col-span-3">
<Input
id="value"
type="number"
step="0.000001"
bind:value={formData.value}
placeholder="Ej: 18.5000"
disabled={loading}
required
/>
<p class="text-[10px] text-muted-foreground mt-1">Hasta 6 decimales.</p>
</div>
</div>
<div class="space-y-2">
<Label for="foreign_currency">Moneda Extranjera</Label>
<Input
id="foreign_currency"
type="text"
maxlength="7"
bind:value={formData.foreign_currency}
placeholder="USD"
disabled={loading}
/>
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label for="local_currency" class="text-right">Moneda Local</Label>
<div class="col-span-3">
<Input
id="local_currency"
bind:value={formData.local_currency}
placeholder="Ej: MXN"
maxlength={7}
disabled={loading}
/>
</div>
</div>
{#if error}
<p class="text-sm text-destructive">{error}</p>
{/if}
<div class="grid grid-cols-4 items-center gap-4">
<Label for="foreign_currency" class="text-right">Moneda Ext.</Label>
<div class="col-span-3">
<Input
id="foreign_currency"
bind:value={formData.foreign_currency}
placeholder="Ej: USD"
maxlength={7}
disabled={loading}
/>
</div>
</div>
</div>
<div class="flex justify-end gap-2">
<Button type="button" variant="outline" onclick={() => onOpenChange(false)} disabled={loading}>
Cancelar
</Button>
<Button type="submit" disabled={loading}>
{#if loading}
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{/if}
{isEdit ? 'Actualizar' : 'Crear'}
</Button>
</div>
</form>
</Dialog.Content>
</Dialog.Root>
<Dialog.Footer>
<Button type="button" variant="outline" onclick={() => open = false} disabled={loading}>
Cancelar
</Button>
<Button type="submit" disabled={loading}>
{loading ? 'Guardando...' : isEdit ? 'Actualizar' : 'Crear'}
</Button>
</Dialog.Footer>
</form>
</Dialog.Content>
</Dialog.Root>

View File

@@ -0,0 +1,115 @@
<script lang="ts">
import { Button } from "$lib/components/ui/button";
import * as Dialog from "$lib/components/ui/dialog";
import { Input } from "$lib/components/ui/input";
import { Label } from "$lib/components/ui/label";
// 👇 1. Importamos la API correcta
import {
updateClassificationConcept,
createClassificationConcept,
type ClassificationConcept
} from "$lib/api/dashboard/a76/general_catalogs/classification-concepts";
// 👇 2. Importamos el Store para el ID de la empresa
import { companyStore } from "$lib/stores/company.svelte";
let {
open = $bindable(false),
item = null,
onSuccess
}: {
open: boolean;
item?: ClassificationConcept | null;
onSuccess?: () => void;
} = $props();
const isEdit = $derived(!!item);
const title = $derived(isEdit ? "Editar Clasificación" : "Nueva Clasificación");
// 👇 3. Estado limpio: Solo lo que existe en la BD
let formData = $state({
classification: ''
});
let loading = $state(false);
let error = $state<string | null>(null);
// Cargar datos al abrir
$effect(() => {
if (item) {
formData = {
classification: item.classification || ''
};
} else {
formData = {
classification: ''
};
}
});
async function handleSubmit() {
error = null;
loading = true;
try {
// 👇 4. Validar Company ID
const companyId = companyStore.activeCompany?.id;
if (!companyId) throw new Error('No hay una compañía seleccionada');
// Validar campos
if (!formData.classification.trim()) throw new Error('El nombre de la clasificación es requerido');
const dataToSend = {
classification: formData.classification.trim()
};
// 👇 5. Llamar a la API pasando el companyId
if (isEdit && item) {
await updateClassificationConcept(item.id, dataToSend, companyId);
} else {
await createClassificationConcept(dataToSend, companyId);
}
open = false;
if (onSuccess) onSuccess();
} catch (e) {
error = e instanceof Error ? e.message : 'Error al guardar';
} finally {
loading = false;
}
}
</script>
<Dialog.Root bind:open>
<Dialog.Content class="max-w-md">
<Dialog.Header>
<Dialog.Title>{title}</Dialog.Title>
</Dialog.Header>
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} class="space-y-4">
{#if error}
<div class="p-3 text-sm text-destructive bg-destructive/10 rounded-md">
{error}
</div>
{/if}
<div class="grid gap-4 py-4">
<div class="grid gap-2">
<Label for="classification">Clasificación <span class="text-destructive">*</span></Label>
<Input
id="classification"
bind:value={formData.classification}
placeholder="Ej: GENERAL"
maxlength={30}
/>
</div>
</div>
<Dialog.Footer>
<Button type="button" variant="outline" onclick={() => open = false}>Cancelar</Button>
<Button type="submit" disabled={loading}>
{loading ? 'Guardando...' : 'Guardar'}
</Button>
</Dialog.Footer>
</form>
</Dialog.Content>
</Dialog.Root>

View File

@@ -0,0 +1,296 @@
<script lang="ts">
import { Button } from "$lib/components/ui/button";
import * as Dialog from "$lib/components/ui/dialog";
import { Input } from "$lib/components/ui/input";
import { Label } from "$lib/components/ui/label";
import * as Tabs from "$lib/components/ui/tabs";
import { Switch } from "$lib/components/ui/switch";
import { createCompany, updateCompany, type Company } from "$lib/api/dashboard/a76/general_catalogs/company";
let {
open = $bindable(false),
item = null,
onSuccess
}: {
open: boolean;
item?: Company | null;
onSuccess?: () => void;
} = $props();
const isEdit = $derived(!!item);
const title = $derived(isEdit ? "Editar Empresa" : "Nueva Empresa");
// Inicialización completa con todos los campos del modelo
let formData = $state({
// General
name: '',
rfc: '',
curp: '',
main_activity: '',
// Programas
program: '',
program_number: '',
prosec: 0,
prosec_authorization: '',
// Responsable
responsible_name: '',
responsible_last_name: '',
responsible_mother_last_name: '',
responsible_rfc: '',
position: '',
// Configuración / Operativo
manufacturer_id: '',
has_express_line: false,
is_service_company: false,
order_format_type: '',
// Certificaciones
ctpat_svi: '',
trusted_exporter_number: ''
});
let loading = $state(false);
let error = $state<string | null>(null);
// Cargar datos al abrir para editar
$effect(() => {
if (item) {
formData = {
name: item.name || '',
rfc: item.rfc || '',
curp: item.curp || '',
main_activity: item.main_activity || '',
program: item.program || '',
program_number: item.program_number || '',
prosec: item.prosec || 0,
prosec_authorization: item.prosec_authorization || '',
responsible_name: item.responsible_name || '',
responsible_last_name: item.responsible_last_name || '',
responsible_mother_last_name: item.responsible_mother_last_name || '',
responsible_rfc: item.responsible_rfc || '',
position: item.position || '',
manufacturer_id: item.manufacturer_id || '',
has_express_line: item.has_express_line || false,
is_service_company: item.is_service_company || false,
order_format_type: item.order_format_type || '',
ctpat_svi: item.ctpat_svi || '',
trusted_exporter_number: item.trusted_exporter_number || ''
};
} else {
// Reset manual si es nuevo
formData = {
name: '', rfc: '', curp: '', main_activity: '',
program: '', program_number: '', prosec: 0, prosec_authorization: '',
responsible_name: '', responsible_last_name: '', responsible_mother_last_name: '', responsible_rfc: '', position: '',
manufacturer_id: '', has_express_line: false, is_service_company: false, order_format_type: '',
ctpat_svi: '', trusted_exporter_number: ''
};
}
});
async function handleSubmit() {
error = null;
loading = true;
try {
if (!formData.name.trim()) throw new Error('El nombre es requerido');
if (!formData.rfc.trim()) throw new Error('El RFC es requerido');
// Construir payload limpiando strings vacíos a null
const dataToSend = {
name: formData.name.trim(),
rfc: formData.rfc.trim(),
curp: formData.curp.trim() || null,
main_activity: formData.main_activity.trim() || null,
program: formData.program.trim() || null,
program_number: formData.program_number.trim() || null,
prosec: formData.prosec || null,
prosec_authorization: formData.prosec_authorization.trim() || null,
// Concatenar nombre completo del responsable si se desea guardar en 'responsible' también
responsible: `${formData.responsible_name} ${formData.responsible_last_name}`.trim() || null,
responsible_name: formData.responsible_name.trim() || null,
responsible_last_name: formData.responsible_last_name.trim() || null,
responsible_mother_last_name: formData.responsible_mother_last_name.trim() || null,
responsible_rfc: formData.responsible_rfc.trim() || null,
position: formData.position.trim() || null,
manufacturer_id: formData.manufacturer_id.trim() || null,
has_express_line: formData.has_express_line,
is_service_company: formData.is_service_company,
order_format_type: formData.order_format_type.trim() || null,
ctpat_svi: formData.ctpat_svi.trim() || null,
trusted_exporter_number: formData.trusted_exporter_number.trim() || null
};
let response;
if (isEdit && item) {
response = await updateCompany(item.id, dataToSend);
} else {
response = await createCompany(dataToSend);
}
if (response.error) {
throw new Error(response.error);
}
open = false;
if (onSuccess) onSuccess();
} catch (e) {
error = e instanceof Error ? e.message : 'Error al guardar';
} finally {
loading = false;
}
}
</script>
<Dialog.Root bind:open>
<Dialog.Content class="max-w-2xl max-h-[90vh] overflow-y-auto">
<Dialog.Header>
<Dialog.Title>{title}</Dialog.Title>
</Dialog.Header>
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} class="space-y-4">
{#if error}
<div class="p-3 text-sm text-red-600 bg-red-50 rounded-md border border-red-200">
{error}
</div>
{/if}
<Tabs.Root value="general" class="w-full">
<Tabs.List class="grid w-full grid-cols-4">
<Tabs.Trigger value="general">General</Tabs.Trigger>
<Tabs.Trigger value="programas">Programas</Tabs.Trigger>
<Tabs.Trigger value="responsable">Responsable</Tabs.Trigger>
<Tabs.Trigger value="config">Config</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="general" class="space-y-4 py-4">
<div class="grid gap-2">
<Label for="name">Razón Social <span class="text-destructive">*</span></Label>
<Input id="name" bind:value={formData.name} placeholder="Nombre de la empresa" />
</div>
<div class="grid grid-cols-2 gap-4">
<div class="grid gap-2">
<Label for="rfc">RFC <span class="text-destructive">*</span></Label>
<Input id="rfc" bind:value={formData.rfc} maxlength={13} />
</div>
<div class="grid gap-2">
<Label for="curp">CURP</Label>
<Input id="curp" bind:value={formData.curp} maxlength={18} />
</div>
</div>
<div class="grid gap-2">
<Label for="main_activity">Actividad Principal</Label>
<Input id="main_activity" bind:value={formData.main_activity} />
</div>
</Tabs.Content>
<Tabs.Content value="programas" class="space-y-4 py-4">
<div class="grid grid-cols-2 gap-4">
<div class="grid gap-2">
<Label for="program">Programa (IMMEX)</Label>
<Input id="program" bind:value={formData.program} />
</div>
<div class="grid gap-2">
<Label for="program_number">No. Programa</Label>
<Input id="program_number" bind:value={formData.program_number} />
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div class="grid gap-2">
<Label for="prosec">Sector PROSEC (ID)</Label>
<Input id="prosec" type="number" bind:value={formData.prosec} />
</div>
<div class="grid gap-2">
<Label for="prosec_auth">Autorización PROSEC</Label>
<Input id="prosec_auth" bind:value={formData.prosec_authorization} />
</div>
</div>
</Tabs.Content>
<Tabs.Content value="responsable" class="space-y-4 py-4">
<div class="grid grid-cols-2 gap-4">
<div class="grid gap-2">
<Label for="resp_name">Nombre</Label>
<Input id="resp_name" bind:value={formData.responsible_name} />
</div>
<div class="grid gap-2">
<Label for="resp_last">Apellido Paterno</Label>
<Input id="resp_last" bind:value={formData.responsible_last_name} />
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div class="grid gap-2">
<Label for="resp_mother">Apellido Materno</Label>
<Input id="resp_mother" bind:value={formData.responsible_mother_last_name} />
</div>
<div class="grid gap-2">
<Label for="resp_rfc">RFC Responsable</Label>
<Input id="resp_rfc" bind:value={formData.responsible_rfc} maxlength={13} />
</div>
</div>
<div class="grid gap-2">
<Label for="position">Puesto / Cargo</Label>
<Input id="position" bind:value={formData.position} />
</div>
</Tabs.Content>
<Tabs.Content value="config" class="space-y-4 py-4">
<div class="grid grid-cols-2 gap-6">
<div class="flex items-center space-x-2">
<Switch id="express" bind:checked={formData.has_express_line} />
<Label for="express">Carril Exprés (OEA/NEEC)</Label>
</div>
<div class="flex items-center space-x-2">
<Switch id="service" bind:checked={formData.is_service_company} />
<Label for="service">Es Empresa de Servicios</Label>
</div>
</div>
<div class="border-t my-2"></div>
<div class="grid grid-cols-2 gap-4">
<div class="grid gap-2">
<Label for="man_id">Manufacturer ID (MID)</Label>
<Input id="man_id" bind:value={formData.manufacturer_id} />
</div>
<div class="grid gap-2">
<Label for="order_format">Formato de Pedido</Label>
<Input id="order_format" bind:value={formData.order_format_type} />
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div class="grid gap-2">
<Label for="ctpat">C-TPAT / SVI</Label>
<Input id="ctpat" bind:value={formData.ctpat_svi} />
</div>
<div class="grid gap-2">
<Label for="exporter">No. Exportador Confiable</Label>
<Input id="exporter" bind:value={formData.trusted_exporter_number} />
</div>
</div>
</Tabs.Content>
</Tabs.Root>
<Dialog.Footer>
<Button type="button" variant="outline" onclick={() => open = false}>Cancelar</Button>
<Button type="submit" disabled={loading}>
{loading ? 'Guardando...' : 'Guardar Empresa'}
</Button>
</Dialog.Footer>
</form>
</Dialog.Content>
</Dialog.Root>

View File

@@ -0,0 +1,213 @@
<script lang="ts">
import { Button } from "$lib/components/ui/button";
import * as Dialog from "$lib/components/ui/dialog";
import { Input } from "$lib/components/ui/input";
import { Label } from "$lib/components/ui/label";
import * as Tabs from "$lib/components/ui/tabs";
import { Switch } from "$lib/components/ui/switch";
import { createConcept, updateConcept, type Concept } from "$lib/api/dashboard/a76/general_catalogs/concepts";
import { companyStore } from "$lib/stores/company.svelte";
let {
open = $bindable(false),
item = null,
onSuccess
}: {
open: boolean;
item?: Concept | null;
onSuccess?: () => void;
} = $props();
const isEdit = $derived(!!item);
const title = $derived(isEdit ? "Editar Concepto" : "Nuevo Concepto");
let formData = $state({
code: '',
description: '',
description_en: '',
detailed_description: '',
priority: '',
priority_ame: '',
first_total: '',
type: '',
is_printed: false,
section: '',
classification: '',
});
let loading = $state(false);
let error = $state<string | null>(null);
// Cargar datos al abrir
$effect(() => {
if (item) {
formData = {
code: item.code || '',
description: item.description || '',
description_en: item.description_en || '',
detailed_description: item.detailed_description || '',
priority: item.priority ? String(item.priority) : '', // Asegurar string
priority_ame: item.priority_ame ? String(item.priority_ame) : '',
first_total: item.first_total ? String(item.first_total) : '',
type: item.type || '',
is_printed: item.is_printed || false,
section: item.section || '',
classification: item.classification || '',
};
} else {
// Limpiar formulario
formData = {
code: '',
description: '',
description_en: '',
detailed_description: '',
priority: '',
priority_ame: '',
first_total: '',
type: '',
is_printed: false,
section: '',
classification: ''
};
}
});
async function handleSubmit() {
error = null;
loading = true;
if (!companyStore.activeCompany) {
error = "No hay una empresa seleccionada. Recarga la página.";
loading = false;
return;
}
const companyId = companyStore.activeCompany.id;
try {
if (!formData.code.trim()) throw new Error('El código es requerido');
const dataToSend = {
code: formData.code.trim(),
company_id: companyId,
description: formData.description.trim(),
description_en: formData.description_en.trim() || null,
detailed_description: formData.detailed_description.trim() || null,
priority: formData.priority ? parseInt(formData.priority) : null,
priority_ame: formData.priority_ame ? parseInt(formData.priority_ame) : null,
section: formData.section ? parseInt(formData.section) : null,
first_total: !!formData.first_total,
type: formData.type.trim() || null,
is_printed: Boolean(formData.is_printed),
classification: formData.classification.trim() || null,
};
let response;
if (isEdit && item) {
response = await updateConcept(item.id, dataToSend, companyId);
} else {
response = await createConcept(dataToSend, companyId);
}
if (response.error) {
throw new Error(response.error); // Axios a veces devuelve error en el body
}
open = false;
if (onSuccess) onSuccess();
} catch (e) {
error = e instanceof Error ? e.message : 'Error al guardar';
} finally {
loading = false;
}
}
</script>
<Dialog.Root bind:open>
<Dialog.Content class="max-w-2xl max-h-[90vh] overflow-y-auto">
<Dialog.Header>
<Dialog.Title>{title}</Dialog.Title>
</Dialog.Header>
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} class="space-y-4">
{#if error}
<div class="p-3 text-sm text-red-600 bg-red-50 rounded-md border border-red-200">
{error}
</div>
{/if}
<Tabs.Root value="general" class="w-full">
<Tabs.Content value="general" class="space-y-4 py-4">
<div class="grid gap-2">
<Label for="code">Código <span class="text-destructive">*</span></Label>
<Input id="code" bind:value={formData.code} maxlength={10} />
</div>
<div class="grid grid-cols-2 gap-4">
<div class="grid gap-2">
<Label for="priority">Prioridad</Label>
<Input id="priority" bind:value={formData.priority} type="number" />
</div>
<div class="grid gap-2">
<Label for="priority_ame">Prioridad AME</Label>
<Input id="priority_ame" bind:value={formData.priority_ame} type="number"/>
</div>
</div>
<div class="grid gap-2">
<Label for="description">Descripción <span class="text-destructive">*</span></Label>
<Input id="description" bind:value={formData.description} maxlength={100} />
</div>
<div class="grid gap-2">
<Label for="description_en">Descripción (Inglés)</Label>
<Input id="description_en" bind:value={formData.description_en} maxlength={100} />
</div>
<div class="grid gap-2">
<Label for="detailed_description">Descripción Detallada</Label>
<Input id="detailed_description" bind:value={formData.detailed_description} />
</div>
<div class="grid grid-cols-2 gap-4">
<div class="grid gap-2">
<Label for="first_total">Primer Total</Label>
<Input id="first_total" bind:value={formData.first_total} type="number" />
</div>
<div class="grid gap-2">
<Label for="type">Tipo</Label>
<Input id="type" bind:value={formData.type} maxlength={5} />
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div class="grid gap-2">
<Label for="section">Sección</Label>
<Input id="section" bind:value={formData.section} maxlength={10} />
</div>
<div class="grid gap-2">
<Label for="classification">Clasificación</Label>
<Input id="classification" bind:value={formData.classification} maxlength={10} />
</div>
</div>
<div class="flex items-center space-x-2 pt-2">
<Switch id="is_printed" bind:checked={formData.is_printed} />
<Label for="is_printed">¿Se Imprime?</Label>
</div>
</Tabs.Content>
</Tabs.Root>
<Dialog.Footer>
<Button type="button" variant="outline" onclick={() => open = false}>Cancelar</Button>
<Button type="submit" disabled={loading}>
{loading ? 'Guardando...' : 'Guardar Concepto'}
</Button>
</Dialog.Footer>
</form>
</Dialog.Content>
</Dialog.Root>

View File

@@ -0,0 +1,165 @@
<script lang="ts">
import { Button } from "$lib/components/ui/button";
import * as Dialog from "$lib/components/ui/dialog";
import { Input } from "$lib/components/ui/input";
import { Label } from "$lib/components/ui/label";
import {
createCustomsBrokerConcept,
updateCustomsBrokerConcept,
type CustomsBrokerConcept
} from "$lib/api/dashboard/a76/general_catalogs/customs-broker-concepts";
import { companyStore } from "$lib/stores/company.svelte";
let {
open = $bindable(false),
item = null,
onSuccess
}: {
open: boolean;
item?: CustomsBrokerConcept | null;
onSuccess?: () => void;
} = $props();
const isEdit = $derived(!!item);
const title = $derived(isEdit ? "Editar Concepto AA" : "Nuevo Concepto AA");
// 3. Estado alineado al modelo de BD
let formData = $state({
broker_key: '',
concept: '',
amount: null as number | null,
priority: null as number | null
});
let loading = $state(false);
let error = $state<string | null>(null);
// 4. Cargar datos al editar
$effect(() => {
if (item) {
formData = {
broker_key: item.broker_key || '',
concept: item.concept || '',
amount: item.amount || null,
priority: item.priority || null
};
} else {
formData = {
broker_key: '',
concept: '',
amount: '',
priority: ''
};
}
});
async function handleSubmit() {
error = null;
loading = true;
try {
const companyId = companyStore.activeCompany?.id;
if (!companyId) throw new Error('No hay una compañía seleccionada');
// Validaciones
if (!formData.broker_key.trim()) throw new Error('La Clave AA es requerida');
if (!formData.concept.trim()) throw new Error('El Concepto es requerido');
// 5. Preparar datos con los tipos correctos (Números)
const dataToSend = {
broker_key: formData.broker_key.trim(),
concept: formData.concept.trim(),
amount: formData.amount ? Number(formData.amount) : null,
priority: formData.priority ? Number(formData.priority) : null
};
// 6. Corregida la sintaxis de llamada a la API
if (isEdit && item) {
// UPDATE: (id, data, companyId)
await updateCustomsBrokerConcept(item.id, dataToSend, companyId);
} else {
// CREATE: (data, companyId)
await createCustomsBrokerConcept(dataToSend, companyId);
}
open = false;
if (onSuccess) onSuccess();
} catch (e) {
error = e instanceof Error ? e.message : 'Error al guardar';
} finally {
loading = false;
}
}
</script>
<Dialog.Root bind:open>
<Dialog.Content class="max-w-md max-h-[90vh] overflow-y-auto">
<Dialog.Header>
<Dialog.Title>{title}</Dialog.Title>
<Dialog.Description>
{isEdit ? 'Modifica el concepto del agente aduanal' : 'Crea un nuevo concepto'}
</Dialog.Description>
</Dialog.Header>
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} class="space-y-4">
{#if error}
<div class="rounded-md bg-destructive/15 p-3 text-sm text-destructive">
{error}
</div>
{/if}
<div class="grid gap-4">
<div class="grid gap-2">
<Label for="broker_key">Clave AA <span class="text-destructive">*</span></Label>
<Input
id="broker_key"
bind:value={formData.broker_key}
placeholder="Ej: 550"
maxlength={5}
disabled={isEdit}
/>
</div>
<div class="grid gap-2">
<Label for="concept">Concepto <span class="text-destructive">*</span></Label>
<Input
id="concept"
bind:value={formData.concept}
placeholder="Ej: FLETE"
maxlength={15}
/>
</div>
<div class="grid gap-2">
<Label for="amount">Importe</Label>
<Input
id="amount"
type="number"
step="0.01"
bind:value={formData.amount}
placeholder="0.00"
/>
</div>
<div class="grid gap-2">
<Label for="priority">Prioridad</Label>
<Input
id="priority"
type="number"
bind:value={formData.priority}
placeholder="Ej: 1"
/>
</div>
</div>
<Dialog.Footer>
<Button type="button" variant="outline" onclick={() => open = false} disabled={loading}>
Cancelar
</Button>
<Button type="submit" disabled={loading}>
{loading ? 'Guardando...' : isEdit ? 'Actualizar' : 'Crear'}
</Button>
</Dialog.Footer>
</form>
</Dialog.Content>
</Dialog.Root>

View File

@@ -0,0 +1,141 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import {
createErrorClassification,
updateErrorClassification,
type ErrorClassification
} from '$lib/api/dashboard/a76/general_catalogs/error-catalogs'; // Ajusta la ruta
export let open: boolean = false;
export let classification: ErrorClassification | null = null;
const dispatch = createEventDispatcher();
let loading = false;
let formData = {
code: '',
level: ''
};
function formatDate(dateString?: string) {
if (!dateString) return 'N/A';
return new Date(dateString).toLocaleString('es-MX', {
year: 'numeric', month: 'short', day: 'numeric',
hour: '2-digit', minute: '2-digit'
});
}
$: if (classification) {
formData = {
code: classification.code,
level: classification.level || ''
};
} else {
formData = { code: '', level: '' };
}
async function handleSubmit() {
loading = true;
try {
if (classification?.id) {
await updateErrorClassification(classification.id, { level: formData.level });
} else {
await createErrorClassification(formData);
}
dispatch('save');
closeModal();
} catch (error) {
console.error('Error:', error);
alert('Error al guardar');
} finally {
loading = false;
}
}
function closeModal() {
open = false;
dispatch('close');
}
</script>
{#if open}
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm p-4">
<div class="w-full max-w-md rounded-xl bg-[#1a1a1a] border border-gray-700 shadow-2xl overflow-hidden">
<div class="bg-[#1a1a1a] px-6 py-4 border-b border-gray-700 flex justify-between items-center">
<h3 class="text-lg font-bold text-white">
{classification ? 'Editar Clasificación' : 'Nueva Clasificación'}
</h3>
<button on:click={closeModal} class="text-gray-400 hover:text-white transition-colors text-2xl">
&times;
</button>
</div>
<form on:submit|preventDefault={handleSubmit} class="p-6 space-y-5">
<div>
<label for="code" class="block text-sm font-medium text-gray-300 mb-1">Código *</label>
<input
type="text"
id="code"
bind:value={formData.code}
required
maxlength="100"
disabled={!!classification}
class="w-full rounded-lg bg-[#252525] border border-gray-600 text-white p-2.5 focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:opacity-50 disabled:cursor-not-allowed placeholder-gray-500"
placeholder="Ej. SYSTEM_ERROR"
/>
{#if classification}
<p class="text-xs text-gray-500 mt-1">El código no se puede cambiar.</p>
{/if}
</div>
<div>
<label for="level" class="block text-sm font-medium text-gray-300 mb-1">Nivel</label>
<input
type="text"
id="level"
bind:value={formData.level}
maxlength="3"
class="w-full rounded-lg bg-[#252525] border border-gray-600 text-white p-2.5 focus:ring-2 focus:ring-blue-500 focus:border-transparent placeholder-gray-500"
placeholder="Ej. CRT"
/>
</div>
{#if classification}
<div class="mt-4 p-3 rounded bg-gray-800/50 border border-gray-700 text-xs text-gray-400 space-y-1">
<div class="flex justify-between">
<span>Creado:</span>
<span class="text-gray-300">{formatDate(classification.created_at)}</span>
</div>
{#if classification.updated_at}
<div class="flex justify-between">
<span>Actualizado:</span>
<span class="text-gray-300">{formatDate(classification.updated_at)}</span>
</div>
{/if}
</div>
{/if}
<div class="flex justify-end gap-3 pt-4 border-t border-gray-700 mt-2">
<button
type="button"
on:click={closeModal}
class="px-4 py-2 text-sm font-medium text-gray-300 bg-transparent border border-gray-600 rounded-lg hover:bg-gray-800 hover:text-white transition-colors"
>
Cancelar
</button>
<button
type="submit"
disabled={loading}
class="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed shadow-lg shadow-blue-900/20"
>
{loading ? 'Guardando...' : (classification ? 'Guardar Cambios' : 'Crear')}
</button>
</div>
</form>
</div>
</div>
{/if}

View File

@@ -0,0 +1,166 @@
<script lang="ts">
import { Button } from "$lib/components/ui/button";
import * as Dialog from "$lib/components/ui/dialog";
import { Input } from "$lib/components/ui/input";
import { Label } from "$lib/components/ui/label";
import {
createINPC,
updateINPC,
type INPC
} from "$lib/api/dashboard/a76/general_catalogs/inpc";
import { companyStore } from "$lib/stores/company.svelte";
let {
open = $bindable(false),
item = null,
onSuccess
}: {
open: boolean;
item?: INPC | null;
onSuccess?: () => void;
} = $props();
const isEdit = $derived(!!item);
const title = $derived(isEdit ? "Editar INPC" : "Nuevo INPC");
// Estado del formulario
let formData = $state({
year: '',
month: '',
value: null as number | null
});
let loading = $state(false);
let error = $state<string | null>(null);
// Cargar datos al abrir
$effect(() => {
if (open) {
if (item) {
formData = {
year: item.year,
month: item.month,
value: item.value || null
};
} else {
formData = {
year: '',
month: '',
value: null
};
}
error = null;
}
});
async function handleSubmit() {
loading = true;
error = null;
try {
const companyId = companyStore.activeCompany?.id;
if (!companyId) throw new Error('No hay una compañía seleccionada');
// Validaciones
if (!formData.year.trim()) throw new Error('El año es requerido');
if (formData.year.length !== 4) throw new Error('El año debe tener 4 dígitos');
if (!formData.month.trim()) throw new Error('El mes es requerido');
// Preparar datos (limpios)
const dataToSend = {
year: formData.year.trim(),
month: formData.month.trim(),
value: formData.value // Ya es número o null
};
let response;
if (isEdit && item) {
response = await updateINPC(item.id, dataToSend, companyId);
} else {
response = await createINPC(dataToSend, companyId);
}
open = false;
if (onSuccess) onSuccess();
} catch (e) {
error = e instanceof Error ? e.message : 'Error al guardar el INPC';
} finally {
loading = false;
}
}
</script>
<Dialog.Root bind:open>
<Dialog.Content class="sm:max-w-[400px]">
<Dialog.Header>
<Dialog.Title>{title}</Dialog.Title>
</Dialog.Header>
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} class="space-y-4 py-4">
{#if error}
<div class="rounded-md bg-destructive/15 p-3 text-sm text-destructive">
{error}
</div>
{/if}
<div class="grid gap-4">
<div class="grid grid-cols-4 items-center gap-4">
<Label for="year" class="text-right">Año <span class="text-destructive">*</span></Label>
<div class="col-span-3">
<Input
id="year"
bind:value={formData.year}
placeholder="Ej: 2025"
maxlength={4}
disabled={loading}
required
/>
</div>
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label for="month" class="text-right">Mes <span class="text-destructive">*</span></Label>
<div class="col-span-3">
<Input
id="month"
bind:value={formData.month}
placeholder="Ej: 01"
maxlength={2}
disabled={loading}
required
/>
<p class="text-[10px] text-muted-foreground mt-1">Formato MM (Ej: 01, 12)</p>
</div>
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label for="value" class="text-right">Valor</Label>
<div class="col-span-3">
<Input
id="value"
type="number"
step="0.00000001"
bind:value={formData.value}
placeholder="0.0000"
disabled={loading}
/>
<p class="text-[10px] text-muted-foreground mt-1">Hasta 8 decimales</p>
</div>
</div>
</div>
<Dialog.Footer>
<Button type="button" variant="outline" onclick={() => open = false} disabled={loading}>
Cancelar
</Button>
<Button type="submit" disabled={loading}>
{loading ? 'Guardando...' : isEdit ? 'Actualizar' : 'Crear'}
</Button>
</Dialog.Footer>
</form>
</Dialog.Content>
</Dialog.Root>

View File

@@ -0,0 +1,149 @@
<script lang="ts">
import { Button } from "$lib/components/ui/button";
import * as Dialog from "$lib/components/ui/dialog";
import { Input } from "$lib/components/ui/input";
import { Label } from "$lib/components/ui/label";
import { Textarea } from "$lib/components/ui/textarea";
import {
createLegend,
updateLegend,
type Legend
} from "$lib/api/dashboard/a76/general_catalogs/legends";
import { companyStore } from "$lib/stores/company.svelte";
let {
open = $bindable(false),
item = null,
onSuccess
}: {
open: boolean;
item?: Legend | null;
onSuccess?: () => void;
} = $props();
const isEdit = $derived(!!item);
const title = $derived(isEdit ? "Editar Leyenda" : "Nueva Leyenda");
// Estado del formulario
// code es number | null para manejar el input type="number"
let formData = $state({
code: null as number | null,
description: ''
});
let loading = $state(false);
let error = $state<string | null>(null);
// Cargar datos al abrir
$effect(() => {
if (open) {
if (item) {
formData = {
code: item.code, // Es number
description: item.description || ''
};
} else {
formData = {
code: null,
description: ''
};
}
error = null;
}
});
async function handleSubmit() {
loading = true;
error = null;
try {
const companyId = companyStore.activeCompany?.id;
if (!companyId) throw new Error('No hay una compañía seleccionada');
// Validaciones
if (formData.code === null) throw new Error('La Clave (Código) es requerida');
// Preparar datos
const dataToSend = {
code: Number(formData.code), // Aseguramos que sea número
description: formData.description.trim() || undefined
};
let response;
if (isEdit && item) {
response = await updateLegend(item.id, dataToSend, companyId);
} else {
response = await createLegend(dataToSend, companyId);
}
open = false;
if (onSuccess) onSuccess();
} catch (e) {
error = e instanceof Error ? e.message : 'Error al guardar la leyenda';
} finally {
loading = false;
}
}
</script>
<Dialog.Root bind:open>
<Dialog.Content class="sm:max-w-[500px]">
<Dialog.Header>
<Dialog.Title>{title}</Dialog.Title>
</Dialog.Header>
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} class="space-y-4 py-4">
{#if error}
<div class="rounded-md bg-destructive/15 p-3 text-sm text-destructive">
{error}
</div>
{/if}
<div class="grid gap-4">
<div class="grid grid-cols-4 items-center gap-4">
<Label for="code" class="text-right">Clave <span class="text-destructive">*</span></Label>
<div class="col-span-3">
<Input
id="code"
type="number"
bind:value={formData.code}
placeholder="Ej: 10"
disabled={loading}
required
/>
<p class="text-[10px] text-muted-foreground mt-1">Debe ser un número entero.</p>
</div>
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label for="description" class="text-right">Descripción</Label>
<div class="col-span-3">
<Textarea
id="description"
bind:value={formData.description}
placeholder="Ej: Esta leyenda aplica para..."
maxlength={2000}
disabled={loading}
class="min-h-[100px]"
/>
<p class="text-[10px] text-muted-foreground mt-1 text-right">
{formData.description.length}/2000
</p>
</div>
</div>
</div>
<Dialog.Footer>
<Button type="button" variant="outline" onclick={() => open = false} disabled={loading}>
Cancelar
</Button>
<Button type="submit" disabled={loading}>
{loading ? 'Guardando...' : isEdit ? 'Actualizar' : 'Crear'}
</Button>
</Dialog.Footer>
</form>
</Dialog.Content>
</Dialog.Root>

View File

@@ -0,0 +1,152 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import {
createLocation,
updateLocation,
type Location
} from '$lib/api/dashboard/a76/general_catalogs/locations';
// Props
export let open: boolean = false;
export let location: Location | null = null;
const dispatch = createEventDispatcher();
let loading = false;
// Form Data
let formData = {
code: '',
description: ''
};
// Función para formatear fechas (auditoría)
function formatDate(dateString?: string) {
if (!dateString) return 'N/A';
return new Date(dateString).toLocaleString('es-MX', {
year: 'numeric', month: 'short', day: 'numeric',
hour: '2-digit', minute: '2-digit'
});
}
// Reactividad: Cargar datos si estamos editando
$: if (location) {
formData = {
code: location.code,
description: location.description || ''
};
} else {
// Limpiar si es nuevo
formData = { code: '', description: '' };
}
async function handleSubmit() {
loading = true;
try {
if (location?.id) {
// EDITAR
await updateLocation(location.id, {
code: formData.code,
description: formData.description
});
} else {
// CREAR
await createLocation({
code: formData.code,
description: formData.description
});
}
dispatch('success'); // Avisamos al padre para que recargue
closeModal();
} catch (error) {
console.error('Error guardando location:', error);
// Aquí puedes poner un toast de error si tienes
alert('Error al guardar la localización.');
} finally {
loading = false;
}
}
function closeModal() {
open = false;
dispatch('close');
}
</script>
{#if open}
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm p-4">
<div class="w-full max-w-md rounded-xl bg-[#1a1a1a] border border-gray-700 shadow-2xl overflow-hidden">
<div class="bg-[#1a1a1a] px-6 py-4 border-b border-gray-700 flex justify-between items-center">
<h3 class="text-lg font-bold text-white">
{location ? 'Editar Ubicación' : 'Nueva Ubicación'}
</h3>
<button on:click={closeModal} class="text-gray-400 hover:text-white transition-colors text-2xl">
&times;
</button>
</div>
<form on:submit|preventDefault={handleSubmit} class="p-6 space-y-5">
<div>
<label for="code" class="block text-sm font-medium text-gray-300 mb-1">Código *</label>
<input
type="text"
id="code"
bind:value={formData.code}
required
maxlength="5"
class="w-full rounded-lg bg-[#252525] border border-gray-600 text-white p-2.5 focus:ring-2 focus:ring-blue-500 focus:border-transparent placeholder-gray-500"
placeholder="Ej. VER"
/>
<p class="text-xs text-gray-500 mt-1 text-right">Máx. 5 caracteres</p>
</div>
<div>
<label for="description" class="block text-sm font-medium text-gray-300 mb-1">Descripción</label>
<textarea
id="description"
bind:value={formData.description}
rows="3"
maxlength="200"
class="w-full rounded-lg bg-[#252525] border border-gray-600 text-white p-2.5 focus:ring-2 focus:ring-blue-500 focus:border-transparent placeholder-gray-500 resize-none"
placeholder="Descripción de la ubicación..."
></textarea>
</div>
{#if location}
<div class="mt-4 p-3 rounded bg-gray-800/50 border border-gray-700 text-xs text-gray-400 space-y-1">
<div class="flex justify-between">
<span>Creado:</span>
<span class="text-gray-300">{formatDate(location.created_at)}</span>
</div>
{#if location.updated_at}
<div class="flex justify-between">
<span>Actualizado:</span>
<span class="text-gray-300">{formatDate(location.updated_at)}</span>
</div>
{/if}
</div>
{/if}
<div class="flex justify-end gap-3 pt-4 border-t border-gray-700 mt-2">
<button
type="button"
on:click={closeModal}
class="px-4 py-2 text-sm font-medium text-gray-300 bg-transparent border border-gray-600 rounded-lg hover:bg-gray-800 hover:text-white transition-colors"
>
Cancelar
</button>
<button
type="submit"
disabled={loading}
class="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed shadow-lg shadow-blue-900/20"
>
{loading ? 'Guardando...' : (location ? 'Guardar Cambios' : 'Crear')}
</button>
</div>
</form>
</div>
</div>
{/if}

View File

@@ -0,0 +1,196 @@
<script lang="ts">
import { Button } from "$lib/components/ui/button";
import * as Dialog from "$lib/components/ui/dialog";
import { Input } from "$lib/components/ui/input";
import { Label } from "$lib/components/ui/label";
// 👇 Verifica la ruta de tu archivo TS
import {
createMultiCurrencyType,
updateMultiCurrencyType,
type MultiCurrencyType
} from "$lib/api/dashboard/a76/general_catalogs/multi-currency-types";
import { companyStore } from "$lib/stores/company.svelte";
let {
open = $bindable(false),
item = null,
onSuccess
}: {
open: boolean;
item?: MultiCurrencyType | null;
onSuccess?: () => void;
} = $props();
const isEdit = $derived(!!item);
const title = $derived(isEdit ? "Editar Tipo de Cambio Múltiple" : "Nuevo Tipo de Cambio Múltiple");
// Estado del formulario
let formData = $state({
currency_type_code: '',
country_key: '',
conversion_factor: null as number | null,
date_str: '' // Usamos un string temporal para el input type="date"
});
let loading = $state(false);
let error = $state<string | null>(null);
// Cargar datos al abrir
$effect(() => {
if (open) {
if (item) {
// Truco: Convertir Entero (20251231) -> String ("2025-12-31")
let dateFormatted = '';
if (item.publication_date) {
const s = item.publication_date.toString();
if (s.length === 8) {
dateFormatted = `${s.slice(0, 4)}-${s.slice(4, 6)}-${s.slice(6, 8)}`;
}
}
formData = {
currency_type_code: item.currency_type_code,
country_key: item.country_key || '',
conversion_factor: item.conversion_factor,
date_str: dateFormatted
};
} else {
// Default: Fecha de hoy
formData = {
currency_type_code: '',
country_key: '',
conversion_factor: null,
date_str: new Date().toISOString().split('T')[0]
};
}
error = null;
}
});
async function handleSubmit() {
loading = true;
error = null;
try {
const companyId = companyStore.activeCompany?.id;
if (!companyId) throw new Error('No hay una compañía seleccionada');
// Validaciones
if (!formData.currency_type_code.trim()) throw new Error('El código de moneda es requerido');
if (!formData.date_str) throw new Error('La fecha de publicación es requerida');
// Truco: Convertir String ("2025-12-31") -> Entero (20251231)
// Quitamos los guiones y parseamos a int
const dateInt = parseInt(formData.date_str.replaceAll('-', ''), 10);
// Preparar datos
const dataToSend = {
currency_type_code: formData.currency_type_code.trim().toUpperCase(),
country_key: formData.country_key.trim().toUpperCase() || null,
conversion_factor: formData.conversion_factor ? Number(formData.conversion_factor) : null,
publication_date: dateInt // Mandamos el INT que espera Python
};
let response;
// 👇 companyId por fuera
if (isEdit && item) {
response = await updateMultiCurrencyType(item.id, dataToSend, companyId);
} else {
response = await createMultiCurrencyType(dataToSend, companyId);
}
open = false;
if (onSuccess) onSuccess();
} catch (e) {
error = e instanceof Error ? e.message : 'Error al guardar';
} finally {
loading = false;
}
}
</script>
<Dialog.Root bind:open>
<Dialog.Content class="sm:max-w-[500px]">
<Dialog.Header>
<Dialog.Title>{title}</Dialog.Title>
</Dialog.Header>
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} class="space-y-4 py-4">
{#if error}
<div class="rounded-md bg-destructive/15 p-3 text-sm text-destructive">
{error}
</div>
{/if}
<div class="grid gap-4">
<div class="grid grid-cols-4 items-center gap-4">
<Label for="currency_code" class="text-right">Moneda <span class="text-destructive">*</span></Label>
<div class="col-span-3">
<Input
id="currency_code"
bind:value={formData.currency_type_code}
placeholder="Ej: USD"
maxlength={3}
disabled={loading}
required
/>
<p class="text-[10px] text-muted-foreground mt-1">Código de moneda (FK).</p>
</div>
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label for="country_key" class="text-right">País</Label>
<div class="col-span-3">
<Input
id="country_key"
bind:value={formData.country_key}
placeholder="Ej: MEX"
maxlength={3}
disabled={loading}
/>
<p class="text-[10px] text-muted-foreground mt-1">Clave M3 del país (FK).</p>
</div>
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label for="pub_date" class="text-right">Fecha <span class="text-destructive">*</span></Label>
<div class="col-span-3">
<Input
id="pub_date"
type="date"
bind:value={formData.date_str}
disabled={loading}
required
/>
<p class="text-[10px] text-muted-foreground mt-1">Se guarda como entero (YYYYMMDD).</p>
</div>
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label for="factor" class="text-right">Factor</Label>
<div class="col-span-3">
<Input
id="factor"
type="number"
step="0.000001"
bind:value={formData.conversion_factor}
placeholder="0.000000"
disabled={loading}
/>
</div>
</div>
</div>
<Dialog.Footer>
<Button type="button" variant="outline" onclick={() => open = false} disabled={loading}>
Cancelar
</Button>
<Button type="submit" disabled={loading}>
{loading ? 'Guardando...' : isEdit ? 'Actualizar' : 'Crear'}
</Button>
</Dialog.Footer>
</form>
</Dialog.Content>
</Dialog.Root>

View File

@@ -0,0 +1,163 @@
<script lang="ts">
import { Button } from "$lib/components/ui/button";
import * as Dialog from "$lib/components/ui/dialog";
import { Input } from "$lib/components/ui/input";
import { Label } from "$lib/components/ui/label";
import { Textarea } from "$lib/components/ui/textarea"; // Para que quepa más texto en la firma
// 👇 Verifica tu ruta de importación
import {
createSignature,
updateSignature,
type Signature
} from "$lib/api/dashboard/a76/general_catalogs/signatures";
import { companyStore } from "$lib/stores/company.svelte";
let {
open = $bindable(false),
item = null,
onSuccess
}: {
open: boolean;
item?: Signature | null;
onSuccess?: () => void;
} = $props();
const isEdit = $derived(!!item);
const title = $derived(isEdit ? "Editar Firma" : "Nueva Firma");
// Estado del formulario
let formData = $state({
code: '',
signature: '',
photo_path: ''
});
let loading = $state(false);
let error = $state<string | null>(null);
// Cargar datos al abrir
$effect(() => {
if (open) {
if (item) {
formData = {
code: item.code,
signature: item.signature || '',
photo_path: item.photo_path || ''
};
} else {
formData = {
code: '',
signature: '',
photo_path: ''
};
}
error = null;
}
});
async function handleSubmit() {
loading = true;
error = null;
try {
const companyId = companyStore.activeCompany?.id;
if (!companyId) throw new Error('No hay una compañía seleccionada');
// Validaciones
if (!formData.code.trim()) throw new Error('El código es requerido');
// Preparar datos
const dataToSend = {
code: formData.code.trim(),
signature: formData.signature.trim() || null,
photo_path: formData.photo_path.trim() || null
};
let response;
// 👇 companyId por fuera (Argumento separado)
if (isEdit && item) {
response = await updateSignature(item.id, dataToSend, companyId);
} else {
response = await createSignature(dataToSend, companyId);
}
open = false;
if (onSuccess) onSuccess();
} catch (e) {
error = e instanceof Error ? e.message : 'Error al guardar la firma';
} finally {
loading = false;
}
}
</script>
<Dialog.Root bind:open>
<Dialog.Content class="sm:max-w-[500px]">
<Dialog.Header>
<Dialog.Title>{title}</Dialog.Title>
</Dialog.Header>
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} class="space-y-4 py-4">
{#if error}
<div class="rounded-md bg-destructive/15 p-3 text-sm text-destructive">
{error}
</div>
{/if}
<div class="grid gap-4">
<div class="grid grid-cols-4 items-center gap-4">
<Label for="code" class="text-right">Código <span class="text-destructive">*</span></Label>
<div class="col-span-3">
<Input
id="code"
bind:value={formData.code}
placeholder="Ej: REP_LEGAL"
maxlength={10}
disabled={loading || isEdit}
required
/>
<p class="text-[10px] text-muted-foreground mt-1">Máximo 10 caracteres.</p>
</div>
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label for="signature" class="text-right">Firma / Nombre</Label>
<div class="col-span-3">
<Textarea
id="signature"
bind:value={formData.signature}
placeholder="Ej: Juan Pérez - Representante Legal"
maxlength={1000}
disabled={loading}
/>
</div>
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label for="photo_path" class="text-right">Ruta Foto</Label>
<div class="col-span-3">
<Input
id="photo_path"
bind:value={formData.photo_path}
placeholder="Ej: /uploads/firmas/juan.png"
maxlength={1000}
disabled={loading}
/>
<p class="text-[10px] text-muted-foreground mt-1">Ruta del archivo (Texto).</p>
</div>
</div>
</div>
<Dialog.Footer>
<Button type="button" variant="outline" onclick={() => open = false} disabled={loading}>
Cancelar
</Button>
<Button type="submit" disabled={loading}>
{loading ? 'Guardando...' : isEdit ? 'Actualizar' : 'Crear'}
</Button>
</Dialog.Footer>
</form>
</Dialog.Content>
</Dialog.Root>

View File

@@ -0,0 +1,164 @@
<script lang="ts">
import { Button } from "$lib/components/ui/button";
import * as Dialog from "$lib/components/ui/dialog";
import { Input } from "$lib/components/ui/input";
import { Label } from "$lib/components/ui/label";
import {
createUnitConversion,
updateUnitConversion,
type UnitConversion
} from "$lib/api/dashboard/a76/general_catalogs/unit-conversions"; // 👈 Asegúrate que la ruta del archivo ts coincida
import { companyStore } from "$lib/stores/company.svelte";
let {
open = $bindable(false),
item = null,
onSuccess
}: {
open: boolean;
item?: UnitConversion | null;
onSuccess?: () => void;
} = $props();
const isEdit = $derived(!!item);
const title = $derived(isEdit ? "Editar Conversión" : "Nueva Conversión");
// Estado del formulario
let formData = $state({
from_unit_code: '',
to_unit_code: '',
conversion_factor: null as number | null
});
let loading = $state(false);
let error = $state<string | null>(null);
// Cargar datos al abrir
$effect(() => {
if (open) {
if (item) {
formData = {
from_unit_code: item.from_unit_code,
to_unit_code: item.to_unit_code,
conversion_factor: item.conversion_factor
};
} else {
formData = {
from_unit_code: '',
to_unit_code: '',
conversion_factor: null
};
}
error = null;
}
});
async function handleSubmit() {
loading = true;
error = null;
try {
const companyId = companyStore.activeCompany?.id;
if (!companyId) throw new Error('No hay una compañía seleccionada');
// Validaciones
if (!formData.from_unit_code.trim()) throw new Error('La unidad origen es requerida');
if (!formData.to_unit_code.trim()) throw new Error('La unidad destino es requerida');
if (formData.conversion_factor === null || formData.conversion_factor === undefined) throw new Error('El factor de conversión es requerido');
// Preparar datos
const dataToSend = {
from_unit_code: formData.from_unit_code.trim().toUpperCase(), // Normalizamos a mayúsculas
to_unit_code: formData.to_unit_code.trim().toUpperCase(),
conversion_factor: Number(formData.conversion_factor)
};
let response;
// 👇 companyId por fuera (Argumento separado)
if (isEdit && item) {
response = await updateUnitConversion(item.id, dataToSend, companyId);
} else {
response = await createUnitConversion(dataToSend, companyId);
}
open = false;
if (onSuccess) onSuccess();
} catch (e) {
error = e instanceof Error ? e.message : 'Error al guardar la conversión';
} finally {
loading = false;
}
}
</script>
<Dialog.Root bind:open>
<Dialog.Content class="sm:max-w-[500px]">
<Dialog.Header>
<Dialog.Title>{title}</Dialog.Title>
</Dialog.Header>
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} class="space-y-4 py-4">
{#if error}
<div class="rounded-md bg-destructive/15 p-3 text-sm text-destructive">
{error}
</div>
{/if}
<div class="grid gap-4">
<div class="grid grid-cols-4 items-center gap-4">
<Label for="from_code" class="text-right">De Unidad <span class="text-destructive">*</span></Label>
<div class="col-span-3">
<Input
id="from_code"
bind:value={formData.from_unit_code}
placeholder="Ej: KGM"
maxlength={5}
disabled={loading}
/>
<p class="text-[10px] text-muted-foreground mt-1">Código de la unidad origen.</p>
</div>
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label for="to_code" class="text-right">A Unidad <span class="text-destructive">*</span></Label>
<div class="col-span-3">
<Input
id="to_code"
bind:value={formData.to_unit_code}
placeholder="Ej: LBR"
maxlength={5}
disabled={loading}
/>
<p class="text-[10px] text-muted-foreground mt-1">Código de la unidad destino.</p>
</div>
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label for="factor" class="text-right">Factor <span class="text-destructive">*</span></Label>
<div class="col-span-3">
<Input
id="factor"
type="number"
step="0.000001"
bind:value={formData.conversion_factor}
placeholder="Ej: 2.20462"
disabled={loading}
/>
<p class="text-[10px] text-muted-foreground mt-1">Numeric(13, 6).</p>
</div>
</div>
</div>
<Dialog.Footer>
<Button type="button" variant="outline" onclick={() => open = false} disabled={loading}>
Cancelar
</Button>
<Button type="submit" disabled={loading}>
{loading ? 'Guardando...' : isEdit ? 'Actualizar' : 'Crear'}
</Button>
</Dialog.Footer>
</form>
</Dialog.Content>
</Dialog.Root>

View File

@@ -1,139 +1,178 @@
<script lang="ts">
import { Button } from "$lib/components/ui/button";
import * as Dialog from "$lib/components/ui/dialog";
import { Input } from "$lib/components/ui/input";
import { Label } from "$lib/components/ui/label";
import { Button } from "$lib/components/ui/button";
import * as Dialog from "$lib/components/ui/dialog";
import { Input } from "$lib/components/ui/input";
import { Label } from "$lib/components/ui/label";
import { Textarea } from "$lib/components/ui/textarea";
import { createIdentifier, updateIdentifier, type Identifier } from "$lib/api/dashboard/a76/general_catalogs/identifiers";
// 👇 Importar tipos correctos
import {
createIdentifier,
updateIdentifier,
type Identifier
} from "$lib/api/dashboard/a76/general_catalogs/identifiers";
import { companyStore } from "$lib/stores/company.svelte";
let {
open = $bindable(false),
mode = 'create',
item = null,
onSuccess
}: {
open: boolean;
mode?: 'create' | 'edit';
item?: Identifier | null;
onSuccess?: () => void;
} = $props();
let {
open = $bindable(false),
item = null,
onSuccess
}: {
open: boolean;
item?: Identifier | null;
onSuccess?: () => void;
} = $props();
const isEdit = $derived(mode === 'edit');
const title = $derived(isEdit ? "Editar Identificador" : "Nuevo Identificador");
// Calculamos si es edición basado en si hay item
const isEdit = $derived(!!item);
const title = $derived(isEdit ? "Editar Identificador" : "Nuevo Identificador");
let formData = $state({
code: '',
description: '',
let formData = $state({
code: '',
description: '',
level: '',
complement: ''
});
});
let loading = $state(false);
let error = $state<string | null>(null);
let loading = $state(false);
let error = $state<string | null>(null);
$effect(() => {
if (open) {
if (isEdit && item) {
formData = {
code: item.code,
description: item.description || '',
// Efecto para cargar o limpiar datos
$effect(() => {
if (open) {
if (item) {
formData = {
code: item.code,
description: item.description || '',
level: item.level || '',
complement: item.complement || ''
};
} else {
formData = {
code: '',
description: '',
};
} else {
formData = {
code: '',
description: '',
level: '',
complement: ''
};
}
error = null;
}
});
async function handleSubmit() {
const companyId = companyStore.activeCompany?.id;
if (!companyId) {
error = 'No hay compañía seleccionada';
return;
};
}
error = null;
}
});
loading = true;
error = null;
async function handleSubmit() {
loading = true;
error = null;
try {
let response;
if (isEdit && item) {
response = await updateIdentifier(item.id, {
code: formData.code,
description: formData.description || null,
level: formData.level || null,
complement: formData.complement || null
});
} else {
response = await createIdentifier({
code: formData.code,
description: formData.description || null,
level: formData.level || null,
complement: formData.complement || null,
company_id: companyId
});
}
try {
const companyId = companyStore.activeCompany?.id;
if (!companyId) throw new Error('No hay una compañía seleccionada');
if (response.error) {
error = response.error;
return;
}
// Validaciones básicas
if (!formData.code.trim()) throw new Error('La clave es requerida');
open = false;
if (onSuccess) onSuccess();
} catch (e) {
error = 'Error de conexión';
console.error(e);
} finally {
loading = false;
}
}
// Preparar payload (SIN company_id adentro)
const dataToSend = {
code: formData.code.trim(),
description: formData.description.trim() || null,
level: formData.level.trim() || null,
complement: formData.complement.trim() || null
};
let response;
// 👇 AQUI ESTA EL CAMBIO IMPORTANTE: companyId va por fuera
if (isEdit && item) {
response = await updateIdentifier(item.id, dataToSend, companyId);
} else {
response = await createIdentifier(dataToSend, companyId);
}
if (response.error) {
throw new Error(response.error);
}
open = false;
if (onSuccess) onSuccess();
} catch (e) {
error = e instanceof Error ? e.message : 'Error al guardar';
} finally {
loading = false;
}
}
</script>
<Dialog.Root bind:open>
<Dialog.Content class="sm:max-w-[425px]">
<Dialog.Header>
<Dialog.Title>{title}</Dialog.Title>
</Dialog.Header>
<div class="grid gap-4 py-4">
{#if error}
<div class="text-red-500 text-sm mb-2">{error}</div>
{/if}
<div class="grid grid-cols-4 items-center gap-4">
<Label for="code" class="text-right">Clave</Label>
<Input id="code" bind:value={formData.code} class="col-span-3" disabled={loading} />
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label for="description" class="text-right">Descripción</Label>
<Textarea id="description" bind:value={formData.description} class="col-span-3" disabled={loading} />
</div>
<Dialog.Content class="sm:max-w-[500px]">
<Dialog.Header>
<Dialog.Title>{title}</Dialog.Title>
</Dialog.Header>
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} class="space-y-4 py-4">
{#if error}
<div class="rounded-md bg-destructive/15 p-3 text-sm text-destructive">
{error}
</div>
{/if}
<div class="grid gap-4">
<div class="grid grid-cols-4 items-center gap-4">
<Label for="code" class="text-right">Clave <span class="text-destructive">*</span></Label>
<div class="col-span-3">
<Input
id="code"
bind:value={formData.code}
disabled={loading || isEdit}
placeholder="Ej: CI"
maxlength={2}
required
/>
<p class="text-[10px] text-muted-foreground mt-1">Máximo 2 caracteres.</p>
</div>
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label for="description" class="text-right">Descripción</Label>
<Textarea
id="description"
bind:value={formData.description}
class="col-span-3"
disabled={loading}
maxlength={1000}
/>
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label for="level" class="text-right">Nivel</Label>
<Input id="level" bind:value={formData.level} class="col-span-3" disabled={loading} />
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label for="level" class="text-right">Nivel</Label>
<div class="col-span-3">
<Input
id="level"
bind:value={formData.level}
disabled={loading}
placeholder="Ej: G"
maxlength={1}
/>
<p class="text-[10px] text-muted-foreground mt-1">Máximo 1 caracter (G, S, etc).</p>
</div>
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label for="complement" class="text-right">Complemento</Label>
<Textarea id="complement" bind:value={formData.complement} class="col-span-3" disabled={loading} />
</div>
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label for="complement" class="text-right">Complemento</Label>
<Textarea
id="complement"
bind:value={formData.complement}
class="col-span-3"
disabled={loading}
maxlength={5000}
/>
</div>
</div>
<Dialog.Footer>
<Button type="submit" onclick={handleSubmit} disabled={loading}>
{loading ? 'Guardando...' : 'Guardar'}
</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Root>
<Dialog.Footer>
<Button type="button" variant="outline" onclick={() => open = false} disabled={loading}>
Cancelar
</Button>
<Button type="submit" disabled={loading}>
{loading ? 'Guardando...' : isEdit ? 'Actualizar' : 'Crear'}
</Button>
</Dialog.Footer>
</form>
</Dialog.Content>
</Dialog.Root>

View File

@@ -3,7 +3,7 @@
import * as Dialog from "$lib/components/ui/dialog";
import { Input } from "$lib/components/ui/input";
import { Label } from "$lib/components/ui/label";
import { createPackage, updatePackage, type Package, type PackageCreate, type PackageUpdate } from "$lib/api/dashboard/a76/general_catalogs/packages";
import { createPackage, updatePackage, type Package} from "$lib/api/dashboard/a76/general_catalogs/packages";
import { companyStore } from "$lib/stores/company.svelte";
let {
@@ -95,9 +95,9 @@
let response;
if (isEdit && item) {
response = await updatePackage(item.id, dataToSend);
response = await updatePackage(item.id, dataToSend, companyId);
} else {
response = await createPackage({ ...dataToSend, company_id: companyId });
response = await createPackage(dataToSend, companyId);
}
if (response.error) {

View File

@@ -1,26 +1,26 @@
<script lang="ts" generics="TData, TValue">
import { onMount } from 'svelte';
import {
type ColumnDef,
getCoreRowModel
getCoreRowModel,
} from "@tanstack/table-core";
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<TData, TValue> = {
columns: ColumnDef<TData, TValue>[];
data: TData[];
loading: boolean;
hasMore: boolean;
loadMore: () => void;
pageCount: number;
totalItems: number;
};
let {
data,
columns,
loading,
hasMore,
loadMore
pageCount,
totalItems
}: DataTableProps<TData, TValue> = $props();
const table = createSvelteTable({
@@ -28,96 +28,79 @@
return data;
},
columns,
getCoreRowModel: getCoreRowModel()
getCoreRowModel: getCoreRowModel(),
manualPagination: true,
pageCount: pageCount,
});
let scrollContainer = $state<HTMLDivElement>();
let loadingTrigger = $state<HTMLDivElement>();
// Intersection Observer para detectar cuando el usuario llega al final
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();
};
});
function handlePageChange(newPage: number) {
const url = new URL($page.url);
url.searchParams.set('page', newPage.toString());
goto(url);
}
</script>
<div class="w-full">
<div class="rounded-md border max-h-[600px] overflow-y-auto" bind:this={scrollContainer}>
<Table.Root>
<Table.Header class="sticky top-0 bg-background z-10">
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
<Table.Row>
{#each headerGroup.headers as header (header.id)}
<Table.Head>
{#if !header.isPlaceholder}
<FlexRender
content={header.column.columnDef.header}
context={header.getContext()}
/>
{/if}
</Table.Head>
{/each}
</Table.Row>
{/each}
</Table.Header>
<Table.Body>
{#each table.getRowModel().rows as row (row.id)}
<Table.Row data-state={row.getIsSelected() && "selected"}>
{#each row.getVisibleCells() as cell (cell.id)}
<Table.Cell>
<div class="rounded-md border">
<Table.Root>
<Table.Header>
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
<Table.Row>
{#each headerGroup.headers as header (header.id)}
<Table.Head>
{#if !header.isPlaceholder}
<FlexRender
content={cell.column.columnDef.cell}
context={cell.getContext()}
content={header.column.columnDef.header}
context={header.getContext()}
/>
</Table.Cell>
{/each}
</Table.Row>
{:else}
<Table.Row>
<Table.Cell colspan={columns.length} class="h-24 text-center">
No hay resultados.
{/if}
</Table.Head>
{/each}
</Table.Row>
{/each}
</Table.Header>
<Table.Body>
{#each table.getRowModel().rows as row (row.id)}
<Table.Row data-state={row.getIsSelected() && "selected"}>
{#each row.getVisibleCells() as cell (cell.id)}
<Table.Cell>
<FlexRender
content={cell.column.columnDef.cell}
context={cell.getContext()}
/>
</Table.Cell>
</Table.Row>
{/each}
<!-- Loading Trigger - Se activa cuando es visible -->
{#if hasMore}
<Table.Row>
<Table.Cell colspan={columns.length} class="h-20 text-center">
<div bind:this={loadingTrigger}>
{#if loading}
<div class="flex items-center justify-center gap-2">
<div class="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent"></div>
<span class="text-muted-foreground text-sm">Cargando más...</span>
</div>
{:else}
<div class="text-muted-foreground text-sm">
Desplázate para cargar más
</div>
{/if}
</div>
</Table.Cell>
</Table.Row>
{/if}
</Table.Body>
</Table.Root>
</div>
{/each}
</Table.Row>
{:else}
<Table.Row>
<Table.Cell colspan={columns.length} class="h-24 text-center">
No hay resultados.
</Table.Cell>
</Table.Row>
{/each}
</Table.Body>
</Table.Root>
</div>
<div class="flex items-center justify-end space-x-2 py-4">
<div class="flex-1 text-sm text-muted-foreground">
Total: {totalItems}
</div>
<div class="space-x-2">
<Button
variant="outline"
size="sm"
onclick={() => handlePageChange(Number($page.url.searchParams.get('page') || 1) - 1)}
disabled={Number($page.url.searchParams.get('page') || 1) <= 1}
>
Anterior
</Button>
<Button
variant="outline"
size="sm"
onclick={() => handlePageChange(Number($page.url.searchParams.get('page') || 1) + 1)}
disabled={Number($page.url.searchParams.get('page') || 1) >= pageCount}
>
Siguiente
</Button>
</div>
</div>

View File

@@ -1,6 +1,7 @@
<script lang="ts">
import { page } from '$app/stores';
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
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';
@@ -81,4 +82,9 @@
totalItems={data.classifications?.total || 0}
/>
</div>
<CreateDialog
bind:open={dialogOpen}
title="Crear Nueva Clasificación de Concepto"
on:success={handleSuccess}
/>
</div>

View File

@@ -1,8 +1,8 @@
<script lang="ts">
import { page } from '$app/stores';
import DataTable from '$lib/components/dashboard/units_of_measure/ace/data-table.svelte';
import { createColumns } from '$lib/components/dashboard/company/columns';
import CreateEditDialog from '$lib/components/dashboard/company/create-edit-dialog.svelte';
import { createColumns } from '$lib/components/dashboard/general_catalogs/company/columns.js';
import CreateEditDialog from '$lib/components/dashboard/general_catalogs/company/create-edit-dialog.svelte';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Plus } from 'lucide-svelte';

View File

@@ -1,6 +1,7 @@
<script lang="ts">
import { page } from '$app/stores';
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
import CreateEditDialog from '$lib/components/dashboard/general_catalogs/concepts/create-edit-dialog.svelte';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Plus } from 'lucide-svelte';
@@ -82,4 +83,9 @@
totalItems={data.concepts?.total || 0}
/>
</div>
<CreateEditDialog
bind:open={dialogOpen}
on:success={handleSuccess}
/>
</div>

View File

@@ -1,6 +1,7 @@
<script lang="ts">
import { page } from '$app/stores';
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
import CreateEditDialog from '$lib/components/dashboard/general_catalogs/customs_broker_concepts/create-edite-dialoge.svelte';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Plus } from 'lucide-svelte';
@@ -82,4 +83,9 @@
totalItems={data.concepts?.total || 0}
/>
</div>
<CreateEditDialog
bind:open={dialogOpen}
on:success={handleSuccess}
/>
</div>

View File

@@ -1,11 +1,13 @@
<script lang="ts">
import { page } from '$app/stores';
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
import CreateDialog from '$lib/components/dashboard/general_catalogs/error_catalogs/create-edite-dialoge.svelte';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Plus } from 'lucide-svelte';
import { goto } from '$app/navigation';
import { browser } from '$app/environment';
import CreateEditDialog from '$lib/components/dashboard/classes/create-edit-dialog.svelte';
let { data } = $props();
let dialogOpen = $state(false);
@@ -81,4 +83,11 @@
totalItems={data.errors?.total || 0}
/>
</div>
<CreateDialog
open={dialogOpen}
on:close={() => dialogOpen = false}
on:success={handleSuccess}
/>
</div>

View File

@@ -211,11 +211,13 @@
{hasMore}
{loadMore}
/>
<CreateEditDialog
bind:open={dialogOpen}
item={editingItem}
onSuccess={handleSuccess}
/>
</div>
<CreateEditDialog
bind:open={dialogOpen}
item={editingItem}
onOpenChange={(open) => (dialogOpen = open)}
onSuccess={handleSuccess}
/>

View File

@@ -1,6 +1,7 @@
<script lang="ts">
import { page } from '$app/stores';
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
import CreateDialog from '$lib/components/dashboard/general_catalogs/inpc/create-edite-dialoge.svelte';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Plus } from 'lucide-svelte';
@@ -81,5 +82,10 @@
pageCount={data.inpcs?.pages || 0}
totalItems={data.inpcs?.total || 0}
/>
<CreateDialog
bind:open={dialogOpen}
on:success={handleSuccess}
/>
</div>
</div>

View File

@@ -1,6 +1,7 @@
<script lang="ts">
import { page } from '$app/stores';
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
import CreateDialog from '$lib/components/dashboard/general_catalogs/legend/create-edite-dialoge.svelte';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Plus } from 'lucide-svelte';
@@ -81,4 +82,9 @@
totalItems={data.legends?.total || 0}
/>
</div>
<CreateDialog
bind:open={dialogOpen}
on:success={handleSuccess}
/>
</div>

View File

@@ -1,48 +1,60 @@
<script lang="ts">
import { invalidateAll } from '$app/navigation';
import { createColumns } from '$lib/components/dashboard/locations/columns';
import DataTable from '$lib/components/dashboard/locations/data-table.svelte';
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { RefreshCw } from 'lucide-svelte';
import type { PageData } from './$types';
import { invalidateAll } from '$app/navigation';
import { createColumns } from '$lib/components/dashboard/locations/columns';
import CreateDialog from '$lib/components/dashboard/general_catalogs/locations/create-edite-dialog.svelte';
import DataTable from '$lib/components/dashboard/locations/data-table.svelte';
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { RefreshCw } from 'lucide-svelte';
import { Plus } from 'lucide-svelte';
import type { PageData } from './$types';
let { data }: { data: PageData } = $props();
let { data }: { data: PageData } = $props();
let loading = $state(false);
let loading = $state(false);
let dialogOpen = $state(false);
const columns = createColumns();
const columns = createColumns();
async function refreshData() {
loading = true;
await invalidateAll();
loading = false;
}
async function refreshData() {
loading = true;
await invalidateAll();
loading = false;
}
</script>
<div class="flex flex-col gap-4 p-4">
<div class="flex items-center justify-between">
<div>
<h2 class="text-2xl font-bold tracking-tight">Ubicaciones</h2>
<p class="text-muted-foreground">
Catálogo de ubicaciones extraídas de puertos
</p>
</div>
<div class="flex items-center gap-2">
<Button variant="outline" size="icon" onclick={refreshData} disabled={loading}>
<RefreshCw class="h-4 w-4 {loading ? 'animate-spin' : ''}" />
</Button>
</div>
</div>
<div class="flex items-center justify-between">
<div>
<h2 class="text-2xl font-bold tracking-tight">Ubicaciones</h2>
<p class="text-muted-foreground">
Catálogo de ubicaciones extraídas de puertos
</p>
</div>
<div class="flex items-center gap-2">
<Button onclick={() => dialogOpen = true}>
<Plus class="mr-2 h-4 w-4" />
Nueva Localización
</Button>
</div>
</div>
<Card.Root>
<Card.Content class="p-0">
<DataTable
data={data.items}
{columns}
pageCount={data.pages}
<Card.Root>
<Card.Content class="p-0">
<DataTable
data={data.items}
{columns}
pageCount={data.pages}
totalItems={data.total}
/>
</Card.Content>
</Card.Root>
</div>
/>
</Card.Content>
</Card.Root>
<CreateDialog
bind:open={dialogOpen}
on:success={refreshData}
/>
</div>

View File

@@ -1,6 +1,7 @@
<script lang="ts">
import { page } from '$app/stores';
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
import CreateDialog from '$lib/components/dashboard/general_catalogs/multi_currency_types/create-edit-dialog.svelte';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Plus } from 'lucide-svelte';
@@ -81,4 +82,10 @@
totalItems={data.types?.total || 0}
/>
</div>
<CreateDialog
bind:open={dialogOpen}
on:success={handleSuccess}
/>
</div>

View File

@@ -1,6 +1,7 @@
<script lang="ts">
import { page } from '$app/stores';
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
import CreateEditDialog from '$lib/components/dashboard/general_catalogs/signatures/create-edit-dialog.svelte';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Plus } from 'lucide-svelte';
@@ -10,7 +11,7 @@
let { data } = $props();
let dialogOpen = $state(false);
// Filtros
let searchName = $state($page.url.searchParams.get('name') || '');
let searchPosition = $state($page.url.searchParams.get('position') || '');
let timeout: ReturnType<typeof setTimeout>;
@@ -82,4 +83,10 @@
totalItems={data.signatures?.total || 0}
/>
</div>
<CreateEditDialog
bind:open={dialogOpen}
title="Crear Nueva Firma Electrónica"
on:success={handleSuccess}
/>
</div>

View File

@@ -1,6 +1,7 @@
<script lang="ts">
import { page } from '$app/stores';
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
import CreateDialogEdit from '$lib/components/dashboard/general_catalogs/unit-conversion/create-edite-dialoge.svelte';
import { Button } from '$lib/components/ui/button';
import { Plus } from 'lucide-svelte';
import { goto } from '$app/navigation';
@@ -43,4 +44,10 @@
totalItems={data.conversions?.total || 0}
/>
</div>
<CreateDialogEdit
open={dialogOpen}
on:close={() => dialogOpen = false}
on:success={handleSuccess}
/>
</div>