feat: Enhance Pedimentos CRUD operations with related data handling

- Updated PedimentosService to create and update related tables for Pedimentos.
- Added company_id filtering in queries for Pedimentos.
- Improved error handling and logging during creation and update processes.
- Refactored router tags for consistency and clarity.
- Adjusted API endpoints for customs brokers to include company_id in requests.
- Enhanced company selection logic in various components to prioritize active company.
- Implemented event listeners for company changes to reload data dynamically.
- Updated frontend components to handle loading states and errors more effectively.
- Ensured all relevant routes and API calls are aligned with the new company context.
This commit is contained in:
2025-11-14 18:14:33 -06:00
parent aa35635397
commit ba40123333
19 changed files with 739 additions and 149 deletions

View File

@@ -3,24 +3,65 @@ from typing import Optional
from pydantic import BaseModel
class CustomsBrokerDTO(BaseModel):
type: Optional[str]
class CustomsBrokerBaseDTO(BaseModel):
"""Base fields for CustomsBroker"""
type: Optional[str] = None
name: Optional[str] = None
address: Optional[str] = None
postal_code: Optional[str] = None
city: Optional[str] = None
state: Optional[str] = None
phone: Optional[str] = None
fax: Optional[str] = None
email: Optional[str] = None
country: Optional[str] = None
tax_id: Optional[str] = None
personal_id: Optional[str] = None
position: Optional[str] = None
license: Optional[str] = None
company: Optional[str] = None
contact: Optional[str] = None
class CustomsBrokerCreateDTO(CustomsBrokerBaseDTO):
"""Schema for creating a new CustomsBroker"""
broker_key: str
name: Optional[str]
address: Optional[str]
postal_code: Optional[str]
city: Optional[str]
state: Optional[str]
phone: Optional[str]
fax: Optional[str]
email: Optional[str]
country: Optional[str]
tax_id: Optional[str]
personal_id: Optional[str]
position: Optional[str]
license: Optional[str]
company: Optional[str]
contact: Optional[str]
class CustomsBrokerUpdateDTO(CustomsBrokerBaseDTO):
"""Schema for updating an existing CustomsBroker"""
pass
class CustomsBrokerResponseDTO(CustomsBrokerBaseDTO):
"""Schema for CustomsBroker response"""
broker_key: str
tenant_id: int
company_id: int
class Config:
from_attributes = True
# Legacy DTO for backwards compatibility (if needed elsewhere)
class CustomsBrokerDTO(BaseModel):
type: Optional[str] = None
broker_key: str
name: Optional[str] = None
address: Optional[str] = None
postal_code: Optional[str] = None
city: Optional[str] = None
state: Optional[str] = None
phone: Optional[str] = None
fax: Optional[str] = None
email: Optional[str] = None
country: Optional[str] = None
tax_id: Optional[str] = None
personal_id: Optional[str] = None
position: Optional[str] = None
license: Optional[str] = None
company: Optional[str] = None
contact: Optional[str] = None
tenant_id: str
company_id: str

View File

