diff --git a/.gitignore b/.gitignore index 48bbc048..2db7f63a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Python __pycache__/ +.mypy_cache/ *.py[cod] *$py.class *.so @@ -48,13 +49,15 @@ logs/ *.sqlite3 # Testing +backend/app_data/ +.mypy_cache/ .pytest_cache/ .coverage htmlcov/ -backend/app_data/ # Node (para frontend) -node_modules/ +**/node_modules/ +**/.svelte-kit/ .npm .yarn @@ -63,3 +66,4 @@ node_modules/ postgres-data/ backend/uploads/ docker-compose.yml +.mypy_cache/ diff --git a/backend/api/v1/common/tenant_crud_routes.py b/backend/api/v1/common/tenant_crud_routes.py index 91b23321..98f4b184 100644 --- a/backend/api/v1/common/tenant_crud_routes.py +++ b/backend/api/v1/common/tenant_crud_routes.py @@ -3,7 +3,7 @@ import logging from core.database import get_core_db from core.security import get_current_user, validate_access_to_resource -from fastapi import APIRouter, Body, Depends, HTTPException, Path, Query +from fastapi import APIRouter, Body, Depends, HTTPException, Path, Query, Request from pydantic import BaseModel from sqlalchemy.orm import Session @@ -137,6 +137,7 @@ class TenantCRUDRoutes( description=f"Get paginated list of {self.resource_name}s with optional filters", ) async def list_resources( + request: Request, company_id: int = Query(..., description="Company ID"), page: int = Query(1, ge=1, description="Page number"), page_size: int = Query( @@ -145,13 +146,6 @@ class TenantCRUDRoutes( le=self.max_page_size, description="Page size", ), - status: Optional[str] = Query(None, description="Filter by status"), - operation_type: Optional[str] = Query( - None, description="Filter by operation type" - ), - invoice_type: Optional[str] = Query( - None, description="Filter by invoice type" - ), db: Session = Depends(self.db_dependency), current_user: Dict[str, Any] = Depends(self.auth_dependency), ): @@ -164,13 +158,15 @@ class TenantCRUDRoutes( ) skip = (page - 1) * page_size - filters = {} - if status: - filters["status"] = status - if operation_type: - filters["operation_type"] = operation_type - if invoice_type: - filters["invoice_type"] = invoice_type + + # Extraer todos los parámetros de búsqueda dinámicamente + # Excluimos los parámetros estándar de paginación y control + standard_params = {"company_id", "page", "page_size"} + filters = { + k: v + for k, v in request.query_params.items() + if k not in standard_params and v is not None and v != "" + } items, total = self.service.get_all( db, tenant_id, company_id, skip, page_size, filters diff --git a/backend/api/v1/modules/a76/clients_and_providers/dto.py b/backend/api/v1/modules/a76/clients_and_providers/dto.py index 624c99f1..292d9e41 100644 --- a/backend/api/v1/modules/a76/clients_and_providers/dto.py +++ b/backend/api/v1/modules/a76/clients_and_providers/dto.py @@ -46,7 +46,7 @@ class ClientProviderProgramsDTO(BaseModel): program_number: Optional[str] = Field( None, max_length=40, description="Program number" ) - prosec: Optional[int] = Field(None, description="PROSEC") + prosec: Optional[str] = Field(None, max_length=8, description="PROSEC") prosec_authorization: Optional[str] = Field( None, max_length=20, description="PROSEC authorization" ) diff --git a/backend/api/v1/modules/a76/clients_and_providers/models.py b/backend/api/v1/modules/a76/clients_and_providers/models.py index 4cf08fe0..db5544ca 100644 --- a/backend/api/v1/modules/a76/clients_and_providers/models.py +++ b/backend/api/v1/modules/a76/clients_and_providers/models.py @@ -136,7 +136,7 @@ class ClientProviderPrograms(Base, TenantScopedMixin, TimestampMixin): # Program information program: Mapped[Optional[str]] = mapped_column(String(7)) program_number: Mapped[Optional[str]] = mapped_column(String(40)) - prosec: Mapped[Optional[int]] = mapped_column(SmallInteger) + prosec: Mapped[Optional[str]] = mapped_column(String(8)) prosec_authorization: Mapped[Optional[str]] = mapped_column(String(20)) secon_auth_date: Mapped[Optional[int]] = mapped_column(Integer) manufacturer_id: Mapped[Optional[str]] = mapped_column(String(25)) diff --git a/backend/api/v1/modules/a76/customs_brokers/dto.py b/backend/api/v1/modules/a76/customs_brokers/dto.py index e7f38cba..9f1309c7 100644 --- a/backend/api/v1/modules/a76/customs_brokers/dto.py +++ b/backend/api/v1/modules/a76/customs_brokers/dto.py @@ -43,7 +43,7 @@ class CustomsBrokerResponseDTO(CustomsBrokerBaseDTO): broker_key: str tenant_id: int company_id: int - vu: Optional["CustomsBrokerVUCreateDTO"] = None + vu: Optional["CustomsBrokerVUResponseDTO"] = None class Config: from_attributes = True @@ -95,6 +95,11 @@ class CustomsBrokerVUCreateDTO(BaseModel): doda_web_service_access_key: Optional[str] = None doda_fiel_access_key: Optional[str] = None doda_xml_files_path: Optional[str] = None + tenant_id: Optional[int] = None + company_id: Optional[int] = None + +class CustomsBrokerVUResponseDTO(CustomsBrokerVUCreateDTO): + customs_broker_id: int class Config: from_attributes = True @@ -112,6 +117,8 @@ class CustomsBrokerPersonnelDTO(BaseModel): last_name: Optional[str] = None middle_name: Optional[str] = None email: Optional[str] = None + tenant_id: Optional[int] = None + company_id: Optional[int] = None class Config: from_attributes = True diff --git a/backend/api/v1/modules/a76/customs_brokers/routes.py b/backend/api/v1/modules/a76/customs_brokers/routes.py index e968ac78..541f41d9 100644 --- a/backend/api/v1/modules/a76/customs_brokers/routes.py +++ b/backend/api/v1/modules/a76/customs_brokers/routes.py @@ -62,7 +62,7 @@ def update_customs_broker( @router.put( "/customs-broker-vu/{broker_key}", - response_model=dto.CustomsBrokerVUCreateDTO, + response_model=dto.CustomsBrokerVUResponseDTO, ) def update_customs_broker_vu( broker_key: str, @@ -77,7 +77,7 @@ def update_customs_broker_vu( if not broker: raise HTTPException(status_code=404, detail="Customs Broker not found") - updated_vu = services.CustomsBrokerVUService.update_vu(db, broker_key, vu_data) + updated_vu = services.CustomsBrokerVUService.update_vu(db, broker_key, vu_data, tenant_id, company_id) if not updated_vu: raise HTTPException(status_code=404, detail="Customs Broker VU not found") return updated_vu @@ -102,7 +102,7 @@ def update_customs_broker_personnel( raise HTTPException(status_code=404, detail="Customs Broker not found") updated_personnel = services.CustomsBrokerPersonnelService.update_personnel( - db, broker_key, line, personnel_data + db, broker_key, line, personnel_data, tenant_id, company_id ) if not updated_personnel: raise HTTPException( diff --git a/backend/api/v1/modules/a76/customs_brokers/services.py b/backend/api/v1/modules/a76/customs_brokers/services.py index c1c10d68..dc00f352 100644 --- a/backend/api/v1/modules/a76/customs_brokers/services.py +++ b/backend/api/v1/modules/a76/customs_brokers/services.py @@ -90,9 +90,17 @@ class CustomsBrokerVUService: return new_vu @staticmethod - def update_vu(db: Session, broker_key: str, vu_data: dto.CustomsBrokerVUCreateDTO): + def update_vu(db: Session, broker_key: str, vu_data: dto.CustomsBrokerVUCreateDTO, tenant_id: int, company_id: int): # We need the custom broker ID to insert a new VU - broker = db.query(models.CustomsBroker).filter(models.CustomsBroker.broker_key == broker_key).first() + broker = ( + db.query(models.CustomsBroker) + .filter( + models.CustomsBroker.broker_key == broker_key, + models.CustomsBroker.tenant_id == tenant_id, + models.CustomsBroker.company_id == company_id, + ) + .first() + ) if not broker: return None @@ -108,6 +116,8 @@ class CustomsBrokerVUService: else: # Create new new_vu_data = vu_data.model_dump() + new_vu_data["tenant_id"] = tenant_id + new_vu_data["company_id"] = company_id new_vu = models.CustomsBrokerVU(customs_broker_id=broker.id, **new_vu_data) db.add(new_vu) db.commit() @@ -125,24 +135,36 @@ class CustomsBrokerVUService: class CustomsBrokerPersonnelService: @staticmethod - def get_by_broker_key_and_line(db: Session, broker_key: str, line: int): + def get_by_broker_key_and_line(db: Session, broker_key: str, line: int, tenant_id: int, company_id: int): return ( db.query(models.CustomsBrokerPersonnel) .join(models.CustomsBroker) .filter( models.CustomsBroker.broker_key == broker_key, models.CustomsBrokerPersonnel.line == line, + models.CustomsBroker.tenant_id == tenant_id, + models.CustomsBroker.company_id == company_id, ) .first() ) @staticmethod - def create_personnel(db: Session, broker_key: str, personnel_data: dto.CustomsBrokerPersonnelDTO): - broker = db.query(models.CustomsBroker).filter(models.CustomsBroker.broker_key == broker_key).first() + def create_personnel(db: Session, broker_key: str, personnel_data: dto.CustomsBrokerPersonnelDTO, tenant_id: int, company_id: int): + broker = ( + db.query(models.CustomsBroker) + .filter( + models.CustomsBroker.broker_key == broker_key, + models.CustomsBroker.tenant_id == tenant_id, + models.CustomsBroker.company_id == company_id, + ) + .first() + ) if not broker: return None new_personnel_data = personnel_data.model_dump() + new_personnel_data["tenant_id"] = tenant_id + new_personnel_data["company_id"] = company_id new_personnel = models.CustomsBrokerPersonnel(customs_broker_id=broker.id, **new_personnel_data) db.add(new_personnel) db.commit() @@ -155,16 +177,22 @@ class CustomsBrokerPersonnelService: broker_key: str, line: int, personnel_data: dto.CustomsBrokerPersonnelDTO, + tenant_id: int, + company_id: int, ): personnel = CustomsBrokerPersonnelService.get_by_broker_key_and_line( - db, broker_key, line + db, broker_key, line, tenant_id, company_id ) if personnel: for key, value in personnel_data.model_dump(exclude_unset=True).items(): setattr(personnel, key, value) db.commit() db.refresh(personnel) - return personnel + return personnel + else: + return CustomsBrokerPersonnelService.create_personnel( + db, broker_key, personnel_data, tenant_id, company_id + ) @staticmethod def delete_personnel(db: Session, broker_key: str, line: int): diff --git a/backend/api/v1/modules/a76/pedmientos/catalog_service.py b/backend/api/v1/modules/a76/pedmientos/catalog_service.py index 13b4a01e..cc0dd1ab 100644 --- a/backend/api/v1/modules/a76/pedmientos/catalog_service.py +++ b/backend/api/v1/modules/a76/pedmientos/catalog_service.py @@ -6,6 +6,8 @@ from sqlalchemy.orm import Session from api.v1.modules.public.reference_data.pedimento_codes.models import PedimentoCode from api.v1.modules.public.reference_data.customs_sections.models import CustomsSection from api.v1.modules.public.reference_data.code_pedimento_regimens.models import CodePedimentoRegimen +from api.v1.modules.public.reference_data.transport_types.models import TransportType +from api.v1.modules.public.reference_data.transport_modes.models import TransportMode # Import A76 Services from api.v1.modules.a76.customs_brokers.services import CustomsBrokerService @@ -15,6 +17,8 @@ from api.v1.modules.a76.clients_and_providers.service import ClientProviderServi from api.v1.modules.public.reference_data.pedimento_codes.dto import PedimentoCodeDTO from api.v1.modules.public.reference_data.customs_sections.dto import CustomsSectionDTO from api.v1.modules.public.reference_data.code_pedimento_regimens.dto import CodePedimentoRegimenDTO +from api.v1.modules.public.reference_data.transport_types.dto import TransportTypeDTO +from api.v1.modules.public.reference_data.transport_modes.dto import TransportModeDTO from api.v1.modules.a76.customs_brokers.dto import CustomsBrokerResponseDTO from api.v1.modules.a76.clients_and_providers.dto import ClientProviderResponseDTO from .dtos.pedimentos import PedimentosResponse @@ -53,6 +57,20 @@ class PedimentoCatalogService: except Exception as e: print(f"Error fetching code_pedimento_regimens: {e}") + try: + response.transport_types = [ + TransportTypeDTO.model_validate(obj) for obj in db.query(TransportType).limit(200).all() + ] + except Exception as e: + print(f"Error fetching transport_types: {e}") + + try: + response.transport_modes = [ + TransportModeDTO.model_validate(obj) for obj in db.query(TransportMode).limit(100).all() + ] + except Exception as e: + print(f"Error fetching transport_modes: {e}") + # Helper to fetch tenant/company specific data def fetch_tenant_data(): # Customs Brokers diff --git a/backend/api/v1/modules/a76/pedmientos/schemas.py b/backend/api/v1/modules/a76/pedmientos/schemas.py index aeec5ee3..8eec714e 100644 --- a/backend/api/v1/modules/a76/pedmientos/schemas.py +++ b/backend/api/v1/modules/a76/pedmientos/schemas.py @@ -11,6 +11,8 @@ from api.v1.modules.public.reference_data.customs_sections.dto import CustomsSec from api.v1.modules.public.reference_data.code_pedimento_regimens.dto import CodePedimentoRegimenDTO from api.v1.modules.a76.customs_brokers.dto import CustomsBrokerResponseDTO from api.v1.modules.a76.clients_and_providers.dto import ClientProviderResponseDTO +from api.v1.modules.public.reference_data.transport_types.dto import TransportTypeDTO +from api.v1.modules.public.reference_data.transport_modes.dto import TransportModeDTO from .dtos.pedimentos import PedimentosResponse @@ -22,6 +24,8 @@ class PedimentoCatalogsResponse(BaseModel): code_pedimento_regimens: List[CodePedimentoRegimenDTO] = [] customs_brokers: List[CustomsBrokerResponseDTO] = [] clients: List[ClientProviderResponseDTO] = [] + transport_types: List[TransportTypeDTO] = [] + transport_modes: List[TransportModeDTO] = [] class PedimentoCreationResponse(PedimentoCatalogsResponse): diff --git a/backend/api/v1/modules/a76/transportation/trailers/services.py b/backend/api/v1/modules/a76/transportation/trailers/services.py index 43332b64..4ffb7c30 100644 --- a/backend/api/v1/modules/a76/transportation/trailers/services.py +++ b/backend/api/v1/modules/a76/transportation/trailers/services.py @@ -25,6 +25,10 @@ class TrailerService: # Apply filters if provided if filters: + if filters.get("trailer_number"): + query = query.filter( + models.Trailer.trailer_number.ilike(f"%{filters['trailer_number']}%") + ) if filters.get("plate_number"): query = query.filter( models.Trailer.plate_number.ilike(f"%{filters['plate_number']}%") @@ -75,8 +79,8 @@ class TrailerService: db: Session, trailer_number: str, tenant_id: int, - company_id: int, trailer_data: dto.TrailerUpdateDTO, + company_id: int, ) -> Optional[models.Trailer]: """Update a trailer""" trailer = TrailerService.get_by_id(db, trailer_number, tenant_id, company_id) diff --git a/backend/api/v1/modules/a76/transportation/transporters/models.py b/backend/api/v1/modules/a76/transportation/transporters/models.py index cc355dbf..9f555c79 100644 --- a/backend/api/v1/modules/a76/transportation/transporters/models.py +++ b/backend/api/v1/modules/a76/transportation/transporters/models.py @@ -9,7 +9,7 @@ class Transporter(Base, TenantScopedMixin, TimestampMixin): {"schema": "a76"}, ) - transporter_key = Column(String(5), primary_key=True, nullable=False) + transporter_key = Column(String(23), primary_key=True, nullable=False) name = Column(String(256), nullable=True) short_name = Column(String(10), nullable=True) responsible = Column(String(100), nullable=True) @@ -27,4 +27,4 @@ class Transporter(Base, TenantScopedMixin, TimestampMixin): ftp_user = Column(String(200), nullable=True) ftp_password = Column(String(100), nullable=True) ftp_directory = Column(String(1000), nullable=True) - filler_code = Column(String(4), nullable=True) + filler_code = Column(String(20), nullable=True) diff --git a/backend/api/v1/modules/a76/transportation/transporters/services.py b/backend/api/v1/modules/a76/transportation/transporters/services.py index 1af393e5..bbf76e69 100644 --- a/backend/api/v1/modules/a76/transportation/transporters/services.py +++ b/backend/api/v1/modules/a76/transportation/transporters/services.py @@ -25,6 +25,10 @@ class TransporterService: # Apply filters if provided if filters: + if filters.get("transporter_key"): + query = query.filter( + models.Transporter.transporter_key.ilike(f"%{filters['transporter_key']}%") + ) if filters.get("name"): query = query.filter( models.Transporter.name.ilike(f"%{filters['name']}%") @@ -75,8 +79,8 @@ class TransporterService: db: Session, transporter_key: str, tenant_id: int, - company_id: int, transporter_data: dto.TransporterUpdateDTO, + company_id: int, ) -> Optional[models.Transporter]: """Update a transporter""" transporter = TransporterService.get_by_id( diff --git a/backend/api/v1/modules/a76/transportation/vehicles/services.py b/backend/api/v1/modules/a76/transportation/vehicles/services.py index d21bd36f..2b9801ac 100644 --- a/backend/api/v1/modules/a76/transportation/vehicles/services.py +++ b/backend/api/v1/modules/a76/transportation/vehicles/services.py @@ -25,6 +25,10 @@ class VehicleService: # Apply filters if provided if filters: + if filters.get("vehicle_key"): + query = query.filter( + models.Vehicle.vehicle_key.ilike(f"%{filters['vehicle_key']}%") + ) if filters.get("plate_number"): query = query.filter( models.Vehicle.plate_number.ilike(f"%{filters['plate_number']}%") @@ -75,8 +79,8 @@ class VehicleService: db: Session, vehicle_key: str, tenant_id: int, - company_id: int, vehicle_data: dto.VehicleUpdateDTO, + company_id: int, ) -> Optional[models.Vehicle]: """Update a vehicle""" vehicle = VehicleService.get_by_id(db, vehicle_key, tenant_id, company_id) diff --git a/backend/main.py b/backend/main.py index c65fa68a..8b468c74 100644 --- a/backend/main.py +++ b/backend/main.py @@ -58,6 +58,11 @@ from api.v1.modules.a76.clients_and_providers.models import ClientProvider from api.v1.modules.a76.customs_brokers.models import CustomsBroker from api.v1.modules.a76.general_catalogs.company.models import Company +# Transportation Modules +from api.v1.modules.a76.transportation.trailers.models import Trailer +from api.v1.modules.a76.transportation.transporters.models import Transporter +from api.v1.modules.a76.transportation.vehicles.models import Vehicle + # Core Modules & Transactional Models from api.v1.modules.a76.items.models import LineItem from api.v1.modules.a76.items.series.models import Serie @@ -146,6 +151,137 @@ def run_migrations(): subprocess.run(["alembic", "upgrade", "head"], check=True) +def create_transportation_tables(): + """Crea las tablas de transporte directamente si no existen. + Se usa en lugar de una migración Alembic para evitar gestionar versiones. + """ + from sqlalchemy import text + from core.database import core_engine + + ddl_statements = [ + """ + CREATE TABLE IF NOT EXISTS a76.transporter ( + transporter_key VARCHAR(23) PRIMARY KEY, + name VARCHAR(256), + short_name VARCHAR(10), + responsible VARCHAR(100), + rfc VARCHAR(30), + streets VARCHAR(100), + postal_code VARCHAR(15), + city VARCHAR(30), + state VARCHAR(30), + country VARCHAR(3), + loader_code VARCHAR(9), + caat_code VARCHAR(49), + transport_code VARCHAR(8), + transport_interface_type VARCHAR(20), + ftp_server VARCHAR(200), + ftp_user VARCHAR(200), + ftp_password VARCHAR(100), + ftp_directory VARCHAR(1000), + filler_code VARCHAR(20), + tenant_id INTEGER NOT NULL REFERENCES core.tenants(id), + company_id INTEGER NOT NULL REFERENCES a76.company(id), + created_at TIMESTAMP NOT NULL DEFAULT now(), + updated_at TIMESTAMP NOT NULL DEFAULT now(), + deleted_at TIMESTAMP + ); + """, + """ + CREATE TABLE IF NOT EXISTS a76.trailer ( + trailer_number VARCHAR(20) PRIMARY KEY, + ace_trailer_number VARCHAR(10), + trailer_type_key VARCHAR(2), + seal VARCHAR(15), + entity_code VARCHAR(1), + plate_number VARCHAR(17), + state VARCHAR(30), + country VARCHAR(3), + container_key VARCHAR(3), + tenant_id INTEGER NOT NULL REFERENCES core.tenants(id), + company_id INTEGER NOT NULL REFERENCES a76.company(id), + created_at TIMESTAMP NOT NULL DEFAULT now(), + updated_at TIMESTAMP NOT NULL DEFAULT now(), + deleted_at TIMESTAMP + ); + """, + """ + CREATE TABLE IF NOT EXISTS a76.vehicle ( + vehicle_key VARCHAR(14) PRIMARY KEY, + ace_vehicle_key VARCHAR(10), + transporter_key VARCHAR(23), + transport_identifier VARCHAR(30), + transport_type VARCHAR(2), + entity_code VARCHAR(1), + transponder_number VARCHAR(16), + dot_number VARCHAR(8), + plate_number VARCHAR(17), + city VARCHAR(30), + state VARCHAR(30), + country VARCHAR(3), + seal VARCHAR(49), + insurance_company_name VARCHAR(30), + insurance_number VARCHAR(20), + insurance_amount NUMERIC(13, 2), + insurance_date INTEGER, + box_number VARCHAR(300), + brand VARCHAR(20), + year VARCHAR(4), + series VARCHAR(30), + description VARCHAR(100), + engine_number VARCHAR(50), + sct_permission VARCHAR(40), + color VARCHAR(20), + container_key VARCHAR(3), + tenant_id INTEGER NOT NULL REFERENCES core.tenants(id), + company_id INTEGER NOT NULL REFERENCES a76.company(id), + created_at TIMESTAMP NOT NULL DEFAULT now(), + updated_at TIMESTAMP NOT NULL DEFAULT now(), + deleted_at TIMESTAMP + ); + """, + ] + + with core_engine.connect() as conn: + for stmt in ddl_statements: + conn.execute(text(stmt)) + # Ampliar columnas que pudieron haberse creado con tamaño incorrecto + conn.execute(text(""" + DO $$ + BEGIN + -- Fix transporter_key si fue creada como VARCHAR(5) + IF EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema='a76' AND table_name='transporter' + AND column_name='transporter_key' + AND character_maximum_length < 23 + ) THEN + ALTER TABLE a76.transporter ALTER COLUMN transporter_key TYPE VARCHAR(23); + END IF; + -- Fix filler_code si fue creada como VARCHAR(4) + IF EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema='a76' AND table_name='transporter' + AND column_name='filler_code' + AND character_maximum_length < 20 + ) THEN + ALTER TABLE a76.transporter ALTER COLUMN filler_code TYPE VARCHAR(20); + END IF; + + -- Eliminar constraint de trailer_type_key en trailer si existe + IF EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE constraint_name='trailer_trailer_type_key_fkey' + AND table_schema='a76' AND table_name='trailer' + ) THEN + ALTER TABLE a76.trailer DROP CONSTRAINT trailer_trailer_type_key_fkey; + END IF; + END$$; + """)) + conn.commit() + logger.info("Tablas de transporte verificadas/creadas correctamente.") + + # Inicializar la base de datos @app.on_event("startup") async def on_startup(): @@ -153,6 +289,7 @@ async def on_startup(): logger.info("Iniciando la aplicación Anexo76...") init_db() run_migrations() + create_transportation_tables() logger.info("Base de datos inicializada correctamente.") @@ -266,6 +403,10 @@ def register_audit(): CustomsBroker, Part, Company, + # Transportation Modules + Trailer, + Transporter, + Vehicle, # Reference Data Country, CurrencyType, diff --git a/frontend/src/app.d.ts b/frontend/src/app.d.ts index 7e328ea8..9cffaf74 100644 --- a/frontend/src/app.d.ts +++ b/frontend/src/app.d.ts @@ -13,4 +13,4 @@ declare global { } } -export {}; +export { }; diff --git a/frontend/src/lib/api/dashboard/a76/customs-brokers.ts b/frontend/src/lib/api/dashboard/a76/customs-brokers.ts index f08c1726..1362a11a 100644 --- a/frontend/src/lib/api/dashboard/a76/customs-brokers.ts +++ b/frontend/src/lib/api/dashboard/a76/customs-brokers.ts @@ -1,4 +1,5 @@ import { api } from '$lib/api'; +import { companyStore } from '$lib/stores/company.svelte'; // <--- NUEVO: Importamos el store para el fallback import type { ApiResponse } from '$lib/api'; export interface CustomsBroker { @@ -26,6 +27,8 @@ export interface CustomsBroker { } export interface CustomsBrokerVU { + tenant_id?: string | null; + company_id?: string | null; certificate_path?: string | null; key_path?: string | null; access_key?: string | null; @@ -95,7 +98,9 @@ export interface CustomsBrokerListResponse { */ export const customsBrokersApi = { list: (companyId: string, page = 1, pageSize = 50) => { - return api.get(`/v1/a76/customs-brokers?company_id=${companyId}&page=${page}&page_size=${pageSize}`); + return api.get( + `/v1/a76/customs-brokers?company_id=${companyId}&page=${page}&page_size=${pageSize}` + ); }, get: (brokerKey: string, companyId: string) => { @@ -111,20 +116,47 @@ export const customsBrokersApi = { */ update: (brokerKey: string, data: CreateCustomsBrokerData) => { const companyId = data.company_id; - return api.put(`/v1/a76/customs-brokers/${brokerKey}/?company_id=${companyId}`, data); + return api.put( + `/v1/a76/customs-brokers/${brokerKey}/?company_id=${companyId}`, + data + ); }, /** * Elimina un agente aduanal */ delete: (brokerKey: string, companyId: string) => { - return api.delete(`/v1/a76/customs-brokers/${brokerKey}?company_id=${companyId}`); + return api.delete( + `/v1/a76/customs-brokers/${brokerKey}?company_id=${companyId}` + ); }, - - + /** + * Actualiza la información de Ventanilla Única (VU) + */ updateVU: (brokerKey: string, data: CustomsBrokerVU, companyId: string) => { - return api.put(`/v1/a76/customs-broker-vu/${brokerKey}?company_id=${companyId}`, data); + // LOGICA DE RESCATE: + // Si companyId llega nulo/undefined, intentamos obtenerlo del store global + let finalCompanyId = companyId; + + if (!finalCompanyId && companyStore.activeCompany?.id) { + finalCompanyId = companyStore.activeCompany.id.toString(); + console.warn("WARN: companyId no fue provisto a updateVU, usando companyStore:", finalCompanyId); + } + + // Aseguramos que el payload tenga los IDs + const payload = { + ...data, + company_id: finalCompanyId, + tenant_id: finalCompanyId + }; + + console.log('[DEBUG] Enviando payload VU:', payload); + + return api.put( + `/v1/a76/customs-broker-vu/${brokerKey}?company_id=${finalCompanyId}`, + payload + ); }, updatePersonnel: (brokerKey: string, line: number, data: CustomsBrokerPersonnel, companyId: string) => { diff --git a/frontend/src/lib/api/dashboard/a76/items.ts b/frontend/src/lib/api/dashboard/a76/items.ts index 2b10b734..28e77fcd 100644 --- a/frontend/src/lib/api/dashboard/a76/items.ts +++ b/frontend/src/lib/api/dashboard/a76/items.ts @@ -18,7 +18,7 @@ export interface LineCustoms { destination_country?: string; advalorem?: string; advalorem_numeric?: number; - advalorem_american?: number; + advalorem_american?: number; advalorem_tlcan?: number; rate?: string; depreciation_rate?: number; @@ -33,7 +33,7 @@ export interface LineFinancials { unit_cost_mxn?: number; unit_cost_capture?: number; unit_cost_commercial_usd?: number; - + // Values value_mc?: number; value_usd?: number; @@ -49,7 +49,7 @@ export interface LineQuantities { line_item_id?: number; quantity?: number; unit_of_measure?: string; - + // Special quantities quantity_temp_export?: number; quantity_returned?: number; @@ -92,7 +92,7 @@ export interface FaLineItem { id?: number; tenant_id?: number; company_id?: number; - + // Asset information (SCAF specific) asset_number?: string; asset_photo?: string; @@ -101,44 +101,46 @@ export interface FaLineItem { return_import_invoice?: string; return_import_date?: number; movement_type_import?: string; - + // Cross-references for import repair search_invoice?: string; search_line?: number; - + // Search type search_type?: string; - - // Subitems - is_subitem?: boolean; - contains_subitems?: boolean; - subitem_number?: number; + + // Subitems + is_subitem?: boolean; + contains_subitems?: boolean; + subitem_number?: number; // Special flags download?: boolean; own_equipment?: boolean; - omit_annex31?: boolean; - + omit_annex31?: boolean; + // Timestamps created_at?: string; updated_at?: string; } export interface Item { - id?: number; - invoice_id: number; + id?: number; + invoice_id: number; line_number: number; - + // Identification part_number?: string; + part_number_id?: number; component_part_number?: string; + component_part_number_id?: number; class_id?: number; identifier?: string; // Unit of Measure unit_of_measure?: number; alternate_unit?: number; - + // Permits permit_number?: string; page_line?: string; @@ -151,29 +153,29 @@ export interface Item { includes_subitems?: boolean; tax_payment?: boolean; is_military_mcia?: boolean; - + // Payment payment_method?: string; igi_amount?: number; - + // Additional notes wildcard_field?: string; // Computed fields from class_info relation class_code?: string; class_description?: string; - + // Computed field from unit_of_measure_info relation - unit_of_measure_code?: string; - reference_number?: string; - order?: string; - guide_number?: string; - depreciation_date?: number; - rectification?: number; - warehouse?: string; - location?: string; - created_at?: string; - updated_at?: string; + unit_of_measure_code?: string; + reference_number?: string; + order?: string; + guide_number?: string; + depreciation_date?: number; + rectification?: number; + warehouse?: string; + location?: string; + created_at?: string; + updated_at?: string; // Nested relations (Singular names to match backend Pydantic models) customs?: LineCustoms; @@ -185,86 +187,86 @@ export interface Item { } export interface ItemListResponse { - items: Item[]; - total: number; - skip: number; - limit: number; + items: Item[]; + total: number; + skip: number; + limit: number; } export interface CreateItemData extends Omit { - invoice_id: number; + invoice_id: number; } -export interface UpdateItemData extends Partial> {} +export interface UpdateItemData extends Partial> { } /** * API para Items */ export const itemsApi = { - /** - * Lista todos los items con paginación - */ - list: (companyId: number, skip = 0, limit = 100, invoiceId?: number) => { - const params = new URLSearchParams({ - company_id: companyId.toString(), - skip: skip.toString(), - limit: limit.toString() - }); + /** + * Lista todos los items con paginación + */ + list: (companyId: number, skip = 0, limit = 100, invoiceId?: number) => { + const params = new URLSearchParams({ + company_id: companyId.toString(), + skip: skip.toString(), + limit: limit.toString() + }); - if (invoiceId) { - params.append('invoice_id', invoiceId.toString()); - } + if (invoiceId) { + params.append('invoice_id', invoiceId.toString()); + } - return api.get(`/v1/a76/items/?${params.toString()}`); - }, + return api.get(`/v1/a76/items/?${params.toString()}`); + }, - /** - * Lista items por invoice ID - */ - listByInvoice: (invoiceId: number, companyId: number) => { - const params = new URLSearchParams({ - company_id: companyId.toString() - }); - return api.get(`/v1/a76/items/invoice/${invoiceId}/items/?${params.toString()}`); - }, + /** + * Lista items por invoice ID + */ + listByInvoice: (invoiceId: number, companyId: number) => { + const params = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.get(`/v1/a76/items/invoice/${invoiceId}/items/?${params.toString()}`); + }, - /** - * Obtiene un item por ID - */ - get: (itemId: number, companyId: number) => { - const params = new URLSearchParams({ - company_id: companyId.toString() - }); - return api.get(`/v1/a76/items/${itemId}/?${params.toString()}`); - }, + /** + * Obtiene un item por ID + */ + get: (itemId: number, companyId: number) => { + const params = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.get(`/v1/a76/items/${itemId}/?${params.toString()}`); + }, - /** - * Crea un nuevo item - */ - create: (companyId: number, data: CreateItemData) => { - const params = new URLSearchParams({ - company_id: companyId.toString() - }); - return api.post(`/v1/a76/items/?${params.toString()}`, data); - }, + /** + * Crea un nuevo item + */ + create: (companyId: number, data: CreateItemData) => { + const params = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.post(`/v1/a76/items/?${params.toString()}`, data); + }, - /** - * Actualiza un item existente - */ - update: (itemId: number, companyId: number, data: UpdateItemData) => { - const params = new URLSearchParams({ - company_id: companyId.toString() - }); - return api.put(`/v1/a76/items/${itemId}/?${params.toString()}`, data); - }, + /** + * Actualiza un item existente + */ + update: (itemId: number, companyId: number, data: UpdateItemData) => { + const params = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.put(`/v1/a76/items/${itemId}/?${params.toString()}`, data); + }, - /** - * Elimina un item - */ - delete: (itemId: number, companyId: number) => { - const params = new URLSearchParams({ - company_id: companyId.toString() - }); - return api.delete(`/v1/a76/items/${itemId}/?${params.toString()}`); - } + /** + * Elimina un item + */ + delete: (itemId: number, companyId: number) => { + const params = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.delete(`/v1/a76/items/${itemId}/?${params.toString()}`); + } }; diff --git a/frontend/src/lib/api/dashboard/a76/pedimentos.ts b/frontend/src/lib/api/dashboard/a76/pedimentos.ts index 582e913d..8b9d46f6 100644 --- a/frontend/src/lib/api/dashboard/a76/pedimentos.ts +++ b/frontend/src/lib/api/dashboard/a76/pedimentos.ts @@ -33,12 +33,19 @@ export interface PedimentoPayments { } export interface PedimentoTransportMeans { + id?: number; destination?: number | null; entry_exit?: string | null; arrival?: string | null; departure?: string | null; } +export interface PedimentoCustomsOffices { + id?: number; + dispatch_customs?: string | null; + entry_exit_customs?: string | null; +} + export interface PedimentoValidation { validator?: string | null; validation_ack?: string | null; @@ -180,6 +187,7 @@ export interface Pedimento { pedimento_incrementables?: PedimentoIncrementables | null; pedimento_decrementables?: PedimentoDecrementables | null; pedimento_indexes?: PedimentoIndexes | null; + pedimento_customs_offices?: PedimentoCustomsOffices | null; pedimento_config_additional?: PedimentoConfigAdditional | null; pedimento_config_calculations?: PedimentoConfigCalculations | null; pedimento_config_surcharges?: PedimentoConfigSurcharges | null; diff --git a/frontend/src/lib/api/dashboard/a76/trailers.ts b/frontend/src/lib/api/dashboard/a76/trailers.ts index df739414..5d74aa2d 100644 --- a/frontend/src/lib/api/dashboard/a76/trailers.ts +++ b/frontend/src/lib/api/dashboard/a76/trailers.ts @@ -2,10 +2,17 @@ import { api, type ApiResponse } from '$lib/api'; export interface Trailer { trailer_number: string; - plate_number?: string; + ace_trailer_number?: string; trailer_type_key?: string; - is_active: boolean; - tenant_id?: string; + seal?: string; + entity_code?: string; + plate_number?: string; + state?: string; + country?: string; + container_key?: string; + is_active?: boolean; + company_id?: number | string; + tenant_id?: number | string; } export interface TrailerResponse { @@ -16,7 +23,7 @@ export interface TrailerResponse { } class TrailersApi { - private baseUrl = '/v1/a76/trailers'; + private baseUrl = '/v1/a76/transportation/trailers'; async list( companyId: string | number, @@ -35,6 +42,27 @@ class TrailersApi { }); return api.get(`${this.baseUrl}/${id}?${queryParams.toString()}`); } + + async create(data: Trailer, companyId: string | number): Promise> { + const queryParams = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.post(`${this.baseUrl}?${queryParams.toString()}`, data); + } + + async update(id: string, data: Trailer, companyId: string | number): Promise> { + const queryParams = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.put(`${this.baseUrl}/${id}/?${queryParams.toString()}`, data); + } + + async delete(id: string, companyId: string | number): Promise> { + const queryParams = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.delete(`${this.baseUrl}/${id}?${queryParams.toString()}`); + } } export const trailersApi = new TrailersApi(); diff --git a/frontend/src/lib/api/dashboard/a76/transporters.ts b/frontend/src/lib/api/dashboard/a76/transporters.ts index 6383828b..70ecbcdd 100644 --- a/frontend/src/lib/api/dashboard/a76/transporters.ts +++ b/frontend/src/lib/api/dashboard/a76/transporters.ts @@ -2,10 +2,26 @@ import { api, type ApiResponse } from '$lib/api'; export interface Transporter { transporter_key: string; - name: string; + name?: string; + short_name?: string; + responsible?: string; rfc?: string; - is_active: boolean; - tenant_id?: string; + streets?: string; + postal_code?: string; + city?: string; + state?: string; + country?: string; + loader_code?: string; + caat_code?: string; + transport_code?: string; + transport_interface_type?: string; + ftp_server?: string; + ftp_user?: string; + ftp_password?: string; + ftp_directory?: string; + filler_code?: string; + company_id?: number | string; + tenant_id?: number | string; } export interface TransporterResponse { @@ -35,6 +51,27 @@ class TransportersApi { }); return api.get(`${this.baseUrl}/${id}?${queryParams.toString()}`); } + + async create(data: Transporter, companyId: string | number): Promise> { + const queryParams = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.post(`${this.baseUrl}?${queryParams.toString()}`, data); + } + + async update(id: string, data: Transporter, companyId: string | number): Promise> { + const queryParams = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.put(`${this.baseUrl}/${id}/?${queryParams.toString()}`, data); + } + + async delete(id: string, companyId: string | number): Promise> { + const queryParams = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.delete(`${this.baseUrl}/${id}?${queryParams.toString()}`); + } } export const transportersApi = new TransportersApi(); diff --git a/frontend/src/lib/api/dashboard/a76/vehicles.ts b/frontend/src/lib/api/dashboard/a76/vehicles.ts index a0c864e4..3fd7d67a 100644 --- a/frontend/src/lib/api/dashboard/a76/vehicles.ts +++ b/frontend/src/lib/api/dashboard/a76/vehicles.ts @@ -1,32 +1,84 @@ -import { api } from '$lib/api'; +import { api, type ApiResponse } from '$lib/api'; export interface Vehicle { vehicle_key: string; - brand?: string; - plate_number?: string; - description?: string; + ace_vehicle_key?: string; + transporter_key?: string; + transport_identifier?: string; transport_type?: string; + entity_code?: string; + transponder_number?: string; + dot_number?: string; + plate_number?: string; + city?: string; + state?: string; + country?: string; + seal?: string; + insurance_company_name?: string; + insurance_number?: string; + insurance_amount?: number; + insurance_date?: number; + box_number?: string; + brand?: string; year?: string; + series?: string; + description?: string; + engine_number?: string; + sct_permission?: string; + color?: string; + container_key?: string; + company_id?: number | string; + tenant_id?: number | string; } -export interface VehicleListResponse { +export interface VehicleResponse { items: Vehicle[]; total: number; + page: number; + page_size: number; } -/** - * API para Vehículos - */ -export const vehiclesApi = { - list: (companyId: string, page = 1, pageSize = 50) => { - return api.get( - `/v1/a76/transportation/vehicles?company_id=${companyId}&page=${page}&page_size=${pageSize}` - ); - }, +class VehiclesApi { + private baseUrl = '/v1/a76/transportation/vehicles'; - get: (vehicleKey: string, companyId: string) => { - return api.get( - `/v1/a76/transportation/vehicles/${vehicleKey}?company_id=${companyId}` - ); + async list( + companyId: string | number, + params?: Record + ): Promise> { + const queryParams = new URLSearchParams({ + company_id: companyId.toString(), + ...params + }); + return api.get(`${this.baseUrl}?${queryParams.toString()}`); } -}; + + async get(id: string, companyId: string | number): Promise> { + const queryParams = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.get(`${this.baseUrl}/${id}?${queryParams.toString()}`); + } + + async create(data: Vehicle, companyId: string | number): Promise> { + const queryParams = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.post(`${this.baseUrl}?${queryParams.toString()}`, data); + } + + async update(id: string, data: Vehicle, companyId: string | number): Promise> { + const queryParams = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.put(`${this.baseUrl}/${id}/?${queryParams.toString()}`, data); + } + + async delete(id: string, companyId: string | number): Promise> { + const queryParams = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.delete(`${this.baseUrl}/${id}?${queryParams.toString()}`); + } +} + +export const vehiclesApi = new VehiclesApi(); diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/items-tab-form.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/items-tab-form.svelte index 372198ff..8bc9b75a 100644 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/items-tab-form.svelte +++ b/frontend/src/lib/components/dashboard/invoices/edit/items/items-tab-form.svelte @@ -20,7 +20,7 @@ } from 'lucide-svelte'; import { toast } from 'svelte-sonner'; import type { Invoice } from '$lib/api/dashboard/a76/invoices'; - import { itemsApi, type Item } from '$lib/api/dashboard/a76/items'; + import { itemsApi, type Item as InvoiceItem } from '$lib/api/dashboard/a76/items'; import { companyStore } from '$lib/stores/company.svelte'; import ItemSheetFa from './fa/item-sheet-fa.svelte'; import ItemSheetInv from './inv/item-sheet-inv.svelte'; @@ -43,7 +43,7 @@ } = $props(); // 1. Core State - let items = $state([]); + let items = $state([]); let displayedItems = $state([]); let imported = $state(0); let net_weight = $state(0); @@ -129,9 +129,9 @@ let showItemSheet = $state(false); let isEditMode = $state(false); let showDeleteDialog = $state(false); - let selectedItem = $state(null); - let originalItemData = $state | null>(null); - let editingItem = $state>({ + let selectedItem = $state(null); + let originalItemData = $state | null>(null); + let editingItem = $state>({ invoice_id: undefined, reference_number: '', order: '', @@ -400,7 +400,7 @@ return cleanLineData({ ...rest }); } - function cloneItemForPreset(item: Item) { + function cloneItemForPreset(item: InvoiceItem) { const { id, tenant_id, company_id, created_at, updated_at, temp_id, ...rest } = item as any; return { ...sanitizeLineForPreset(rest), @@ -502,7 +502,7 @@ isSavingPreset = true; try { // We group everything as items for the template - const lines = builderItems.map((item: Item, idx: number) => { + const lines = builderItems.map((item: InvoiceItem, idx: number) => { return { ...cleanLineData(item), line_number: item.line_number || idx + 1, // Ensure line_number is present @@ -552,7 +552,7 @@ } // Enrich item with descriptive data for display - async function enrichItemData(item: Partial) { + async function enrichItemData(item: Partial) { if (!item || !activeCompanyId) return; // Load class data @@ -697,7 +697,7 @@ } // Normalize numeric values from strings to numbers - function normalizeItemData(item: Partial): Partial { + function normalizeItemData(item: Partial): Partial { if (item) { const normalizedItem = { ...item }; diff --git a/frontend/src/lib/components/dashboard/pedimentos/edit/contributions-tab-form.svelte b/frontend/src/lib/components/dashboard/pedimentos/edit/contributions-tab-form.svelte index d14f69f6..61c3c86b 100644 --- a/frontend/src/lib/components/dashboard/pedimentos/edit/contributions-tab-form.svelte +++ b/frontend/src/lib/components/dashboard/pedimentos/edit/contributions-tab-form.svelte @@ -1,4 +1,5 @@ - +
-
+
{#if activeTab === 'DTA'}
@@ -397,7 +405,7 @@
{:else if activeTab === 'PREV'} -
+
-
+
@@ -497,7 +505,7 @@ {#if !formData?.contribuciones || formData.contribuciones.length === 0} - + No hay contribuciones registradas @@ -555,7 +563,7 @@ onCheckedChange={(checked: boolean | 'indeterminate') => formData && (formData.calculo_manual = checked === true)} /> - @@ -567,7 +575,7 @@ onCheckedChange={(checked: boolean | 'indeterminate') => formData && (formData.operaciones_regla_31_40 = checked === true)} /> - @@ -609,7 +617,7 @@ - + {editingIndex !== null ? 'Editar Contribución' : 'Insertando'} @@ -619,8 +627,8 @@
-

Tasas Pedimento - Registro 509

- +

Tasas Pedimento - Registro 509

+
{currentContribucion.tipo_tasa || 'Seleccionar'} - Porcentual - Específico - Cuota minima (DTA) - Cuota fija (DTA) - Tasa de descuento sobre ad valorem - Factor de aplicación sobre tigie. - Al millar (DTA) - Tasa de descuento sobre el arancel específico - Tasa especifica sobre precios de referencia - Tasa especifica sobre precios de referencia con UM - - - + Porcentual + Específico + Cuota minima (DTA) + Cuota fija (DTA) + Tasa de descuento sobre ad valorem + Factor de aplicación sobre tigie. + Al millar (DTA) + Tasa de descuento sobre el arancel específico + Tasa especifica sobre precios de referencia + Tasa especifica sobre precios de referencia con UM + + + +
+
+ +
+ +
-
- - -
- + +
+

Contribuciones Generales - Registro 510

- -
-

Contribuciones Generales - Registro 510

- -
+
@@ -696,7 +714,7 @@ placeholder="0.00000" {#if !currentContribucion.contribuciones_generales || currentContribucion.contribuciones_generales.length === 0} - + Sin registros @@ -704,9 +722,9 @@ placeholder="0.00000" {#each currentContribucion.contribuciones_generales as contribGen, index} {contribGen.clave_contribucion} - {contribGen.clave_forma_pago} - - {contribGen.importe_contribucion.toLocaleString('es-MX', { + {contribGen.clave_forma_pago} + + {contribGen.importe_contribucion.toLocaleString('es-MX', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} @@ -720,13 +738,13 @@ placeholder="0.00000"
@@ -767,11 +785,7 @@ placeholder="0.00000"
- +
diff --git a/frontend/src/lib/components/dashboard/pedimentos/edit/general-tab-form.svelte b/frontend/src/lib/components/dashboard/pedimentos/edit/general-tab-form.svelte index adf9f65b..aa8d3f8e 100644 --- a/frontend/src/lib/components/dashboard/pedimentos/edit/general-tab-form.svelte +++ b/frontend/src/lib/components/dashboard/pedimentos/edit/general-tab-form.svelte @@ -1,5 +1,5 @@ @@ -381,18 +428,18 @@
Información General - - Edita los datos principales del pedimento - + Edita los datos principales del pedimento
- + -
+
{formData.fecha_captura}
-
+
{formData.hora_captura} @@ -401,39 +448,39 @@
-
-
+
+
- +
- formData.customs_office = v ?? ''} + onValueChange={(v: string) => (formData.customs_office = v ?? '')} > {formData.customs_office ? formData.customs_office.substring(0, 2) : 'Sel...'} - + {#if customsSections.length === 0}
No hay aduanas disponibles @@ -441,7 +488,10 @@ {:else} {#each customsSections as section} - + {section.customs_code} - {section.section_name} @@ -452,23 +502,23 @@
- +
- formData.license = v || null} + onValueChange={(v: string | undefined) => (formData.license = v || null)} > {formData.license || 'Sel...'} - - {@const validBrokers = customsBrokers.filter(b => b.license)} + + {@const validBrokers = customsBrokers.filter((b) => b.license)} {#if customsBrokers.length === 0}
No hay agentes aduanales registrados @@ -481,7 +531,10 @@ {#each validBrokers as broker} {@const displayName = broker.name || broker.broker_key || ''} - + {displayName ? `${displayName} - ${broker.license}` : broker.license} @@ -492,11 +545,13 @@
- +
- +
-
+
- formData.pedimento_code = v ?? ''} + onValueChange={(v: string) => (formData.pedimento_code = v ?? '')} > - {pedimentoCodes.find(o => o.code === formData.pedimento_code)?.code || 'Sel...'} + {pedimentoCodes.find((o) => o.code === formData.pedimento_code)?.code || 'Sel...'} - + {#each pedimentoCodes as code}
@@ -552,13 +607,14 @@
- formData.operation_type = v || null} + onValueChange={(v: string) => (formData.operation_type = v || null)} > - {operationOptions.find(o => o.value === formData.operation_type)?.label || 'Seleccionar...'} + {operationOptions.find((o) => o.value === formData.operation_type)?.label || + 'Seleccionar...'} {#each filteredOperationTypes as option} @@ -571,11 +627,11 @@
- {#if hasMultipleRegimens} - formData.regime = v ?? ''} + onValueChange={(v: string) => (formData.regime = v ?? '')} > @@ -583,9 +639,12 @@ - {#each filteredRegimens as regimen} + {#each filteredRegimens as regimen} - + {regimen.code} @@ -605,18 +664,18 @@
-
+
- formData.client_id = v ? Number(v) : null} + onValueChange={(v: string) => (formData.client_id = v ? Number(v) : null)} > - {clients.find(c => c.id === formData.client_id)?.name || 'Seleccionar cliente...'} + {clients.find((c) => c.id === formData.client_id)?.name || 'Seleccionar cliente...'} @@ -627,7 +686,10 @@ {:else} {#each clients as client} - + {client.name} @@ -640,13 +702,14 @@
- formData.pedimento_type = v ?? ''} + onValueChange={(v: string) => (formData.pedimento_type = v ?? '')} > - {pedimentoTypeOptions.find(o => o.value === formData.pedimento_type)?.label || 'Seleccionar...'} + {pedimentoTypeOptions.find((o) => o.value === formData.pedimento_type)?.label || + 'Seleccionar...'} {#each pedimentoTypeOptions as option} @@ -655,12 +718,9 @@
-
- - -
+
@@ -700,145 +760,237 @@
- + + (formData.pedimento_customs_offices.dispatch_customs = v || '')} + > + + {formData.pedimento_customs_offices.dispatch_customs || 'Sel...'} + + + {#each customsSections.filter((s) => !formData.customs_office || s.customs_code.startsWith(formData.customs_office.substring(0, 2))) as section} + + {section.customs_code} - {section.section_name} + + {/each} + + +
+ + +
+ + + (formData.pedimento_transport_means.destination = v ? Number(v) : null)} + > + + {formData.pedimento_transport_means.destination === 1 + ? '1 - REGION FRONTERIZA' + : formData.pedimento_transport_means.destination === 8 + ? '8 - RECTIFICACION REGION FRONTERIZA' + : formData.pedimento_transport_means.destination === 9 + ? '9 - INTERIOR DEL PAIS' + : 'Seleccionar...'} + + + 1 - REGION FRONTERIZA + 8 - RECTIFICACION REGION FRONTERIZA + 9 - INTERIOR DEL PAIS + +
- +
- +
- - -
+ + (formData.pedimento_transport_means.entry_exit = v || '')} + > + + + {transportModes.find((m) => m.key === formData.pedimento_transport_means.entry_exit) + ?.name || + transportTypes.find( + (t) => t.transport_code === formData.pedimento_transport_means.entry_exit + )?.description || + formData.pedimento_transport_means.entry_exit || + 'Seleccionar...'} + + + + {#each transportModes as mode} + {mode.key} - {mode.name} + {/each} + + +
+ + +
+ + (formData.pedimento_transport_means.arrival = v || '')} + > + + + {transportModes.find((m) => m.key === formData.pedimento_transport_means.arrival) + ?.name || + transportTypes.find( + (t) => t.transport_code === formData.pedimento_transport_means.arrival + )?.description || + formData.pedimento_transport_means.arrival || + 'Seleccionar...'} + + + + {#each transportModes as mode} + {mode.key} - {mode.name} + {/each} + + +
+ + +
+ + (formData.pedimento_transport_means.departure = v || '')} + > + + + {transportModes.find((m) => m.key === formData.pedimento_transport_means.departure) + ?.name || + transportTypes.find( + (t) => t.transport_code === formData.pedimento_transport_means.departure + )?.description || + formData.pedimento_transport_means.departure || + 'Seleccionar...'} + + + + {#each transportModes as mode} + {mode.key} - {mode.name} + {/each} + + +
-
-
- - - - - - + + + + + + +
-
{#if activeSection === 'fechas'} -
+
- +
{#if formData.pedimento_type === 'consolidated'}
- +
{/if}
- +
- +
- +
{#if formData.pedimento_code === 'R1'} @@ -856,125 +1008,118 @@
- -
+ +
{:else if activeSection === 'incrementables'}
-
- Factor: 1.00000 -
+
Factor: 1.00000
-
- -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- +
+
-
- - -
-
- - + + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ +
+
+ + +
+
+ + +
-
{:else if activeSection === 'identificadores'} {:else if activeSection === 'indices'} -
+
- formData.tipo_factor = v ?? ''} + onValueChange={(v: string) => (formData.tipo_factor = v ?? '')} > - {formData.tipo_factor === 'INPC' ? 'I.N.P.C' : formData.tipo_factor === 'variacion_cambiaria' ? 'Variación Cambiaria' : 'Seleccionar'} + {formData.tipo_factor === 'INPC' + ? 'I.N.P.C' + : formData.tipo_factor === 'variacion_cambiaria' + ? 'Variación Cambiaria' + : 'Seleccionar'} @@ -998,7 +1143,7 @@
-
+
{:else if activeSection === 'adicional'} -
+
@@ -1023,14 +1168,14 @@ bind:value={formData.anio_impresion} placeholder="25" maxlength={2} - class="text-center w-40 focus-visible:ring-0 focus-visible:ring-offset-0" + class="w-40 text-center focus-visible:ring-0 focus-visible:ring-offset-0" />
-
+
-
+
-
+
-
+
-
+
-
+
-
- Factor: 0.05000 -
+
Factor: 0.05000
-
- -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- -
- + +
+ + -
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ +
+ + +
@@ -1220,9 +1348,11 @@ - {/* Optional: maybe refresh something or just let user continue */}} + onSuccess={() => { + /* Optional: maybe refresh something or just let user continue */ + }} /> diff --git a/frontend/src/lib/components/dashboard/pedimentos/edit/other-data-tab-form.svelte b/frontend/src/lib/components/dashboard/pedimentos/edit/other-data-tab-form.svelte index 1682fadb..340f0d4b 100644 --- a/frontend/src/lib/components/dashboard/pedimentos/edit/other-data-tab-form.svelte +++ b/frontend/src/lib/components/dashboard/pedimentos/edit/other-data-tab-form.svelte @@ -1,4 +1,5 @@ + + + + + Seleccionar Sector PROSEC + + Seleccione el sector del catálogo. Escrolea para ver más. + + + +
+ + +
+ +
+ {#if loading && items.length === 0} +
+ +

Cargando catálogo...

+
+ {:else if items.length === 0} +
+

No se encontraron sectores.

+
+ {:else} + + + + Clave + Descripción + Autorizado + + + + {#each items as item} + handleSelect(item)} + > + +
+ + + {item.key} + +
+
+ + {item.description} + + + + {item.authorized ? 'Sí' : 'No'} + + +
+ {/each} +
+
+ +
+ {#if loadingMore} + + {/if} +
+ {/if} +
+ + +
+ {items.length} de {totalItems} registros +
+ +
+
+
diff --git a/frontend/src/lib/components/dashboard/shared/modals/state-selector-dialog.svelte b/frontend/src/lib/components/dashboard/shared/modals/state-selector-dialog.svelte new file mode 100644 index 00000000..eb6f9c4c --- /dev/null +++ b/frontend/src/lib/components/dashboard/shared/modals/state-selector-dialog.svelte @@ -0,0 +1,224 @@ + + + + + + Seleccionar Estado + + Seleccione el estado del catálogo. Escrolea para ver más. + + + +
+ + +
+ +
+ {#if loading && items.length === 0} +
+ +

Cargando catálogo...

+
+ {:else if items.length === 0} +
+

No se encontraron estados.

+
+ {:else} + + + + Clave M3 + Descripción + MEX + + + + {#each items as item} + handleSelect(item)} + > + +
+ + + {item.m3_key} + +
+
+ + {item.description} + + + {item.mex_key || '-'} + +
+ {/each} +
+
+ +
+ {#if loadingMore} + + {/if} +
+ {/if} +
+ + +
+ {items.length} de {totalItems} registros +
+ +
+
+
diff --git a/frontend/src/lib/components/dashboard/transportation/trailers/columns.ts b/frontend/src/lib/components/dashboard/transportation/trailers/columns.ts new file mode 100644 index 00000000..e00011c6 --- /dev/null +++ b/frontend/src/lib/components/dashboard/transportation/trailers/columns.ts @@ -0,0 +1,64 @@ +/** + * Definición de columnas para la tabla de Trailers + */ +import type { Trailer } from '$lib/api/dashboard/a76/trailers'; +import type { ColumnDef } from '@tanstack/table-core'; +import { renderComponent } from '$lib/components/ui/data-table'; +import DataTableActions from './data-table-actions.svelte'; + +export function createColumns(onSuccess?: () => void): ColumnDef[] { + return [ + { + accessorKey: 'trailer_number', + header: 'Número de Trailer', + cell: ({ row }) => { + return row.original.trailer_number; + } + }, + { + accessorKey: 'plate_number', + header: 'Placas', + cell: ({ row }) => { + return row.original.plate_number || '-'; + } + }, + { + accessorKey: 'trailer_type_key', + header: 'Tipo de Trailer', + cell: ({ row }) => { + return row.original.trailer_type_key || '-'; + } + }, + { + accessorKey: 'container_key', + header: 'Contenedor', + cell: ({ row }) => { + return row.original.container_key || '-'; + } + }, + { + accessorKey: 'state', + header: 'Estado', + cell: ({ row }) => { + return row.original.state || '-'; + } + }, + { + accessorKey: 'country', + header: 'País', + cell: ({ row }) => { + return row.original.country || '-'; + } + }, + { + id: 'actions', + header: 'Acciones', + cell: ({ row }) => { + return renderComponent(DataTableActions, { + item: row.original, + onSuccess + }); + } + } + ]; +} diff --git a/frontend/src/lib/components/dashboard/transportation/trailers/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/transportation/trailers/create-edit-dialog.svelte new file mode 100644 index 00000000..6959aed2 --- /dev/null +++ b/frontend/src/lib/components/dashboard/transportation/trailers/create-edit-dialog.svelte @@ -0,0 +1,207 @@ + + + + + + {title} + + {isEdit + ? 'Modifica los datos del trailer' + : 'Completa los datos para crear un nuevo trailer'} + + + +
{ + e.preventDefault(); + handleSubmit(); + }} + class="space-y-6" + > + {#if error} +
+ {error} +
+ {/if} + +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+
+ + + + + + +
+
diff --git a/frontend/src/lib/components/dashboard/transportation/trailers/data-table-actions.svelte b/frontend/src/lib/components/dashboard/transportation/trailers/data-table-actions.svelte new file mode 100644 index 00000000..6dce1ab9 --- /dev/null +++ b/frontend/src/lib/components/dashboard/transportation/trailers/data-table-actions.svelte @@ -0,0 +1,111 @@ + + + + + {#snippet child({ props })} + + {/snippet} + + + Acciones + + + + Editar + + + + {#if loading} + + {:else} + + {/if} + Eliminar + + + + + diff --git a/frontend/src/lib/components/dashboard/transportation/trailers/data-table.svelte b/frontend/src/lib/components/dashboard/transportation/trailers/data-table.svelte new file mode 100644 index 00000000..e0cdf7c1 --- /dev/null +++ b/frontend/src/lib/components/dashboard/transportation/trailers/data-table.svelte @@ -0,0 +1,99 @@ + + +
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} + + {#each headerGroup.headers as header (header.id)} + + {#if !header.isPlaceholder} + + {/if} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getVisibleCells() as cell (cell.id)} + + + + {/each} + + {:else} + + + No hay resultados. + + + {/each} + + +
+ +
+
+ Total: {totalItems} +
+
+ + +
+
diff --git a/frontend/src/lib/components/dashboard/transportation/transporters/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/transportation/transporters/create-edit-dialog.svelte new file mode 100644 index 00000000..0cc296fd --- /dev/null +++ b/frontend/src/lib/components/dashboard/transportation/transporters/create-edit-dialog.svelte @@ -0,0 +1,306 @@ + + + + + + {title} + + {isEdit + ? 'Modifica los datos del transportista' + : 'Completa los datos para crear un nuevo transportista'} + + + +
{ + e.preventDefault(); + handleSubmit(); + }} + class="space-y-6" + > + {#if error} +
+ {error} +
+ {/if} + +
+ +
+

Información General

+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
+

Códigos de Transporte

+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
+

Dirección

+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+
+ + +
+

Configuración FTP

+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
+ + + + + + +
+
diff --git a/frontend/src/lib/components/dashboard/transportation/transporters/data-table-actions.svelte b/frontend/src/lib/components/dashboard/transportation/transporters/data-table-actions.svelte new file mode 100644 index 00000000..8715b474 --- /dev/null +++ b/frontend/src/lib/components/dashboard/transportation/transporters/data-table-actions.svelte @@ -0,0 +1,114 @@ + + + + + {#snippet child({ props })} + + {/snippet} + + + Acciones + + + + Editar + + + + {#if loading} + + {:else} + + {/if} + Eliminar + + + + + diff --git a/frontend/src/lib/components/dashboard/transportation/transporters/data-table.svelte b/frontend/src/lib/components/dashboard/transportation/transporters/data-table.svelte new file mode 100644 index 00000000..e0cdf7c1 --- /dev/null +++ b/frontend/src/lib/components/dashboard/transportation/transporters/data-table.svelte @@ -0,0 +1,99 @@ + + +
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} + + {#each headerGroup.headers as header (header.id)} + + {#if !header.isPlaceholder} + + {/if} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getVisibleCells() as cell (cell.id)} + + + + {/each} + + {:else} + + + No hay resultados. + + + {/each} + + +
+ +
+
+ Total: {totalItems} +
+
+ + +
+
diff --git a/frontend/src/lib/components/dashboard/transportation/transporters/transporter-columns.ts b/frontend/src/lib/components/dashboard/transportation/transporters/transporter-columns.ts new file mode 100644 index 00000000..32e69333 --- /dev/null +++ b/frontend/src/lib/components/dashboard/transportation/transporters/transporter-columns.ts @@ -0,0 +1,64 @@ +/** + * Definición de columnas para la tabla de Transportistas + */ +import type { Transporter } from '$lib/api/dashboard/a76/transporters'; +import type { ColumnDef } from '@tanstack/table-core'; +import { renderComponent } from '$lib/components/ui/data-table'; +import DataTableActions from './data-table-actions.svelte'; + +export function createColumns(onSuccess?: () => void): ColumnDef[] { + return [ + { + accessorKey: 'transporter_key', + header: 'Clave', + cell: ({ row }) => { + return row.original.transporter_key; + } + }, + { + accessorKey: 'name', + header: 'Nombre', + cell: ({ row }) => { + return row.original.name || '-'; + } + }, + { + accessorKey: 'short_name', + header: 'Nombre Corto', + cell: ({ row }) => { + return row.original.short_name || '-'; + } + }, + { + accessorKey: 'rfc', + header: 'RFC', + cell: ({ row }) => { + return row.original.rfc || '-'; + } + }, + { + accessorKey: 'caat_code', + header: 'CAAT', + cell: ({ row }) => { + return row.original.caat_code || '-'; + } + }, + { + accessorKey: 'transport_code', + header: 'Código Transporte', + cell: ({ row }) => { + return row.original.transport_code || '-'; + } + }, + { + id: 'actions', + header: 'Acciones', + cell: ({ row }) => { + return renderComponent(DataTableActions, { + item: row.original, + onSuccess + }); + } + } + ]; +} diff --git a/frontend/src/lib/components/dashboard/transportation/vehicles/columns.ts b/frontend/src/lib/components/dashboard/transportation/vehicles/columns.ts new file mode 100644 index 00000000..2bacea83 --- /dev/null +++ b/frontend/src/lib/components/dashboard/transportation/vehicles/columns.ts @@ -0,0 +1,64 @@ +/** + * Definición de columnas para la tabla de Vehículos + */ +import type { Vehicle } from '$lib/api/dashboard/a76/vehicles'; +import type { ColumnDef } from '@tanstack/table-core'; +import { renderComponent } from '$lib/components/ui/data-table'; +import DataTableActions from './data-table-actions.svelte'; + +export function createColumns(onSuccess?: () => void): ColumnDef[] { + return [ + { + accessorKey: 'vehicle_key', + header: 'Clave', + cell: ({ row }) => { + return row.original.vehicle_key; + } + }, + { + accessorKey: 'brand', + header: 'Marca', + cell: ({ row }) => { + return row.original.brand || '-'; + } + }, + { + accessorKey: 'year', + header: 'Año', + cell: ({ row }) => { + return row.original.year || '-'; + } + }, + { + accessorKey: 'plate_number', + header: 'Placas', + cell: ({ row }) => { + return row.original.plate_number || '-'; + } + }, + { + accessorKey: 'transporter_key', + header: 'Transportista', + cell: ({ row }) => { + return row.original.transporter_key || '-'; + } + }, + { + accessorKey: 'transport_type', + header: 'Tipo Transporte', + cell: ({ row }) => { + return row.original.transport_type || '-'; + } + }, + { + id: 'actions', + header: 'Acciones', + cell: ({ row }) => { + return renderComponent(DataTableActions, { + item: row.original, + onSuccess + }); + } + } + ]; +} diff --git a/frontend/src/lib/components/dashboard/transportation/vehicles/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/transportation/vehicles/create-edit-dialog.svelte new file mode 100644 index 00000000..313dd45b --- /dev/null +++ b/frontend/src/lib/components/dashboard/transportation/vehicles/create-edit-dialog.svelte @@ -0,0 +1,317 @@ + + + + + + {title} + + {isEdit + ? 'Modifica los datos del vehículo' + : 'Completa los datos para crear un nuevo vehículo de transporte'} + + + +
{ + e.preventDefault(); + handleSubmit(); + }} + class="space-y-6" + > + {#if error} +
+ {error} +
+ {/if} + +
+ +
+

Identificación del Vehículo

+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ + +
+
+ + +
+

Datos de Transporte

+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
+

Seguro y Otros

+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+ + +
+

Ubicación y Detalles

+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+
+
+ + + + + + +
+
diff --git a/frontend/src/lib/components/dashboard/transportation/vehicles/data-table-actions.svelte b/frontend/src/lib/components/dashboard/transportation/vehicles/data-table-actions.svelte new file mode 100644 index 00000000..9ec4eb00 --- /dev/null +++ b/frontend/src/lib/components/dashboard/transportation/vehicles/data-table-actions.svelte @@ -0,0 +1,111 @@ + + + + + {#snippet child({ props })} + + {/snippet} + + + Acciones + + + + Editar + + + + {#if loading} + + {:else} + + {/if} + Eliminar + + + + + diff --git a/frontend/src/lib/components/dashboard/transportation/vehicles/data-table.svelte b/frontend/src/lib/components/dashboard/transportation/vehicles/data-table.svelte new file mode 100644 index 00000000..e0cdf7c1 --- /dev/null +++ b/frontend/src/lib/components/dashboard/transportation/vehicles/data-table.svelte @@ -0,0 +1,99 @@ + + +
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} + + {#each headerGroup.headers as header (header.id)} + + {#if !header.isPlaceholder} + + {/if} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getVisibleCells() as cell (cell.id)} + + + + {/each} + + {:else} + + + No hay resultados. + + + {/each} + + +
+ +
+
+ Total: {totalItems} +
+
+ + +
+
diff --git a/frontend/src/lib/components/sidebar/modules.ts b/frontend/src/lib/components/sidebar/modules.ts index 8b7a658b..5a00a88d 100644 --- a/frontend/src/lib/components/sidebar/modules.ts +++ b/frontend/src/lib/components/sidebar/modules.ts @@ -15,6 +15,7 @@ import { Settings2, Shield, Ship, + Truck, Users, } from 'lucide-svelte'; import * as m from "$lib/paraglide/messages.js"; @@ -192,6 +193,7 @@ export function getSidebarData(): SidebarData { title: m["sidebar.general_catalogs.identifiers"](), url: "/dashboard/general_catalogs/identifiers", }, + // ------------------------------------- { title: m["sidebar.general_catalogs.incoterms"](), url: "/dashboard/reference_data/incoterms", @@ -333,6 +335,25 @@ export function getSidebarData(): SidebarData { }, ], }, + { + title: "Transportes", + url: "#", + icon: Truck, + items: [ + { + title: "Transportistas", + url: "/dashboard/general_catalogs/transporters", + }, + { + title: "Trailers", + url: "/dashboard/general_catalogs/trailers", + }, + { + title: "Vehículos", + url: "/dashboard/general_catalogs/vehicles", + }, + ], + }, { title: m["sidebar.goods.title"](), url: "#", @@ -522,4 +543,4 @@ export function getSidebarData(): SidebarData { } // Exportar también como constante para compatibilidad (deprecado) -export const sidebarData: SidebarData = getSidebarData(); +export const sidebarData: SidebarData = getSidebarData(); \ No newline at end of file diff --git a/frontend/src/routes/dashboard/clients_and_providers/edit/[[id]]/+page.svelte b/frontend/src/routes/dashboard/clients_and_providers/edit/[[id]]/+page.svelte index 8f5bbad8..991a2507 100644 --- a/frontend/src/routes/dashboard/clients_and_providers/edit/[[id]]/+page.svelte +++ b/frontend/src/routes/dashboard/clients_and_providers/edit/[[id]]/+page.svelte @@ -22,10 +22,29 @@ FileText, Settings, User, - Trash2 + Trash2, + Search, + Globe, + MapPin as MapPinIcon, + Factory, + Calendar, + Hash, + ShieldCheck, + Award, + Fingerprint, + Briefcase } from 'lucide-svelte'; import { toast } from 'svelte-sonner'; + // Componentes Compartidos (Modales) + import CountrySelectorDialog from '$lib/components/dashboard/goods/modales/country-selector-dialog.svelte'; + import StateSelectorDialog from '$lib/components/dashboard/shared/modals/state-selector-dialog.svelte'; + import SectorSelectorDialog from '$lib/components/dashboard/shared/modals/sector-selector-dialog.svelte'; + + import { type Country } from '$lib/api/dashboard/reference_data/countries'; + import { type State } from '$lib/api/dashboard/reference_data/states'; + import { type Sector } from '$lib/api/dashboard/reference_data/sectors'; + // API & Stores import { clientsProvidersApi } from '$lib/api/dashboard/a76/clients-providers'; import { companyStore } from '$lib/stores/company.svelte'; @@ -73,17 +92,30 @@ program: '', program_number: '', authorization_date_str: '', // String para input date - prosec: 0, + prosec: '', manufacturer_id: '', tax_id: '', ctpat_svi: '', - is_certified_company: '0' + is_certified_company: false }); let formData = $state(getEmptyForm()); let loading = $state(false); let error = $state(null); + // --- ESTADO PARA MODALES --- + let countryModalOpen = $state(false); + let stateModalOpen = $state(false); + let sectorModalOpen = $state(false); + + const scaiiPrograms = [ + { value: 'IMMEX', label: 'IMMEX' }, + { value: 'PROSEC', label: 'PROSEC' }, + { value: 'ALTEX', label: 'ALTEX' }, + { value: 'ECEX', label: 'ECEX' }, + { value: 'DRAWBACK', label: 'DRAWBACK' } + ]; + // --- UTILIDADES --- const intDateToString = (d?: number | null) => d @@ -138,11 +170,11 @@ program: prog.program || '', program_number: prog.program_number || '', authorization_date_str: intDateToString(prog.secon_auth_date), - prosec: prog.prosec || 0, + prosec: prog.prosec ? String(prog.prosec) : '', manufacturer_id: prog.manufacturer_id || '', tax_id: prog.tax_id || '', ctpat_svi: prog.ctpat_svi || '', - is_certified_company: prog.is_certified_company || '0' + is_certified_company: prog.is_certified_company === '1' }; } } catch (e: any) { @@ -202,11 +234,11 @@ program: clean(formData.program), program_number: clean(formData.program_number), secon_auth_date: stringDateToInt(formData.authorization_date_str), - prosec: Number(formData.prosec) || null, + prosec: clean(formData.prosec), manufacturer_id: clean(formData.manufacturer_id), tax_id: clean(formData.tax_id), ctpat_svi: clean(formData.ctpat_svi), - is_certified_company: clean(formData.is_certified_company) + is_certified_company: formData.is_certified_company ? '1' : '0' } }; @@ -470,17 +502,44 @@
- +
+ + +
- +
+ + +
@@ -509,77 +568,160 @@ >Información sobre IMMEX, PROSEC y otras certificaciones. - -
-
- - + + +
+
+ + Programas de Fomento
-
- - + +
+
+ + (formData.program = v)} + disabled={loading} + > + + {formData.program || 'Selecciona un programa'} + + + {#each scaiiPrograms as prog} + + {prog.label} + + {/each} + + +
+
+ + +
+
+ + +
+
+ +
+ + +
+
-
-
- - + +
+
+ + Identificación Industrial
-
- - -
-
- - + +
+
+ + +
+
+ + +
-
-
- - + +
+
+ + Certificaciones y Seguridad
-
- - + +
+
+ + +
+
+ +
+ +

+ Indica si cuenta con certificación de empresa +

+
+
@@ -670,3 +812,24 @@
+ + + (formData.country = country.m3_key)} +/> + + { + formData.state = state.description; + if (state.m3_key && !formData.country) { + formData.country = state.m3_key; + } + }} +/> + + (formData.prosec = sector.key)} +/> diff --git a/frontend/src/routes/dashboard/customs_brokers/+page.svelte b/frontend/src/routes/dashboard/customs_brokers/+page.svelte index 5a92b518..611ce839 100644 --- a/frontend/src/routes/dashboard/customs_brokers/+page.svelte +++ b/frontend/src/routes/dashboard/customs_brokers/+page.svelte @@ -187,8 +187,7 @@ const brokerColumns = createBrokerColumns(handleActionSuccess); -
- +

GESTIÓN ADUANAL

@@ -196,17 +195,17 @@
- - + + Agentes Aduanales Secciones Aduanales @@ -214,13 +213,11 @@ - -
- -
-
+
+
+

Filtros

Busque por nombre o patente @@ -244,23 +241,20 @@ oninput={handleSearch} />
-
- -
+
- -
-
+
+

Listado

{filteredItems.length} registros
@@ -275,9 +269,8 @@ idField="broker_key" />
- {#if totalItems > pageSize} -
+
- + Página {currentPage} de {Math.ceil(totalItems / pageSize)}
-
-
-

+

+

Detalles del Agente

{selectedItem?.name || '---'}

-
- + Patente: {selectedItem?.broker_key || ''}
-
+
{#if selectedItem}
@@ -338,21 +330,21 @@ {#if selectedItem.tax_id}
-

{selectedItem.tax_id}

+

{selectedItem.tax_id}

{/if} -
+
-
+

{selectedItem.address || ''}

{[selectedItem.city, selectedItem.state].filter(Boolean).join(', ')} @@ -363,9 +355,9 @@

-
+
@@ -391,9 +383,9 @@
{:else}
- +

Selecciona un agente

{/if} @@ -401,9 +393,8 @@
- - - + +
-
+
{#if activeTab === 'brokers'} - {:else} {/if} diff --git a/frontend/src/routes/dashboard/general_catalogs/trailers/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/trailers/+page.svelte new file mode 100644 index 00000000..596c3d13 --- /dev/null +++ b/frontend/src/routes/dashboard/general_catalogs/trailers/+page.svelte @@ -0,0 +1,117 @@ + + +
+
+
+

Trailers

+

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

+
+ +
+ +
+ + +
+ + {#if loading && data.length === 0} +
+ Cargando trailers... +
+ {:else} + + {/if} + + +
diff --git a/frontend/src/routes/dashboard/general_catalogs/transporters/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/transporters/+page.svelte new file mode 100644 index 00000000..150e006b --- /dev/null +++ b/frontend/src/routes/dashboard/general_catalogs/transporters/+page.svelte @@ -0,0 +1,117 @@ + + +
+
+
+

Transportistas

+

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

+
+ +
+ +
+ + +
+ + {#if loading && data.length === 0} +
+ Cargando transportistas... +
+ {:else} + + {/if} + + +
diff --git a/frontend/src/routes/dashboard/general_catalogs/vehicles/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/vehicles/+page.svelte new file mode 100644 index 00000000..1bcc8916 --- /dev/null +++ b/frontend/src/routes/dashboard/general_catalogs/vehicles/+page.svelte @@ -0,0 +1,119 @@ + + +
+
+
+

Vehículos (Transporte)

+

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

+
+ +
+ +
+ + +
+ + {#if loading && data.length === 0} +
+ Cargando vehículos... +
+ {:else} + + {/if} + + +
diff --git a/frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.server.ts b/frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.server.ts index 2a67bd94..2e923b7d 100644 --- a/frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.server.ts +++ b/frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.server.ts @@ -39,6 +39,8 @@ export const load: PageServerLoad = async ({ params, cookies, fetch }) => { customsBrokers: [], clients: [], codePedimentoRegimens: [], + transportTypes: [], + transportModes: [], error: 'Error al cargar catálogos. Verifique la conexión con el backend.' }; } @@ -53,7 +55,9 @@ export const load: PageServerLoad = async ({ params, cookies, fetch }) => { customsSections: data.customs_sections || [], customsBrokers: data.customs_brokers || [], clients: data.clients || [], - codePedimentoRegimens: data.code_pedimento_regimens || [] + codePedimentoRegimens: data.code_pedimento_regimens || [], + transportTypes: data.transport_types || [], + transportModes: data.transport_modes || [] }; } catch (e) { console.error('❌ Error loading new pedimento data:', e); @@ -67,6 +71,8 @@ export const load: PageServerLoad = async ({ params, cookies, fetch }) => { customsBrokers: [], clients: [], codePedimentoRegimens: [], + transportTypes: [], + transportModes: [], error: 'Error al cargar catálogos. Verifique la conexión con el backend.' }; } @@ -106,7 +112,9 @@ export const load: PageServerLoad = async ({ params, cookies, fetch }) => { customsSections: data.customs_sections || [], customsBrokers: data.customs_brokers || [], clients: data.clients || [], - codePedimentoRegimens: data.code_pedimento_regimens || [] + codePedimentoRegimens: data.code_pedimento_regimens || [], + transportTypes: data.transport_types || [], + transportModes: data.transport_modes || [] }; } catch (e) { console.error('Error loading pedimento:', e); diff --git a/frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.svelte b/frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.svelte index 7151a7e0..8f306d7a 100644 --- a/frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.svelte +++ b/frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.svelte @@ -1,4 +1,5 @@