diff --git a/backend/alembic/versions/add_timestamps_to_classes.py b/backend/alembic/versions/add_timestamps_to_classes.py
deleted file mode 100644
index a2b4f554..00000000
--- a/backend/alembic/versions/add_timestamps_to_classes.py
+++ /dev/null
@@ -1,67 +0,0 @@
-"""Add timestamp fields to classes table
-
-Revision ID: add_timestamps_classes
-Revises: 7937209f9718
-Create Date: 2025-11-16 00:20:00.000000
-
-"""
-from typing import Sequence, Union
-
-from alembic import op
-import sqlalchemy as sa
-
-
-# revision identifiers, used by Alembic.
-revision: str = 'add_timestamps_classes'
-down_revision: Union[str, None] = '7937209f9718'
-branch_labels: Union[str, Sequence[str], None] = None
-depends_on: Union[str, Sequence[str], None] = None
-
-
-def upgrade() -> None:
- # Add created_at column with default value
- op.execute("""
- ALTER TABLE a76.classes
- ADD COLUMN created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL
- """)
-
- # Add updated_at column with default value
- op.execute("""
- ALTER TABLE a76.classes
- ADD COLUMN updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL
- """)
-
- # Add deleted_at column (nullable for soft deletes)
- op.execute("""
- ALTER TABLE a76.classes
- ADD COLUMN deleted_at TIMESTAMP WITH TIME ZONE
- """)
-
- # Create trigger to auto-update updated_at
- op.execute("""
- CREATE OR REPLACE FUNCTION a76.update_classes_updated_at()
- RETURNS TRIGGER AS $$
- BEGIN
- NEW.updated_at = CURRENT_TIMESTAMP;
- RETURN NEW;
- END;
- $$ language 'plpgsql';
- """)
-
- op.execute("""
- CREATE TRIGGER update_classes_updated_at
- BEFORE UPDATE ON a76.classes
- FOR EACH ROW
- EXECUTE FUNCTION a76.update_classes_updated_at();
- """)
-
-
-def downgrade() -> None:
- # Drop trigger and function
- op.execute("DROP TRIGGER IF EXISTS update_classes_updated_at ON a76.classes")
- op.execute("DROP FUNCTION IF EXISTS a76.update_classes_updated_at()")
-
- # Drop columns
- op.execute("ALTER TABLE a76.classes DROP COLUMN IF EXISTS deleted_at")
- op.execute("ALTER TABLE a76.classes DROP COLUMN IF EXISTS updated_at")
- op.execute("ALTER TABLE a76.classes DROP COLUMN IF EXISTS created_at")
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 10fa8858..f77df843 100644
--- a/backend/api/v1/modules/a76/clients_and_providers/dto.py
+++ b/backend/api/v1/modules/a76/clients_and_providers/dto.py
@@ -4,7 +4,7 @@ Reemplaza schemas.py siguiendo enfoque DDD y estilo NestJS
"""
from decimal import Decimal
-from typing import Literal, Optional
+from typing import List, Literal, Optional
from pydantic import BaseModel, Field
@@ -119,7 +119,7 @@ class ClientProviderCreateDTO(BaseModel):
is_national_provider: Optional[str] = Field(
None, max_length=2, description="Is national provider"
)
- enabled_disabled: Optional[int] = Field(None, description="Enabled/Disabled status")
+ is_active: Optional[bool] = Field(None, description="Enabled/Disabled status")
# Nested DTOs
address: Optional[ClientProviderAddressDTO] = Field(
@@ -162,7 +162,7 @@ class ClientProviderUpdateDTO(BaseModel):
is_national_provider: Optional[str] = Field(
None, max_length=2, description="Is national provider"
)
- enabled_disabled: Optional[int] = Field(None, description="Enabled/Disabled status")
+ is_active: Optional[bool] = Field(None, description="Enabled/Disabled status")
# Nested DTOs
address: Optional[ClientProviderAddressDTO] = Field(
@@ -194,7 +194,7 @@ class ClientProviderResponseDTO(BaseModel):
position: Optional[str] = None
incoterm: Optional[str] = None
is_national_provider: Optional[str] = None
- enabled_disabled: Optional[int] = None
+ is_active: Optional[bool] = None
tenant_id: int
company_id: int
@@ -215,7 +215,7 @@ class ClientProviderBasicDTO(BaseModel):
short_name: Optional[str] = None
rfc: Optional[str] = None
client_or_provider: Optional[str] = None
- enabled_disabled: Optional[int] = None
+ is_active: Optional[bool] = None
class Config:
from_attributes = True
@@ -231,3 +231,15 @@ class ClientProviderListDTO(BaseModel):
class Config:
from_attributes = True
+
+
+class ClientProviderPaginatedResponseDTO(BaseModel):
+ """DTO para respuesta paginada de clientes/proveedores"""
+
+ items: List[ClientProviderResponseDTO]
+ total: int
+ page: int
+ page_size: int
+
+ class Config:
+ from_attributes = True
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 da2a02eb..ea9a7f7d 100644
--- a/backend/api/v1/modules/a76/clients_and_providers/models.py
+++ b/backend/api/v1/modules/a76/clients_and_providers/models.py
@@ -15,7 +15,8 @@ from sqlalchemy import (
PrimaryKeyConstraint,
SmallInteger,
String,
- Enum as PgEnum
+ Enum as PgEnum,
+ Boolean
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from enum import Enum
@@ -60,8 +61,8 @@ class ClientProvider(Base, TenantScopedMixin):
responsible: Mapped[Optional[str]] = mapped_column(String(80))
position: Mapped[Optional[str]] = mapped_column(String(30))
incoterm: Mapped[Optional[str]] = mapped_column(String(19))
- is_national_provider: Mapped[Optional[str]] = mapped_column(String(2))
- enabled_disabled: Mapped[Optional[int]] = mapped_column(SmallInteger)
+ is_national_provider: Mapped[Optional[bool]] = mapped_column(Boolean)
+ is_active: Mapped[Optional[bool]] = mapped_column(Boolean)
# Relationships
address: Mapped[Optional["ClientProviderAddress"]] = relationship(
diff --git a/backend/api/v1/modules/a76/clients_and_providers/routes.py b/backend/api/v1/modules/a76/clients_and_providers/routes.py
index 68e74d13..970b71ad 100644
--- a/backend/api/v1/modules/a76/clients_and_providers/routes.py
+++ b/backend/api/v1/modules/a76/clients_and_providers/routes.py
@@ -9,136 +9,57 @@ from core.security import get_current_user, validate_access_to_resource
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from api.v1.common.tenant_crud_routes import TenantCRUDRoutes
+from .models import ClientOrProviderEnum
from .dto import (
ClientProviderBasicDTO,
ClientProviderCreateDTO,
ClientProviderResponseDTO,
ClientProviderUpdateDTO,
+ ClientProviderPaginatedResponseDTO,
)
from .service import ClientProviderService
from .models import ClientProvider
-# Create base CRUD router using TenantCRUDRoutes factory
-base_router = TenantCRUDRoutes(
- service=ClientProviderService,
- create_schema=ClientProviderCreateDTO,
- update_schema=ClientProviderUpdateDTO,
- response_schema=ClientProviderResponseDTO,
- prefix="",
- tags=[],
- resource_name="Client/Provider",
- id_name="id", # Using numeric ID
- enable_list=True, # Enable GET /clients-providers with pagination
- enable_filters=True, # Enable filtering
- default_page_size=50,
- max_page_size=100,
-).router
-
# Create main router to add custom endpoints
router = APIRouter(prefix="/clients-providers")
-# Include base CRUD routes
-router.include_router(base_router, prefix="")
-
-# Custom endpoints
-@router.get("/clients", response_model=List[ClientProviderBasicDTO])
-async def get_clients_only(
+@router.get("/", response_model=ClientProviderPaginatedResponseDTO)
+async def get_clients_and_providers(
company_id: int = Query(..., description="Company ID"),
+ type: Optional[ClientOrProviderEnum] = Query(
+ None, description="Type of entity (client or provider)"
+ ),
+ active: Optional[bool] = Query(None, description="Active status"),
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
- """Get only clients (client_or_provider = 'client')"""
+ """Get clients and providers"""
tenant_id = validate_access_to_resource(db, company_id, current_user)
-
- clients = (
- db.query(ClientProvider)
- .filter(
- ClientProvider.tenant_id == tenant_id,
- ClientProvider.company_id == company_id,
- ClientProvider.client_or_provider == "client"
- )
- .offset(skip)
- .limit(limit)
- .all()
+
+ query = db.query(ClientProvider).filter(
+ ClientProvider.tenant_id == tenant_id,
+ ClientProvider.company_id == company_id,
)
- return [ClientProviderBasicDTO.model_validate(c) for c in clients]
+ if type is not None:
+ query = query.filter(ClientProvider.client_or_provider == type)
-@router.get("/providers", response_model=List[ClientProviderBasicDTO])
-async def get_providers_only(
- company_id: int = Query(..., description="Company ID"),
- skip: int = Query(0, ge=0),
- limit: int = Query(100, ge=1, le=1000),
- db: Session = Depends(get_core_db),
- current_user: dict = Depends(get_current_user),
-):
- """Get only providers (client_or_provider = 'provider')"""
- tenant_id = validate_access_to_resource(db, company_id, current_user)
-
- providers = (
- db.query(ClientProvider)
- .filter(
- ClientProvider.tenant_id == tenant_id,
- ClientProvider.company_id == company_id,
- ClientProvider.client_or_provider == "provider"
- )
- .offset(skip)
- .limit(limit)
- .all()
- )
- return [ClientProviderBasicDTO.model_validate(p) for p in providers]
+ if active is not None:
+ query = query.filter(ClientProvider.is_active == active)
+ total = query.count()
+ clients = query.offset(skip).limit(limit).all()
-@router.get("/search/rfc/{rfc}", response_model=List[ClientProviderBasicDTO])
-async def search_by_rfc(
- rfc: str,
- company_id: int = Query(..., description="Company ID"),
- db: Session = Depends(get_core_db),
- current_user: dict = Depends(get_current_user),
-):
- """Search clients/providers by RFC"""
- tenant_id = validate_access_to_resource(db, company_id, current_user)
-
- clients = (
- db.query(ClientProvider)
- .filter(
- ClientProvider.tenant_id == tenant_id,
- ClientProvider.company_id == company_id,
- ClientProvider.rfc.ilike(f"%{rfc}%")
- )
- .all()
- )
- return [ClientProviderBasicDTO.model_validate(c) for c in clients]
-
-
-@router.patch("/{client_id}/toggle-status", response_model=ClientProviderResponseDTO)
-async def toggle_clients_and_providers_status(
- client_id: int,
- company_id: int = Query(..., description="Company ID"),
- db: Session = Depends(get_core_db),
- current_user: dict = Depends(get_current_user),
-):
- """Toggle client/provider enabled/disabled status"""
- tenant_id = validate_access_to_resource(db, company_id, current_user)
-
- client = ClientProviderService.get_by_id(db, client_id, tenant_id, company_id)
- if not client:
- raise HTTPException(status_code=404, detail="Client/Provider not found")
-
- # Toggle status (1 = enabled, 0 = disabled)
- client.enabled_disabled = 1 if client.enabled_disabled == 0 else 0
-
- try:
- db.commit()
- db.refresh(client)
- return client
- except Exception as e:
- db.rollback()
- raise HTTPException(status_code=500, detail="Error updating status")
+ return {
+ "items": [ClientProviderResponseDTO.model_validate(c) for c in clients],
+ "total": total,
+ "page": (skip // limit) + 1,
+ "page_size": limit,
+ }
@router.get("/{client_id}/basic", response_model=ClientProviderBasicDTO)
@@ -150,9 +71,59 @@ async def get_clients_and_providers_basic_info(
):
"""Get basic information for a client/provider (without address and programs)"""
tenant_id = validate_access_to_resource(db, company_id, current_user)
-
+
client = ClientProviderService.get_by_id(db, client_id, tenant_id, company_id)
if not client:
raise HTTPException(status_code=404, detail="Client/Provider not found")
-
+
return ClientProviderBasicDTO.model_validate(client)
+
+
+@router.post("/", response_model=ClientProviderResponseDTO)
+async def create_client_provider(
+ client_data: ClientProviderCreateDTO,
+ company_id: int = Query(..., description="Company ID"),
+ db: Session = Depends(get_core_db),
+ current_user: dict = Depends(get_current_user),
+):
+ """Create a new client/provider"""
+ tenant_id = validate_access_to_resource(db, company_id, current_user)
+
+ return ClientProviderService.create(db, client_data, tenant_id, company_id)
+
+
+@router.patch("/{client_id}", response_model=ClientProviderResponseDTO)
+async def update_client_provider(
+ client_id: int,
+ client_data: ClientProviderUpdateDTO,
+ company_id: int = Query(..., description="Company ID"),
+ db: Session = Depends(get_core_db),
+ current_user: dict = Depends(get_current_user),
+):
+ """Update a client/provider"""
+ tenant_id = validate_access_to_resource(db, company_id, current_user)
+
+ client = ClientProviderService.update(
+ db, client_id, tenant_id, company_id, client_data
+ )
+ if not client:
+ raise HTTPException(status_code=404, detail="Client/Provider not found")
+
+ return client
+
+
+@router.delete("/{client_id}", response_model=bool)
+async def delete_client_provider(
+ client_id: int,
+ company_id: int = Query(..., description="Company ID"),
+ db: Session = Depends(get_core_db),
+ current_user: dict = Depends(get_current_user),
+):
+ """Delete a client/provider"""
+ tenant_id = validate_access_to_resource(db, company_id, current_user)
+
+ success = ClientProviderService.delete(db, client_id, tenant_id, company_id)
+ if not success:
+ raise HTTPException(status_code=404, detail="Client/Provider not found")
+
+ return success
diff --git a/backend/api/v1/modules/a76/clients_and_providers/service.py b/backend/api/v1/modules/a76/clients_and_providers/service.py
index c9bc22e3..5a329673 100644
--- a/backend/api/v1/modules/a76/clients_and_providers/service.py
+++ b/backend/api/v1/modules/a76/clients_and_providers/service.py
@@ -61,13 +61,17 @@ class ClientProviderService:
)
if filters.get("status"):
enabled = 1 if filters["status"] == "enabled" else 0
- query = query.filter(ClientProvider.enabled_disabled == enabled)
+ query = query.filter(ClientProvider.is_active == enabled)
total = query.count()
- clients = query.options(
- joinedload(ClientProvider.address),
- joinedload(ClientProvider.programs)
- ).offset(skip).limit(limit).all()
+ clients = (
+ query.options(
+ joinedload(ClientProvider.address), joinedload(ClientProvider.programs)
+ )
+ .offset(skip)
+ .limit(limit)
+ .all()
+ )
return clients, total
@@ -79,8 +83,7 @@ class ClientProviderService:
return (
db.query(ClientProvider)
.options(
- joinedload(ClientProvider.address),
- joinedload(ClientProvider.programs)
+ joinedload(ClientProvider.address), joinedload(ClientProvider.programs)
)
.filter(
ClientProvider.id == client_id,
@@ -102,9 +105,7 @@ class ClientProviderService:
# Create main client/provider
data_dict = client_data.model_dump(exclude={"address", "programs"})
db_client = ClientProvider(
- **data_dict,
- tenant_id=tenant_id,
- company_id=company_id
+ **data_dict, tenant_id=tenant_id, company_id=company_id
)
db.add(db_client)
@@ -112,8 +113,8 @@ class ClientProviderService:
# Create address if provided
if client_data.address:
- db_address = ClientProviderAddress(
- tenant_id=tenant_id,
+ db_address = ClientProviderAddress(
+ tenant_id=tenant_id,
company_id=company_id,
client_id=db_client.id,
**client_data.address.model_dump(exclude_unset=True),
@@ -122,7 +123,7 @@ class ClientProviderService:
# Create programs if provided
if client_data.programs:
- db_programs = ClientProviderPrograms(
+ db_programs = ClientProviderPrograms(
tenant_id=tenant_id,
company_id=company_id,
client_id=db_client.id,
@@ -210,9 +211,7 @@ class ClientProviderService:
)
@staticmethod
- def delete(
- db: Session, client_id: int, tenant_id: int, company_id: int
- ) -> bool:
+ def delete(db: Session, client_id: int, tenant_id: int, company_id: int) -> bool:
"""Delete a client/provider"""
client = ClientProviderService.get_by_id(db, client_id, tenant_id, company_id)
if not client:
@@ -351,7 +350,7 @@ class ClientProviderService:
)
if enabled_only:
- query = query.filter(ClientProvider.enabled_disabled == 1)
+ query = query.filter(ClientProvider.is_active == 1)
# Contar total
total = query.count()
@@ -478,59 +477,4 @@ class ClientProviderService:
logger.error(f"Error deleting client/provider {client_id}: {str(e)}")
raise HTTPException(
status_code=500, detail="Error deleting client/provider"
- )
-
- def get_clients_only(
- self, skip: int = 0, limit: int = 100
- ) -> List[ClientProviderBasicDTO]:
- """Obtiene solo clientes (client)"""
- query = self.db.query(ClientProvider).filter(
- ClientProvider.client_or_provider == "client"
- )
- clients = query.offset(skip).limit(limit).all()
- return [ClientProviderBasicDTO.model_validate(client) for client in clients]
-
- def get_providers_only(
- self, skip: int = 0, limit: int = 100
- ) -> List[ClientProviderBasicDTO]:
- """Obtiene solo proveedores (provider)"""
- query = self.db.query(ClientProvider).filter(
- ClientProvider.client_or_provider == "provider"
- )
- providers = query.offset(skip).limit(limit).all()
- return [
- ClientProviderBasicDTO.model_validate(provider) for provider in providers
- ]
-
- def search_by_rfc(self, rfc: str) -> List[ClientProviderBasicDTO]:
- """Busca clientes/proveedores por RFC"""
- clients = (
- self.db.query(ClientProvider)
- .filter(ClientProvider.rfc.ilike(f"%{rfc}%"))
- .all()
- )
- return [ClientProviderBasicDTO.model_validate(client) for client in clients]
-
- def toggle_status(self, client_id: str) -> Optional[ClientProviderResponseDTO]:
- """Cambia el estado habilitado/deshabilitado"""
- client = (
- self.db.query(ClientProvider)
- .filter(ClientProvider.client_id == client_id)
- .first()
- )
- if not client:
- return None
-
- # Toggle status (1 = habilitado, 0 = deshabilitado)
- client.enabled_disabled = 1 if client.enabled_disabled == 0 else 0
-
- try:
- self.db.commit()
- logger.info(
- f"Client/Provider status toggled: {client_id} -> {client.enabled_disabled}"
- )
- return self._get_client_with_relations(client_id)
- except Exception as e:
- self.db.rollback()
- logger.error(f"Error toggling status for {client_id}: {str(e)}")
- raise HTTPException(status_code=500, detail="Error updating status")
+ )
\ No newline at end of file
diff --git a/backend/api/v1/modules/a76/parts/dto.py b/backend/api/v1/modules/a76/parts/dto.py
index 1414fd82..e21e7772 100644
--- a/backend/api/v1/modules/a76/parts/dto.py
+++ b/backend/api/v1/modules/a76/parts/dto.py
@@ -67,7 +67,7 @@ class PartCreateDTO(BaseModel):
added_value: Optional[Decimal] = Field(None, description="Added value")
# Status and media
- enabled_disabled: Optional[int] = Field(None, description="Enabled/Disabled status")
+ is_active: Optional[bool] = Field(None, description="Enabled/Disabled status")
creation_date: Optional[int] = Field(None, description="Creation date")
part_photo: Optional[str] = Field(
None, max_length=255, description="Part photo URL"
@@ -132,7 +132,7 @@ class PartUpdateDTO(BaseModel):
added_value: Optional[Decimal] = Field(None, description="Added value")
# Status and media
- enabled_disabled: Optional[int] = Field(None, description="Enabled/Disabled status")
+ is_active: Optional[bool] = Field(None, description="Enabled/Disabled status")
part_photo: Optional[str] = Field(
None, max_length=255, description="Part photo URL"
)
@@ -178,7 +178,7 @@ class PartResponseDTO(BaseModel):
added_value: Optional[Decimal] = None
# Status and dates
- enabled_disabled: Optional[int] = None
+ is_active: Optional[bool] = None
creation_date: Optional[int] = None
modification_date: Optional[int] = None
modification_date_iso: Optional[datetime] = None
@@ -200,7 +200,7 @@ class PartBasicDTO(BaseModel):
part_class: Optional[str] = None
unit_cost: Optional[Decimal] = None
currency_key: Optional[str] = None
- enabled_disabled: Optional[int] = None
+ is_active: Optional[bool] = None
class Config:
from_attributes = True
diff --git a/backend/api/v1/modules/a76/parts/models.py b/backend/api/v1/modules/a76/parts/models.py
index ef8b325b..b961c272 100644
--- a/backend/api/v1/modules/a76/parts/models.py
+++ b/backend/api/v1/modules/a76/parts/models.py
@@ -16,6 +16,7 @@ from sqlalchemy import (
SmallInteger,
String,
UniqueConstraint,
+ Boolean,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
@@ -90,7 +91,7 @@ class Part(Base, TenantScopedMixin):
added_value: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8))
# Status and dates
- enabled_disabled: Mapped[Optional[int]] = mapped_column(SmallInteger)
+ is_active: Mapped[Optional[bool]] = mapped_column(Boolean)
creation_date: Mapped[Optional[int]] = mapped_column() # FECHACREACIONPARTE
modification_date: Mapped[Optional[int]] = mapped_column() # FECHAMODIFICA
modification_date_iso: Mapped[Optional[datetime]] = (
diff --git a/backend/api/v1/modules/a76/parts/routes.py b/backend/api/v1/modules/a76/parts/routes.py
index e271cb12..0f5996b6 100644
--- a/backend/api/v1/modules/a76/parts/routes.py
+++ b/backend/api/v1/modules/a76/parts/routes.py
@@ -348,7 +348,7 @@ async def get_part_basic_info(
part_class=part.part_class,
unit_cost=part.unit_cost,
currency_key=part.currency_key,
- enabled_disabled=part.enabled_disabled,
+ is_active=part.is_active,
)
diff --git a/backend/api/v1/modules/a76/parts/service.py b/backend/api/v1/modules/a76/parts/service.py
index d8ba3d06..48c2eab9 100644
--- a/backend/api/v1/modules/a76/parts/service.py
+++ b/backend/api/v1/modules/a76/parts/service.py
@@ -221,7 +221,7 @@ class PartService:
return None
# Toggle status (assuming 1 = enabled, 0 = disabled)
- db_part.enabled_disabled = 1 if db_part.enabled_disabled == 0 else 0
+ db_part.is_active = 1 if db_part.is_active == 0 else 0
db.commit()
db.refresh(db_part)
@@ -257,8 +257,8 @@ class PartService:
)
# Partes habilitadas vs deshabilitadas
- enabled_parts = db.query(Part).filter(Part.enabled_disabled == 1).count()
- disabled_parts = db.query(Part).filter(Part.enabled_disabled == 0).count()
+ enabled_parts = db.query(Part).filter(Part.is_active == 1).count()
+ disabled_parts = db.query(Part).filter(Part.is_active == 0).count()
return {
"total_parts": total_parts,
diff --git a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_dates.py b/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_dates.py
index bd69786e..101b58b0 100644
--- a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_dates.py
+++ b/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_dates.py
@@ -7,9 +7,8 @@ from pydantic import BaseModel, ConfigDict, Field
class PedimentoDatesBase(BaseModel):
"""Base schema for Pedimento Dates"""
- entry_date: Optional[datetime] = Field(None, description="Entry date")
- pedimento_date: Optional[datetime] = Field(None, description="Pedimento date")
- payment_date: Optional[datetime] = Field(None, description="Payment date")
+ entry_date: Optional[datetime] = Field(None, description="Entry date")
+ payment_date: datetime = Field(None, description="Payment date")
rectification_payment_date: Optional[datetime] = Field(
None, description="Rectification payment date"
)
@@ -19,21 +18,26 @@ class PedimentoDatesBase(BaseModel):
original_date: Optional[datetime] = Field(None, description="Original date")
start_date: Optional[datetime] = Field(None, description="Start date")
end_date: Optional[datetime] = Field(None, description="End date")
- capture_date: Optional[datetime] = Field(None, description="Capture date")
- capture_time: Optional[time] = Field(None, description="Capture time")
-class PedimentoDatesCreate(PedimentoDatesBase):
- """Schema for creating a new Pedimento Dates"""
+class PedimentoDatesCreate(BaseModel):
+ """Schema for creating a new Pedimento Dates - pedimento_id and tenant_id are set by backend"""
- pass
+ entry_date: Optional[datetime] = Field(None, description="Entry date")
+ payment_date: datetime = Field(..., description="Payment date")
+ rectification_payment_date: Optional[datetime] = Field(None, description="Rectification payment date")
+ extraction_date: Optional[datetime] = Field(None, description="Extraction date")
+ submission_date: Optional[datetime] = Field(None, description="Submission date")
+ eucan_date: Optional[datetime] = Field(None, description="EUCAN date")
+ original_date: Optional[datetime] = Field(None, description="Original date")
+ start_date: Optional[datetime] = Field(None, description="Start date")
+ end_date: Optional[datetime] = Field(None, description="End date")
class PedimentoDatesUpdate(BaseModel):
"""Schema for updating a Pedimento Dates"""
- entry_date: Optional[datetime] = None
- pedimento_date: Optional[datetime] = None
+ entry_date: Optional[datetime] = None
payment_date: Optional[datetime] = None
rectification_payment_date: Optional[datetime] = None
extraction_date: Optional[datetime] = None
@@ -42,8 +46,6 @@ class PedimentoDatesUpdate(BaseModel):
original_date: Optional[datetime] = None
start_date: Optional[datetime] = None
end_date: Optional[datetime] = None
- capture_date: Optional[datetime] = None
- capture_time: Optional[time] = None
class PedimentoDatesResponse(PedimentoDatesBase):
diff --git a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_payments.py b/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_payments.py
index eeaf384c..bdd98381 100644
--- a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_payments.py
+++ b/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_payments.py
@@ -25,14 +25,23 @@ class PedimentoPaymentsBase(BaseModel):
total_cash_paid: Optional[int] = Field(None, description="Total cash paid")
total_contributions: Optional[int] = Field(None, description="Total contributions")
counter_payment: Optional[int] = Field(None, description="Counter payment")
- pece_code: Optional[str] = Field(None, max_length=5, description="PECE code")
- payment_id: Optional[int] = Field(None, description="Payment ID")
+ pece_code: Optional[str] = Field(None, max_length=5, description="PECE code")
-class PedimentoPaymentsCreate(PedimentoPaymentsBase):
- """Schema for creating a new Pedimento Payments"""
+class PedimentoPaymentsCreate(BaseModel):
+ """Schema for creating a new Pedimento Payments - pedimento_id and tenant_id are set by backend"""
- pass
+ acknowledgment: Optional[str] = Field(None, max_length=20, description="Acknowledgment")
+ operation_number: Optional[str] = Field(None, max_length=14, description="Operation number")
+ bank_code: Optional[int] = Field(None, description="Bank code")
+ cashier: Optional[str] = Field(None, max_length=2, description="Cashier")
+ date: Optional[Date] = Field(None, description="Date")
+ time: Optional[Time] = Field(None, description="Time")
+ shift: Optional[str] = Field(None, max_length=1, description="Shift")
+ total_cash_paid: Optional[int] = Field(None, description="Total cash paid")
+ total_contributions: Optional[int] = Field(None, description="Total contributions")
+ counter_payment: Optional[int] = Field(None, description="Counter payment")
+ pece_code: Optional[str] = Field(None, max_length=5, description="PECE code")
class PedimentoPaymentsUpdate(BaseModel):
@@ -48,8 +57,7 @@ class PedimentoPaymentsUpdate(BaseModel):
total_cash_paid: Optional[int] = None
total_contributions: Optional[int] = None
counter_payment: Optional[int] = None
- pece_code: Optional[str] = Field(None, max_length=5)
- payment_id: Optional[int] = None
+ pece_code: Optional[str] = Field(None, max_length=5)
class PedimentoPaymentsResponse(PedimentoPaymentsBase):
diff --git a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_transport_means.py b/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_transport_means.py
index 560446f2..f5639249 100644
--- a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_transport_means.py
+++ b/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_transport_means.py
@@ -15,10 +15,13 @@ class PedimentoTransportMeansBase(BaseModel):
departure: Optional[str] = Field(None, max_length=2, description="Departure")
-class PedimentoTransportMeansCreate(PedimentoTransportMeansBase):
- """Schema for creating a new Pedimento Transport Means"""
+class PedimentoTransportMeansCreate(BaseModel):
+ """Schema for creating a new Pedimento Transport Means - pedimento_id and tenant_id are set by backend"""
- pass
+ destination: Optional[int] = Field(None, description="Destination")
+ entry_exit: Optional[str] = Field(None, max_length=2, description="Entry/exit")
+ arrival: Optional[str] = Field(None, max_length=2, description="Arrival")
+ departure: Optional[str] = Field(None, max_length=2, description="Departure")
class PedimentoTransportMeansUpdate(BaseModel):
diff --git a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_validation.py b/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_validation.py
index 568f784d..649ce3cb 100644
--- a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_validation.py
+++ b/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_validation.py
@@ -27,10 +27,17 @@ class PedimentoValidationBase(BaseModel):
responsible_id: Optional[int] = Field(None, description="Responsible ID")
-class PedimentoValidationCreate(PedimentoValidationBase):
- """Schema for creating a new Pedimento Validation"""
+class PedimentoValidationCreate(BaseModel):
+ """Schema for creating a new Pedimento Validation - pedimento_id and tenant_id are set by backend"""
- pass
+ validator: Optional[str] = Field(None, max_length=3, description="Validator")
+ validation_ack: Optional[str] = Field(None, max_length=8, description="Validation acknowledgment")
+ pre_ack: Optional[str] = Field(None, max_length=8, description="Previous acknowledgment")
+ line_signature: Optional[str] = Field(None, max_length=50, description="Line signature")
+ electronic_signature: Optional[str] = Field(None, max_length=999, description="Electronic signature")
+ certificate_number: Optional[str] = Field(None, max_length=99, description="Certificate number")
+ validator_id: Optional[int] = Field(None, description="Validator ID")
+ responsible_id: Optional[int] = Field(None, description="Responsible ID")
class PedimentoValidationUpdate(BaseModel):
diff --git a/backend/api/v1/modules/a76/pedmientos/dtos/pedimentos.py b/backend/api/v1/modules/a76/pedmientos/dtos/pedimentos.py
index 7a89c050..c34dc9ff 100644
--- a/backend/api/v1/modules/a76/pedmientos/dtos/pedimentos.py
+++ b/backend/api/v1/modules/a76/pedmientos/dtos/pedimentos.py
@@ -5,22 +5,22 @@ 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
+from .pedimento_config_additional import PedimentoConfigAdditionalCreate, PedimentoConfigAdditionalResponse
+from .pedimento_config_calculations import PedimentoConfigCalculationsCreate, PedimentoConfigCalculationsResponse
+from .pedimento_config_parameters import PedimentoConfigParametersCreate, PedimentoConfigParametersResponse
+from .pedimento_config_surcharges import PedimentoConfigSurchargesCreate, PedimentoConfigSurchargesResponse
+from .pedimento_config_update_rectification import PedimentoConfigUpdateRectificationCreate, PedimentoConfigUpdateRectificationResponse
+from .pedimento_config_updates import PedimentoConfigUpdatesCreate, PedimentoConfigUpdatesResponse
+from .pedimento_customs_offices import PedimentoCustomsOfficesCreate, PedimentoCustomsOfficesResponse
+from .pedimento_dates import PedimentoDatesCreate, PedimentoDatesResponse
+from .pedimento_decrementables import PedimentoDecrementablesCreate, PedimentoDecrementablesResponse
+from .pedimento_incrementables import PedimentoIncrementablesCreate, PedimentoIncrementablesResponse
+from .pedimento_indexes import PedimentoIndexesCreate, PedimentoIndexesResponse
+from .pedimento_payments import PedimentoPaymentsCreate, PedimentoPaymentsResponse
+from .pedimento_rectification_destination import PedimentoRectificationDestinationCreate, PedimentoRectificationDestinationResponse
+from .pedimento_rectification_origin import PedimentoRectificationOriginCreate, PedimentoRectificationOriginResponse
+from .pedimento_transport_means import PedimentoTransportMeansCreate, PedimentoTransportMeansResponse
+from .pedimento_validation import PedimentoValidationCreate, PedimentoValidationResponse
class OperationType(IntEnum):
@@ -50,8 +50,22 @@ class PedimentosBase(BaseModel):
usd_value: Optional[Decimal] = Field(None, description="USD value")
paid_price: Optional[Decimal] = Field(None, description="Paid price")
gross_weight: Optional[Decimal] = Field(None, description="Gross weight")
- exchange_rate: Optional[Decimal] = Field(None, description="Exchange rate")
+ exchange_rate: Optional[Decimal] = Field(None, description="Exchange rate")
+class PedimentosCreate(PedimentosBase):
+ """Schema for creating a new Pedimento"""
+
+ # Override to make required fields non-optional
+ year: str = Field(..., max_length=2, description="Year")
+ customs_office: str = Field(..., max_length=2, description="Customs office")
+ license: str = Field(..., max_length=4, description="License")
+ pedimento_number: str = Field(..., max_length=7, description="Pedimento number")
+ client_id: int = Field(..., description="Client ID")
+ 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")
+
pedimento_dates: Optional[PedimentoDatesCreate] = None
pedimento_decrementables: Optional[PedimentoDecrementablesCreate] = None
pedimento_incrementables: Optional[PedimentoIncrementablesCreate] = None
@@ -69,37 +83,41 @@ class PedimentosBase(BaseModel):
pedimento_config_update_rectification: Optional[PedimentoConfigUpdateRectificationCreate] = None
pedimento_config_updates: Optional[PedimentoConfigUpdatesCreate] = None
-class PedimentosCreate(PedimentosBase):
- """Schema for creating a new Pedimento"""
-
- # Override to make required fields non-optional
- year: str = Field(..., max_length=2, description="Year")
- customs_office: str = Field(..., max_length=2, description="Customs office")
- license: str = Field(..., max_length=4, description="License")
- pedimento_number: str = Field(..., max_length=7, description="Pedimento number")
- client_id: int = Field(..., description="Client ID")
- 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")
-
class PedimentosUpdate(BaseModel):
"""Schema for updating a Pedimento"""
- year: Optional[str] = Field(..., max_length=2)
- customs_office: Optional[str] = Field(..., max_length=2)
- license: Optional[str] = Field(..., max_length=4)
- pedimento_number: Optional[str] = Field(..., max_length=7)
- client_id: Optional[int]
- operation_type: Optional[OperationType]
- pedimento_type: Optional[int]
- pedimento_code: Optional[str] = Field(..., max_length=2)
- regime: Optional[str] = Field(..., max_length=3)
- status: Optional[str] = Field(..., max_length=30)
+ year: Optional[str] = Field(None, max_length=2)
+ customs_office: Optional[str] = Field(None, max_length=2)
+ license: Optional[str] = Field(None, max_length=4)
+ pedimento_number: Optional[str] = Field(None, max_length=7)
+ client_id: Optional[int] = None
+ operation_type: Optional[int] = None
+ pedimento_type: Optional[int] = None
+ pedimento_code: Optional[str] = Field(None, max_length=2)
+ regime: Optional[str] = Field(None, max_length=3)
+ status: Optional[str] = Field(None, max_length=30)
usd_value: Optional[Decimal] = None
paid_price: Optional[Decimal] = None
gross_weight: Optional[Decimal] = None
exchange_rate: Optional[Decimal] = None
+
+ # Sub-resources
+ 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 PedimentosResponse(PedimentosBase):
@@ -108,5 +126,22 @@ class PedimentosResponse(PedimentosBase):
id: int
tenant_id: int
created_at: datetime
+
+ pedimento_dates: Optional[PedimentoDatesResponse] = None
+ pedimento_decrementables: Optional[PedimentoDecrementablesResponse] = None
+ pedimento_incrementables: Optional[PedimentoIncrementablesResponse] = None
+ pedimento_indexes: Optional[PedimentoIndexesResponse] = None
+ pedimento_validation: Optional[PedimentoValidationResponse] = None
+ pedimento_customs_offices: Optional[PedimentoCustomsOfficesResponse] = None
+ pedimento_payments: Optional[PedimentoPaymentsResponse] = None
+ pedimento_rectification_destination: Optional[PedimentoRectificationDestinationResponse] = None
+ pedimento_rectification_origin: Optional[PedimentoRectificationOriginResponse] = None
+ pedimento_transport_means: Optional[PedimentoTransportMeansResponse] = None
+ pedimento_config_additional: Optional[PedimentoConfigAdditionalResponse] = None
+ pedimento_config_calculations: Optional[PedimentoConfigCalculationsResponse] = None
+ pedimento_config_parameters: Optional[PedimentoConfigParametersResponse] = None
+ pedimento_config_surcharges: Optional[PedimentoConfigSurchargesResponse] = None
+ pedimento_config_update_rectification: Optional[PedimentoConfigUpdateRectificationResponse] = None
+ pedimento_config_updates: Optional[PedimentoConfigUpdatesResponse] = None
model_config = ConfigDict(from_attributes=True)
diff --git a/backend/api/v1/modules/a76/pedmientos/models/pedimento_dates.py b/backend/api/v1/modules/a76/pedmientos/models/pedimento_dates.py
index a6f1958b..cea9f34d 100644
--- a/backend/api/v1/modules/a76/pedmientos/models/pedimento_dates.py
+++ b/backend/api/v1/modules/a76/pedmientos/models/pedimento_dates.py
@@ -1,6 +1,6 @@
from datetime import datetime
from datetime import time as datetime_time
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, Optional
from api.v1.common.base_models import TenantScopedMixin, TimestampMixin
from core.database import Base
@@ -48,16 +48,16 @@ class PedimentoDates(Base, TenantScopedMixin, TimestampMixin):
id: Mapped[int] = mapped_column(Integer)
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
- entry_date: Mapped[datetime] = mapped_column(DateTime)
+ entry_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
pedimento_date: Mapped[datetime] = mapped_column(DateTime)
payment_date: Mapped[datetime] = mapped_column(DateTime)
- rectification_payment_date: Mapped[datetime] = mapped_column(DateTime)
- extraction_date: Mapped[datetime] = mapped_column(DateTime)
- submission_date: Mapped[datetime] = mapped_column(DateTime)
- eucan_date: Mapped[datetime] = mapped_column(DateTime)
- original_date: Mapped[datetime] = mapped_column(DateTime)
- start_date: Mapped[datetime] = mapped_column(DateTime)
- end_date: Mapped[datetime] = mapped_column(DateTime)
+ rectification_payment_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
+ extraction_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
+ submission_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
+ eucan_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
+ original_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
+ start_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
+ end_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
capture_date: Mapped[datetime] = mapped_column(DateTime)
capture_time: Mapped[datetime_time] = mapped_column(Time)
diff --git a/backend/api/v1/modules/a76/pedmientos/models/pedimento_payments.py b/backend/api/v1/modules/a76/pedmientos/models/pedimento_payments.py
index f67f06c5..2f636f99 100644
--- a/backend/api/v1/modules/a76/pedmientos/models/pedimento_payments.py
+++ b/backend/api/v1/modules/a76/pedmientos/models/pedimento_payments.py
@@ -49,7 +49,6 @@ class PedimentoPayments(Base, TenantScopedMixin, TimestampMixin):
id: Mapped[int] = mapped_column(Integer)
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
- payment_id: Mapped[int] = mapped_column(Integer)
acknowledgment: Mapped[str] = mapped_column(String(20))
operation_number: Mapped[str] = mapped_column(String(14))
diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_payments.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_payments.py
index 02dbfd0d..7078e26f 100644
--- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_payments.py
+++ b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_payments.py
@@ -35,10 +35,10 @@ async def list_payments(
return payments
-@router.get("/{payment_id}", response_model=PedimentoPaymentsResponse)
+@router.get("/{id}", response_model=PedimentoPaymentsResponse)
async def get_payment(
pedimento_id: int,
- payment_id: int,
+ id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
@@ -47,7 +47,7 @@ async def get_payment(
tenant_id = validate_access_to_resource(db, company_id, current_user)
payment = PedimentoPaymentsService.get_by_id(
- db, payment_id, pedimento_id, tenant_id, company_id
+ db, id, pedimento_id, tenant_id, company_id
)
if not payment:
raise HTTPException(status_code=404, detail="Payment not found")
@@ -74,10 +74,10 @@ async def create_payment(
return payment
-@router.put("/{payment_id}", response_model=PedimentoPaymentsResponse)
+@router.put("/{id}", response_model=PedimentoPaymentsResponse)
async def update_payment(
pedimento_id: int,
- payment_id: int,
+ id: int,
data: PedimentoPaymentsUpdate,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
@@ -87,7 +87,7 @@ async def update_payment(
tenant_id = validate_access_to_resource(db, company_id, current_user)
payment = PedimentoPaymentsService.update(
- db, payment_id, pedimento_id, tenant_id, company_id, data
+ db, id, pedimento_id, tenant_id, company_id, data
)
if not payment:
raise HTTPException(status_code=404, detail="Payment not found")
@@ -95,10 +95,10 @@ async def update_payment(
return payment
-@router.delete("/{payment_id}", status_code=204)
+@router.delete("/{id}", status_code=204)
async def delete_payment(
pedimento_id: int,
- payment_id: int,
+ id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
@@ -107,7 +107,7 @@ async def delete_payment(
tenant_id = validate_access_to_resource(db, company_id, current_user)
success = PedimentoPaymentsService.delete(
- db, payment_id, pedimento_id, tenant_id, company_id
+ db, id, pedimento_id, tenant_id, company_id
)
if not success:
raise HTTPException(status_code=404, detail="Payment not found")
diff --git a/backend/api/v1/modules/a76/pedmientos/services/pedimentos.py b/backend/api/v1/modules/a76/pedmientos/services/pedimentos.py
index ddfca359..b2ef0719 100644
--- a/backend/api/v1/modules/a76/pedmientos/services/pedimentos.py
+++ b/backend/api/v1/modules/a76/pedmientos/services/pedimentos.py
@@ -6,7 +6,8 @@ import logging
from typing import Any, Dict, List, Optional
from sqlalchemy import desc
-from sqlalchemy.orm import Session
+from sqlalchemy.orm import Session, joinedload
+from sqlalchemy.orm import selectinload
from ..dtos.pedimentos import PedimentosCreate, PedimentosUpdate
@@ -85,8 +86,31 @@ class PedimentosService:
query = query.filter(Pedimentos.year == filters["year"])
total = query.count()
+
+ # Eager load all relationships for the response schema
items = (
- query.order_by(desc(Pedimentos.created_at)).offset(skip).limit(limit).all()
+ query.options(
+ selectinload(Pedimentos.pedimento_dates),
+ selectinload(Pedimentos.pedimento_decrementables),
+ selectinload(Pedimentos.pedimento_incrementables),
+ selectinload(Pedimentos.pedimento_indexes),
+ selectinload(Pedimentos.pedimento_validation),
+ selectinload(Pedimentos.pedimento_customs_offices),
+ selectinload(Pedimentos.pedimento_payments),
+ selectinload(Pedimentos.pedimento_rectification_destination),
+ selectinload(Pedimentos.pedimento_rectification_origin),
+ selectinload(Pedimentos.pedimento_transport_means),
+ selectinload(Pedimentos.pedimento_config_additional),
+ selectinload(Pedimentos.pedimento_config_calculations),
+ selectinload(Pedimentos.pedimento_config_parameters),
+ selectinload(Pedimentos.pedimento_config_surcharges),
+ selectinload(Pedimentos.pedimento_config_update_rectification),
+ selectinload(Pedimentos.pedimento_config_updates),
+ )
+ .order_by(desc(Pedimentos.created_at))
+ .offset(skip)
+ .limit(limit)
+ .all()
)
return items, total
@@ -113,6 +137,26 @@ class PedimentosService:
if company_id is not None:
query = query.filter(Pedimentos.company_id == company_id)
+
+ # Eager load all relationships for the response schema
+ query = query.options(
+ selectinload(Pedimentos.pedimento_dates),
+ selectinload(Pedimentos.pedimento_decrementables),
+ selectinload(Pedimentos.pedimento_incrementables),
+ selectinload(Pedimentos.pedimento_indexes),
+ selectinload(Pedimentos.pedimento_validation),
+ selectinload(Pedimentos.pedimento_customs_offices),
+ selectinload(Pedimentos.pedimento_payments),
+ selectinload(Pedimentos.pedimento_rectification_destination),
+ selectinload(Pedimentos.pedimento_rectification_origin),
+ selectinload(Pedimentos.pedimento_transport_means),
+ selectinload(Pedimentos.pedimento_config_additional),
+ selectinload(Pedimentos.pedimento_config_calculations),
+ selectinload(Pedimentos.pedimento_config_parameters),
+ selectinload(Pedimentos.pedimento_config_surcharges),
+ selectinload(Pedimentos.pedimento_config_update_rectification),
+ selectinload(Pedimentos.pedimento_config_updates),
+ )
return query.first()
@@ -247,23 +291,25 @@ class PedimentosService:
# 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):
+ # Obtener datos del payload completo (no solo exclude_unset)
+ full_data = pedimento_data.model_dump()
+
+ if data_attr not in full_data:
return
- data = getattr(pedimento_data, data_attr)
+ data = full_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)
+ for field, value in data.items():
+ if hasattr(existing, field):
+ setattr(existing, field, value)
else:
# Crear nuevo
- obj_dict = data.model_dump()
- obj = model_class(**obj_dict)
+ obj = model_class(**data)
obj.pedimento_id = pedimento_id
obj.tenant_id = tenant_id
obj.company_id = company_id
diff --git a/backend/api/v1/modules/public/reference_data/code_pedimento_regimens/routes.py b/backend/api/v1/modules/public/reference_data/code_pedimento_regimens/routes.py
index de7fc176..76533e83 100644
--- a/backend/api/v1/modules/public/reference_data/code_pedimento_regimens/routes.py
+++ b/backend/api/v1/modules/public/reference_data/code_pedimento_regimens/routes.py
@@ -15,11 +15,22 @@ router = APIRouter(prefix="/code-pedimento-regimens")
def list_code_pedimento_regimens(
page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
+ code: str = Query(None, description="Filter by code"),
+ regime: str = Query(None, description="Filter by regime"),
+ type: str = Query(None, description="Filter by type"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
skip = (page - 1) * page_size
query = db.query(CodePedimentoRegimen)
+
+ if code is not None:
+ query = query.filter(CodePedimentoRegimen.pedimento_code == code)
+ if regime is not None:
+ query = query.filter(CodePedimentoRegimen.regime == regime)
+ if type is not None:
+ query = query.filter(CodePedimentoRegimen.type == type)
+
items = query.offset(skip).limit(page_size).all()
total = query.count()
return {
diff --git a/frontend/src/lib/api/dashboard/a76/clients-providers.ts b/frontend/src/lib/api/dashboard/a76/clients-providers.ts
index 40faa07d..773de3b0 100644
--- a/frontend/src/lib/api/dashboard/a76/clients-providers.ts
+++ b/frontend/src/lib/api/dashboard/a76/clients-providers.ts
@@ -31,8 +31,8 @@ export interface ClientProvider {
domicile_fiscal?: string | null;
foreign_tax_id?: string | null;
client_or_provider?: string | null;
- enabled_disabled?: number;
- tenant_id: number;
+ is_active?: boolean;
+ tenant_id: number;
address?: ClientProviderAddress | null;
programs?: ClientProviderPrograms | null;
}
@@ -46,7 +46,7 @@ export interface ClientProviderBasic {
domicile_fiscal?: string | null;
foreign_tax_id?: string | null;
client_or_provider?: string | null;
- enabled_disabled?: number;
+ is_active?: boolean;
tenant_id: number;
}
@@ -65,7 +65,7 @@ export interface CreateClientProviderData {
domicile_fiscal?: string | null;
foreign_tax_id?: string | null;
client_or_provider?: string | null;
- enabled_disabled?: number;
+ is_active?: boolean;
address?: Omit | null;
programs?: Omit | null;
}
@@ -78,7 +78,7 @@ export interface UpdateClientProviderData {
domicile_fiscal?: string | null;
foreign_tax_id?: string | null;
client_or_provider?: string | null;
- enabled_disabled?: number;
+ is_active?: boolean;
address?: Partial | null;
programs?: Partial | null;
}
@@ -100,7 +100,7 @@ export const clientsProvidersApi = {
page: page.toString(),
page_size: pageSize.toString()
});
-
+
if (filters) {
Object.entries(filters).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== '') {
@@ -108,7 +108,7 @@ export const clientsProvidersApi = {
}
});
}
-
+
return api.get(
`/v1/a76/clients-providers?${params.toString()}`
);
@@ -166,7 +166,7 @@ export const clientsProvidersApi = {
/**
* Crea un nuevo cliente/proveedor
- * @param companyId - ID de la compañía
+ * @param companyId - ID de la compañía
* @param data - Datos del cliente/proveedor a crear
*/
create: (companyId: number, data: CreateClientProviderData) =>
diff --git a/frontend/src/lib/api/dashboard/a76/customs-brokers.ts b/frontend/src/lib/api/dashboard/a76/customs-brokers.ts
index d6a15c7c..17a82200 100644
--- a/frontend/src/lib/api/dashboard/a76/customs-brokers.ts
+++ b/frontend/src/lib/api/dashboard/a76/customs-brokers.ts
@@ -19,7 +19,7 @@ export interface CustomsBroker {
tax_id?: string | null;
personal_id?: string | null;
position?: string | null;
- license?: string | null;
+ license: string;
company?: string | null;
contact?: string | null;
tenant_id: string;
@@ -117,6 +117,14 @@ export const customsBrokersApi = {
return api.delete(`/v1/a76/customs-brokers/${brokerKey}?company_id=${companyId}`);
},
+ /**
+ * Actualiza la información de un agente aduanal
+ */
+ update: (brokerKey: string, data: CreateCustomsBrokerData) => {
+ const companyId = data.company_id;
+ return api.put(`/v1/a76/customs-brokers/${brokerKey}?company_id=${companyId}`, data);
+ },
+
/**
* Actualiza la información de VU de un agente aduanal
*/
diff --git a/frontend/src/lib/api/dashboard/a76/pedimento-dates.ts b/frontend/src/lib/api/dashboard/a76/pedimento-dates.ts
index 7e99ef9e..1cbe8ef4 100644
--- a/frontend/src/lib/api/dashboard/a76/pedimento-dates.ts
+++ b/frontend/src/lib/api/dashboard/a76/pedimento-dates.ts
@@ -17,8 +17,6 @@ export interface PedimentoDates {
original_date?: string | null;
start_date?: string | null;
end_date?: string | null;
- capture_date?: string | null;
- capture_time?: string | null;
created_at: string;
}
@@ -33,8 +31,6 @@ export interface CreatePedimentoDatesData {
original_date?: string | null;
start_date?: string | null;
end_date?: string | null;
- capture_date?: string | null;
- capture_time?: string | null;
}
export interface UpdatePedimentoDatesData {
@@ -48,8 +44,6 @@ export interface UpdatePedimentoDatesData {
original_date?: string | null;
start_date?: string | null;
end_date?: string | null;
- capture_date?: string | null;
- capture_time?: string | null;
}
export const pedimentoDatesApi = {
diff --git a/frontend/src/lib/api/dashboard/a76/pedimento-payments.ts b/frontend/src/lib/api/dashboard/a76/pedimento-payments.ts
index cbe39e93..ac5bfa08 100644
--- a/frontend/src/lib/api/dashboard/a76/pedimento-payments.ts
+++ b/frontend/src/lib/api/dashboard/a76/pedimento-payments.ts
@@ -17,8 +17,7 @@ export interface PedimentoPayments {
total_cash_paid?: number | null;
total_contributions?: number | null;
counter_payment?: number | null;
- pece_code?: string | null;
- payment_id?: number | null;
+ pece_code?: string | null;
created_at: string;
}
@@ -33,8 +32,7 @@ export interface CreatePedimentoPaymentsData {
total_cash_paid?: number | null;
total_contributions?: number | null;
counter_payment?: number | null;
- pece_code?: string | null;
- payment_id?: number | null;
+ pece_code?: string | null;
}
export interface UpdatePedimentoPaymentsData {
@@ -48,8 +46,7 @@ export interface UpdatePedimentoPaymentsData {
total_cash_paid?: number | null;
total_contributions?: number | null;
counter_payment?: number | null;
- pece_code?: string | null;
- payment_id?: number | null;
+ pece_code?: string | null;
}
export const pedimentoPaymentsApi = {
diff --git a/frontend/src/lib/api/dashboard/a76/pedimentos.ts b/frontend/src/lib/api/dashboard/a76/pedimentos.ts
index 4532022a..3e93e440 100644
--- a/frontend/src/lib/api/dashboard/a76/pedimentos.ts
+++ b/frontend/src/lib/api/dashboard/a76/pedimentos.ts
@@ -9,22 +9,45 @@ export interface PedimentoDates {
entry_date?: string | null;
pedimento_date?: string | null;
payment_date?: string | null;
+ rectification_payment_date?: string | null;
+ extraction_date?: string | null;
+ submission_date?: string | null;
+ eucan_date?: string | null;
+ original_date?: string | null;
+ start_date?: string | null;
+ end_date?: string | null;
}
export interface PedimentoPayments {
- payment_form?: string | null;
- bank_identifier?: string | null;
+ acknowledgment?: string | null;
+ operation_number?: string | null;
+ bank_code?: string | null;
+ cashier?: string | null;
+ date?: string | null;
+ time?: string | null;
+ shift?: string | null;
+ total_cash_paid?: string | null;
+ total_contributions?: string | null;
+ counter_payment?: string | null;
+ pece_code?: string | null;
}
export interface PedimentoTransportMeans {
- arrival_key?: string | null;
- arrival_data?: string | null;
- departure_key?: string | null;
- departure_data?: string | null;
+ destination?: number | null;
+ entry_exit?: string | null;
+ arrival?: string | null;
+ departure?: string | null;
}
export interface PedimentoValidation {
- document?: string | null;
+ validator?: string | null;
+ validation_ack?: string | null;
+ pre_ack?: string | null;
+ line_signature?: string | null;
+ electronic_signature?: string | null;
+ certificate_number?: string | null;
+ validator_id?: number | null;
+ responsible_id?: number | null;
}
export interface Pedimento {
@@ -49,7 +72,7 @@ export interface Pedimento {
pedimento_dates?: PedimentoDates | null;
pedimento_payments?: PedimentoPayments | null;
pedimento_transport_means?: PedimentoTransportMeans | null;
- pedimento_validation?: PedimentoValidation | null;
+ pedimento_validation?: PedimentoValidation | null;
}
export interface PedimentoListResponse {
diff --git a/frontend/src/lib/components/dashboard/classes/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/classes/create-edit-dialog.svelte
index cac2f932..a61219e4 100644
--- a/frontend/src/lib/components/dashboard/classes/create-edit-dialog.svelte
+++ b/frontend/src/lib/components/dashboard/classes/create-edit-dialog.svelte
@@ -9,6 +9,7 @@
import { materialTypesApi, type MaterialType } from "$lib/api/dashboard/refrence_data/material_types";
import { companyStore } from "$lib/stores/company.svelte";
import { onMount } from 'svelte';
+ import { LoaderCircle, Home } from 'lucide-svelte';
let {
open = $bindable(false),
@@ -248,21 +249,7 @@
{#if companyStore.activeCompany}
-
+
{companyStore.activeCompany.name}
@@ -426,26 +413,7 @@
{#if loading}
-
+
{/if}
{isEditing ? "Guardar cambios" : "Crear"}
diff --git a/frontend/src/lib/components/dashboard/clients_and_providers/data-table-actions.svelte b/frontend/src/lib/components/dashboard/clients_and_providers/data-table-actions.svelte
index 982cc4dc..888b9488 100644
--- a/frontend/src/lib/components/dashboard/clients_and_providers/data-table-actions.svelte
+++ b/frontend/src/lib/components/dashboard/clients_and_providers/data-table-actions.svelte
@@ -90,7 +90,7 @@
Ver detalles
Editar
- {isToggling ? 'Cambiando...' : item.enabled_disabled === 1 ? 'Desactivar' : 'Activar'}
+ {isToggling ? 'Cambiando...' : item.is_active === true ? 'Desactivar' : 'Activar'}
Eliminar
diff --git a/frontend/src/lib/components/dashboard/clients_and_providers/delete-dialog.svelte b/frontend/src/lib/components/dashboard/clients_and_providers/delete-dialog.svelte
index 2d0963be..f0b8d48c 100644
--- a/frontend/src/lib/components/dashboard/clients_and_providers/delete-dialog.svelte
+++ b/frontend/src/lib/components/dashboard/clients_and_providers/delete-dialog.svelte
@@ -3,6 +3,7 @@
import * as AlertDialog from "$lib/components/ui/alert-dialog";
import { clientsProvidersApi, type ClientProvider } from "$lib/api/dashboard/a76/clients-providers";
import { companyStore } from "$lib/stores/company.svelte";
+ import { LoaderCircle } from 'lucide-svelte';
let {
open = $bindable(false),
@@ -97,26 +98,7 @@
class="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{#if loading}
-
+
{/if}
Eliminar
diff --git a/frontend/src/lib/components/dashboard/clients_and_providers/details-dialog.svelte b/frontend/src/lib/components/dashboard/clients_and_providers/details-dialog.svelte
index 592a85e7..fe478312 100644
--- a/frontend/src/lib/components/dashboard/clients_and_providers/details-dialog.svelte
+++ b/frontend/src/lib/components/dashboard/clients_and_providers/details-dialog.svelte
@@ -85,7 +85,7 @@
Estado
- {#if item.enabled_disabled === 1}
+ {#if item.is_active === true}
Activo
diff --git a/frontend/src/lib/components/dashboard/customs_brokers/data-table-actions.svelte b/frontend/src/lib/components/dashboard/customs_brokers/data-table-actions.svelte
index 5a6f7281..3696cbd7 100644
--- a/frontend/src/lib/components/dashboard/customs_brokers/data-table-actions.svelte
+++ b/frontend/src/lib/components/dashboard/customs_brokers/data-table-actions.svelte
@@ -5,6 +5,7 @@
import type { CustomsBroker } from "./columns.js";
import DetailsDialog from "./details-dialog.svelte";
import DeleteDialog from "./delete-dialog.svelte";
+ import EditDialog from "./edit-dialog.svelte";
let {
broker,
@@ -16,6 +17,7 @@
let showDetailsDialog = $state(false);
let showDeleteDialog = $state(false);
+ let showEditDialog = $state(false);
function handleCopyKey() {
navigator.clipboard.writeText(broker.broker_key);
@@ -31,6 +33,10 @@
showDetailsDialog = true;
}
+ function handleEdit() {
+ showEditDialog = true;
+ }
+
function handleDelete() {
showDeleteDialog = true;
}
@@ -64,6 +70,9 @@
Ver detalles
+
+ Editar
+
Eliminar
@@ -72,4 +81,5 @@
+
diff --git a/frontend/src/lib/components/dashboard/customs_brokers/delete-dialog.svelte b/frontend/src/lib/components/dashboard/customs_brokers/delete-dialog.svelte
index bf2ffa39..0658bd95 100644
--- a/frontend/src/lib/components/dashboard/customs_brokers/delete-dialog.svelte
+++ b/frontend/src/lib/components/dashboard/customs_brokers/delete-dialog.svelte
@@ -2,6 +2,7 @@
import { Button } from "$lib/components/ui/button";
import * as AlertDialog from "$lib/components/ui/alert-dialog";
import { customsBrokersApi, type CustomsBroker } from "$lib/api/dashboard/a76/customs-brokers";
+ import { LoaderCircle } from 'lucide-svelte';
let {
open = $bindable(false),
@@ -92,26 +93,7 @@
class="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{#if loading}
-
+
Eliminando...
{:else}
Eliminar
diff --git a/frontend/src/lib/components/dashboard/customs_brokers/edit-dialog.svelte b/frontend/src/lib/components/dashboard/customs_brokers/edit-dialog.svelte
new file mode 100644
index 00000000..e5bfeebb
--- /dev/null
+++ b/frontend/src/lib/components/dashboard/customs_brokers/edit-dialog.svelte
@@ -0,0 +1,384 @@
+
+
+
+
+
+ Editar Agente Aduanal
+
+ Modifica los datos del agente aduanal {broker?.broker_key}.
+
+
+
+
+
+
diff --git a/frontend/src/lib/components/dashboard/pedimentos/data-table-actions.svelte b/frontend/src/lib/components/dashboard/pedimentos/data-table-actions.svelte
index 01f9bba9..1aa00270 100644
--- a/frontend/src/lib/components/dashboard/pedimentos/data-table-actions.svelte
+++ b/frontend/src/lib/components/dashboard/pedimentos/data-table-actions.svelte
@@ -2,6 +2,7 @@
import { Button } from "$lib/components/ui/button";
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
import { pedimentosApi, type Pedimento } from "$lib/api/dashboard/a76/pedimentos";
+ import { EllipsisVertical, Pencil, LoaderCircle, Trash2 } from 'lucide-svelte';
let {
item,
@@ -62,21 +63,7 @@
{#snippet child({ props })}
Abrir menú
-
+
{/snippet}
@@ -84,63 +71,15 @@
Acciones
-
+
Editar
{#if loading}
-
+
{:else}
-
+
{/if}
Eliminar
diff --git a/frontend/src/lib/components/dashboard/pedimentos/edit/dates-tab-form.svelte b/frontend/src/lib/components/dashboard/pedimentos/edit/dates-tab-form.svelte
index e8847fc9..5377d232 100644
--- a/frontend/src/lib/components/dashboard/pedimentos/edit/dates-tab-form.svelte
+++ b/frontend/src/lib/components/dashboard/pedimentos/edit/dates-tab-form.svelte
@@ -1,90 +1,40 @@
@@ -113,15 +60,8 @@
- {#if loading}
-
-
-
-
-
- {:else}
-
-
+
+
@@ -220,29 +160,8 @@
type="date"
bind:value={formData.end_date}
/>
-
-
-
-
-
-
-
-
-
-
-
-
-
+
- {/if}
-
-
+
+
\ No newline at end of file
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 02123c1b..6c7e5ffc 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
@@ -2,20 +2,178 @@
import * as Card from '$lib/components/ui/card';
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
+ import * as Select from '$lib/components/ui/select';
import type { Pedimento } from '$lib/api/dashboard/a76/pedimentos';
+ import type { PedimentoCode } from '$lib/api/dashboard/refrence_data/pedimento_codes';
+ import type { CustomsSection } from '$lib/api/dashboard/refrence_data/customs_sections';
+ import type { CustomsBroker } from '$lib/api/dashboard/a76/customs-brokers';
+ import type { ClientProvider } from '$lib/api/dashboard/a76/clients-providers';
+ import type { CodePedimentoRegimen } from '$lib/api/dashboard/refrence_data/code_pedimento_regimens';
let {
pedimento,
- formData = $bindable()
+ formData = $bindable(),
+ pedimentoCodes = [],
+ customsSections = [],
+ customsBrokers = [],
+ clients = [],
+ codePedimentoRegimens = []
}: {
pedimento: Pedimento | null;
formData?: any;
+ pedimentoCodes?: PedimentoCode[];
+ customsSections?: CustomsSection[];
+ customsBrokers?: CustomsBroker[];
+ clients?: ClientProvider[];
+ codePedimentoRegimens?: CodePedimentoRegimen[];
} = $props();
+ // Extraer regímenes únicos de codePedimentoRegimens
+ const uniqueRegimens = $derived(
+ Array.from(new Set(codePedimentoRegimens.map(r => r.regimen_code).filter((code): code is string => code !== null)))
+ .sort()
+ .map(code => ({ code, label: code }))
+ );
+
+ // Funciones de mapeo entre type_code (E/I) y operation_type (1/2)
+ function typeCodeToOperationType(typeCode: string | null | undefined): number | null {
+ if (!typeCode) return null;
+ // E = Exportación = 1, I = Importación = 2
+ if (typeCode.toUpperCase() === 'E') return 1;
+ if (typeCode.toUpperCase() === 'I') return 2;
+ return null;
+ }
+
+ function operationTypeToTypeCode(operationType: number | null | undefined): string | null {
+ if (operationType === null || operationType === undefined) return null;
+ // 1 = Exportación = E, 2 = Importación = I
+ if (operationType === 1) return 'E';
+ if (operationType === 2) return 'I';
+ return null;
+ }
+
+ // Opciones filtradas para Régimen y Tipo de Operación basadas en las selecciones actuales
+ // NOTA: La Clave NO se filtra, siempre muestra todas las opciones
+
+ const filteredRegimens = $derived(() => {
+ // Si hay clave o tipo de operación seleccionado, filtrar
+ if (formData.pedimento_code || formData.operation_type !== null) {
+ const matches = codePedimentoRegimens.filter(r => {
+ const matchesCode = !formData.pedimento_code || r.pedimento_code === formData.pedimento_code;
+ const matchesType = formData.operation_type === null || r.type_code === operationTypeToTypeCode(formData.operation_type);
+ return matchesCode && matchesType;
+ });
+ const validRegimens = new Set(matches.map(m => m.regimen_code).filter((code): code is string => code !== null));
+ return Array.from(validRegimens).sort().map(code => ({ code, label: code }));
+ }
+ return uniqueRegimens;
+ });
+
+ const filteredOperationTypes = $derived(() => {
+ // Si hay clave o régimen seleccionado, filtrar
+ if (formData.pedimento_code || formData.regime) {
+ const matches = codePedimentoRegimens.filter(r => {
+ const matchesCode = !formData.pedimento_code || r.pedimento_code === formData.pedimento_code;
+ const matchesRegime = !formData.regime || r.regimen_code === formData.regime;
+ return matchesCode && matchesRegime;
+ });
+ const validTypes = new Set(matches.map(m => typeCodeToOperationType(m.type_code)).filter(t => t !== null));
+ return operationOptions.filter(opt => validTypes.has(opt.value));
+ }
+ return operationOptions;
+ });
+
+ // Reactive synchronization between Clave, Régimen, and Tipo de Operación
+ // REGLA: La Clave es el campo principal y NUNCA se modifica automáticamente
+ // Solo se auto-llenan Régimen y Tipo de Operación basándose en la Clave
+
+ // Cuando cambia la Clave del Pedimento
+ $effect(() => {
+ const currentCode = formData.pedimento_code;
+ if (!currentCode) return;
+
+ const matches = codePedimentoRegimens.filter(r => r.pedimento_code === currentCode);
+
+ if (matches.length === 0) return;
+
+ // Verificar si los valores actuales de régimen y tipo son válidos para esta clave
+ const currentIsValid = matches.some(m => {
+ const matchesRegime = !formData.regime || m.regimen_code === formData.regime;
+ const matchesType = formData.operation_type === null || m.type_code === operationTypeToTypeCode(formData.operation_type);
+ return matchesRegime && matchesType;
+ });
+
+ // Si los valores actuales son válidos, NO auto-llenar
+ if (currentIsValid && (formData.regime || formData.operation_type !== null)) {
+ return;
+ }
+
+ // Si solo hay un match y no hay valores válidos, auto-llenar
+ if (matches.length === 1) {
+ const match = matches[0];
+ if (match.regimen_code && formData.regime !== match.regimen_code) {
+ formData.regime = match.regimen_code;
+ }
+ const expectedOpType = typeCodeToOperationType(match.type_code);
+ if (expectedOpType !== null && formData.operation_type !== expectedOpType) {
+ formData.operation_type = expectedOpType;
+ }
+ }
+ });
+
+ // Cuando cambia el Régimen
+ $effect(() => {
+ const currentRegime = formData.regime;
+ if (!currentRegime) return;
+
+ const matches = codePedimentoRegimens.filter(r => r.regimen_code === currentRegime);
+
+ if (matches.length === 0) return;
+
+ // Si hay clave seleccionada, solo validar (NO auto-llenar tipo de operación)
+ if (formData.pedimento_code) {
+ const exactMatch = matches.find(m => m.pedimento_code === formData.pedimento_code);
+ }
+ // Si hay tipo de operación pero no clave, no hacer nada
+ // (el usuario debe seleccionar la clave primero)
+ });
+
+ // Cuando cambia el Tipo de Operación
+ $effect(() => {
+ const currentType = formData.operation_type;
+ if (currentType === null || currentType === undefined) return;
+
+ const expectedTypeCode = operationTypeToTypeCode(currentType);
+ const matches = codePedimentoRegimens.filter(r => r.type_code === expectedTypeCode);
+
+ if (matches.length === 0) return;
+
+ // Si hay clave seleccionada, actualizar régimen (forzar si no hay match exacto)
+ if (formData.pedimento_code) {
+ const exactMatch = matches.find(m => m.pedimento_code === formData.pedimento_code);
+ if (exactMatch?.regimen_code && formData.regime !== exactMatch.regimen_code) {
+ formData.regime = exactMatch.regimen_code;
+ } else if (!exactMatch) {
+ // No hay match exacto - buscar cualquier match con la clave actual
+ const allMatchesForClave = codePedimentoRegimens.filter(r => r.pedimento_code === formData.pedimento_code);
+ if (allMatchesForClave.length > 0) {
+ // Forzar el régimen al primer match disponible para esta clave
+ const firstMatch = allMatchesForClave[0];
+ if (firstMatch.regimen_code) formData.regime = firstMatch.regimen_code;
+ }
+ }
+ }
+ // Si hay régimen pero no clave, no hacer nada
+ // (el usuario debe seleccionar la clave primero)
+ });
+
+ // Obtener el año actual (últimos 2 dígitos)
+ const currentYear = String(new Date().getFullYear()).slice(-2);
+
// Inicializar formData con los valores del pedimento (o vacío si es null)
if (!formData) {
formData = {
- year: pedimento?.year || '',
+ year: pedimento?.year || currentYear,
customs_office: pedimento?.customs_office || '',
license: pedimento?.license || '',
pedimento_number: pedimento?.pedimento_number || '',
@@ -32,6 +190,18 @@
};
}
+ // Asegurar que el año siempre esté actualizado con el año actual
+ $effect(() => {
+ if (formData && !pedimento?.year) {
+ formData.year = currentYear;
+ }
+ });
+
+ const operationOptions = [
+ { value: 1, label: 'Exportación' },
+ { value: 2, label: 'Importación' },
+ ];
+
const statusOptions = [
{ value: 'MODIFICABLE', label: 'Modificable' },
{ value: 'ESPERA FIRMA PREVIO', label: 'Espera de firma previo' },
@@ -71,6 +241,8 @@
placeholder="23"
maxlength={2}
class="text-center"
+ disabled
+ readonly
/>
@@ -78,15 +250,28 @@
-
-
-
-
-
-
-
+
+
+
+
+
+ formData.pedimento_code = v ?? ''}
+ >
+
+
+ {pedimentoCodes.find(o => o.code === formData.pedimento_code)?.code || 'Sel...'}
+
+
+
+ {#each pedimentoCodes as code}
+
+
+ {code.code} - {code.description}
+
+
+ {/each}
+
+
+
+
+
+
+
+ formData.regime = v ?? ''}
+ >
+
+
+ {formData.regime || 'Sel...'}
+
+
+
+ {#each filteredRegimens() as regimen}
+
+
+ {regimen.code}
+
+
+ {/each}
+
+
+
+
+
+
+
+ formData.operation_type = v ? Number(v) : null}
+ >
+
+ {operationOptions.find(o => o.value === formData.operation_type)?.label || 'Seleccionar...'}
+
+
+ {#each filteredOperationTypes() as option}
+
+ {/each}
+
+
+
+
+
+
-
+
+ {statusOptions.find(o => o.value === formData.status)?.label || 'Seleccionar...'}
+
+
+ {#each statusOptions as option}
+
+ {/each}
+
+
diff --git a/frontend/src/lib/components/dashboard/pedimentos/edit/payments-tab-form.svelte b/frontend/src/lib/components/dashboard/pedimentos/edit/payments-tab-form.svelte
index 430726a4..fb004236 100644
--- a/frontend/src/lib/components/dashboard/pedimentos/edit/payments-tab-form.svelte
+++ b/frontend/src/lib/components/dashboard/pedimentos/edit/payments-tab-form.svelte
@@ -1,106 +1,54 @@
@@ -113,145 +61,128 @@
- {#if loading}
-
-
-
-
- {:else}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/frontend/src/lib/components/dashboard/pedimentos/edit/transport-tab-form.svelte b/frontend/src/lib/components/dashboard/pedimentos/edit/transport-tab-form.svelte
index 3c42a633..17ffeeb1 100644
--- a/frontend/src/lib/components/dashboard/pedimentos/edit/transport-tab-form.svelte
+++ b/frontend/src/lib/components/dashboard/pedimentos/edit/transport-tab-form.svelte
@@ -1,74 +1,40 @@
@@ -81,61 +47,52 @@
- {#if loading}
-
-
-
-
-
-
- {:else}
-
-
-
-
-
-
-
+
diff --git a/frontend/src/lib/components/dashboard/pedimentos/edit/validation-tab-form.svelte b/frontend/src/lib/components/dashboard/pedimentos/edit/validation-tab-form.svelte
index baabf978..3c5d8c35 100644
--- a/frontend/src/lib/components/dashboard/pedimentos/edit/validation-tab-form.svelte
+++ b/frontend/src/lib/components/dashboard/pedimentos/edit/validation-tab-form.svelte
@@ -1,79 +1,39 @@
@@ -98,110 +56,96 @@
- {#if loading}
-
-
-
-
-
-
- {:else}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
- Firma electrónica del pedimento (máximo 999 caracteres).
-
- {/if}
+
diff --git a/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/delete-dialog.svelte b/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/delete-dialog.svelte
index 73d891e6..3f6031ce 100644
--- a/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/delete-dialog.svelte
+++ b/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/delete-dialog.svelte
@@ -2,6 +2,7 @@
import { Button } from "$lib/components/ui/button";
import * as AlertDialog from "$lib/components/ui/alert-dialog";
import { codePedimentoRegimensApi, type CodePedimentoRegimen } from "$lib/api/dashboard/refrence_data/code_pedimento_regimens";
+ import { LoaderCircle } from 'lucide-svelte';
let {
open = $bindable(false),
diff --git a/frontend/src/lib/components/dashboard/reference_data/containers/delete-dialog.svelte b/frontend/src/lib/components/dashboard/reference_data/containers/delete-dialog.svelte
index 88ddcb9b..45c75b8d 100644
--- a/frontend/src/lib/components/dashboard/reference_data/containers/delete-dialog.svelte
+++ b/frontend/src/lib/components/dashboard/reference_data/containers/delete-dialog.svelte
@@ -2,6 +2,7 @@
import { Button } from "$lib/components/ui/button";
import * as AlertDialog from "$lib/components/ui/alert-dialog";
import { containersApi, type Container } from "$lib/api/dashboard/refrence_data/containers";
+ import { LoaderCircle } from 'lucide-svelte';
let {
open = $bindable(false),
@@ -84,26 +85,7 @@
class="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{#if loading}
-
+
{/if}
Eliminar
diff --git a/frontend/src/lib/components/dashboard/reference_data/customs_sections/delete-dialog.svelte b/frontend/src/lib/components/dashboard/reference_data/customs_sections/delete-dialog.svelte
index f71a9d60..f2e3a3ff 100644
--- a/frontend/src/lib/components/dashboard/reference_data/customs_sections/delete-dialog.svelte
+++ b/frontend/src/lib/components/dashboard/reference_data/customs_sections/delete-dialog.svelte
@@ -2,6 +2,7 @@
import { Button } from "$lib/components/ui/button";
import * as AlertDialog from "$lib/components/ui/alert-dialog";
import { customsSectionsApi, type CustomsSection } from "$lib/api/dashboard/refrence_data/customs_sections";
+ import { LoaderCircle } from 'lucide-svelte';
let {
open = $bindable(false),
diff --git a/frontend/src/lib/components/dashboard/reference_data/customs_warehouses/delete-dialog.svelte b/frontend/src/lib/components/dashboard/reference_data/customs_warehouses/delete-dialog.svelte
index 17878734..220f9e5c 100644
--- a/frontend/src/lib/components/dashboard/reference_data/customs_warehouses/delete-dialog.svelte
+++ b/frontend/src/lib/components/dashboard/reference_data/customs_warehouses/delete-dialog.svelte
@@ -2,6 +2,7 @@
import { Button } from "$lib/components/ui/button";
import * as AlertDialog from "$lib/components/ui/alert-dialog";
import { customsWarehousesApi, type CustomsWarehouse } from "$lib/api/dashboard/refrence_data/customs_warehouses";
+ import { LoaderCircle } from 'lucide-svelte';
let {
open = $bindable(false),
diff --git a/frontend/src/lib/components/dashboard/reference_data/material_types/delete-dialog.svelte b/frontend/src/lib/components/dashboard/reference_data/material_types/delete-dialog.svelte
index 7bfb8a17..642bb106 100644
--- a/frontend/src/lib/components/dashboard/reference_data/material_types/delete-dialog.svelte
+++ b/frontend/src/lib/components/dashboard/reference_data/material_types/delete-dialog.svelte
@@ -2,6 +2,7 @@
import { Button } from "$lib/components/ui/button";
import * as AlertDialog from "$lib/components/ui/alert-dialog";
import { materialTypesApi, type MaterialType } from "$lib/api/dashboard/refrence_data/material_types";
+ import { LoaderCircle } from 'lucide-svelte';
let {
open = $bindable(false),
@@ -88,26 +89,7 @@
class="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{#if loading}
-
+
{/if}
Eliminar
diff --git a/frontend/src/lib/components/dashboard/reference_data/payment_methods/delete-dialog.svelte b/frontend/src/lib/components/dashboard/reference_data/payment_methods/delete-dialog.svelte
index f5ff98e7..2b23c0cf 100644
--- a/frontend/src/lib/components/dashboard/reference_data/payment_methods/delete-dialog.svelte
+++ b/frontend/src/lib/components/dashboard/reference_data/payment_methods/delete-dialog.svelte
@@ -2,6 +2,7 @@
import { Button } from "$lib/components/ui/button";
import * as AlertDialog from "$lib/components/ui/alert-dialog";
import { paymentMethodsApi, type PaymentMethod } from "$lib/api/dashboard/refrence_data/payment_methods";
+ import { LoaderCircle } from 'lucide-svelte';
let {
open = $bindable(false),
@@ -84,26 +85,7 @@
class="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{#if loading}
-
+
{/if}
Eliminar
diff --git a/frontend/src/lib/components/dashboard/reference_data/pedimento_regimens/delete-dialog.svelte b/frontend/src/lib/components/dashboard/reference_data/pedimento_regimens/delete-dialog.svelte
index 4c7ba519..27eceaa1 100644
--- a/frontend/src/lib/components/dashboard/reference_data/pedimento_regimens/delete-dialog.svelte
+++ b/frontend/src/lib/components/dashboard/reference_data/pedimento_regimens/delete-dialog.svelte
@@ -2,6 +2,7 @@
import { Button } from "$lib/components/ui/button";
import * as AlertDialog from "$lib/components/ui/alert-dialog";
import { pedimentoRegimensApi, type PedimentoRegimen } from "$lib/api/dashboard/refrence_data/pedimento_regimens";
+ import { LoaderCircle } from 'lucide-svelte';
let {
open = $bindable(false),
@@ -84,26 +85,7 @@
class="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{#if loading}
-
+
{/if}
Eliminar
diff --git a/frontend/src/lib/components/dashboard/reference_data/states/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/reference_data/states/create-edit-dialog.svelte
index 927f1ccf..cb24372d 100644
--- a/frontend/src/lib/components/dashboard/reference_data/states/create-edit-dialog.svelte
+++ b/frontend/src/lib/components/dashboard/reference_data/states/create-edit-dialog.svelte
@@ -4,6 +4,7 @@
import { Input } from "$lib/components/ui/input";
import { Label } from "$lib/components/ui/label";
import { statesApi, type State, type CreateStateData, type UpdateStateData } from "$lib/api/dashboard/refrence_data/states";
+ import { LoaderCircle } from 'lucide-svelte';
let {
open = $bindable(false),
@@ -195,26 +196,7 @@
{#if loading}
-
+
{/if}
{isEditing ? "Guardar cambios" : "Crear"}
diff --git a/frontend/src/lib/components/dashboard/reference_data/states/delete-dialog.svelte b/frontend/src/lib/components/dashboard/reference_data/states/delete-dialog.svelte
index d52d5553..24e0c6e1 100644
--- a/frontend/src/lib/components/dashboard/reference_data/states/delete-dialog.svelte
+++ b/frontend/src/lib/components/dashboard/reference_data/states/delete-dialog.svelte
@@ -2,6 +2,7 @@
import { Button } from "$lib/components/ui/button";
import * as AlertDialog from "$lib/components/ui/alert-dialog";
import { statesApi, type State } from "$lib/api/dashboard/refrence_data/states";
+ import { LoaderCircle } from 'lucide-svelte';
let {
open = $bindable(false),
@@ -96,26 +97,7 @@
class="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{#if loading}
-
+
{/if}
Eliminar
diff --git a/frontend/src/lib/components/dashboard/reference_data/transport_modes/delete-dialog.svelte b/frontend/src/lib/components/dashboard/reference_data/transport_modes/delete-dialog.svelte
index 13462cc5..cc05607a 100644
--- a/frontend/src/lib/components/dashboard/reference_data/transport_modes/delete-dialog.svelte
+++ b/frontend/src/lib/components/dashboard/reference_data/transport_modes/delete-dialog.svelte
@@ -2,6 +2,7 @@
import { Button } from "$lib/components/ui/button";
import * as AlertDialog from "$lib/components/ui/alert-dialog";
import { transportModesApi, type TransportMode } from "$lib/api/dashboard/refrence_data/transport_modes";
+ import { LoaderCircle } from 'lucide-svelte';
let {
open = $bindable(false),
@@ -84,26 +85,7 @@
class="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{#if loading}
-
+
{/if}
Eliminar
diff --git a/frontend/src/lib/components/login-form.svelte b/frontend/src/lib/components/login-form.svelte
index 11961b10..ebb3726a 100644
--- a/frontend/src/lib/components/login-form.svelte
+++ b/frontend/src/lib/components/login-form.svelte
@@ -10,6 +10,7 @@
import { Input } from "$lib/components/ui/input/index.js";
import { Button } from "$lib/components/ui/button/index.js";
import { cn } from "$lib/utils.js";
+ import { FileText, ShieldCheck } from 'lucide-svelte';
import type { HTMLAttributes } from "svelte/elements";
import { page } from '$app/state';
import { enhance } from '$app/forms';
@@ -203,14 +204,10 @@
diff --git a/frontend/src/lib/stores/company.svelte.ts b/frontend/src/lib/stores/company.svelte.ts
index 74b13c0d..c90d0261 100644
--- a/frontend/src/lib/stores/company.svelte.ts
+++ b/frontend/src/lib/stores/company.svelte.ts
@@ -69,7 +69,7 @@ class CompanyStore {
// Si no hay datos pre-cargados, hacer fetch (fallback)
this._loading = true;
try {
- const response = await fetch('/api/company/my-companies');
+ const response = await fetch('/api/v1/a76/company/my-companies');
if (response.ok) {
const newCompanies = await response.json();
diff --git a/frontend/src/routes/dashboard/+page.svelte b/frontend/src/routes/dashboard/+page.svelte
index 105fd63a..71473c62 100644
--- a/frontend/src/routes/dashboard/+page.svelte
+++ b/frontend/src/routes/dashboard/+page.svelte
@@ -1,5 +1,6 @@
+
+
+
\ No newline at end of file
diff --git a/frontend/src/routes/dashboard/clients_and_providers/+page.server.ts b/frontend/src/routes/dashboard/clients_and_providers/+page.server.ts
index f27c7557..dc458a5f 100644
--- a/frontend/src/routes/dashboard/clients_and_providers/+page.server.ts
+++ b/frontend/src/routes/dashboard/clients_and_providers/+page.server.ts
@@ -4,9 +4,9 @@ import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
// Esperar a que el layout padre valide/refresque el token
const parentData = await parent();
-
+
const { accessToken } = getAuthTokens(cookies);
-
+
if (!accessToken) {
return {
error: 'No authenticated',
@@ -22,20 +22,20 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
// Obtener parámetros de paginación de la URL
const page = parseInt(url.searchParams.get('page') || '1');
const pageSize = parseInt(url.searchParams.get('page_size') || '50');
-
+
// 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
+
+ const companyId = companyIdParam
? parseInt(companyIdParam)
: cookieCompanyId
? parseInt(cookieCompanyId)
: parentData.companies?.[0]?.id;
-
+
if (!companyId) {
return {
error: 'No company selected',
@@ -47,9 +47,17 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
};
}
+ // Construir URL con parámetros
+ let apiUrl = `v1/a76/clients-providers?company_id=${companyId}&page=${page}&page_size=${pageSize}`;
+
+ const type = url.searchParams.get('type');
+ if (type && type !== 'both') {
+ apiUrl += `&type=${type}`;
+ }
+
// Usar authenticatedFetch para manejar automáticamente el refresh de tokens
const response = await authenticatedFetch(
- `v1/a76/clients-providers?company_id=${companyId}&page=${page}&page_size=${pageSize}`,
+ apiUrl,
{},
cookies,
fetch
@@ -62,7 +70,7 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
statusText: response.statusText,
error: errorText
});
-
+
return {
error: `Error ${response.status}: ${response.statusText}`,
items: [],
diff --git a/frontend/src/routes/dashboard/clients_and_providers/+page.svelte b/frontend/src/routes/dashboard/clients_and_providers/+page.svelte
index 0995b3d6..2d25421a 100644
--- a/frontend/src/routes/dashboard/clients_and_providers/+page.svelte
+++ b/frontend/src/routes/dashboard/clients_and_providers/+page.svelte
@@ -6,6 +6,10 @@
import CreateEditDialog from '$lib/components/dashboard/clients_and_providers/create-edit-dialog.svelte';
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
+ import * as Select from '$lib/components/ui/select';
+ import { Plus, RefreshCw } from 'lucide-svelte';
+ import { page } from '$app/stores';
+ import { goto } from '$app/navigation';
import type { PageData } from './$types';
import { browser } from '$app/environment';
import { companyStore } from '$lib/stores/company.svelte';
@@ -16,6 +20,22 @@
// Estado para el diálogo de crear
let showCreateDialog = $state(false);
+ // Estado para el filtro de tipo
+ let selectedType = $state
($page.url.searchParams.get('type') || 'both');
+
+ // Actualizar URL cuando cambia el filtro
+ function handleTypeChange(value: string) {
+ selectedType = value;
+ const url = new URL(window.location.href);
+ if (value === 'both') {
+ url.searchParams.delete('type');
+ } else {
+ url.searchParams.set('type', value);
+ }
+ goto(url.toString(), { keepFocus: true, noScroll: true, replaceState: true });
+ reloadData();
+ }
+
// Sincronizar token de cookies a localStorage al montar el componente
onMount(() => {
if (browser) {
@@ -77,7 +97,8 @@
const response = await clientsProvidersApi.list(
companyStore.activeCompany.id,
currentPage + 1,
- pageSize
+ pageSize,
+ selectedType !== 'both' ? { type: selectedType } : undefined
);
if (response.error) {
@@ -121,7 +142,8 @@
const response = await clientsProvidersApi.list(
companyStore.activeCompany.id,
1,
- pageSize
+ pageSize,
+ selectedType !== 'both' ? { type: selectedType } : undefined
);
if (response.error) {
@@ -174,21 +196,7 @@
-
+
Nuevo Cliente/Proveedor
@@ -216,25 +224,23 @@
{/if}
-
-
+
+
+
+ {selectedType === 'both' ? 'Todos' : selectedType === 'client' ? 'Clientes' : 'Proveedores'}
+
+
+ Todos
+ Clientes
+ Proveedores
+
+
+
+
Actualizar
+
diff --git a/frontend/src/routes/dashboard/customs_brokers/+page.svelte b/frontend/src/routes/dashboard/customs_brokers/+page.svelte
index 7ccfb44c..fe9682fd 100644
--- a/frontend/src/routes/dashboard/customs_brokers/+page.svelte
+++ b/frontend/src/routes/dashboard/customs_brokers/+page.svelte
@@ -11,6 +11,7 @@
import type { PageData } from './$types';
import { browser } from '$app/environment';
import { companyStore } from '$lib/stores/company.svelte';
+ import { Plus, Search, Trash2 } from 'lucide-svelte';
// Los datos iniciales vienen del servidor
let { data }: { data: PageData } = $props();
@@ -175,21 +176,7 @@
-
+
Nuevo Agente Aduanal
@@ -226,44 +213,15 @@
Buscando...
{:else}
-
+
Buscar
{/if}
- {#if searchedBroker}
-
-
- Limpiar
-
+ {#if searchedBroker}
+
+
+ Limpiar
+
{/if}
diff --git a/frontend/src/routes/dashboard/general_catalogs/classes/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/classes/+page.svelte
index 458d83ec..247de471 100644
--- a/frontend/src/routes/dashboard/general_catalogs/classes/+page.svelte
+++ b/frontend/src/routes/dashboard/general_catalogs/classes/+page.svelte
@@ -9,6 +9,7 @@
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
+ import { Plus, Filter, Trash2, RefreshCw } from 'lucide-svelte';
import { browser } from '$app/environment';
// Estado para la lista de classes
@@ -255,21 +256,7 @@
-
+
Nueva Clase
@@ -305,38 +292,11 @@
-
+
Filtrar
-
+
@@ -364,21 +324,7 @@
-
+
Actualizar
diff --git a/frontend/src/routes/dashboard/pedimentos/+page.svelte b/frontend/src/routes/dashboard/pedimentos/+page.svelte
index 8a90390c..711d5173 100644
--- a/frontend/src/routes/dashboard/pedimentos/+page.svelte
+++ b/frontend/src/routes/dashboard/pedimentos/+page.svelte
@@ -10,6 +10,7 @@
import type { PageData } from './$types';
import { browser } from '$app/environment';
import { companyStore } from '$lib/stores/company.svelte';
+ import { Plus, Filter, Trash2, RefreshCw } from 'lucide-svelte';
// Los datos iniciales vienen del servidor
let { data }: { data: PageData } = $props();
@@ -260,21 +261,7 @@
-
+
Nuevo Pedimento
@@ -322,38 +309,11 @@
-
+
Filtrar
-
+
@@ -381,21 +341,7 @@
-
+
Actualizar
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 c011c52c..d34b2f0c 100644
--- a/frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.server.ts
+++ b/frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.server.ts
@@ -9,12 +9,78 @@ export const load: PageServerLoad = async ({ params, cookies, fetch }) => {
throw redirect(302, '/login');
}
+ // Obtener el company_id de la cookie para ambas ramas (new y edit)
+ const companyId = await getActiveCompanyId(cookies, fetch);
+
+ if (!companyId) {
+ throw error(400, 'No se encontró una compañía seleccionada');
+ }
+
+ // Cargar pedimento_codes (datos de referencia)
+ const pedimentoCodesPromise = authenticatedFetch(
+ 'v1/public/refrence_data/pedimento-codes?page=1&page_size=100',
+ {},
+ cookies,
+ fetch
+ );
+
+ // Cargar customs_sections (datos de referencia)
+ const customsSectionsPromise = authenticatedFetch(
+ 'v1/public/refrence_data/customs-sections?page=1&page_size=100',
+ {},
+ cookies,
+ fetch
+ );
+
+ // Cargar customs_brokers (datos de referencia)
+ const customsBrokersPromise = authenticatedFetch(
+ `v1/a76/customs-brokers?company_id=${companyId}`,
+ {},
+ cookies,
+ fetch
+ );
+
+ // Cargar clientes (para el select de client_id)
+ const clientsPromise = authenticatedFetch(
+ `v1/a76/clients-providers?company_id=${companyId}&type=client&page=1&page_size=1000`,
+ {},
+ cookies,
+ fetch
+ );
+
+ // Cargar code-pedimento-regimens (para interdependencia de campos)
+ const codePedimentoRegimensPromise = authenticatedFetch(
+ 'v1/public/refrence_data/code-pedimento-regimens?page=1&page_size=100',
+ {},
+ cookies,
+ fetch
+ );
+
// Si el ID es "new", es una creación
if (params.id === 'new') {
+ const [pedimentoCodesResponse, customsSectionsResponse, customsBrokersResponse, clientsResponse, codePedimentoRegimensResponse] = await Promise.all([
+ pedimentoCodesPromise,
+ customsSectionsPromise,
+ customsBrokersPromise,
+ clientsPromise,
+ codePedimentoRegimensPromise
+ ]);
+
+ const pedimentoCodes = pedimentoCodesResponse.ok ? await pedimentoCodesResponse.json() : { items: [] };
+ const customsSections = customsSectionsResponse.ok ? await customsSectionsResponse.json() : { items: [] };
+ const customsBrokers = customsBrokersResponse.ok ? await customsBrokersResponse.json() : { items: [] };
+ const clients = clientsResponse.ok ? await clientsResponse.json() : { items: [] };
+ const codePedimentoRegimens = codePedimentoRegimensResponse.ok ? await codePedimentoRegimensResponse.json() : { items: [] };
+
return {
pedimento: null,
pedimentoId: null,
- isCreate: true
+ isCreate: true,
+ pedimentoCodes: pedimentoCodes.items || [],
+ customsSections: customsSections.items || [],
+ customsBrokers: customsBrokers.items || [],
+ clients: clients.items || [],
+ codePedimentoRegimens: codePedimentoRegimens.items || []
};
}
@@ -24,12 +90,7 @@ 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(
@@ -51,10 +112,30 @@ export const load: PageServerLoad = async ({ params, cookies, fetch }) => {
const pedimento = await response.json();
+ // Cargar pedimento_codes y customs_sections
+ const [pedimentoCodesResponse, customsSectionsResponse, customsBrokersResponse, clientsResponse, codePedimentoRegimensResponse] = await Promise.all([
+ pedimentoCodesPromise,
+ customsSectionsPromise,
+ customsBrokersPromise,
+ clientsPromise,
+ codePedimentoRegimensPromise
+ ]);
+
+ const pedimentoCodes = pedimentoCodesResponse.ok ? await pedimentoCodesResponse.json() : { items: [] };
+ const customsSections = customsSectionsResponse.ok ? await customsSectionsResponse.json() : { items: [] };
+ const customsBrokers = customsBrokersResponse.ok ? await customsBrokersResponse.json() : { items: [] };
+ const clients = clientsResponse.ok ? await clientsResponse.json() : { items: [] };
+ const codePedimentoRegimens = codePedimentoRegimensResponse.ok ? await codePedimentoRegimensResponse.json() : { items: [] };
+
return {
pedimento,
pedimentoId,
- isCreate: false
+ isCreate: false,
+ pedimentoCodes: pedimentoCodes.items || [],
+ customsSections: customsSections.items || [],
+ customsBrokers: customsBrokers.items || [],
+ clients: clients.items || [],
+ codePedimentoRegimens: codePedimentoRegimens.items || []
};
} 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 7d30ef2c..27e9f9b8 100644
--- a/frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.svelte
+++ b/frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.svelte
@@ -7,6 +7,18 @@
import { Button } from '$lib/components/ui/button';
import { Badge } from '$lib/components/ui/badge';
import { Separator } from '$lib/components/ui/separator';
+ import {
+ ArrowLeft,
+ CircleAlert,
+ CircleCheck,
+ FileText,
+ Calendar,
+ CreditCard,
+ Truck,
+ ShieldCheck,
+ LoaderCircle,
+ Save
+ } from 'lucide-svelte';
import type { PageData } from './$types';
// Importar los componentes de cada pestaña (ahora sin botones de guardar propios)
@@ -18,8 +30,27 @@
// Importar solo la API de pedimentos
import { pedimentosApi, type CreatePedimentoData, type UpdatePedimentoData } from '$lib/api/dashboard/a76/pedimentos';
+ import type { PedimentoCode } from '$lib/api/dashboard/refrence_data/pedimento_codes';
+ import type { CustomsSection } from '$lib/api/dashboard/refrence_data/customs_sections';
+ import type { CustomsBroker } from '$lib/api/dashboard/a76/customs-brokers';
+ import type { ClientProvider } from '$lib/api/dashboard/a76/clients-providers';
+ import type { CodePedimentoRegimen } from '$lib/api/dashboard/refrence_data/code_pedimento_regimens';
- let { data }: { data: PageData } = $props();
+ interface ExtendedPageData {
+ pedimentoId?: number | null;
+ pedimento?: any;
+ isCreate?: boolean;
+ pedimentoCodes?: PedimentoCode[];
+ customsSections?: CustomsSection[];
+ customsBrokers?: CustomsBroker[];
+ clients?: ClientProvider[];
+ codePedimentoRegimens?: CodePedimentoRegimen[];
+ user?: any;
+ companies?: any[];
+ authenticated?: boolean;
+ }
+
+ let { data }: { data: ExtendedPageData } = $props();
let activeTab = $state('general');
let saving = $state(false);
@@ -27,7 +58,7 @@
let success = $state(false);
// ID del pedimento
- let pedimentoId = $state(data.pedimentoId);
+ let pedimentoId = $state(data.pedimentoId ?? null);
// Referencias a los componentes de formulario para obtener sus datos
let generalFormData = $state(null);
@@ -66,6 +97,7 @@
success = false;
try {
+
// Validar campos requeridos para creación
if (data.isCreate && generalFormData) {
const requiredFields = {
@@ -109,28 +141,95 @@
usd_value: generalFormData?.usd_value || undefined,
paid_price: generalFormData?.paid_price || undefined,
gross_weight: generalFormData?.gross_weight || undefined,
- exchange_rate: generalFormData?.exchange_rate || undefined,
- // Sub-recursos
- pedimento_dates: (datesFormData?.entry_date || datesFormData?.pedimento_date || datesFormData?.payment_date) ? {
- entry_date: datesFormData.entry_date || null,
- pedimento_date: datesFormData.pedimento_date || null,
- payment_date: datesFormData.payment_date || null
- } : undefined,
- pedimento_payments: (paymentsFormData?.payment_form || paymentsFormData?.bank_identifier) ? {
- payment_form: paymentsFormData.payment_form || null,
- bank_identifier: paymentsFormData.bank_identifier || null
- } : undefined,
- pedimento_transport_means: (transportFormData?.arrival_key || transportFormData?.arrival_data || transportFormData?.departure_key || transportFormData?.departure_data) ? {
- arrival_key: transportFormData.arrival_key || null,
- arrival_data: transportFormData.arrival_data || null,
- departure_key: transportFormData.departure_key || null,
- departure_data: transportFormData.departure_data || null
- } : undefined,
- pedimento_validation: validationFormData?.document ? {
- document: validationFormData.document || null
- } : undefined
+ exchange_rate: generalFormData?.exchange_rate || undefined
};
+ // Solo agregar sub-recursos en modo UPDATE (no en CREATE)
+ // Y solo si tienen valores reales (no enviar objetos vacíos/null)
+ if (!data.isCreate) {
+ // Dates - solo enviar si hay al menos un campo con valor
+ if (datesFormData) {
+ const hasDateValue = datesFormData.entry_date || datesFormData.pedimento_date ||
+ datesFormData.payment_date || datesFormData.rectification_payment_date ||
+ datesFormData.extraction_date || datesFormData.submission_date ||
+ datesFormData.eucan_date || datesFormData.original_date ||
+ datesFormData.start_date || datesFormData.end_date;
+
+ if (hasDateValue) {
+ payload.pedimento_dates = {
+ entry_date: datesFormData.entry_date || null,
+ pedimento_date: datesFormData.pedimento_date || null,
+ payment_date: datesFormData.payment_date || null,
+ rectification_payment_date: datesFormData.rectification_payment_date || null,
+ extraction_date: datesFormData.extraction_date || null,
+ submission_date: datesFormData.submission_date || null,
+ eucan_date: datesFormData.eucan_date || null,
+ original_date: datesFormData.original_date || null,
+ start_date: datesFormData.start_date || null,
+ end_date: datesFormData.end_date || null,
+ };
+ }
+ }
+
+ // Payments - solo enviar si hay al menos un campo con valor
+ if (paymentsFormData) {
+ const hasPaymentValue = paymentsFormData.acknowledgment || paymentsFormData.operation_number ||
+ paymentsFormData.bank_code || paymentsFormData.cashier || paymentsFormData.date ||
+ paymentsFormData.time || paymentsFormData.shift || paymentsFormData.total_cash_paid ||
+ paymentsFormData.total_contributions || paymentsFormData.counter_payment ||
+ paymentsFormData.pece_code;
+ if (hasPaymentValue) {
+ payload.pedimento_payments = {
+ acknowledgment: paymentsFormData.acknowledgment || null,
+ operation_number: paymentsFormData.operation_number || null,
+ bank_code: paymentsFormData.bank_code || null,
+ cashier: paymentsFormData.cashier || null,
+ date: paymentsFormData.date || null,
+ time: paymentsFormData.time || null,
+ shift: paymentsFormData.shift || null,
+ total_cash_paid: paymentsFormData.total_cash_paid || null,
+ total_contributions: paymentsFormData.total_contributions || null,
+ counter_payment: paymentsFormData.counter_payment || null,
+ pece_code: paymentsFormData.pece_code || null,
+ };
+ }
+ }
+
+ // Transport - solo enviar si hay al menos un campo con valor
+ if (transportFormData) {
+ const hasTransportValue = transportFormData.destination || transportFormData.entry_exit ||
+ transportFormData.arrival || transportFormData.departure;
+ if (hasTransportValue) {
+ payload.pedimento_transport_means = {
+ destination: transportFormData.destination || null,
+ entry_exit: transportFormData.entry_exit || null,
+ arrival: transportFormData.arrival || null,
+ departure: transportFormData.departure || null
+ };
+ }
+ }
+
+ // Validation - solo enviar si hay al menos un campo con valor
+ if (validationFormData) {
+ const hasValidationValue = validationFormData.validator || validationFormData.validation_ack ||
+ validationFormData.pre_ack || validationFormData.line_signature ||
+ validationFormData.electronic_signature || validationFormData.certificate_number ||
+ validationFormData.validator_id || validationFormData.responsible_id;
+ if (hasValidationValue) {
+ payload.pedimento_validation = {
+ validator: validationFormData.validator || null,
+ validation_ack: validationFormData.validation_ack || null,
+ pre_ack: validationFormData.pre_ack || null,
+ line_signature: validationFormData.line_signature || null,
+ electronic_signature: validationFormData.electronic_signature || null,
+ certificate_number: validationFormData.certificate_number || null,
+ validator_id: validationFormData.validator_id || null,
+ responsible_id: validationFormData.responsible_id || null
+ };
+ }
+ }
+ }
+
// Eliminar campos undefined para no enviarlos
Object.keys(payload).forEach(key => {
if (payload[key as keyof typeof payload] === undefined) {
@@ -140,9 +239,9 @@
let newPedimentoId = pedimentoId;
- if (data.isCreate) {
+ if (data.isCreate) {
// Crear nuevo pedimento con todos sus sub-recursos
- const response = await pedimentosApi.create(payload as CreatePedimentoData);
+ const response = await pedimentosApi.create(payload as CreatePedimentoData);
if (response.error) {
const errorMsg = typeof response.error === 'string' ? response.error : 'Error al crear el pedimento';
throw new Error(errorMsg);
@@ -153,12 +252,12 @@
// Redirigir a la página de edición
await goto(`/dashboard/pedimentos/edit/${newPedimentoId}`);
return;
- } else {
+ } else {
// Actualizar pedimento existente con todos sus sub-recursos
- const response = await pedimentosApi.update(pedimentoId!, payload as UpdatePedimentoData);
+ const response = await pedimentosApi.update(pedimentoId!, payload as UpdatePedimentoData);
if (response.error) throw new Error(response.error);
}
-
+
success = true;
setTimeout(() => {
success = false;
@@ -185,20 +284,7 @@
-
+
{#if data.isCreate}
@@ -230,21 +316,7 @@
{#if error}
-
+
Error
{error}
@@ -252,216 +324,105 @@
{#if success}
-
+
Éxito
Todos los cambios se guardaron correctamente
{/if}
-
-
-
-
-
- General
-
-
-
- Fechas
-
-
-
- Pagos
-
-
-
- Transporte
-
-
-
- Validación
-
-
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Cancelar
-
-
- {#if saving}
-
- Guardando todos los cambios...
- {:else}
-
- Guardar Todos los Cambios
- {/if}
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/routes/dashboard/reference_data/code_pedimento_regimens/+page.svelte b/frontend/src/routes/dashboard/reference_data/code_pedimento_regimens/+page.svelte
index a72beb5f..6da7f104 100644
--- a/frontend/src/routes/dashboard/reference_data/code_pedimento_regimens/+page.svelte
+++ b/frontend/src/routes/dashboard/reference_data/code_pedimento_regimens/+page.svelte
@@ -8,6 +8,7 @@
import { Button } from '$lib/components/ui/button';
import type { PageData } from './$types';
import { browser } from '$app/environment';
+ import { Plus, RefreshCw } from 'lucide-svelte';
// Los datos iniciales vienen del servidor
let { data }: { data: PageData } = $props();
@@ -120,21 +121,7 @@
-
+
Nuevo Registro
@@ -160,21 +147,7 @@
-
+
Actualizar
diff --git a/frontend/src/routes/dashboard/reference_data/containers/+page.svelte b/frontend/src/routes/dashboard/reference_data/containers/+page.svelte
index 98af455c..060fce7d 100644
--- a/frontend/src/routes/dashboard/reference_data/containers/+page.svelte
+++ b/frontend/src/routes/dashboard/reference_data/containers/+page.svelte
@@ -8,6 +8,7 @@
import { Button } from '$lib/components/ui/button';
import type { PageData } from './$types';
import { browser } from '$app/environment';
+ import { Plus, RefreshCw } from 'lucide-svelte';
// Los datos iniciales vienen del servidor
let { data }: { data: PageData } = $props();
@@ -120,21 +121,7 @@
-
+
Nuevo Contenedor
@@ -160,21 +147,7 @@
-
+
Actualizar
diff --git a/frontend/src/routes/dashboard/reference_data/countries/+page.svelte b/frontend/src/routes/dashboard/reference_data/countries/+page.svelte
index fd5ec3ef..4a54e322 100644
--- a/frontend/src/routes/dashboard/reference_data/countries/+page.svelte
+++ b/frontend/src/routes/dashboard/reference_data/countries/+page.svelte
@@ -6,6 +6,7 @@
import CreateEditDialog from '$lib/components/dashboard/reference_data/countries/create-edit-dialog.svelte';
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
+ import { Plus, RefreshCw } from 'lucide-svelte';
import type { PageData } from './$types';
import { browser } from '$app/environment';
@@ -120,21 +121,7 @@
-
+
Nuevo País
@@ -160,21 +147,7 @@
-
+
Actualizar
diff --git a/frontend/src/routes/dashboard/reference_data/currency_types/+page.svelte b/frontend/src/routes/dashboard/reference_data/currency_types/+page.svelte
index f47264c8..38062c10 100644
--- a/frontend/src/routes/dashboard/reference_data/currency_types/+page.svelte
+++ b/frontend/src/routes/dashboard/reference_data/currency_types/+page.svelte
@@ -6,6 +6,7 @@
import CreateEditDialog from '$lib/components/dashboard/reference_data/currency_types/create-edit-dialog.svelte';
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
+ import { Plus, RefreshCw } from 'lucide-svelte';
import type { PageData } from './$types';
import { browser } from '$app/environment';
@@ -120,22 +121,8 @@
-
- Nueva Moneda
+
+ Nuevo Tipo de Moneda
@@ -160,21 +147,7 @@
-
+
Actualizar
diff --git a/frontend/src/routes/dashboard/reference_data/customs_sections/+page.svelte b/frontend/src/routes/dashboard/reference_data/customs_sections/+page.svelte
index a45491b7..b8b95f42 100644
--- a/frontend/src/routes/dashboard/reference_data/customs_sections/+page.svelte
+++ b/frontend/src/routes/dashboard/reference_data/customs_sections/+page.svelte
@@ -8,6 +8,7 @@
import { Button } from '$lib/components/ui/button';
import type { PageData } from './$types';
import { browser } from '$app/environment';
+ import { Plus, RefreshCw } from 'lucide-svelte';
// Los datos iniciales vienen del servidor
let { data }: { data: PageData } = $props();
@@ -120,21 +121,7 @@
-
+
Nueva Sección
@@ -160,21 +147,7 @@
-
+
Actualizar
diff --git a/frontend/src/routes/dashboard/reference_data/customs_warehouses/+page.svelte b/frontend/src/routes/dashboard/reference_data/customs_warehouses/+page.svelte
index 82b7c178..f09cab30 100644
--- a/frontend/src/routes/dashboard/reference_data/customs_warehouses/+page.svelte
+++ b/frontend/src/routes/dashboard/reference_data/customs_warehouses/+page.svelte
@@ -8,6 +8,7 @@
import { Button } from '$lib/components/ui/button';
import type { PageData } from './$types';
import { browser } from '$app/environment';
+ import { Plus, RefreshCw } from 'lucide-svelte';
// Los datos iniciales vienen del servidor
let { data }: { data: PageData } = $props();
@@ -120,21 +121,7 @@
-
+
Nuevo Recinto
@@ -160,21 +147,7 @@
-
+
Actualizar
diff --git a/frontend/src/routes/dashboard/reference_data/incoterms/+page.svelte b/frontend/src/routes/dashboard/reference_data/incoterms/+page.svelte
index 217d1c6c..201ce115 100644
--- a/frontend/src/routes/dashboard/reference_data/incoterms/+page.svelte
+++ b/frontend/src/routes/dashboard/reference_data/incoterms/+page.svelte
@@ -6,6 +6,7 @@
import CreateEditDialog from '$lib/components/dashboard/reference_data/incoterms/create-edit-dialog.svelte';
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
+ import { Plus, RefreshCw } from 'lucide-svelte';
import type { PageData } from './$types';
import { browser } from '$app/environment';
@@ -120,21 +121,7 @@
-
+
Nuevo Incoterm
@@ -160,21 +147,7 @@
-
+
Actualizar
diff --git a/frontend/src/routes/dashboard/reference_data/invoice_types/+page.svelte b/frontend/src/routes/dashboard/reference_data/invoice_types/+page.svelte
index 1ba35154..72f1f62c 100644
--- a/frontend/src/routes/dashboard/reference_data/invoice_types/+page.svelte
+++ b/frontend/src/routes/dashboard/reference_data/invoice_types/+page.svelte
@@ -6,6 +6,7 @@
import CreateEditDialog from '$lib/components/dashboard/reference_data/invoice_types/create-edit-dialog.svelte';
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
+ import { Plus, RefreshCw } from 'lucide-svelte';
import type { PageData } from './$types';
import { browser } from '$app/environment';
@@ -120,21 +121,7 @@
-
+
Nuevo Tipo de Factura
@@ -160,21 +147,7 @@
-
+
Actualizar
diff --git a/frontend/src/routes/dashboard/reference_data/material_types/+page.svelte b/frontend/src/routes/dashboard/reference_data/material_types/+page.svelte
index 66589b3f..94ee7d33 100644
--- a/frontend/src/routes/dashboard/reference_data/material_types/+page.svelte
+++ b/frontend/src/routes/dashboard/reference_data/material_types/+page.svelte
@@ -8,6 +8,7 @@
import { Button } from '$lib/components/ui/button';
import type { PageData } from './$types';
import { browser } from '$app/environment';
+ import { Plus, RefreshCw } from 'lucide-svelte';
// Los datos iniciales vienen del servidor
let { data }: { data: PageData } = $props();
@@ -120,21 +121,7 @@
-
+
Nuevo Tipo de Material
@@ -160,21 +147,7 @@
-
+
Actualizar
diff --git a/frontend/src/routes/dashboard/reference_data/payment_methods/+page.svelte b/frontend/src/routes/dashboard/reference_data/payment_methods/+page.svelte
index 860a48f3..a4800ec3 100644
--- a/frontend/src/routes/dashboard/reference_data/payment_methods/+page.svelte
+++ b/frontend/src/routes/dashboard/reference_data/payment_methods/+page.svelte
@@ -8,6 +8,7 @@
import { Button } from '$lib/components/ui/button';
import type { PageData } from './$types';
import { browser } from '$app/environment';
+ import { Plus, RefreshCw } from 'lucide-svelte';
// Los datos iniciales vienen del servidor
let { data }: { data: PageData } = $props();
@@ -120,21 +121,7 @@
-
+
Nuevo Método de Pago
@@ -160,21 +147,7 @@
-
+
Actualizar
diff --git a/frontend/src/routes/dashboard/reference_data/pedimento_codes/+page.svelte b/frontend/src/routes/dashboard/reference_data/pedimento_codes/+page.svelte
index dec3a448..3761f69a 100644
--- a/frontend/src/routes/dashboard/reference_data/pedimento_codes/+page.svelte
+++ b/frontend/src/routes/dashboard/reference_data/pedimento_codes/+page.svelte
@@ -6,6 +6,7 @@
import CreateEditDialog from '$lib/components/dashboard/reference_data/pedimento_codes/create-edit-dialog.svelte';
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
+ import { Plus, RefreshCw } from 'lucide-svelte';
import type { PageData } from './$types';
import { browser } from '$app/environment';
@@ -120,22 +121,8 @@
-
- Nueva Clave de Pedimento
+
+ Nuevo Código de Pedimento
@@ -160,21 +147,7 @@
-
+
Actualizar
diff --git a/frontend/src/routes/dashboard/reference_data/pedimento_regimens/+page.svelte b/frontend/src/routes/dashboard/reference_data/pedimento_regimens/+page.svelte
index 39d1a31f..e636522f 100644
--- a/frontend/src/routes/dashboard/reference_data/pedimento_regimens/+page.svelte
+++ b/frontend/src/routes/dashboard/reference_data/pedimento_regimens/+page.svelte
@@ -8,6 +8,7 @@
import { Button } from '$lib/components/ui/button';
import type { PageData } from './$types';
import { browser } from '$app/environment';
+ import { Plus, RefreshCw } from 'lucide-svelte';
// Los datos iniciales vienen del servidor
let { data }: { data: PageData } = $props();
@@ -120,21 +121,7 @@
-
+
Nuevo Régimen de Pedimento
@@ -160,21 +147,7 @@
-
+
Actualizar
diff --git a/frontend/src/routes/dashboard/reference_data/sectors/+page.svelte b/frontend/src/routes/dashboard/reference_data/sectors/+page.svelte
index 3621eb21..fd4fe48a 100644
--- a/frontend/src/routes/dashboard/reference_data/sectors/+page.svelte
+++ b/frontend/src/routes/dashboard/reference_data/sectors/+page.svelte
@@ -6,6 +6,7 @@
import CreateEditDialog from '$lib/components/dashboard/reference_data/sectors/create-edit-dialog.svelte';
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
+ import { Plus, RefreshCw } from 'lucide-svelte';
import type { PageData } from './$types';
import { browser } from '$app/environment';
@@ -120,21 +121,7 @@
-
+
Nuevo Sector
@@ -160,21 +147,7 @@
-
+
Actualizar
diff --git a/frontend/src/routes/dashboard/reference_data/states/+page.svelte b/frontend/src/routes/dashboard/reference_data/states/+page.svelte
index b164088c..82b50e2d 100644
--- a/frontend/src/routes/dashboard/reference_data/states/+page.svelte
+++ b/frontend/src/routes/dashboard/reference_data/states/+page.svelte
@@ -8,6 +8,7 @@
import { Button } from '$lib/components/ui/button';
import type { PageData } from './$types';
import { browser } from '$app/environment';
+ import { Plus, RefreshCw } from 'lucide-svelte';
// Los datos iniciales vienen del servidor
let { data }: { data: PageData } = $props();
@@ -120,21 +121,7 @@
-
+
Nuevo Estado
@@ -160,21 +147,7 @@
-
+
Actualizar
diff --git a/frontend/src/routes/dashboard/reference_data/transport_modes/+page.svelte b/frontend/src/routes/dashboard/reference_data/transport_modes/+page.svelte
index 774372d8..fd4c7553 100644
--- a/frontend/src/routes/dashboard/reference_data/transport_modes/+page.svelte
+++ b/frontend/src/routes/dashboard/reference_data/transport_modes/+page.svelte
@@ -8,6 +8,7 @@
import { Button } from '$lib/components/ui/button';
import type { PageData } from './$types';
import { browser } from '$app/environment';
+ import { Plus, RefreshCw } from 'lucide-svelte';
// Los datos iniciales vienen del servidor
let { data }: { data: PageData } = $props();
@@ -120,21 +121,7 @@
-
+
Nuevo Modo de Transporte
@@ -160,21 +147,7 @@
-
+
Actualizar
diff --git a/frontend/src/routes/dashboard/reference_data/transport_types/+page.svelte b/frontend/src/routes/dashboard/reference_data/transport_types/+page.svelte
index 28c96469..a8a57d47 100644
--- a/frontend/src/routes/dashboard/reference_data/transport_types/+page.svelte
+++ b/frontend/src/routes/dashboard/reference_data/transport_types/+page.svelte
@@ -6,6 +6,7 @@
import CreateEditDialog from '$lib/components/dashboard/reference_data/transport_types/create-edit-dialog.svelte';
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
+ import { Plus, RefreshCw } from 'lucide-svelte';
import type { PageData } from './$types';
import { browser } from '$app/environment';
@@ -120,21 +121,7 @@
-
+
Nuevo Tipo de Transporte
@@ -160,21 +147,7 @@
-
+
Actualizar
diff --git a/frontend/src/routes/dashboard/reference_data/valuation_methods/+page.svelte b/frontend/src/routes/dashboard/reference_data/valuation_methods/+page.svelte
index 81e18284..380c9771 100644
--- a/frontend/src/routes/dashboard/reference_data/valuation_methods/+page.svelte
+++ b/frontend/src/routes/dashboard/reference_data/valuation_methods/+page.svelte
@@ -6,6 +6,7 @@
import CreateEditDialog from '$lib/components/dashboard/reference_data/valuation_methods/create-edit-dialog.svelte';
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
+ import { Plus, RefreshCw } from 'lucide-svelte';
import type { PageData } from './$types';
import { browser } from '$app/environment';
@@ -120,21 +121,7 @@
-
+
Nuevo Método de Valoración
@@ -160,21 +147,7 @@
-
+
Actualizar
diff --git a/scripts/init_first_time.sh b/scripts/init_first_time.sh
index 591a52ac..223908ab 100755
--- a/scripts/init_first_time.sh
+++ b/scripts/init_first_time.sh
@@ -22,7 +22,7 @@
# se actualiza con el ID real del tenant creado en PostgreSQL.
###############################################################################
-set -e # Salir si hay algún error
+# set -e # Comentado para permitir que el script continúe aunque algunos comandos fallen (ej: mapper ya existe)
# Colores para output
RED='\033[0;31m'
@@ -309,87 +309,84 @@ echo -e "\n${YELLOW}[5/8] Configurando mappers para tenant_id...${NC}"
if [ -n "$BACKEND_CLIENT_ID" ]; then
echo "Configurando mapper para Backend..."
- # Obtener el dedicated scope del cliente backend
- BACKEND_SCOPES=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients/${BACKEND_CLIENT_ID}/optional-client-scopes" \
+ # Verificar si el mapper tenant_id ya existe en el cliente
+ MAPPER_TENANT_EXISTS=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients/${BACKEND_CLIENT_ID}/protocol-mappers/models" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
- -H "Content-Type: application/json")
+ -H "Content-Type: application/json" | grep -o "\"name\":\"tenant-id-mapper\"")
- # Buscar el scope dedicado
- BACKEND_DEDICATED_SCOPE_ID=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/client-scopes" \
- -H "Authorization: Bearer ${ACCESS_TOKEN}" \
- -H "Content-Type: application/json" | grep -o "\"id\":\"[^\"]*\",\"name\":\"anexo76-backend-dedicated\"" | grep -o "\"id\":\"[^\"]*" | sed 's/"id":"//')
-
- if [ -n "$BACKEND_DEDICATED_SCOPE_ID" ]; then
- # Verificar si el mapper tenant_id ya existe
- MAPPER_TENANT_EXISTS=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/client-scopes/${BACKEND_DEDICATED_SCOPE_ID}/protocol-mappers/models" \
+ if [ -z "$MAPPER_TENANT_EXISTS" ]; then
+ # Crear mapper para tenant_id directamente en el cliente
+ CREATE_MAPPER_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients/${BACKEND_CLIENT_ID}/protocol-mappers/models" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
- -H "Content-Type: application/json" | grep -o "\"name\":\"tenant-id-mapper\"")
+ -H "Content-Type: application/json" \
+ -d '{
+ "name": "tenant-id-mapper",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "config": {
+ "user.attribute": "tenant_id",
+ "claim.name": "tenant_id",
+ "jsonType.label": "String",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "userinfo.token.claim": "true"
+ }
+ }')
- if [ -z "$MAPPER_TENANT_EXISTS" ]; then
- # Crear mapper para tenant_id
- curl -s -X POST "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/client-scopes/${BACKEND_DEDICATED_SCOPE_ID}/protocol-mappers/models" \
- -H "Authorization: Bearer ${ACCESS_TOKEN}" \
- -H "Content-Type: application/json" \
- -d '{
- "name": "tenant-id-mapper",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-attribute-mapper",
- "config": {
- "user.attribute": "tenant_id",
- "claim.name": "tenant_id",
- "jsonType.label": "String",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "userinfo.token.claim": "true"
- }
- }'
+ HTTP_CODE=$(echo "$CREATE_MAPPER_RESPONSE" | tail -n1)
+ if [ "$HTTP_CODE" = "201" ]; then
echo -e "${GREEN}✓ Mapper tenant_id creado para Backend${NC}"
else
- echo -e "${YELLOW}⚠ Mapper tenant_id ya existe para Backend${NC}"
+ echo -e "${YELLOW}⚠ Error al crear mapper para Backend (HTTP ${HTTP_CODE})${NC}"
+ echo "Respuesta: $(echo "$CREATE_MAPPER_RESPONSE" | head -n -1)"
fi
+ else
+ echo -e "${YELLOW}⚠ Mapper tenant_id ya existe para Backend${NC}"
fi
fi
+
# 4.2 Configurar mapper para Frontend
if [ -n "$FRONTEND_CLIENT_ID" ]; then
echo "Configurando mapper para Frontend..."
- # Buscar el scope dedicado del frontend
- FRONTEND_DEDICATED_SCOPE_ID=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/client-scopes" \
+ # Verificar si el mapper tenant_id ya existe en el cliente
+ MAPPER_TENANT_EXISTS=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients/${FRONTEND_CLIENT_ID}/protocol-mappers/models" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
- -H "Content-Type: application/json" | grep -o "\"id\":\"[^\"]*\",\"name\":\"anexo76-frontend-dedicated\"" | grep -o "\"id\":\"[^\"]*" | sed 's/"id":"//')
+ -H "Content-Type: application/json" | grep -o "\"name\":\"tenant-id-mapper\"")
- if [ -n "$FRONTEND_DEDICATED_SCOPE_ID" ]; then
- # Verificar si el mapper tenant_id ya existe
- MAPPER_TENANT_EXISTS=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/client-scopes/${FRONTEND_DEDICATED_SCOPE_ID}/protocol-mappers/models" \
+ if [ -z "$MAPPER_TENANT_EXISTS" ]; then
+ # Crear mapper para tenant_id directamente en el cliente
+ CREATE_MAPPER_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients/${FRONTEND_CLIENT_ID}/protocol-mappers/models" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
- -H "Content-Type: application/json" | grep -o "\"name\":\"tenant-id-mapper\"")
+ -H "Content-Type: application/json" \
+ -d '{
+ "name": "tenant-id-mapper",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "config": {
+ "user.attribute": "tenant_id",
+ "claim.name": "tenant_id",
+ "jsonType.label": "String",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "userinfo.token.claim": "true"
+ }
+ }')
- if [ -z "$MAPPER_TENANT_EXISTS" ]; then
- # Crear mapper para tenant_id
- curl -s -X POST "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/client-scopes/${FRONTEND_DEDICATED_SCOPE_ID}/protocol-mappers/models" \
- -H "Authorization: Bearer ${ACCESS_TOKEN}" \
- -H "Content-Type: application/json" \
- -d '{
- "name": "tenant-id-mapper",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-attribute-mapper",
- "config": {
- "user.attribute": "tenant_id",
- "claim.name": "tenant_id",
- "jsonType.label": "String",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "userinfo.token.claim": "true"
- }
- }'
+ HTTP_CODE=$(echo "$CREATE_MAPPER_RESPONSE" | tail -n1)
+ if [ "$HTTP_CODE" = "201" ]; then
echo -e "${GREEN}✓ Mapper tenant_id creado para Frontend${NC}"
else
- echo -e "${YELLOW}⚠ Mapper tenant_id ya existe para Frontend${NC}"
+ echo -e "${YELLOW}⚠ Error al crear mapper para Frontend (HTTP ${HTTP_CODE})${NC}"
+ echo "Respuesta: $(echo "$CREATE_MAPPER_RESPONSE" | head -n -1)"
fi
+ else
+ echo -e "${YELLOW}⚠ Mapper tenant_id ya existe para Frontend${NC}"
fi
fi
+
###############################################################################
# 6. Crear usuario demo en Keycloak
###############################################################################