@@ -1,23 +1,26 @@
from api.v1.common.base_models import TenantScopedMixin
from core.database import Base
from sqlalchemy import Column, ForeignKey, ForeignKeyConstraint, Integer, String
from sqlalchemy import Column, ForeignKey, ForeignKeyConstraint, Integer, String, UniqueConstraint, PrimaryKeyConstraint
from sqlalchemy.orm import relationship
class CustomsBroker(Base, TenantScopedMixin):
__tablename__ = "customs_brokers"
__table_args__ = (
PrimaryKeyConstraint("id", name="customs_brokers_pkey"),
ForeignKeyConstraint(
["tenant_id"], ["a76.tenants.id"], name="fk_customs_brokers_tenants"
),
ForeignKeyConstraint(
["company_id"], ["a76.company.id"], name="fk_customs_brokers_company"
),
UniqueConstraint("broker_key", "tenant_id", "company_id", name="uq_broker_key_tenant_company"),
{"schema": "a76"},
)
id = Column(Integer, primary_key=True)
type = Column(String(9), nullable=True)
broker_key = Column(String(5), primary_key=True, nullable=False)
broker_key = Column(String(5), nullable=False)
name = Column(String(80), nullable=True)
address = Column(String(1500), nullable=True)
postal_code = Column(String(15), nullable=True)
@@ -44,10 +47,11 @@ class CustomsBroker(Base, TenantScopedMixin):
class CustomsBrokerVU(Base):
__tablename__ = "customs_brokers_vu"
__table_args__ = {"schema": "a76"}
broker_key = Column(
String(5),
ForeignKey("a76.customs_brokers.broker_key", ondelete="CASCADE"),
customs_broker_id = Column(
Integer,
ForeignKey("a76.customs_brokers.id", ondelete="CASCADE"),
primary_key=True,
)
certificate_path = Column(String(1499), nullable=True)
@@ -75,10 +79,11 @@ class CustomsBrokerVU(Base):
class CustomsBrokerPersonnel(Base):
__tablename__ = "customs_brokers_personnel"
__table_args__ = {"schema": "a76"}
broker_key = Column(
String(5),
ForeignKey("a76.customs_brokers.broker_key", ondelete="CASCADE"),
customs_broker_id = Column(
Integer,
ForeignKey("a76.customs_brokers.id", ondelete="CASCADE"),
primary_key=True,
)
line = Column(Integer, primary_key=True, nullable=False)

View File

@@ -1,43 +1,54 @@
from typing import Dict, Any
from api.v1.common.tenant_crud_routes import TenantCRUDRoutes
from core.database import get_core_db
from fastapi import APIRouter, Depends, HTTPException
from core.security import get_current_user, validate_access_to_resource
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from . import dto, services
# Create main router
router = APIRouter()
# Create CRUD routes for CustomsBroker using TenantCRUDRoutes
customs_broker_crud = TenantCRUDRoutes(
service=services.CustomsBrokerService,
create_schema=dto.CustomsBrokerCreateDTO,
update_schema=dto.CustomsBrokerUpdateDTO,
response_schema=dto.CustomsBrokerResponseDTO,
prefix="/customs-brokers", # No prefix since it's already in the parent router
tags=[],
resource_name="Customs Broker",
id_name="broker_key",
id_type=str,
enable_list=True, # Enable list endpoint with pagination
)
@router.get("/customs-broker/{broker_key}", response_model=dto.CustomsBrokerDTO)
def get_customs_broker(broker_key: str, db: Session = Depends(get_core_db)):
broker = services.CustomsBrokerService.get_by_broker_key(db, broker_key)
if not broker:
raise HTTPException(status_code=404, detail="Customs Broker not found")
return broker
# Include the CRUD routes
router.include_router(customs_broker_crud.router)
@router.post("/customs-broker", response_model=dto.CustomsBrokerDTO)
def create_customs_broker(
broker_data: dto.CustomsBrokerDTO, db: Session = Depends(get_core_db)
):
return services.CustomsBrokerService.create_customs_broker(db, broker_data)
@router.delete("/customs-broker/{broker_key}", response_model=dto.CustomsBrokerDTO)
def delete_customs_broker(broker_key: str, db: Session = Depends(get_core_db)):
broker = services.CustomsBrokerService.delete_customs_broker(db, broker_key)
if not broker:
raise HTTPException(status_code=404, detail="Customs Broker not found")
return broker
# Additional routes for child resources (CustomsBrokerVU and CustomsBrokerPersonnel)
# These remain as manual routes since they have different patterns
@router.put(
"/customs-broker-vu/{broker_key}", response_model=dto.CustomsBrokerVUCreateDTO
"/customs-broker-vu/{broker_key}",
response_model=dto.CustomsBrokerVUCreateDTO,
)
def update_customs_broker_vu(
broker_key: str,
vu_data: dto.CustomsBrokerVUCreateDTO,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
tenant_id = validate_access_to_resource(db, company_id, current_user)
# Verify the broker exists and belongs to the tenant/company
broker = services.CustomsBrokerService.get_by_id(db, broker_key, tenant_id, company_id)
if not broker:
raise HTTPException(status_code=404, detail="Customs Broker not found")
updated_vu = services.CustomsBrokerVUService.update_vu(db, broker_key, vu_data)
if not updated_vu:
raise HTTPException(status_code=404, detail="Customs Broker VU not found")
@@ -52,8 +63,17 @@ def update_customs_broker_personnel(
broker_key: str,
line: int,
personnel_data: dto.CustomsBrokerPersonnelDTO,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
tenant_id = validate_access_to_resource(db, company_id, current_user)
# Verify the broker exists and belongs to the tenant/company
broker = services.CustomsBrokerService.get_by_id(db, broker_key, tenant_id, company_id)
if not broker:
raise HTTPException(status_code=404, detail="Customs Broker not found")
updated_personnel = services.CustomsBrokerPersonnelService.update_personnel(
db, broker_key, line, personnel_data
)
@@ -62,3 +82,4 @@ def update_customs_broker_personnel(
status_code=404, detail="Customs Broker Personnel not found"
)
return updated_personnel

View File

@@ -5,28 +5,70 @@ from . import dto, models
class CustomsBrokerService:
@staticmethod
def get_by_broker_key(db: Session, broker_key: str):
def get_by_id(db: Session, broker_key: str, tenant_id: int, company_id: int):
"""Get a customs broker by broker_key with tenant/company validation"""
return (
db.query(models.CustomsBroker)
.filter(models.CustomsBroker.broker_key == broker_key)
.filter(
models.CustomsBroker.broker_key == broker_key,
models.CustomsBroker.tenant_id == tenant_id,
models.CustomsBroker.company_id == company_id,
)
.first()
)
@staticmethod
def create_customs_broker(db: Session, broker_data: dto.CustomsBrokerDTO):
new_broker = models.CustomsBroker(**broker_data.dict())
def get_all(
db: Session,
tenant_id: int,
company_id: int,
skip: int = 0,
limit: int = 100,
filters: dict = None,
):
"""Get all customs brokers for a tenant/company with pagination"""
query = db.query(models.CustomsBroker).filter(
models.CustomsBroker.tenant_id == tenant_id,
models.CustomsBroker.company_id == company_id,
)
total = query.count()
items = query.offset(skip).limit(limit).all()
return items, total
@staticmethod
def create(db: Session, broker_data: dto.CustomsBrokerCreateDTO, tenant_id: int, company_id: int):
"""Create a new customs broker"""
broker_dict = broker_data.model_dump()
broker_dict["tenant_id"] = tenant_id
broker_dict["company_id"] = company_id
new_broker = models.CustomsBroker(**broker_dict)
db.add(new_broker)
db.commit()
db.refresh(new_broker)
return new_broker
@staticmethod
def delete_customs_broker(db: Session, broker_key: str):
broker = CustomsBrokerService.get_by_broker_key(db, broker_key)
def update(db: Session, broker_key: str, tenant_id: int, broker_data: dto.CustomsBrokerUpdateDTO, company_id: int):
"""Update an existing customs broker"""
broker = CustomsBrokerService.get_by_id(db, broker_key, tenant_id, company_id)
if broker:
for key, value in broker_data.model_dump(exclude_unset=True).items():
setattr(broker, key, value)
db.commit()
db.refresh(broker)
return broker
@staticmethod
def delete(db: Session, broker_key: str, tenant_id: int, company_id: int):
"""Delete a customs broker"""
broker = CustomsBrokerService.get_by_id(db, broker_key, tenant_id, company_id)
if broker:
db.delete(broker)
db.commit()
return broker
return True
return False
class CustomsBrokerVUService:

View File

@@ -5,6 +5,23 @@ from typing import Optional
from pydantic import BaseModel, ConfigDict, Field
from .pedimento_config_additional import PedimentoConfigAdditionalCreate
from .pedimento_config_calculations import PedimentoConfigCalculationsCreate
from .pedimento_config_parameters import PedimentoConfigParametersCreate
from .pedimento_config_surcharges import PedimentoConfigSurchargesCreate
from .pedimento_config_update_rectification import PedimentoConfigUpdateRectificationCreate
from .pedimento_config_updates import PedimentoConfigUpdatesCreate
from .pedimento_customs_offices import PedimentoCustomsOfficesCreate
from .pedimento_dates import PedimentoDatesCreate
from .pedimento_decrementables import PedimentoDecrementablesCreate
from .pedimento_incrementables import PedimentoIncrementablesCreate
from .pedimento_indexes import PedimentoIndexesCreate
from .pedimento_payments import PedimentoPaymentsCreate
from .pedimento_rectification_destination import PedimentoRectificationDestinationCreate
from .pedimento_rectification_origin import PedimentoRectificationOriginCreate
from .pedimento_transport_means import PedimentoTransportMeansCreate
from .pedimento_validation import PedimentoValidationCreate
class OperationType(IntEnum):
EXPORTACION = 1
@@ -35,6 +52,22 @@ class PedimentosBase(BaseModel):
gross_weight: Optional[Decimal] = Field(None, description="Gross weight")
exchange_rate: Optional[Decimal] = Field(None, description="Exchange rate")
pedimento_dates: Optional[PedimentoDatesCreate] = None
pedimento_decrementables: Optional[PedimentoDecrementablesCreate] = None
pedimento_incrementables: Optional[PedimentoIncrementablesCreate] = None
pedimento_indexes: Optional[PedimentoIndexesCreate] = None
pedimento_validation: Optional[PedimentoValidationCreate] = None
pedimento_customs_offices: Optional[PedimentoCustomsOfficesCreate] = None
pedimento_payments: Optional[PedimentoPaymentsCreate] = None
pedimento_rectification_destination: Optional[PedimentoRectificationDestinationCreate] = None
pedimento_rectification_origin: Optional[PedimentoRectificationOriginCreate] = None
pedimento_transport_means: Optional[PedimentoTransportMeansCreate] = None
pedimento_config_additional: Optional[PedimentoConfigAdditionalCreate] = None
pedimento_config_calculations: Optional[PedimentoConfigCalculationsCreate] = None
pedimento_config_parameters: Optional[PedimentoConfigParametersCreate] = None
pedimento_config_surcharges: Optional[PedimentoConfigSurchargesCreate] = None
pedimento_config_update_rectification: Optional[PedimentoConfigUpdateRectificationCreate] = None
pedimento_config_updates: Optional[PedimentoConfigUpdatesCreate] = None
class PedimentosCreate(PedimentosBase):
"""Schema for creating a new Pedimento"""
@@ -48,8 +81,7 @@ class PedimentosCreate(PedimentosBase):
operation_type: int = Field(..., description="Operation type")
pedimento_type: int = Field(..., description="Pedimento type")
regime: str = Field(..., max_length=3, description="Regime")
status: str = Field(..., max_length=30, description="Status")
status: str = Field(..., max_length=30, description="Status")
class PedimentosUpdate(BaseModel):
"""Schema for updating a Pedimento"""

View File

@@ -2,13 +2,51 @@
Service layer for Pedimentos CRUD operations
"""
import logging
from typing import Any, Dict, List, Optional
from sqlalchemy import desc
from sqlalchemy.orm import Session
from ..dtos.pedimentos import PedimentosCreate, PedimentosUpdate
from .pedimento_config_additional import PedimentoConfigAdditionalService
from .pedimento_config_calculations import PedimentoConfigCalculationsService
from .pedimento_config_parameters import PedimentoConfigParametersService
from .pedimento_config_surcharges import PedimentoConfigSurchargesService
from .pedimento_config_update_rectification import PedimentoConfigUpdateRectificationService
from .pedimento_config_updates import PedimentoConfigUpdatesService
from .pedimento_customs_offices import PedimentoCustomsOfficesService
from .pedimento_dates import PedimentoDatesService
from .pedimento_decrementables import PedimentoDecrementablesService
from .pedimento_incrementables import PedimentoIncrementablesService
from .pedimento_indexes import PedimentoIndexesService
from .pedimento_payments import PedimentoPaymentsService
from .pedimento_rectification_destination import PedimentoRectificationDestinationService
from .pedimento_rectification_origin import PedimentoRectificationOriginService
from .pedimento_transport_means import PedimentoTransportMeansService
from .pedimento_validation import PedimentoValidationService
# Crear tablas relacionadas si existen datos
from ..models.pedimentos import Pedimentos
from ..models.pedimento_dates import PedimentoDates
from ..models.pedimento_decrementables import PedimentoDecrementables
from ..models.pedimento_incrementables import PedimentoIncrementables
from ..models.pedimento_indexes import PedimentoIndexes
from ..models.pedimento_validation import PedimentoValidation
from ..models.pedimento_customs_offices import PedimentoCustomsOffices
from ..models.pedimento_payments import PedimentoPayments
from ..models.pedimento_rectification_destination import PedimentoRectificationDestination
from ..models.pedimento_rectification_origin import PedimentoRectificationOrigin
from ..models.pedimento_transport_means import PedimentoTransportMeans
from ..models.pedimento_config_additional import PedimentoConfigAdditional
from ..models.pedimento_config_calculations import PedimentoConfigCalculations
from ..models.pedimento_config_parameters import PedimentoConfigParameters
from ..models.pedimento_config_surcharges import PedimentoConfigSurcharges
from ..models.pedimento_config_update_rectification import PedimentoConfigUpdateRectification
from ..models.pedimento_config_updates import PedimentoConfigUpdates
logger = logging.getLogger(__name__)
class PedimentosService:
@@ -36,7 +74,7 @@ class PedimentosService:
Returns:
Tuple of (list of pedimentos, total count)
"""
query = db.query(Pedimentos).filter(Pedimentos.tenant_id == tenant_id)
query = db.query(Pedimentos).filter(Pedimentos.tenant_id == tenant_id, Pedimentos.company_id == company_id)
if filters:
if filters.get("status"):
@@ -70,7 +108,7 @@ class PedimentosService:
Pedimento or None if not found
"""
query = db.query(Pedimentos).filter(
Pedimentos.id == pedimento_id, Pedimentos.tenant_id == tenant_id
Pedimentos.id == pedimento_id, Pedimentos.tenant_id == tenant_id, Pedimentos.company_id == company_id
)
if company_id is not None:
@@ -83,7 +121,7 @@ class PedimentosService:
db: Session, pedimento_data: PedimentosCreate, tenant_id: int, company_id: int
) -> Pedimentos:
"""
Create a new pedimento
Create a new pedimento with related tables
Args:
db: Database session
@@ -94,21 +132,87 @@ class PedimentosService:
Returns:
Created pedimento
"""
pedimento = Pedimentos(**pedimento_data.model_dump())
pedimento.tenant_id = tenant_id
pedimento.company_id = company_id
try:
# Extraer datos de tablas relacionadas
related_data = {
'pedimento_dates': pedimento_data.pedimento_dates,
'pedimento_decrementables': pedimento_data.pedimento_decrementables,
'pedimento_incrementables': pedimento_data.pedimento_incrementables,
'pedimento_indexes': pedimento_data.pedimento_indexes,
'pedimento_validation': pedimento_data.pedimento_validation,
'pedimento_customs_offices': pedimento_data.pedimento_customs_offices,
'pedimento_payments': pedimento_data.pedimento_payments,
'pedimento_rectification_destination': pedimento_data.pedimento_rectification_destination,
'pedimento_rectification_origin': pedimento_data.pedimento_rectification_origin,
'pedimento_transport_means': pedimento_data.pedimento_transport_means,
'pedimento_config_additional': pedimento_data.pedimento_config_additional,
'pedimento_config_calculations': pedimento_data.pedimento_config_calculations,
'pedimento_config_parameters': pedimento_data.pedimento_config_parameters,
'pedimento_config_surcharges': pedimento_data.pedimento_config_surcharges,
'pedimento_config_update_rectification': pedimento_data.pedimento_config_update_rectification,
'pedimento_config_updates': pedimento_data.pedimento_config_updates,
}
# Crear pedimento principal (excluyendo relaciones)
pedimento_dict = pedimento_data.model_dump(exclude={
'pedimento_dates', 'pedimento_decrementables', 'pedimento_incrementables',
'pedimento_indexes', 'pedimento_validation', 'pedimento_customs_offices',
'pedimento_payments', 'pedimento_rectification_destination',
'pedimento_rectification_origin', 'pedimento_transport_means',
'pedimento_config_additional', 'pedimento_config_calculations',
'pedimento_config_parameters', 'pedimento_config_surcharges',
'pedimento_config_update_rectification', 'pedimento_config_updates'
})
pedimento = Pedimentos(**pedimento_dict)
pedimento.tenant_id = tenant_id
pedimento.company_id = company_id
db.add(pedimento)
db.commit()
db.refresh(pedimento)
return pedimento
db.add(pedimento)
db.flush() # Flush para obtener el ID sin commit
# Helper function para crear objetos relacionados
def create_related(model_class, data):
if data:
obj_dict = data.model_dump()
obj = model_class(**obj_dict)
obj.pedimento_id = pedimento.id
obj.tenant_id = tenant_id
obj.company_id = company_id
db.add(obj)
create_related(PedimentoDates, related_data['pedimento_dates'])
create_related(PedimentoDecrementables, related_data['pedimento_decrementables'])
create_related(PedimentoIncrementables, related_data['pedimento_incrementables'])
create_related(PedimentoIndexes, related_data['pedimento_indexes'])
create_related(PedimentoValidation, related_data['pedimento_validation'])
create_related(PedimentoCustomsOffices, related_data['pedimento_customs_offices'])
create_related(PedimentoPayments, related_data['pedimento_payments'])
create_related(PedimentoRectificationDestination, related_data['pedimento_rectification_destination'])
create_related(PedimentoRectificationOrigin, related_data['pedimento_rectification_origin'])
create_related(PedimentoTransportMeans, related_data['pedimento_transport_means'])
create_related(PedimentoConfigAdditional, related_data['pedimento_config_additional'])
create_related(PedimentoConfigCalculations, related_data['pedimento_config_calculations'])
create_related(PedimentoConfigParameters, related_data['pedimento_config_parameters'])
create_related(PedimentoConfigSurcharges, related_data['pedimento_config_surcharges'])
create_related(PedimentoConfigUpdateRectification, related_data['pedimento_config_update_rectification'])
create_related(PedimentoConfigUpdates, related_data['pedimento_config_updates'])
db.commit()
db.refresh(pedimento)
return pedimento
except Exception as e:
db.rollback()
logger.error(f"Error creating pedimento with related data: {e}")
raise
@staticmethod
def update(
db: Session, pedimento_id: int, tenant_id: int, pedimento_data: PedimentosUpdate, company_id: int = None
) -> Optional[Pedimentos]:
"""
Update a pedimento
Update a pedimento and its related tables
Args:
db: Database session
@@ -124,13 +228,73 @@ class PedimentosService:
if not pedimento:
return None
update_data = pedimento_data.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(pedimento, field, value)
try:
# Actualizar campos principales del pedimento
update_data = pedimento_data.model_dump(exclude_unset=True, exclude={
'pedimento_dates', 'pedimento_decrementables', 'pedimento_incrementables',
'pedimento_indexes', 'pedimento_validation', 'pedimento_customs_offices',
'pedimento_payments', 'pedimento_rectification_destination',
'pedimento_rectification_origin', 'pedimento_transport_means',
'pedimento_config_additional', 'pedimento_config_calculations',
'pedimento_config_parameters', 'pedimento_config_surcharges',
'pedimento_config_update_rectification', 'pedimento_config_updates'
})
for field, value in update_data.items():
setattr(pedimento, field, value)
db.commit()
db.refresh(pedimento)
return pedimento
db.flush()
# Helper function para actualizar o crear objetos relacionados
def update_or_create_related(service_class, model_class, data_attr):
if not hasattr(pedimento_data, data_attr):
return
data = getattr(pedimento_data, data_attr)
if not data:
return
existing = service_class.get_by_pedimento_id(db, pedimento_id, tenant_id)
if existing:
# Actualizar existente
update_dict = data.model_dump(exclude_unset=True)
for field, value in update_dict.items():
setattr(existing, field, value)
else:
# Crear nuevo
obj_dict = data.model_dump()
obj = model_class(**obj_dict)
obj.pedimento_id = pedimento_id
obj.tenant_id = tenant_id
obj.company_id = company_id
db.add(obj)
# Actualizar o crear tablas relacionadas
update_or_create_related(PedimentoDatesService, PedimentoDates, 'pedimento_dates')
update_or_create_related(PedimentoDecrementablesService, PedimentoDecrementables, 'pedimento_decrementables')
update_or_create_related(PedimentoIncrementablesService, PedimentoIncrementables, 'pedimento_incrementables')
update_or_create_related(PedimentoIndexesService, PedimentoIndexes, 'pedimento_indexes')
update_or_create_related(PedimentoValidationService, PedimentoValidation, 'pedimento_validation')
update_or_create_related(PedimentoCustomsOfficesService, PedimentoCustomsOffices, 'pedimento_customs_offices')
update_or_create_related(PedimentoPaymentsService, PedimentoPayments, 'pedimento_payments')
update_or_create_related(PedimentoRectificationDestinationService, PedimentoRectificationDestination, 'pedimento_rectification_destination')
update_or_create_related(PedimentoRectificationOriginService, PedimentoRectificationOrigin, 'pedimento_rectification_origin')
update_or_create_related(PedimentoTransportMeansService, PedimentoTransportMeans, 'pedimento_transport_means')
update_or_create_related(PedimentoConfigAdditionalService, PedimentoConfigAdditional, 'pedimento_config_additional')
update_or_create_related(PedimentoConfigCalculationsService, PedimentoConfigCalculations, 'pedimento_config_calculations')
update_or_create_related(PedimentoConfigParametersService, PedimentoConfigParameters, 'pedimento_config_parameters')
update_or_create_related(PedimentoConfigSurchargesService, PedimentoConfigSurcharges, 'pedimento_config_surcharges')
update_or_create_related(PedimentoConfigUpdateRectificationService, PedimentoConfigUpdateRectification, 'pedimento_config_update_rectification')
update_or_create_related(PedimentoConfigUpdatesService, PedimentoConfigUpdates, 'pedimento_config_updates')
db.commit()
db.refresh(pedimento)
return pedimento
except Exception as e:
db.rollback()
logger.error(f"Error updating pedimento with related data: {e}")
raise
@staticmethod
def delete(db: Session, pedimento_id: int, tenant_id: int, company_id: int = None) -> bool:
@@ -153,5 +317,3 @@ class PedimentosService:
db.delete(pedimento)
db.commit()
return True
db.commit()
return True

View File

@@ -38,27 +38,27 @@ router.include_router(user_tenant_router, prefix="/a76", tags=["a76 / user-tenan
router.include_router(licenses_router, prefix="/a76", tags=["a76 / licenses"])
router.include_router(pedimentos_router, prefix="/a76")
router.include_router(
client_and_provider_router, prefix="/a76", tags=["a76 / clients and providers"]
client_and_provider_router, prefix="/a76", tags=["a76 / clients_and_providers"]
)
router.include_router(company_router, prefix="/a76", tags=["a76 / company"])
router.include_router(classes_router, prefix="/a76", tags=["a76 / classes"])
router.include_router(parts_router, prefix="/a76", tags=["a76 / parts"])
router.include_router(
permission_rule_oct_router, prefix="/a76", tags=["a76 / PermissionRuleOct"]
permission_rule_oct_router, prefix="/a76", tags=["a76 / permission_rule_oct"]
)
router.include_router(package_router, prefix="/a76", tags=["a76 / Package"])
router.include_router(seal_router, prefix="/a76", tags=["a76 / Seal"])
router.include_router(package_router, prefix="/a76", tags=["a76 / package"])
router.include_router(seal_router, prefix="/a76", tags=["a76 / seal"])
router.include_router(
fraction_rule_octave_router, prefix="/a76", tags=["a76 / FractionRuleOctave"]
fraction_rule_octave_router, prefix="/a76", tags=["a76 / fraction_rule_octave"]
)
router.include_router(
country_rule_oct_router, prefix="/a76", tags=["a76 / CountryRuleOct"]
country_rule_oct_router, prefix="/a76", tags=["a76 / country_rule_oct"]
)
router.include_router(exchange_rate_router, prefix="/a76", tags=["a76 / ExchangeRate"])
router.include_router(trailers_router, prefix="/a76", tags=["a76 / Trailers"])
router.include_router(exchange_rate_router, prefix="/a76", tags=["a76 / exchange_rate"])
router.include_router(trailers_router, prefix="/a76", tags=["a76 / trailers"])
router.include_router(
customs_broker_router, prefix="/a76", tags=["a76 / CustomsBroker"]
customs_broker_router, prefix="/a76", tags=["a76 / customs_broker"]
)
router.include_router(drivers_router, prefix="/a76", tags=["a76 / Drivers"])
router.include_router(transporters_router, prefix="/a76", tags=["a76 / Transporters"])
router.include_router(vehicles_router, prefix="/a76", tags=["a76 / Vehicles"])
router.include_router(drivers_router, prefix="/a76", tags=["a76 / drivers"])
router.include_router(transporters_router, prefix="/a76", tags=["a76 / transporters"])
router.include_router(vehicles_router, prefix="/a76", tags=["a76 / vehicles"])

View File

@@ -162,7 +162,7 @@ def validate_company_access(
# Consultar si la compañía pertenece al tenant
try:
from api.v1.modules.a76.company.models import Company
from api.v1.modules.a76.general_catalogs.company.models import Company
company = (
db.query(Company)

View File

@@ -88,32 +88,40 @@ export interface CreateCustomsBrokerData {
* API para Agentes Aduanales
*/
export const customsBrokersApi = {
/**
* Lista todos los agentes aduanales
*/
list: (companyId: string) => {
return api.get<CustomsBroker[]>(`/v1/a76/customs-brokers?company_id=${companyId}`);
},
/**
* Obtiene un agente aduanal por su clave
*/
get: (brokerKey: string) => {
return api.get<CustomsBroker>(`/api/v1/a76/customs-broker/${brokerKey}`);
return api.get<CustomsBroker>(`/v1/a76/customs-brokers/${brokerKey}`);
},
/**
* Crea un nuevo agente aduanal
*/
create: (data: CreateCustomsBrokerData) => {
return api.post<CustomsBroker>('/api/v1/a76/customs-broker', data);
const companyId = data.company_id;
return api.post<CustomsBroker>(`/v1/a76/customs-brokers?company_id=${companyId}`, data);
},
/**
* Elimina un agente aduanal
*/
delete: (brokerKey: string) => {
return api.delete<CustomsBroker>(`/api/v1/a76/customs-broker/${brokerKey}`);
delete: (brokerKey: string, companyId: string) => {
return api.delete<CustomsBroker>(`/v1/a76/customs-brokers/${brokerKey}?company_id=${companyId}`);
},
/**
* Actualiza la información de VU de un agente aduanal
*/
updateVU: (brokerKey: string, data: CustomsBrokerVU) => {
return api.put<CustomsBrokerVU>(`/api/v1/a76/customs-broker-vu/${brokerKey}`, data);
return api.put<CustomsBrokerVU>(`/v1/a76/customs-broker-vu/${brokerKey}`, data);
},
/**
@@ -121,7 +129,7 @@ export const customsBrokersApi = {
*/
updatePersonnel: (brokerKey: string, line: number, data: CustomsBrokerPersonnel) => {
return api.put<CustomsBrokerPersonnel>(
`/api/v1/a76/customs-broker-personnel/${brokerKey}/${line}`,
`/v1/a76/customs-broker-personnel/${brokerKey}/${line}`,
data
);
}

View File

@@ -23,7 +23,7 @@
error = null;
try {
const response = await customsBrokersApi.delete(broker.broker_key);
const response = await customsBrokersApi.delete(broker.broker_key, broker.company_id);
if (response.error) {
error = response.error;

View File

@@ -55,13 +55,13 @@ class CompanyStore {
if (savedId) {
const company = this._companies.find(c => c.id === parseInt(savedId));
if (company) {
this.setActiveCompany(company);
this.setActiveCompany(company, true); // silent=true para inicialización
return;
}
}
}
// Si no hay guardada, seleccionar la primera
this.setActiveCompany(this._companies[0]);
this.setActiveCompany(this._companies[0], true); // silent=true para inicialización
}
return;
}
@@ -90,7 +90,7 @@ class CompanyStore {
// Si hay compañías y no hay una activa, seleccionar la primera
if (this._companies.length > 0 && !this._activeCompany) {
this.setActiveCompany(this._companies[0]);
this.setActiveCompany(this._companies[0], true); // silent=true para inicialización
}
} else {
console.error('Error loading companies:', response.statusText);
@@ -108,8 +108,11 @@ class CompanyStore {
/**
* Establece la compañía activa
* @param company - La compañía a establecer como activa
* @param silent - Si es true, no dispara el evento companyChanged (para inicialización)
*/
setActiveCompany(company: Company) {
setActiveCompany(company: Company, silent: boolean = false) {
const previousCompanyId = this._activeCompany?.id;
this._activeCompany = company;
// Guardar en localStorage para persistencia
@@ -122,8 +125,10 @@ class CompanyStore {
document.cookie = `active_company_id=${company.id}; path=/; max-age=${60 * 60 * 24 * 30}; SameSite=Lax`;
}
// Despachar evento personalizado para que otros componentes reaccionen
if (typeof window !== 'undefined') {
// Despachar evento personalizado solo si:
// 1. No es silent (no es inicialización)
// 2. Y realmente cambió la compañía (el ID es diferente)
if (!silent && typeof window !== 'undefined' && previousCompanyId !== company.id) {
window.dispatchEvent(new CustomEvent('companyChanged', {
detail: { companyId: company.id }
}));

View File

@@ -38,13 +38,11 @@ export const GET: RequestHandler = async ({ cookies, fetch }) => {
cookies.delete('active_company_id', { path: '/' });
}
return json({ error: 'Failed to fetch companies' }, { status: response.status });
}
console.log('✅ Token', token);
}
const companies = await response.json();
return json(companies);
} catch (error) {
console.error('Error fetching companies:', error);
console.log('✅ Token', token);
console.error('Error fetching companies:', error);
return json({ error: 'Internal server error' }, { status: 500 });
}
};

View File

@@ -23,11 +23,18 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
const page = parseInt(url.searchParams.get('page') || '1');
const pageSize = parseInt(url.searchParams.get('page_size') || '50');
// Obtener company_id de la URL o de las companies del usuario
// Obtener company_id de múltiples fuentes (en orden de prioridad):
// 1. URL query param (permite cambiar vía navegación)
// 2. Cookie active_company_id (setted por el team-switcher)
// 3. Primera compañía del usuario (fallback)
const companyIdParam = url.searchParams.get('company_id');
const cookieCompanyId = cookies.get('active_company_id');
const companyId = companyIdParam
? parseInt(companyIdParam)
: parentData.companies?.[0]?.id; // Usar la primera compañía por defecto
: cookieCompanyId
? parseInt(cookieCompanyId)
: parentData.companies?.[0]?.id;
if (!companyId) {
return {

View File

@@ -42,6 +42,19 @@
if (cookieRefreshToken && cookieRefreshToken !== localRefreshToken) {
localStorage.setItem('refresh_token', cookieRefreshToken);
}
// Escuchar cambios de compañía
const handleCompanyChange = (event: CustomEvent) => {
// Recargar la página para obtener datos de la nueva compañía
reloadData();
};
window.addEventListener('companyChanged', handleCompanyChange as EventListener);
// Cleanup
return () => {
window.removeEventListener('companyChanged', handleCompanyChange as EventListener);
};
}
});
@@ -97,9 +110,45 @@
}
}
function reloadData() {
// Reset y recargar desde el principio
window.location.reload();
async function reloadData() {
// Reset y recargar desde el principio usando la API
if (!companyStore.activeCompany) return;
loading = true;
error = null;
try {
const response = await clientsProvidersApi.list(
companyStore.activeCompany.id,
1,
pageSize
);
if (response.error) {
console.error('📊 [Page] Error en reloadData:', response.error);
if (response.status === 401 || response.status === 403) {
error = 'Sesión expirada. Recargando página...';
setTimeout(() => {
window.location.reload();
}, 2000);
} else {
error = response.error;
}
return;
}
if (response.data?.items) {
// Reemplazar todos los items con los nuevos datos
allItems = response.data.items;
currentPage = 1;
totalItems = response.data.total;
}
} catch (e) {
error = 'Error recargando datos';
console.error('📊 [Page] Error reloading:', e);
} finally {
loading = false;
}
}
function handleCreateClick() {

View File

@@ -1,7 +1,7 @@
import type { PageServerLoad } from './$types';
import { getAuthTokens } from '$lib/server/api';
import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
export const load: PageServerLoad = async ({ cookies, parent }) => {
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
// Esperar a que el layout padre valide/refresque el token
const parentData = await parent();
@@ -10,12 +10,75 @@ export const load: PageServerLoad = async ({ cookies, parent }) => {
if (!accessToken) {
return {
error: 'No authenticated',
brokers: [],
companies: parentData.companies || []
};
}
return {
companies: parentData.companies || [],
error: null
};
try {
// Obtener company_id de múltiples fuentes (en orden de prioridad):
// 1. URL query param (permite cambiar vía navegación)
// 2. Cookie active_company_id (setted por el team-switcher)
// 3. Primera compañía del usuario (fallback)
const companyIdParam = url.searchParams.get('company_id');
const cookieCompanyId = cookies.get('active_company_id');
const companyId = companyIdParam
? parseInt(companyIdParam)
: cookieCompanyId
? parseInt(cookieCompanyId)
: parentData.companies?.[0]?.id;
if (!companyId) {
return {
error: 'No company selected',
brokers: [],
companies: parentData.companies || []
};
}
// Usar authenticatedFetch para manejar automáticamente el refresh de tokens
const response = await authenticatedFetch(
`v1/a76/customs-brokers?company_id=${companyId}`,
{},
cookies,
fetch
);
if (!response.ok) {
const errorText = await response.text();
console.error('📊 [CustomsBrokers] API Error:', {
status: response.status,
statusText: response.statusText,
error: errorText
});
return {
error: `Error ${response.status}: ${response.statusText}`,
brokers: [],
companies: parentData.companies || [],
currentCompanyId: companyId
};
}
const data = await response.json();
// El endpoint devuelve un objeto con items, total, page, page_size
// Extraer el array de items
const brokers = Array.isArray(data) ? data : (data.items || []);
return {
brokers: brokers,
error: null,
companies: parentData.companies || [],
currentCompanyId: companyId
};
} catch (error) {
console.error('📊 [CustomsBrokers] Load error:', error);
return {
error: 'Error loading data',
brokers: [],
companies: parentData.companies || []
};
}
};

View File

@@ -18,6 +18,11 @@
// Estado para el diálogo de crear
let showCreateDialog = $state(false);
// Estado para la lista de agentes aduanales (inicializar con datos del servidor)
let brokersList = $state<CustomsBroker[]>(data.brokers || []);
let listLoading = $state(false);
let listError = $state<string | null>(data.error || null);
// Estado para búsqueda
let searchKey = $state('');
let searchedBroker = $state<CustomsBroker | null>(null);
@@ -50,6 +55,19 @@
if (cookieRefreshToken && cookieRefreshToken !== localRefreshToken) {
localStorage.setItem('refresh_token', cookieRefreshToken);
}
// Escuchar cambios de compañía
const handleCompanyChange = (event: CustomEvent) => {
// Recargar la página para obtener datos de la nueva compañía
reloadData();
};
window.addEventListener('companyChanged', handleCompanyChange as EventListener);
// Cleanup
return () => {
window.removeEventListener('companyChanged', handleCompanyChange as EventListener);
};
}
});
@@ -91,11 +109,44 @@
}
}
function reloadData() {
async function reloadData() {
// Limpiar búsqueda
searchKey = '';
searchedBroker = null;
searchError = null;
// Recargar lista de brokers desde la API
if (!companyStore.activeCompany) return;
listLoading = true;
listError = null;
try {
const response = await customsBrokersApi.list(companyStore.activeCompany.id.toString());
if (response.error) {
console.error('📊 [CustomsBrokers] Error en reloadData:', response.error);
if (response.status === 401 || response.status === 403) {
listError = 'Sesión expirada. Recargando página...';
setTimeout(() => {
window.location.reload();
}, 2000);
} else {
listError = response.error;
}
return;
}
if (response.data) {
// Reemplazar la lista con los nuevos datos
brokersList = response.data;
}
} catch (e) {
listError = 'Error recargando datos';
console.error('📊 [CustomsBrokers] Error reloading:', e);
} finally {
listLoading = false;
}
}
function handleCreateClick() {
@@ -110,8 +161,8 @@
// Crear columnas con el callback onSuccess
const columns = createColumns(handleSuccess);
// Array para mostrar en la tabla (vacío o con el broker buscado)
const brokers = $derived(searchedBroker ? [searchedBroker] : []);
// Array para mostrar en la tabla (búsqueda o lista completa)
const brokers = $derived(searchedBroker ? [searchedBroker] : brokersList);
</script>
<div class="space-y-6">
@@ -227,26 +278,49 @@
</Card.Root>
<!-- Resultados -->
{#if searchedBroker}
<Card.Root>
<Card.Header>
<div class="flex items-center justify-between">
<div>
<Card.Title>Resultado de la Búsqueda</Card.Title>
<Card.Description>
<Card.Root>
<Card.Header>
<div class="flex items-center justify-between">
<div>
<Card.Title>
{#if searchedBroker}
Resultado de la Búsqueda
{:else}
Agentes Aduanales
{/if}
</Card.Title>
<Card.Description>
{#if searchedBroker}
Se encontró 1 agente aduanal
</Card.Description>
{:else if listLoading}
Cargando agentes aduanales...
{:else}
Total: {brokersList.length} agente{brokersList.length !== 1 ? 's' : ''} aduanal{brokersList.length !== 1 ? 'es' : ''}
{/if}
</Card.Description>
</div>
</div>
</Card.Header>
<Card.Content>
{#if listError}
<div class="rounded-lg border border-destructive bg-destructive/10 p-4 text-sm text-destructive">
{listError}
</div>
{:else if listLoading}
<div class="flex items-center justify-center py-8">
<div class="flex items-center gap-2 text-muted-foreground">
<div class="h-6 w-6 animate-spin rounded-full border-2 border-primary border-t-transparent"></div>
Cargando agentes aduanales...
</div>
</div>
</Card.Header>
<Card.Content>
{:else}
<DataTable
data={brokers}
{columns}
/>
</Card.Content>
</Card.Root>
{/if}
{/if}
</Card.Content>
</Card.Root>
</div>
<!-- Diálogo de crear -->

View File

@@ -2,11 +2,13 @@ import type { PageServerLoad } from './$types';
import { redirect } from '@sveltejs/kit';
import {
getAuthTokens,
getActiveCompanyId,
authenticatedFetch
} from '$lib/server/api';
export const load: PageServerLoad = async ({ fetch, cookies }) => {
export const load: PageServerLoad = async ({ fetch, cookies, url, parent }) => {
// Esperar a que el layout padre valide/refresque el token
const parentData = await parent();
// Verificar autenticación
const { accessToken } = getAuthTokens(cookies);
@@ -15,8 +17,18 @@ export const load: PageServerLoad = async ({ fetch, cookies }) => {
}
try {
// Obtener el company_id de la cookie o usar la primera disponible
const companyId = await getActiveCompanyId(cookies, fetch);
// Obtener company_id de múltiples fuentes (en orden de prioridad):
// 1. URL query param (permite cambiar vía navegación)
// 2. Cookie active_company_id (setted por el team-switcher)
// 3. Primera compañía del usuario (fallback)
const companyIdParam = url.searchParams.get('company_id');
const cookieCompanyId = cookies.get('active_company_id');
const companyId = companyIdParam
? parseInt(companyIdParam)
: cookieCompanyId
? parseInt(cookieCompanyId)
: parentData.companies?.[0]?.id;
// Si aún no hay companyId, mostrar error
if (!companyId) {
@@ -25,7 +37,8 @@ export const load: PageServerLoad = async ({ fetch, cookies }) => {
total: 0,
page: 1,
page_size: 50,
error: 'No se encontró una compañía seleccionada'
error: 'No se encontró una compañía seleccionada',
companies: parentData.companies || []
};
}
@@ -44,7 +57,9 @@ export const load: PageServerLoad = async ({ fetch, cookies }) => {
total: 0,
page: 1,
page_size: 50,
error: 'Error al cargar pedimentos'
error: 'Error al cargar pedimentos',
companies: parentData.companies || [],
currentCompanyId: companyId
};
}
@@ -54,7 +69,9 @@ export const load: PageServerLoad = async ({ fetch, cookies }) => {
items: data.items || [],
total: data.total || 0,
page: data.page || 1,
page_size: data.page_size || 50
page_size: data.page_size || 50,
companies: parentData.companies || [],
currentCompanyId: companyId
};
} catch (error) {
console.error('Error loading pedimentos:', error);
@@ -63,7 +80,8 @@ export const load: PageServerLoad = async ({ fetch, cookies }) => {
total: 0,
page: 1,
page_size: 50,
error: 'Error al cargar pedimentos'
error: 'Error al cargar pedimentos',
companies: parentData.companies || []
};
}
};

View File

@@ -9,6 +9,7 @@
import { Label } from '$lib/components/ui/label';
import type { PageData } from './$types';
import { browser } from '$app/environment';
import { companyStore } from '$lib/stores/company.svelte';
// Los datos iniciales vienen del servidor
let { data }: { data: PageData } = $props();
@@ -46,12 +47,25 @@
if (cookieRefreshToken && cookieRefreshToken !== localRefreshToken) {
localStorage.setItem('refresh_token', cookieRefreshToken);
}
// Escuchar cambios de compañía
const handleCompanyChange = (event: CustomEvent) => {
// Recargar los datos sin recargar la página completa
reloadData();
};
window.addEventListener('companyChanged', handleCompanyChange as EventListener);
// Cleanup
return () => {
window.removeEventListener('companyChanged', handleCompanyChange as EventListener);
};
}
});
// Estado para infinite scroll
let allItems = $state<Pedimento[]>(data.items || []);
let currentPage = $state(data.page || 1);
let currentPage = $state(data.page);
let pageSize = $state(50);
let totalItems = $state(data.total || 0);
let loading = $state(false);
@@ -65,13 +79,15 @@
error = null;
try {
const companyId = companyStore.activeCompany?.id;
const filterParams = {
status: filters.status || undefined,
client_id: filters.client_id ? parseInt(filters.client_id) : undefined,
year: filters.year || undefined
};
const response = await pedimentosApi.list(currentPage + 1, pageSize, filterParams);
const response = await pedimentosApi.list(currentPage + 1, pageSize, filterParams, companyId);
if (response.error) {
console.error('📊 [Page] Error en loadMore:', response.error, 'Status:', response.status);
@@ -109,13 +125,15 @@
error = null;
try {
const companyId = companyStore.activeCompany?.id || 1;
const filterParams = {
status: filters.status || undefined,
client_id: filters.client_id ? parseInt(filters.client_id) : undefined,
year: filters.year || undefined
};
const response = await pedimentosApi.list(1, pageSize, filterParams);
const response = await pedimentosApi.list(1, pageSize, filterParams, companyId);
if (response.error) {
console.error('📊 [Page] Error aplicando filtros:', response.error);
@@ -150,12 +168,52 @@
client_id: '',
year: ''
};
window.location.reload();
applyFilters();
}
function reloadData() {
// Reset y recargar desde el principio
window.location.reload();
async function reloadData() {
// Reset y recargar desde el principio usando la API
if (!companyStore.activeCompany) return;
loading = true;
error = null;
try {
const companyId = companyStore.activeCompany.id;
const filterParams = {
status: filters.status || undefined,
client_id: filters.client_id ? parseInt(filters.client_id) : undefined,
year: filters.year || undefined
};
const response = await pedimentosApi.list(1, pageSize, filterParams, companyId);
if (response.error) {
console.error('📊 [Pedimentos] Error en reloadData:', response.error);
if (response.status === 401 || response.status === 403) {
error = 'Sesión expirada. Recargando página...';
setTimeout(() => {
window.location.reload();
}, 2000);
} else {
error = response.error;
}
return;
}
if (response.data?.items) {
// Reemplazar todos los items con los nuevos datos
allItems = response.data.items;
currentPage = 1;
totalItems = response.data.total;
}
} catch (e) {
error = 'Error recargando datos';
console.error('📊 [Pedimentos] Error reloading:', e);
} finally {
loading = false;
}
}
function handleCreateClick() {

View File

@@ -1,6 +1,6 @@
import type { PageServerLoad } from './$types';
import { error, redirect } from '@sveltejs/kit';
import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
import { getAuthTokens, getActiveCompanyId, authenticatedFetch } from '$lib/server/api';
export const load: PageServerLoad = async ({ params, cookies, fetch }) => {
const { accessToken } = getAuthTokens(cookies);
@@ -24,9 +24,16 @@ export const load: PageServerLoad = async ({ params, cookies, fetch }) => {
}
try {
// Obtener el company_id de la cookie
const companyId = await getActiveCompanyId(cookies, fetch);
if (!companyId) {
throw error(400, 'No se encontró una compañía seleccionada');
}
// Cargar el pedimento desde el backend usando authenticatedFetch
const response = await authenticatedFetch(
`v1/a76/pedimentos/${pedimentoId}`,
`v1/a76/pedimentos/${pedimentoId}?company_id=${companyId}`,
{},
cookies,
fetch